/jEdit/tags/jedit-4-5-pre1/doc/users-guide/plugin-implement.xml
XML | 1250 lines | 1027 code | 207 blank | 16 comment | 0 complexity | 21f5d6d6a09114153c2b06f452e1a5b7 MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, Apache-2.0, LGPL-2.0, LGPL-3.0, GPL-2.0, CC-BY-SA-3.0, LGPL-2.1, GPL-3.0, MPL-2.0-no-copyleft-exception, IPL-1.0
Large files files are truncated, but you can click here to view the full file
- <?xml version="1.0" encoding="UTF-8"?>
- <chapter id="plugin-implement">
- <!-- jEdit buffer-local properties: -->
- <!-- :xml.root=users-guide.xml: -->
- <!-- :maxLineLen=80:wrap=soft: -->
- <title>Implementing a Simple Plugin</title>
- <!-- jEdit 4 Plugin Guide, (C) 2001, 2002 John Gellene -->
- <!-- jEdit 4.5 Plugin Guide, (C) 2005, 2011 Alan Ezust -->
- <para>There are many applications for the leading operating systems that
- provide a <quote>scratch-pad</quote> or <quote>sticky note</quote> facility
- for the desktop display. A similar type of facility operating within the
- jEdit display would be a convenience. The use of dockable windows would
- allow the notepad to be displayed or hidden with a single mouse click or
- keypress (if a keyboard shortcut were defined). The contents of the notepad
- could be saved at program exit (or, if earlier, deactivation of the plugin)
- and retrieved at program startup or plugin activation.</para>
- <para>We will keep the capabilities of this plugin modest, but a few other
- features would be worthwhile. The user should be able to write the contents
- of the notepad to storage on demand. It should also be possible to choose
- the name and location of the file that will be used to hold the notepad
- text. This would allow the user to load other files into the notepad
- display. The path of the notepad file should be displayed in the plugin
- window, but will give the user the option to hide the file name. Finally,
- there should be an action by which a single click or keypress would cause
- the contents of the notepad to be written to the new text buffer for further
- processing.</para>
- <para>The full source code for QuickNotepad is contained in jEdit's source
- code distribution. We will provide excerpts in this discussion where it is
- helpful to illustrate specific points. You are invited to obtain the source
- code for further study or to use as a starting point for your own
- plugin.</para>
- <section id="plugin-load">
- <title><indexterm>
- <primary>Plugin API</primary>
- <secondary>loading at startup</secondary>
- </indexterm> How Plugins are Loaded</title>
- <para>We will discuss the implementation of the
- <application>QuickNotepad</application> plugin, along with the jEdit
- APIs it makes use of. But first, we describe how plugins are
- loaded.</para>
- <para>As part of its startup routine, jEdit's <function>main</function>
- method calls various methods to load and initialize plugins.</para>
- <para>Additionally, plugins using the jEdit 4.2 plugin API can be loaded
- and unloaded at any time. This is a great help when developing your own
- plugins -- there is no need to restart the editor after making changes
- (see <xref linkend="plugin-implement-reloading" /> ).</para>
- <para>Plugins are loaded from files with the <filename>.jar</filename>
- filename extension located in the <filename>jars</filename>
- subdirectories of the jEdit installation and user settings directories
- (see <xref linkend="settings-directory" />).</para>
- <para>For each JAR archive file it finds, jEdit scans its entries and
- performs the following tasks:</para>
- <itemizedlist>
- <listitem>
- <para>Adds to a collection maintained by jEdit a new object of
- type <ulink url="../api/org/gjt/sp/jedit/PluginJAR.html">
- <classname>PluginJAR</classname></ulink>. This is a data
- structure holding the name of the JAR archive file, a reference
- to the <ulink url="../api/org/gjt/sp/jedit/JARClassLoader.html">
- <classname>JARClassLoader</classname></ulink>, and a collection
- of plugins found in the archive file.</para>
- </listitem>
- <listitem>
- <para>Loads any properties defined in files ending with the
- extension <filename>.props</filename> that are contained in the
- archive. See <xref
- linkend="plugin-implement-properties" />.</para>
- </listitem>
- <listitem>
- <para>Reads action definitions from any file named
- <filename>actions.xml</filename> in the archive (the file need
- not be at the top level). See <xref
- linkend="plugin-implement-actions" />.</para>
- </listitem>
- <listitem>
- <para>Parses and loads the contents of any file named
- <filename>dockables.xml</filename> in the archive (the file need
- not be at the top level). This file contains BeanShell code for
- creating docking or floating windows that will contain the
- visible components of the plugin. Not all plugins define
- dockable windows, but those that do need a
- <filename>dockables.xml</filename> file. See <xref
- linkend="plugin-implement-dockables" />.</para>
- </listitem>
- <listitem>
- <para>Checks for a class name with a name ending with
- <filename>Plugin.class</filename>.</para>
- <para>Such a class is known as a <firstterm>plugin core
- class</firstterm> and must extend jEdit's abstract <ulink
- url="../api/org/gjt/sp/jedit/EditPlugin.html">
- <classname>EditPlugin</classname></ulink> class.</para>
- <para>The initialization routine checks the plugin's properties
- to see if it is subject to any dependencies. For example, a
- plugin may require that the version of the Java runtime
- environment or of jEdit itself be equal to or above some
- threshold version. A plugin can also require the presence of
- another plugin.</para>
- <para>If any dependency is not satisfied, the loader marks the
- plugin as <quote>broken</quote> and logs an error
- message.</para>
- </listitem>
- </itemizedlist>
- <para>After scanning the plugin JAR file and loading any resources, a
- new instance of the plugin core class is created and added to the
- collection maintained by the appropriate <ulink
- url="../api/org/gjt/sp/jedit/PluginJAR.html">
- <classname>PluginJAR</classname></ulink>. jEdit then calls the
- <function>start()</function> method of the plugin core class. The
- <function>start()</function> method can perform initialization of the
- object's data members. Because this method is defined as an empty
- <quote>no-op</quote> in the <ulink
- url="../api/org/gjt/sp/jedit/EditPlugin.html">
- <classname>EditPlugin</classname></ulink> abstract class, a plugin need
- not provide an implementation if no unique initialization is
- required.</para>
- </section>
- <section id="plugin-implement-quicknotepadplugin">
- <title>The QuickNotepadPlugin Class</title>
- <para>The major issues encountered when writing a plugin core class
- arise from the developer's decisions on what features the plugin will
- make available. These issues have implications for other plugin elements
- as well.</para>
- <itemizedlist>
- <listitem>
- <para>Will the plugin provide for actions that the user can
- trigger using jEdit's menu items, toolbar buttons and keyboard
- shortcuts?</para>
- </listitem>
- <listitem>
- <para>Will the plugin have its own visible interface?</para>
- </listitem>
- <listitem>
- <para>Will the plugin have settings that the user can
- configure?</para>
- </listitem>
- <listitem>
- <para>Will the plugin respond to any messages reflecting changes
- in the host application's state?</para>
- </listitem>
- <listitem>
- <para>Should the plugin do something special when it gets
- focus?</para>
- </listitem>
- </itemizedlist>
- <para>Recall that the plugin core class must extend <ulink
- url="../api/org/gjt/sp/jedit/EditPlugin.html">
- <classname>EditPlugin</classname></ulink>. In QuickNotepad's plugin core
- class, there are no special initialization or shutdown chores to
- perform, so we will not need a <function>start()</function> or
- <function>stop()</function> method.</para>
- <para>The resulting plugin core class is lightweight and straightforward
- to implement:</para>
- <itemizedlist>
- <listitem>
- <informalexample>
- <programlisting>public class QuickNotepadPlugin extends EditPlugin {
- public static final String NAME = "quicknotepad";
- public static final String OPTION_PREFIX = "options.quicknotepad.";
- }
- </programlisting>
- </informalexample>
- <para>The class has been simplified since 4.1, and all we
- defined here were a couple of <classname>String</classname> data
- members to enforce consistent syntax for the name of properties
- we will use throughout the plugin.</para>
- </listitem>
- <listitem>
- <para>These names are used in <filename>actions.xml</filename>
- for each of the menu choices. This file is discussed in more
- detail in <xref linkend="plugin-implement-actions" />. Each
- action is a beanshell script.</para>
- <informalexample>
- <programlisting>
- <!DOCTYPE ACTIONS SYSTEM "actions.dtd">
- <ACTIONS>
- <ACTION NAME="quicknotepad.choose-file">
- <CODE>
- wm.addDockableWindow(QuickNotepadPlugin.NAME);
- wm.getDockableWindow(QuickNotepadPlugin.NAME).chooseFile();
- </CODE>
- </ACTION>
- <ACTION NAME="quicknotepad.save-file">
- <CODE>
- wm.addDockableWindow(QuickNotepadPlugin.NAME);
- wm.getDockableWindow(QuickNotepadPlugin.NAME).saveFile();
- </CODE>
- </ACTION>
- <ACTION NAME="quicknotepad.copy-to-buffer">
- <CODE>
- wm.addDockableWindow(QuickNotepadPlugin.NAME);
- wm.getDockableWindow(QuickNotepadPlugin.NAME).copyToBuffer();
- </CODE>
- </ACTION>
- </ACTIONS>
- </programlisting>
- </informalexample>
- </listitem>
- <listitem>
- <para>The names also come up in the properties file,
- <filename>QuickNotePad.props</filename> file. The properties
- define option panes and strings used by the plugin. It is
- explained in more detail in <xref
- linkend="plugin-implement-properties" /> and the <ulink
- url="../api/org/gjt/sp/jedit/EditPlugin.html">
- <classname>EditPlugin</classname></ulink> API docs.</para>
- <informalexample>
- <programlisting>
- # jEdit only needs to load the plugin the first time the user accesses it
- # the presence of this property also tells jEdit the plugin is using the new API
- plugin.QuickNotepadPlugin.activate=defer
- # These two properties are required for all plugins
- plugin.QuickNotepadPlugin.name=QuickNotepad
- plugin.QuickNotepadPlugin.author=John Gellene
- # version number == jEdit version number
- plugin.QuickNotepadPlugin.version=4.4
- # online help
- plugin.QuickNotepadPlugin.docs=index.html
- # we only have one dependency, jEdit 4.4.1
- plugin.QuickNotepadPlugin.depend.0=jedit 04.04.99.01
- # plugin menu
- plugin.QuickNotepadPlugin.menu=quicknotepad \
- - \
- quicknotepad.choose-file \
- quicknotepad.save-file \
- quicknotepad.copy-to-buffer
- # action labels for actions supplied by dockables.xml
- quicknotepad.label=QuickNotepad
- # action labels for actions supplied by actions.xml
- quicknotepad.choose-file.label=Choose notepad file
- quicknotepad.save-file.label=Save notepad file
- quicknotepad.copy-to-buffer.label=Copy notepad to buffer
- # plugin option pane
- plugin.QuickNotepadPlugin.option-pane=quicknotepad
- # Option pane activation BeanShell snippet
- options.quicknotepad.code=new QuickNotepadOptionPane();
- # Option pane labels
- options.quicknotepad.label=QuickNotepad
- options.quicknotepad.file=File:
- options.quicknotepad.choose-file=Choose
- options.quicknotepad.choose-file.title=Choose a notepad file
- options.quicknotepad.choose-font=Font:
- options.quicknotepad.show-filepath.title=Display notepad file path
- # window title
- quicknotepad.title=QuickNotepad
- # window toolbar buttons
- quicknotepad.choose-file.icon=Open.png
- quicknotepad.save-file.icon=Save.png
- quicknotepad.copy-to-buffer.icon=CopyToBuffer.png
- # default settings
- options.quicknotepad.show-filepath=true
- options.quicknotepad.font=Monospaced
- options.quicknotepad.fontstyle=0
- options.quicknotepad.fontsize=14
- # Setting not defined but supplied for completeness
- options.quicknotepad.filepath=
- </programlisting>
- </informalexample>
- </listitem>
- </itemizedlist>
- </section>
- <section id="plugin-implement-properties">
- <title>The Property File</title>
- <para>jEdit maintains a list of <quote>properties</quote>, which are
- name/value pairs used to store human-readable strings, user settings,
- and various other forms of meta-data. During startup, jEdit loads the
- default set of properties, followed by plugin properties stored in
- plugin JAR files, finally followed by user properties.</para>
- <para>Some properties are used by the plugin API itself. Others are
- accessed by the plugin using methods in the <ulink
- url="../api/org/gjt/sp/jedit/jEdit.html">
- <classname>jEdit</classname></ulink> class. Others are accessed by the
- scripts used by plugin packagers <footnote>
- <para>See the <guimenuitem>Macros/Properties/Create Plugin
- Announcement</guimenuitem> macro for an example.</para>
- </footnote>.</para>
- <para>Property files contained in plugin JARs must end with the filename
- extension <filename>.props</filename>, and have a very simple syntax,
- which the following example illustrates:</para>
- <informalexample>
- <programlisting># Lines starting with '#' are ignored.
- name=value
- another.name=another value
- long.property=Long property value, split over \
- several lines
- escape.property=Newlines and tabs can be inserted \
- using the \t and \n escapes
- backslash.property=A backslash can be inserted by writing \\.</programlisting>
- </informalexample>
- <para>Now we look at a fragment from the
- <filename>QuickNotepad.props</filename> file <footnote>
- <para>Examine the actual file for a more complete example</para>
- </footnote> which contains properties for the QuickNotepad plugin.
- The first type of property data is information about the plugin itself;
- these are the only properties that must be specified in order for the
- plugin to load:</para>
- <informalexample>
- <programlisting># general plugin information
- plugin.QuickNotepadPlugin.activate=defer
- plugin.QuickNotepadPlugin.name=QuickNotepad
- plugin.QuickNotepadPlugin.author=John Gellene
- plugin.QuickNotepadPlugin.version=4.03
- plugin.QuickNotepadPlugin.docs=QuickNotepad.html
- # depends on jEdit 4.4.1
- plugin.QuickNotepadPlugin.depend.0=jedit 04.04.99.01
- plugin.QuickNotepadPlugin.description=A demo jEdit plugin that provides a notepad dockable.
- plugin.QuickNotepadPlugin.longdescription=description.html
- </programlisting>
- </informalexample>
- <para>These properties are each described in detail in the documentation
- for the <ulink url="../api/org/gjt/sp/jedit/EditPlugin.html">
- <classname>EditPlugin</classname></ulink> class and do not require
- further discussion here.</para>
- <para>Next in the file comes a property that sets the title of the
- plugin's dockable window. Dockable windows are discussed in detail in
- <xref linkend="plugin-implement-dockables" />.</para>
- <informalexample>
- <programlisting># dockable window name
- quicknotepad.title=QuickNotepad</programlisting>
- </informalexample>
- <para>Next, we see menu item labels for the plugin's actions. All of
- these but the first are defined in <literal>actions.xml</literal> file,
- and that is because the dockable itself has its own actions. Actions are
- discussed further in <xref linkend="plugin-implement-actions" />.</para>
- <informalexample>
- <programlisting># action labels
- # Dockable label
- quicknotepad.label=QuickNotepad
- # Additional strings extracted from the plugin java source
- quicknotepad.choose-file.label=Choose notepad file
- quicknotepad.save-file.label=Save notepad file
- quicknotepad.copy-to-buffer.label=Copy notepad to buffer
- </programlisting>
- </informalexample>
- <para>Next, the plugin's menu is defined. See <xref
- linkend="plugin-implement-quicknotepadplugin" />.</para>
- <informalexample>
- <programlisting># application menu items
- quicknotepad.menu.label=QuickNotepad
- quicknotepad.menu=quicknotepad - quicknotepad.choose-file \
- quicknotepad.save-file quicknotepad.copy-to-buffer</programlisting>
- </informalexample>
- <para>We have created a small toolbar as a component of QuickNotepad, so
- file names for the button icons follow:</para>
- <informalexample>
- <programlisting># plugin toolbar buttons
- quicknotepad.choose-file.icon=Open.png
- quicknotepad.save-file.icon=Save.png
- quicknotepad.copy-to-buffer.icon=Edit.png</programlisting>
- </informalexample>
- <para>The menu item labels corresponding to these icons will also serve
- as tooltip text.</para>
- <para>Finally, the properties file set forth the labels and settings
- used by the option pane:</para>
- <informalexample>
- <programlisting># Option pane labels
- options.quicknotepad.label=QuickNotepad
- options.quicknotepad.file=File:
- options.quicknotepad.choose-file=Choose
- options.quicknotepad.choose-file.title=Choose a notepad file
- options.quicknotepad.choose-font=Font:
- options.quicknotepad.show-filepath.title=Display notepad file path
- # Initial default font settings
- options.quicknotepad.show-filepath=true
- options.quicknotepad.font=Monospaced
- options.quicknotepad.fontstyle=0
- options.quicknotepad.fontsize=14
- # Setting not defined but supplied for completeness
- options.quicknotepad.filepath=</programlisting>
- </informalexample>
- <tip>
- <title>PropertySideKick</title>
- <para>There is a SideKick for Property files, provided in the
- JavaSideKick plugin. This gives you a compact and sorted tree view
- of property files.</para>
- </tip>
- </section>
- <section id="plugin-implement-editbus">
- <title>The EditBus</title>
- <para>jEdit (and some plugins) generate several kinds of messages to
- alert plugins and other components of jedit-specific events. The message
- classes, all derived from <ulink
- url="../api/org/gjt/sp/jedit/EBMessage.html">
- <classname>EBMessage</classname></ulink> cover the opening and closing
- of the application, changes in the status of buffers and views, changes
- in user settings, as well as changes in the state of other program
- features. A full list of messages can be found in the <ulink
- url="../api/org/gjt/sp/jedit/msg/package-summary.html">org.gjt.sp.jedit.msg</ulink>
- package.</para>
- <para>For example, the ViewUpdate messages are all related to the jEdit
- View, or the top-level window. If the user creates multiple Views, a
- plugin may need to know when they are created or destroyed, so it would
- monitor ViewUpdate messages.</para>
- <para>BufferUpdate messages are all related to jEdit buffers. They let
- plugins know when a buffer has become dirty, when it is about to be
- closed, after it is closed, created, loaded, or saved. Each of these
- messages are described in further detail in the API docs.</para>
- <para>As another example, The Navigator plugin monitors a newly added
- (to jEdit 4.3) EBMessage of the kind <ulink
- url="../api/org/gjt/sp/jedit/BufferChanging.html">BufferChanging</ulink>.
- The BufferChanging event provides Navigator enough advance notice to
- save the TextArea's caret just before the current EditPane changes its
- active Buffer. The <literal>BufferChanged</literal> event, another
- <literal>EditPaneUpdate</literal> message, is thrown shortly afterward.
- This is not used by Navigator, but it is used by SideKick to determine
- when it is time to reparse the buffer.</para>
- <para>Plugins register <ulink
- url="../api/org/gjt/sp/jedit/EBComponent.html">
- <function>EBComponent</function></ulink> instances with the <ulink
- url="../api/org/gjt/sp/jedit/EditBus.html">
- <classname>EditBus</classname></ulink> to receive messages reflecting
- changes in jEdit's state.</para>
- <para><ulink url="../api/org/gjt/sp/jedit/EBComponent.html">
- <function>EBComponent</function></ulink>s are added and removed with the
- <ulink
- url="../api/org/gjt/sp/jedit/EditBus.html#addToBus(org.gjt.sp.jedit.EBComponent)">
- <function>EditBus.addToBus()</function></ulink> and <ulink
- url="../api/org/gjt/sp/jedit/EditBus.html#removeFromBus(org.gjt.sp.jedit.EBComponent)">
- <function>EditBus.removeFromBus()</function></ulink> methods.</para>
- <para>Typically, the <ulink
- url="../api/org/gjt/sp/jedit/EBComponent.html#handleMessage(org.gjt.sp.jedit.EBMessage)">
- <function>EBComponent.handleMessage()</function></ulink> method is
- implemented with one or more <function>if</function> blocks that test
- whether the message is an instance of a derived message class in which
- the component has an interest.</para>
- <programlisting>if(msg instanceof BufferUpdate) {
- // a buffer's state has changed!
- }
- else if(msg instanceof ViewUpdate) {
- // a view's state has changed!
- }
- // ... and so on</programlisting>
- <para>If a plugin core class will respond to EditBus messages, it can be
- derived from <ulink url="../api/org/gjt/sp/jedit/EBPlugin.html">
- <classname>EBPlugin</classname></ulink>, in which case no explicit
- <function>addToBus()</function> call is necessary. Otherwise, <ulink
- url="../api/org/gjt/sp/jedit/EditPlugin.html">
- <classname>EditPlugin</classname></ulink> will suffice as a plugin base
- class. Note that QuickNotepad uses the latter.</para>
- <tip>
- <title>Using the Activity Log to see the EditBus</title>
- <para> To determine precisely which EditBus messages are being sent by
- jEdit or the plugins, start up jEdit with an additional argument,
- <literal>-log=5</literal>. You can set an even lower log level to see
- further details (the default is 7). With a log level of 5 or lower, the
- Activity Log will include [notice]s, which will show us exactly which
- EditBus message is sent and when. See <xref linkend="activity-log" />
- for more details. </para> </tip>
- </section>
- <section id="plugin-implement-actions">
- <title>The Actions Catalog</title>
- <para>Actions define procedures that can be bound to a menu item, a
- toolbar button or a keyboard shortcut. Most plugin Actions <footnote>
- <para>Some plugins, such as Sidekick, Console, and
- ProjectViewer, create pure Java EditAction-derived Actions,
- based which services are available, or which files are found in
- a certain path. However, this is an advanced topic you can
- explore further in the source and API docs of those
- plugins.</para>
- </footnote> are short scripts written in BeanShell, jEdit's macro
- scripting language. These scripts either direct the action themselves,
- delegate to a method in one of the plugin's classes that encapsulates
- the action, or do a little of both. The scripts are usually short;
- elaborate action protocols are usually contained in compiled code,
- rather than an interpreted macro script, to speed execution.</para>
- <para>Actions are defined by creating an XML file entitled
- <filename>actions.xml</filename> and placing it in the plugin JAR
- file.</para>
- <para>The <filename>actions.xml</filename> file from the
- <application>QuickNotepad</application> plugin looks as follows:</para>
- <informalexample>
- <programlisting><ACTIONS>
- <ACTION NAME="quicknotepad.choose-file">
- <CODE>
- wm.addDockableWindow(QuickNotepadPlugin.NAME);
- wm.getDockableWindow(QuickNotepadPlugin.NAME).chooseFile();
- </CODE>
- </ACTION>
- <ACTION NAME="quicknotepad.save-file">
- <CODE>
- wm.addDockableWindow(QuickNotepadPlugin.NAME);
- wm.getDockableWindow(QuickNotepadPlugin.NAME).saveFile();
- </CODE>
- </ACTION>
- <ACTION NAME="quicknotepad.copy-to-buffer">
- <CODE>
- wm.addDockableWindow(QuickNotepadPlugin.NAME);
- wm.getDockableWindow(QuickNotepadPlugin.NAME).copyToBuffer();
- </CODE>
- </ACTION>
- </ACTIONS></programlisting>
- </informalexample>
- <para>This file defines three actions. They each use a built-in variable
- <literal>wm</literal>, which refers to the current view's <ulink
- url="../api/org/gjt/sp/jedit/gui/DockableWindowManager.html">
- <classname>DockableWindowManager</classname></ulink>. Whenever you need
- to obtain a reference to the current dockable, or create a new one, this
- is the class to use. We use the method <filename>addDockable() followed
- by getDockable()</filename> to create if necessary, and then bring up
- the QuickNotepad plugin dockable. This will be docked or floating,
- depending on how it was last used.</para>
- <para>When an action is invoked, the BeanShell scripts address the
- plugin through static methods, or if instance data is needed, the
- current <ulink url="../api/org/gjt/sp/jedit/View.html">
- <classname>View</classname></ulink>, its <ulink
- url="../api/org/gjt/sp/jedit/gui/DockableWindowManager.html">
- <classname>DockableWindowManager</classname></ulink>, and the plugin
- object return by the <filename>getDockable()</filename> method.</para>
- <para>If you are unfamiliar with BeanShell code, you may nevertheless
- notice that the code statements bear a strong resemblance to Java code,
- with one exception: the variable <varname>view</varname> is never
- assigned any value.</para>
- <para>For complete answers to this and other BeanShell mysteries, see
- <xref linkend="writing-macros-part" />; two observations will suffice
- here. First, the variable <varname>view</varname> is predefined by
- jEdit's implementation of BeanShell to refer to the current
- <classname>View</classname> object. Second, the BeanShell scripting
- language is based upon Java syntax, but allows variables to be typed at
- run time, so explicit types for variables need not be declared.</para>
- <para>A formal description of each element of the
- <filename>actions.xml</filename> file can be found in the documentation
- of the <ulink url="../api/org/gjt/sp/jedit/ActionSet.html">
- <classname>ActionSet</classname></ulink> class.</para>
- </section>
- <section id="plugin-implement-dockables">
- <title>The dockables.xml Window Catalog</title>
- <para>A Dockable is a window that can float like a dialog, or dock into
- jEdit's docking area. Each dockable needs a label (for display in menus,
- and on small buttons) and a title (for display in the floating window's
- title bar).</para>
- <para>The jEdit plugin API uses BeanShell to create the top-level
- visible container of a plugin's interface. The BeanShell code is
- contained in a file named <filename>dockables.xml</filename>. It usually
- is quite short, providing only a single BeanShell expression used to
- create a visible plugin window.</para>
- <para>The following example from the QuickNotepad plugin illustrates the
- requirements of the data file:</para>
- <informalexample>
- <programlisting><?xml version="1.0"?>
- <!DOCTYPE DOCKABLES SYSTEM "dockables.dtd">
- <DOCKABLES>
- <DOCKABLE NAME="quicknotepad">
- new QuickNotepad(view, position);
- </DOCKABLE>
- </DOCKABLES></programlisting>
- </informalexample>
- <para>In this example, the <classname><DOCKABLE></classname>
- element has a single attribute, the dockable window's identifier. This
- attribute is used to key a property where the window title is stored;
- see <xref linkend="plugin-implement-properties" />.</para>
- <para>For each dockable, jedit defines an action with the same name.
- This means you do not need to define an explicit action to create your
- dockable - in fact, jEdit defines three actions: "toggle", "get" and
- "new floating instance" for each.</para>
- <para>The contents of the <classname><DOCKABLE></classname>
- element itself is a BeanShell expression that constructs a new
- <classname>QuickNotepad</classname> object. The <varname>view</varname>
- and <varname>position</varname> are predefined by the plugin API as the
- view in which the plugin window will reside, and the docking position of
- the plugin. You can use <varname>position</varname> to customize the
- layout of your plugin depending on whether it appears on the sides, or
- the top/bottom, or as a floating dockable.</para>
- <para>A formal description of each element of the
- <filename>dockables.xml</filename> file can be found in the
- documentation of the <ulink
- url="../api/org/gjt/sp/jedit/gui/DockableWindowManager.html">
- <classname>DockableWindowManager</classname></ulink> class. This class
- also contains the public interface you should use for getting, showing,
- hiding, and other interactions with the plugin's top-level
- windows.</para>
- </section>
- <section id="plugin-implement-services">
- <title>The services.xml file</title>
- <para>A "service" is a mechanism by which one plugin can work with other
- plugins and avoid a bidirectional build-dependency. For example, the XML
- plugin "depends" on Sidekick, but in fact, it is SideKick which creates
- and operates on an object (a <literal>SideKickParser</literal>, in fact)
- defined in the XML plugin. In a way, the dependency is
- bidirectional.</para>
- <para>Similarly, the AntFarm plugin defines but does not instantiate a
- <literal>Shell</literal> object. It is the Console plugin which creates
- a specific shell for each available service. SideKick and Console use
- the ServiceManager to search for services offered by other
- plugins.</para>
- <para>Here is an example of a service from the XML plugin, which extends
- Sidekick:</para>
- <informalexample>
- <programlisting>
- <!DOCTYPE SERVICES SYSTEM "services.dtd">
- <SERVICES>
- <SERVICE CLASS="sidekick.SideKickParser" NAME="xml">
- new xml.parser.SAXParserImpl();
- </SERVICE>
- </SERVICES>
- </programlisting>
- </informalexample>
- <para>The object it returns tells Sidekick how it can parse files of a
- specific type. The API docs for SideKickParser indicate exactly which
- methods must be implemented in a plugin which offers this service. It
- should be enough information to let Sidekick, which has its own
- dockable, display the tree information in its own view.</para>
- <para>For more information about services, refer to the <ulink
- url="../api/org/gjt/sp/jedit/ServiceManager.html">ServiceManager</ulink>
- class API documentation. There, you can find out what the tags and
- attributes mean, as well as how to register and use services.</para>
- </section>
- <section id="plugin-implement-quicknotepad">
- <title>The QuickNotepad Class</title>
- <para>Here is where most of the features of the plugin will be
- implemented. To work with the dockable window API, the top level window
- will be a <classname>JPanel</classname>. The visible components reflect
- a simple layout. Inside the top-level panel we will place a scroll pane
- with a text area. Above the scroll pane we will place a panel containing
- a small tool bar and a label displaying the path of the current notepad
- file.</para>
- <para>We have identified three user actions that need implementation
- here: <function>chooseFile()</function>,
- <function>saveFile()</function>, and
- <function>copyToBuffer()</function>. As noted earlier, we also want the
- text area to change its appearance in immediate response to a change in
- user options settings. In order to do that, the window class must
- respond to a <classname>PropertiesChanged</classname> message from the
- EditBus.</para>
- <!-- <para>
- We could have the plugin core class receive and delegate
- <classname>PropertiesChanged</classname> messages to the window class.
- However, this would require the plugin core class to hold a reference
- to either the plugin window class or the visible window class and to
- update that reference when the user activates or deactivates the
- plugin. It is simpler to have the plugin window class subscribe to the
- EditBus directly; many plugins take this approach. This means that
- <classname>QuickNotepad</classname> must implement the
- <classname>EBComponent</classname> interface.
- </para> -->
- <para>Unlike the <classname>EBPlugin</classname> class, the
- <classname>EBComponent</classname> interface does not deal with the
- component's actual subscribing and unsubscribing to the EditBus. To
- accomplish this, we use a pair of methods inherited from the Java
- platform's <classname>JComponent</classname> class that are called when
- the window is made visible, and when it is hidden. These two methods,
- <function>addNotify()</function> and
- <function>removeNotify()</function>, are overridden to add and remove
- the visible window from the list of EditBus subscribers.</para>
- <para>We will provide for two minor features when the notepad is
- displayed in the floating window. First, when a floating plugin window
- is created, we will give the notepad text area input focus. Second, when
- the notepad if floating and has input focus, we will have the
- <keycap>Escape</keycap> key dismiss the notepad window. An
- <classname>AncestorListener</classname> and a
- <classname>KeyListener</classname> will implement these details.</para>
- <para>Here is the listing for the data members, the constructor, and the
- implementation of the <classname>EBComponent</classname>
- interface:</para>
- <informalexample>
- <programlisting>public class QuickNotepad extends JPanel
- implements EBComponent
- {
- private String filename;
- private String defaultFilename;
- private View view;
- private boolean floating;
- private QuickNotepadTextArea textArea;
- private QuickNotepadToolPanel toolPanel;
- //
- // Constructor
- //
- public QuickNotepad(View view, String position)
- {
- super(new BorderLayout());
- this.view = view;
- this.floating = position.equals(
- DockableWindowManager.FLOATING);
- this.filename = jEdit.getProperty(
- QuickNotepadPlugin.OPTION_PREFIX
- + "filepath");
- if(this.filename == null || this.filename.length() == 0)
- {
- this.filename = new String(jEdit.getSettingsDirectory()
- + File.separator + "qn.txt");
- jEdit.setProperty(QuickNotepadPlugin.OPTION_PREFIX
- + "filepath",this.filename);
- }
- this.defaultFilename = new String(this.filename);
- this.toolPanel = new QuickNotepadToolPanel(this);
- add(BorderLayout.NORTH, this.toolPanel);
- if(floating)
- this.setPreferredSize(new Dimension(500, 250));
- textArea = new QuickNotepadTextArea();
- textArea.setFont(QuickNotepadOptionPane.makeFont());
- textArea.addKeyListener(new KeyHandler());
- textArea.addAncestorListener(new AncestorHandler());
- JScrollPane pane = new JScrollPane(textArea);
- add(BorderLayout.CENTER, pane);
- readFile();
- }
- //
- // Attribute methods
- //
- // for toolBar display
- public String getFilename()
- {
- return filename;
- }
- //
- // EBComponent implementation
- //
- public void handleMessage(EBMessage message)
- {
- if (message instanceof PropertiesChanged)
- {
- propertiesChanged();
- }
- }
- private void propertiesChanged()
- {
- String propertyFilename = jEdit.getProperty(
- QuickNotepadPlugin.OPTION_PREFIX + "filepath");
- if(!defaultFilename.equals(propertyFilename))
- {
- saveFile();
- toolPanel.propertiesChanged();
- defaultFilename = propertyFilename.clone();
- filename = defaultFilename.clone();
- readFile();
- }
- Font newFont = QuickNotepadOptionPane.makeFont();
- if(!newFont.equals(textArea.getFont()))
- {
- textArea.setFont(newFont);
- textArea.invalidate();
- }
- }
- // These JComponent methods provide the appropriate points
- // to subscribe and unsubscribe this object to the EditBus
- public void addNotify()
- {
- super.addNotify();
- EditBus.addToBus(this);
- }
- public void removeNotify()
- {
- saveFile();
- super.removeNotify();
- EditBus.removeFromBus(this);
- }
- ...
- }</programlisting>
- </informalexample>
- <para>This listing refers to a
- <classname>QuickNotebookTextArea</classname> object. It is currently
- implemented as a <classname>JTextArea</classname> with word wrap and tab
- sizes hard-coded. Placing the object in a separate class will simply
- future modifications.</para>
- </section>
- <section id="plugin-implement-quicknotepadtoolbar">
- <title>The QuickNotepadToolBar Class</title>
- <para>There is nothing remarkable about the toolbar panel that is placed
- inside the <classname>QuickNotepad</classname> object. The constructor
- shows the continued use of items from the plugin's properties
- file.</para>
- <informalexample>
- <programlisting>public class QuickNotepadToolPanel extends JPanel
- {
- private QuickNotepad pad;
- private JLabel label;
- public QuickNotepadToolPanel(QuickNotepad qnpad)
- {
- pad = qnpad;
- JToolBar toolBar = new JToolBar();
- toolBar.setFloatable(false);
- toolBar.add(makeCustomButton("quicknotepad.choose-file",
- new ActionListener() {
- public void actionPerformed(ActionEvent evt) {
- QuickNotepadToolPanel.this.pad.chooseFile();
- }
- }));
- toolBar.add(makeCustomButton("quicknotepad.save-file",
- new ActionListener() {
- public void actionPerformed(ActionEvent evt) {
- QuickNotepadToolPanel.this.pad.saveFile();
- }
- }));
- toolBar.add(makeCustomButton("quicknotepad.copy-to-buffer",
- new ActionListener() {
- public void actionPerformed(ActionEvent evt) {
- QuickNotepadToolPanel.this.pad.copyToBuffer();
- }
- }));
- label = new JLabel(pad.getFilename(),
- SwingConstants.RIGHT);
- label.setForeground(Color.black);
- label.setVisible(jEdit.getProperty(
- QuickNotepadPlugin.OPTION_PREFIX
- + "show-filepath").equals("true"));
- this.setLayout(new BorderLayout(10, 0));
- this.add(BorderLayout.WEST, toolBar);
- this.add(BorderLayout.CENTER, label);
- this.setBorder(BorderFactory.createEmptyBorder(0, 0, 3, 10));
- }
- ...
- }</programlisting>
- </informalexample>
- <para>The method <classname>makeCustomButton()</classname> provides
- uniform attributes for the three toolbar buttons corresponding to three
- of the plugin's use actions. The menu titles for the user actions serve
- double duty as tooltip text for the buttons. There is also a
- <function>propertiesChanged()</function> method for the toolbar that
- sets the text and visibility of the label containing the notepad file
- path.</para>
- </section>
- <section id="plugin-implement-options">
- <title>The QuickNotepadOptionPane Class</title>
- <para>Using the default implementation provided by
- <classname>AbstractOptionPane</classname> reduces the preparation of an
- option pane to two principal tasks: writing a
- <function>_init()</function> method to layout and initialize the pane,
- and writing a <function>_save()</function> method to commit any settings
- changed by user input. If a button on the option pane should trigger
- another dialog, such as a <classname>JFileChooser</classname> or jEdit's
- own enhanced <classname>VFSFileChooserDialog</classname>, the option
- pane will also have to implement the
- <classname>ActionListener</classname> interface to display additional
- components.</para>
- <para>The QuickNotepad plugin has only three options to set: the path
- name of the file that will store the notepad text, the visibility of the
- path name on the tool bar, and the notepad's display font. Using the
- shortcut methods of the plugin API, the implementation of
- <function>_init()</function> looks like this:</para>
- <informalexample>
- <programlisting>public class QuickNotepadOptionPane extends AbstractOptionPane
- implements ActionListener
- {
- private JTextField pathName;
- private JButton pickPath;
- private FontSelector font;
- ...
- public void _init()
- {
- showPath = new JCheckBox(jEdit.getProperty(
- QuickNotepadPlugin.OPTION_PREFIX
- + "show-filepath.title"),
- jEdit.getProperty(
- QuickNotepadPlugin.OPTION_PREFIX + "show-filepath")
- .equals("true"));
- addComponent(showPath);
- pathName = new JTextField(jEdit.getProperty(
- QuickNotepadPlugin.OPTION_PREFIX
- + "filepath"));
- JButton pickPath = new JButton(jEdit.getProperty(
- QuickNotepadPlugin.OPTION_PREFIX
- + "choose-file"));
- pickPath.addActionListener(this);
- JPanel pathPanel = new JPanel(new BorderLayout(0, 0));
- pathPanel.add(pathName, BorderLayout.CENTER);
- pathPanel.add(pickPath, BorderLayout.EAST);
- addComponent(jEdit.getProperty(
- QuickNotepadPlugin.OPTION_PREFIX + "file"),
- pathPanel);
- font = new FontSelector(makeFont());
- addComponent(jEdit.getProperty(
- QuickNotepadPlugin.OPTION_PREFIX + "choose-font"),
- font);
- }
- ...
- }</programlisting>
- </informalexample>
- <para>Here we adopt the vertical arrangement offered by use of the
- <function>addComponent()</function> method with one embellishment. We
- want the first <quote>row</quote> of the option pane to contain a text
- field with the current notepad file path and a button that will trigger
- a file chooser dialog when pressed. To place both of them on the same
- line (along with an identifying label for the file option), we create a
- <classname>JPanel</classname> to contain both components and pass the
- configured panel to <function>addComponent()</function>.</para>
- <para>The <function>_init()</function> method uses properties from the
- plugin's property file to provide the names of label for the components
- placed in the option pane. It also uses a property whose name begins
- with <function>PROPERTY_PREFIX</function> as a persistent data item -
- the path of the current notepad file. The elements of the notepad's font
- are also extracted from properties using a static method of the option
- pane class.</para>
- <para>The <function>_save()</function> method extracts data from the
- user input components and assigns them to the plugin's properties. The
- implementation is straightforward:</para>
- <informalexample>
- <programlisting>public void _save()
- {
- jEdit.setProperty(QuickNotepadPlugin.OPTION_PREFIX
- + "filepath", pathName.getText());
- Font _font = font.getFont();
- jEdit.setProperty(QuickNotepadPlugin.OPTION_PREFIX
- + "font", _font.getFamily());
- jEdit.setProperty(QuickNotepadPlugin.OPTION_PREFIX
- + "fontsize", String.valueOf(_font.getSize()));
- jEdit.setProperty(QuickNotepadPlugin.OPTION_PREFIX
- + "fontstyle", String.valueOf(_font.getStyle()));
- jEdit.setProperty(QuickNotepadPlugin.OPTION_PREFIX
- + "show-filepath", String.valueOf(showPath.isSelected()));
- }</programlisting>
- </informalexample>
- <para>The class has only two other methods, one to display a file
- chooser dialog in response to user action, and the other to construct a
- <classname>Font</classname> object from the plugin's font properties.
- They do not require discussion here.</para>
- </section>
- <section id="plugin-implement-docs">
- <title>Plugin Documentation</title>
- <para>While not required by the plugin API, a help file is an essential
- element of any plugin written for public release. A single web page is
- often all that is required. There are no specific requirements on
- layout, but because of the design of jEdit's help viewer, the use of
- frames should be avoided. Topics that would be useful include the
- following:</para>
- <itemizedlist>
- <listitem>
- <para>a description of the purpose of the plugin;</para>
- </listitem>
- <listitem>
- <para>an explanation of the type of input the user can supply
- through its visible interface (such as mouse action or text
- entry in controls);</para>
- </listitem>
- <listitem>
- <para>a listing of available user actions that can be taken when
- the plugin does not have input focus;</para>
- </listitem>
- <listitem>
- <para>a summary of configuration options;</para>
- </listitem>
- <listitem>
- <para>information on development of the plugin (such as a change
- log, a list of <quote>to do</quote> items, and contact
- information for the plugin's author); and</para>
- </listitem>
- <listitem>
- <para>licensing information, including acknowledgments for any
- library software used by the plugin.</para>
- </listitem>
- </itemizedlist>
- <para>The location of the plugin's help file is stored in the
- <literal>plugin.QuickNotepad.docs</literal> property; see <xref
- linkend="plugin-implement-properties" />.</para>
- </section>
- <section id="plugin-implement-building">
- <title>The build.xml Ant build file</title>
- <para>We have already outlined the contents of the user action catalog,
- the properties file and the documentation file in our earlier
- discussion. The final step is to compile the source file and build the
- archive file that will hold the class files and the plugin's other
- resources.</para>
- <para>Publicly released plugins include with their source a makefile in
- XML format for the <application>Ant</application> utility. The format
- for this file requires few changes from plugin to plugin. Here is a
- version of <filename>build.xml</filename> that could be used by
- QuickNotepad:</para>
- <informalexample>
- <programlisting>
- <project name="QuickNotepad"
- default="build">
- <description>
- This is an ant build.xml file for building the QuickNotepad plugin for jEdit.
- </description>
- <property name="user-doc.xml"
- value="users-guide.xml" />
- <property file="build.properties" />
- <property file="../build.properties" />
- <property name="build.support…
Large files files are truncated, but you can click here to view the full file