All Downloads are FREE. Search and download functionalities are using the official Maven repository.

docbook.xml.plugin-implement.xml Maven / Gradle / Ivy

There is a newer version: 9.11.2
Show newest version
<!-- 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                      -->
<?xml-stylesheet href="/docbook/wysiwygdocbook1.01/driver.css" type="text/css"?>
<!-- ?xml-stylesheet href="/docbook/MozBook/MozBook.css" type="text/css"? -->
<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>
  Additionally, plugins using the new 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>
  Note that plugins using the older jEdit 4.1 API are still only loaded on editor startup, and unloaded on editor exit. The jEdit 4.1 API is deprecated and will not be described in this guide.
</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. 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>

<sidebar>

<title>Updating 4.1 plugins</title>

<para>
 Note that while jEdit 4.1 plugins were only loaded on startup, jEdit 4.2 plugins can be loaded at any time. As a result, the <function>start()</function> method needs to cope with being called at any time, and <function>stop()</function> needs to fully clean up after the plugin. See the API documentation for the <ulink url="../api/org/gjt/sp/jedit/EditPlugin.html">
  <classname>EditPlugin</classname></ulink> class for details.
 </para>
</sidebar>

</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.activate=defer
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.02.10.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>

<sidebar>

<title>Updating 4.1 plugins</title>

<para>
 jEdit 4.2 plugins are distinguished from jEdit 4.1 plugins by the presence of the <literal>plugin.<replaceable>name</replaceable>.activate</literal> property. If this property is set, the plugin is treated like a jEdit 4.2 plugin. Usually, this property should be set to <literal>defer</literal>. See the API documentation for the <ulink url="../api/org/gjt/sp/jedit/EditPlugin.html">
  <classname>EditPlugin</classname></ulink> class for details.
 </para>
</sidebar>

</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><![CDATA[<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 use the current view's
  <ulink url="../api/org/gjt/sp/jedit/gui/DockableWindowManager.html">
  <classname>DockableWindowManager</classname></ulink> 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 <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>

</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>&lt;DOCKABLE&gt;</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>&lt;DOCKABLE&gt;</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>

</sect1>

<sect1 id="plugin-implement-reloading"><title>Reloading the Plugin</title>

<para>
  Once you have compiled your plugin using the 4.2 API you will need to reload
  it to test it. Follow these steps to reload your plugin without restarting jEdit:
</para>

<itemizedlist>
  <listitem><para>
   From the Plugins menu open the Plugin Manager.
  </para></listitem>
  <listitem><para>
    On the Manage tab uncheck Hide libraries. This will
    allow you to see plugins that are not loaded.
  </para></listitem>
  <listitem><para>
    Find the plugin on the Manage tab and uncheck it. This will unload the plugin.
    You will get a warning if this plugin does not support dynamic reloading.
    If you get that warning you will need to restart jEdit to reload the plugin
    until the plugin is converted over to the 4.2 API.
  </para></listitem>
  <listitem><para>
    Recheck the plugin to reload it.
  </para></listitem>
</itemizedlist>


<para>
  The jEdit web site contains a macro and an Ant task that can be used as an alternative
  method for dynamically reloading plugins.
</para>



<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>




© 2015 - 2025 Weber Informatics LLC | Privacy Policy