/jEdit/tags/jedit-4-2-pre8/doc/users-guide/plugin-implement.xml
# · XML · 1129 lines · 931 code · 180 blank · 18 comment · 0 complexity · 1c5173736de1a4921285d18c73916e5a MD5 · raw file
- <!-- jEdit 4.0 Plugin Guide, (C) 2001, 2002 John Gellene -->
- <!-- jEdit buffer-local properties: -->
- <!-- :indentSize=1:tabSize=2:noTabs=true:maxLineLen=72: -->
- <!-- :xml.root=users-guide.xml: -->
- <!-- Sat Jun 23 08:54:21 EDT 2001 @579 /Internet Time/ -->
- <!-- This chapter of the jEdit 3.2 Plugin Guide -->
- <!-- discusses how to implement a plugin -->
- <chapter id="plugin-implement"><title>Implementing a Simple Plugin</title>
- <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>
- <sect1 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>
- Plugin loading occurs after jEdit has loaded application properties, any
- user-supplied properties, and the application's set of actions that
- will be available from jEdit's menu bar (as well as the toolbar and
- keyboard shortcuts).
- </para>
- <para>
- Plugin loading occurs before jEdit opens the initial view or loads any files
- for editing. It also occurs before jEdit runs any startup scripts.
- </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/EditPlugin.JAR.html">
- <classname>EditPlugin.JAR</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. 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/EditPlugin.JAR.html">
- <classname>EditPlugin.JAR</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>
- </sect1>
- <sect1 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>
- </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 MENU = "quicknotepad.menu";
- public static final String PROPERTY_PREFIX
- = "plugin.QuickNotepadPlugin.";
- public static final String OPTION_PREFIX
- = "options.quicknotepad.";
- </programlisting></informalexample>
- <para>
- First we define a few static
- <classname>String</classname> data members to enforce consistent syntax
- for the name of properties we will use throughout the plugin.
- </para>
- </listitem>
- <listitem>
- <informalexample><programlisting>
- public void createMenuItems(Vector menuItems) {
- menuItems.addElement(GUIUtilities.loadMenu(MENU));
- }</programlisting></informalexample>
- <para>
- This implementation of
- the <ulink url="../api/org/gjt/sp/jedit/EditPlugin.html#createMenuItems(java.util.Vector)">
- <classname>EditPlugin.createMenuItems()</classname></ulink> method
- is very typical.
- It uses a jEdit utility function to create the menu, taking the list
- of actions from the <filename>quicknotepad</filename> property, and
- the label from <filename>quotenotepad.label</filename>.
- </para>
- <para>
- If the plugin only had a single menu item (for example, an item
- activating a dockable window), we would call
- <ulink url="../api/org/gjt/sp/jedit/GUIUtilities.html#loadMenuItem(java.lang.String)">
- <function>GUIUtilities.loadMenuItem()</function></ulink> instead of
- <ulink url="../api/org/gjt/sp/jedit/GUIUtilities.html#loadMenu(java.lang.String)">
- <function>GUIUtilities.loadMenu()</function></ulink>.
- </para>
- </listitem>
- <listitem>
- <informalexample><programlisting>public void createOptionPanes(OptionsDialog od) {
- od.addOptionPane(new QuickNotepadOptionPane());
- }
- }</programlisting></informalexample>
- <para>
- This implementation of
- the <ulink
- url="../api/org/gjt/sp/jedit/EditPlugin.html#createOptionPanes(org.gjt.sp.jedit.gui.OptionsDialog)">
- <classname>EditPlugin.createOptionPanes()</classname></ulink> method
- adds a new instance of <classname>QuickNotepadOptionPane</classname>
- to the given instance of the <guimenuitem>Global Options</guimenuitem>
- dialog box.
- </para>
- </listitem>
- </itemizedlist>
- </sect1>
- <sect1 id="plugin-implement-editbus"><title>The EditBus</title>
- <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>
- The message
- classes 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>
- <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>
- </sect1>
- <sect1 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.
- </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 the <filename>QuickNotepad.props</filename> file
- 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.name=QuickNotepad
- plugin.QuickNotepadPlugin.author=John Gellene
- plugin.QuickNotepadPlugin.version=4.1
- plugin.QuickNotepadPlugin.docs=QuickNotepad.html
- plugin.QuickNotepadPlugin.depend.0=jedit 04.00.01.00</programlisting></informalexample>
- <para>
- These properties are 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.
- Actions are discussed in detail
- in <xref linkend="plugin-implement-actions"/>.
- </para>
- <informalexample><programlisting># action labels
- quicknotepad.label=QuickNotepad
- 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>
- </sect1>
- <sect1 id="plugin-implement-actions"><title>The Action Catalog</title>
- <para>
- Actions define procedures that can be bound to a menu
- item, a toolbar button or a keyboard shortcut. Actions 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><?xml version="1.0"?>
- <!DOCTYPE ACTIONS SYSTEM "actions.dtd">
- <ACTIONS>
- <ACTION NAME="quicknotepad.choose-file">
- <CODE>
- view.getDockableWindowManager()
- .getDockable(QuickNotepadPlugin.NAME).chooseFile();
- </CODE>
- </ACTION>
- <ACTION NAME="quicknotepad.save-file">
- <CODE>
- view.getDockableWindowManager()
- .getDockable(QuickNotepadPlugin.NAME).saveFile();
- </CODE>
- </ACTION>
- <ACTION NAME="quicknotepad.copy-to-buffer">
- <CODE>
- view.getDockableWindowManager()
- .getDockable(QuickNotepadPlugin.NAME).copyToBuffer();
- </CODE>
- </ACTION>
- </ACTIONS></programlisting></informalexample>
- <para>
- This file defines three actions. They use the current view's
- <classname>DockableWindowManager</classname> object and the method
- <filename>getDockable()</filename> to find the QuickNotepad plugin
- window and call the desired method.
- </para>
- <para>
- When an action is invoked, the BeanShell scripts address
- the plugin through static methods, or if instance data is needed, the
- current <classname>View</classname>, its
- <classname>DockableWindowManager</classname>, 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>
- </sect1>
- <sect1 id="plugin-implement-dockables"><title>The Dockable Window Catalog</title>
- <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><![CDATA[<?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>
- 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.
- </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.
- </para>
- </sect1>
- <sect1 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>
- </sect1>
- <sect1 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>
- </sect1>
- <sect1 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>
- </sect1>
- <sect1 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>
- </sect1>
- <sect1 id="plugin-implement-building"><title>Compiling the Plugin</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 the version of
- <filename>build.xml</filename> used by QuickNotepad and many other
- plugins:
- </para>
- <informalexample><programlisting><![CDATA[<project name="QuickNotepad" default="dist" basedir=".">
- <property name="jedit.install.dir" value="../.."/>
- <property name="jar.name" value="QuickNotepad.jar"/>
- <property name="install.dir" value=".."/>
- <path id="project.class.path">
- <pathelement location="${jedit.install.dir}/jedit.jar"/>
- <pathelement location="."/>
- </path>
- <target name="compile">
- <javac
- srcdir="."
- deprecation="on"
- includeJavaRuntime="yes"
- >
- <classpath refid="project.class.path"/>
- </javac>
- </target>
- <target name="dist" depends="compile">
- <mkdir dir="${install.dir}"/>
- <jar jarfile="${install.dir}/${jar.name}">
- <fileset dir=".">
- <include name="**/*.class"/>
- <include name="**/*.props"/>
- <include name="**/*.html"/>
- <include name="actions.xml"/>
- <include name="dockables.xml"/>
- </fileset>
- </jar>
- </target>
- </project>]]></programlisting></informalexample>
- <para>
- For a full discussion of the <filename>Ant</filename> file format and
- command syntax, you should consult the <ulink
- url="http://jakarta.apache.org/ant/manual/index.html">Ant
- documentation site</ulink>. Modifying this makefile for a different
- plugin will likely only require three changes:
- </para>
- <itemizedlist>
- <listitem><para>
- the name of the plugin;
- </para></listitem>
- <listitem><para>
- the choice of compiler (made by inserting and deleting the comment character
- <userinput>'#'</userinput>); and
- </para> </listitem>
- <listitem><para>
- the classpath variables for <filename>jedit.jar</filename>
- any plugins this one depends on.
- </para></listitem>
- </itemizedlist>
- <para>
- If you have reached this point in the text, you are probably serious
- about writing a plugin for jEdit. Good luck with your efforts, and
- thank you for contributing to the jEdit project.
- </para>
- </sect1>
- </chapter>