PageRenderTime 45ms CodeModel.GetById 29ms app.highlight 10ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-1-pre5/doc/users-guide/plugin-implement.xml

#
XML | 1089 lines | 874 code | 176 blank | 39 comment | 0 complexity | 27f06d50d03eb531e87a4b2b33fe410f MD5 | raw file
   1<!-- jEdit 4.0 Plugin Guide, (C) 2001, 2002 John Gellene            -->
   2
   3<!-- jEdit buffer-local properties:                           -->
   4<!-- :indentSize=1:tabSize=2:noTabs=true:maxLineLen=72:       -->
   5
   6<!-- Sat Jun 23 08:54:21 EDT 2001 @579 /Internet Time/        -->
   7
   8<!-- This chapter of the jEdit 3.2 Plugin Guide               -->
   9<!-- discusses how to implement a plugin                      -->
  10
  11<chapter id="plugin-implement"><title>Writing a Plugin</title>
  12
  13<para>
  14    One way to organize a plugin project is to design the software
  15    as if it were a <quote>stand alone</quote> application, with three
  16    exceptions:
  17</para>
  18
  19<itemizedlist>
  20  <listitem>
  21    <para>
  22      The plugin can access the <classname>View</classname> object with
  23      which it is associated, as well as static methods of the
  24      <classname>jEdit</classname> class, to obtain and manipulate various
  25      data and host application objects;
  26    </para>
  27  </listitem>
  28  <listitem>
  29    <para>
  30      If the plugin has visible components, they are ultimately contained in a
  31      <classname>JPanel</classname> object instead of a top-level frame
  32      window; and
  33    </para>
  34  </listitem>
  35  <listitem>
  36    <para>
  37      The plugin implements the necessary elements of the jEdit plugin API
  38      that were outlined in the last chapter: a plugin core class,
  39      perhaps a number of plugin
  40      window classes, maybe a plugin option pane class, and a set of
  41      required plugin resources.
  42    </para>
  43    <para>
  44      Not every plugin has configurable options; some do not have a
  45      visible window. However, all will need a plugin
  46      core class and a minimum set of other resources.
  47    </para>
  48  </listitem>
  49</itemizedlist>
  50
  51<para>
  52  We will now illustrate this approach by introducing an example plugin.
  53</para>
  54
  55<sect1 id="plugin-example"><title>QuickNotepad: An Example Plugin</title>
  56
  57<para>
  58  There are many applications for the leading operating systems that
  59  provide a <quote>scratch-pad</quote> or <quote>sticky note</quote>
  60  facility for the desktop display. A similar type of facility operating
  61  within the jEdit display would be a convenience. The use of docking
  62  windows would allow the notepad to be displayed or hidden with a single
  63  mouse click or keypress (if a keyboard shortcut were defined). The
  64  contents of the notepad could be saved at program exit (or, if earlier,
  65  deactivation of the plugin) and retrieved at program startup or plugin
  66  activation.
  67</para>
  68
  69<para>
  70  We will keep the capabilities of this plugin modest, but a few
  71  other features would be worthwhile. The user should be able to write
  72  the contents of the notepad to storage on demand.  It should also be
  73  possible to choose the name and location of the file that will be
  74  used to hold the notepad text.  This would allow the user to load
  75  other files into the notepad display.  The path of the notepad file
  76  should be displayed in the plugin window, but will give the user the
  77  option to hide the file name. Finally, there should be an action
  78  by which a single click or keypress would cause the contents of the
  79  notepad to be written to the new text buffer for further processing.
  80</para>
  81
  82<para>
  83  The full source code for QuickNotepad is contained in jEdit's
  84  source code distribution.  We will provide excerpts in this discussion
  85  where it is helpful to illustrate specific points. You are invited
  86  to obtain the source code for further study or to use as a starting point
  87  for your own plugin.
  88</para>
  89
  90</sect1>
  91
  92<sect1 id="plugin-implement-class"><title>Writing a Plugin Core Class</title>
  93
  94<para>
  95    The major issues encountered when writing a plugin core class arise
  96    from the developer's decisions on what features the plugin will make
  97    available. These issues have implications for other plugin elements
  98    as well.
  99</para>
 100
 101<itemizedlist>
 102  <listitem><para>
 103    Will the plugin provide for actions that the user can trigger using
 104    jEdit's menu items, toolbar buttons and keyboard shortcuts?
 105  </para></listitem>
 106  <listitem><para>
 107    Will the plugin have its own visible interface?
 108  </para></listitem>
 109  <listitem><para>
 110    Will the plugin
 111    respond to any EditBus messages reflecting changes in the host
 112    application's state?
 113  </para></listitem>
 114  <listitem><para>
 115    Will the plugin have settings that the user can configure?
 116  </para></listitem>
 117
 118</itemizedlist>
 119
 120<sect2 id="implement-plugin-choose-base"><title>Choosing a Base Class</title>
 121
 122<para>
 123  If the plugin will respond to EditBus
 124  messages, it must be derived from <classname>EBPlugin</classname>.
 125  Otherwise, <classname>EditPlugin</classname> will suffice as a
 126  base class.
 127</para>
 128
 129<para>
 130  Knowing what types of messages are made available by the plugin API is
 131  obviously helpful is determining both the base plugin class and the
 132  contents of a <filename>handleMessage()</filename> method. The message
 133  classes derived from <classname>EBMessage</classname> cover the opening
 134  and closing of the application, changes in the status of text buffers
 135  and their container and changes in user settings, as well as changes in
 136  the state of other program features. Specific message classes of potential
 137  interest to a plugin include the following:
 138</para>
 139
 140<itemizedlist>
 141  <listitem>
 142    <para>
 143      <classname>EditorStarted</classname>, sent during the
 144      application's startup routine, just prior to the creation of the
 145      initial <classname>View</classname>;
 146    </para>
 147  </listitem>
 148  <listitem>
 149    <para>
 150      <classname>EditorExitRequested</classname>, sent when a request to
 151      exit has been made, but before saving open buffers and writing user
 152      settings to storage;
 153    </para>
 154  </listitem>
 155  <listitem>
 156    <para>
 157      <classname>EditorExiting</classname>, sent just before jEdit
 158      actually exits;
 159    </para>
 160  </listitem>
 161  <listitem>
 162    <para>
 163      <classname>EditPaneUpdate</classname>, sent when an edit pane
 164      containing a text area (including a pane created by splitting
 165      an existing window) has been created or destroyed, or when a
 166      buffer displayed in an edit pane has been changed;
 167    </para>
 168  </listitem>
 169  <listitem>
 170    <para>
 171      <classname>BufferUpdate</classname>, sent when a text buffer is
 172      created, loaded, or being saved, or when its editing mode or
 173      markers have changed;
 174    </para>
 175  </listitem>
 176  <listitem>
 177    <para>
 178      <classname>ViewUpdate</classname>, sent when a <classname>View</classname>
 179      is created or closed; and
 180    </para>
 181  </listitem>
 182  <listitem>
 183    <para>
 184      <classname>PropertiesChanged</classname>, sent when the properties
 185      of the application or a plugin has been changed through the
 186      <guimenuitem>General Options</guimenuitem> dialog;
 187    </para>
 188  </listitem>
 189</itemizedlist>
 190
 191<para>
 192  Detailed documentation for each message class can be found in <xref
 193  linkend="api-message" />.
 194</para>
 195
 196<!-- TODO: include data members of derived message classes in appendix -->
 197
 198</sect2>
 199
 200<sect2 id="plugin-implement-base-methods">
 201<title>Implementing Base Class Methods</title>
 202
 203<sect3 id="plugin-implement-base-general">
 204<title>General Considerations</title>
 205
 206<para>
 207  Whether <classname>EditPlugin</classname> or <classname>EBPlugin</classname>
 208  is selected as the base of the plugin core
 209  class, the implementations of <function>start()</function> and
 210  <function>stop()</function> in the plugin's derived class are likely to
 211  be trivial, or not present at all (in which case they will be
 212  <quote>no-ops</quote> inherited from <classname>EditPlugin</classname>).
 213</para>
 214
 215<!--
 216<para>
 217  If the plugin is to use the dockable window API, the
 218  <quote> internal names</quote> of any dockable windows must be
 219  registered with the EditBus component.
 220  The EditBus stores such information in one of a number of
 221  <quote>named lists</quote>. Here is how the QuickNotepad plugin
 222  registers its dockable window:
 223</para>
 224
 225<informalexample>
 226<programlisting>EditBus.addToNamedList(DockableWindow.DOCKABLE_WINDOW_LIST, NAME);</programlisting>
 227</informalexample>
 228
 229<para>
 230  The first parameter is a <classname>String</classname> constant
 231  identifying the dockable window list. The second is a static
 232  <classname>String</classname> constant which is initialized in the
 233  plugin core class as the dockable window's internal name.
 234</para>
 235 -->
 236<para>
 237  The plugin core class can include <literal>static
 238  final String</literal> data members containing information to be registered
 239  with the EditBus or key names for certain types of plugin properties.
 240  This makes it easier to refer to the
 241  information when a method such as <function>handleMessage()</function>
 242  examines the contents of a message. The kind of data that can be handled
 243  in this fashion include the following:
 244</para>
 245
 246<itemizedlist>
 247  <listitem>
 248    <para>the name of the plugin;</para>
 249  </listitem>
 250  <listitem>
 251    <para>
 252      a label for identifying the plugin's menu;
 253    </para>
 254  </listitem>
 255  <listitem>
 256  <para>
 257     a prefix for labeling properties required by the plugin API; and
 258    </para>
 259  </listitem>
 260  <listitem>
 261    <para>
 262      a prefix to be used in labeling items used in the plugin's option pane
 263    </para>
 264  </listitem>
 265</itemizedlist>
 266
 267</sect3>
 268
 269<sect3 id="plugin-implement-base-example">
 270<title>Example Plugin Core Class</title>
 271
 272<para>
 273  We will derive the plugin core class for QuickNotepad from
 274  <classname>EditPlugin</classname>, since there are no EditBus
 275  messages to which the plugin core class need respond.  There are no special
 276  initialization or shut down chores to perform, so we will not need
 277  a <filename>start()</filename> or <filename>stop()</filename> method.
 278  We will define a few static
 279  <classname>String</classname> data members to enforce consistent syntax
 280  for the name of properties we will use throughout the plugin.
 281  Finally, we will use a standalone plugin
 282  window class to separate the functions of that class from the visible
 283  component class we will create.
 284</para>
 285
 286<para>
 287  The resulting plugin core class is lightweight and straightforward to implement:
 288</para>
 289
 290
 291
 292<informalexample><programlisting>public class QuickNotepadPlugin extends EBPlugin {
 293    public static final String NAME = "quicknotepad";
 294    public static final String MENU = "quicknotepad.menu";
 295    public static final String PROPERTY_PREFIX
 296        = "plugin.QuickNotepadPlugin.";
 297    public static final String OPTION_PREFIX
 298        = "options.quicknotepad.";
 299
 300    public void createMenuItems(Vector menuItems) {
 301        menuItems.addElement(GUIUtilities.loadMenu(MENU));
 302    }
 303
 304    public void createOptionPanes(OptionsDialog od) {
 305        od.addOptionPane(new QuickNotepadOptionPane());
 306    }
 307
 308
 309}</programlisting></informalexample>
 310
 311
 312<para>
 313  The implementations of <function>createMenuItems()</function> and
 314  <function>createOptionPane()</function>
 315  are typically trivial, because the real work will be done using other
 316  plugin elements. Menu creation is performed by a utility function in
 317  jEdit's API, using properties defined in the plugin's properties file.
 318  The option pane is constructed in its own class.
 319</para>
 320
 321<para>
 322  If the plugin only had a single menu item (for example, a checkbox item
 323  that toggled activation of a dockable window), we would call
 324  <function>GUIUtilities.loadMenuItem()</function> instead of
 325  <function>loadMenu()</function>. We will explain the use of both methods
 326  in the next section.
 327</para>
 328
 329</sect3>
 330
 331</sect2>
 332
 333<sect2 id="plugin-implement-resources">
 334<title>Resources for the Plugin Core Class</title>
 335<sect3 id="plugin-implement-actions"><title>Actions</title>
 336<para>
 337  The plugin's user action catalog, <filename>actions.xml</filename>, is
 338  the resource used by the plugin API to get the names and definitions of
 339  user actions. The following <filename>actions.xml</filename>
 340  file from the <application>QuickNotepad</application> plugin can
 341  provide a model:
 342</para>
 343
 344<informalexample><programlisting>&lt;!DOCTYPE ACTIONS SYSTEM "actions.dtd"&gt;
 345
 346&lt;ACTIONS&gt;
 347    &lt;ACTION NAME="quicknotepad.choose-file"&gt;
 348        &lt;CODE&gt;
 349            view.getDockableWindowManager()
 350              .getDockable(QuickNotepadPlugin.NAME).chooseFile();
 351        &lt;/CODE&gt;
 352    &lt;/ACTION&gt;
 353
 354    &lt;ACTION NAME="quicknotepad.save-file"&gt;
 355        &lt;CODE&gt;
 356            view.getDockableWindowManager()
 357              .getDockable(QuickNotepadPlugin.NAME).saveFile();
 358        &lt;/CODE&gt;
 359    &lt;/ACTION&gt;
 360
 361    &lt;ACTION NAME="quicknotepad.copy-to-buffer"&gt;
 362        &lt;CODE&gt;
 363            view.getDockableWindowManager()
 364              .getDockable(QuickNotepadPlugin.NAME).copyToBuffer();
 365        &lt;/CODE&gt;
 366    &lt;/ACTION&gt;
 367&lt;/ACTIONS&gt;</programlisting></informalexample>
 368
 369
 370
 371<para>
 372  This file defines three actions. They use the current view's
 373  <classname>DockableWindowManager</classname> object and the method
 374  <filename>getDockable()</filename> to find the QuickNotepad plugin
 375  window and call the desired method.
 376</para>
 377
 378<para>
 379  When an action is invoked, program control must pass to the
 380  component responsible for executing the action. The use of an internal
 381  table of BeanShell scripts that implement actions avoids the need for
 382  plugins to implement <classname>ActionListener</classname> or similar
 383  objects to respond to actions. Instead, the BeanShell scripts address
 384  the plugin through static methods, or if instance data is needed, the
 385  current <classname>View</classname>, its
 386  <classname>DockableWindowManager</classname>, and the plugin
 387  object return by the <filename>getDockable()</filename> method.
 388</para>
 389
 390<para>
 391  If you are unfamiliar with BeanShell code, you may nevertheless notice
 392  that the code statements bear a strong resemblance to Java code, with
 393  one exception: the
 394  variable <varname>view</varname> is never assigned any value.
 395</para>
 396
 397<para>
 398  For complete answers to this and other BeanShell
 399  mysteries, see <xref linkend="writing-macros-part" />; two
 400  observations will suffice here. First, the variable
 401  <varname>view</varname> is predefined by jEdit's implementation of
 402  BeanShell to refer to the current <classname>View</classname> object.
 403  Second, the
 404  BeanShell scripting language is based upon Java syntax, but allows
 405  variables to be typed at run time, so explicit types for variables
 406  need not be declared.
 407</para>
 408
 409<para>
 410  A formal description of each element of the
 411  <filename>actions.xml</filename> file can be found in
 412  <xref linkend="resources-action" />.
 413</para>
 414
 415</sect3>
 416
 417<sect3 id="plugin-implement-menu"><title>Action Labels and Menu Items</title>
 418<para>
 419  Now that we have named and defined actions for the plugin, we have to
 420  put them to work. To do so, we must first give them labels that can be
 421  used in menu items and in the sections of jEdit's options dialog that
 422  deal with toolbar buttons, keyboard shortcuts and context menu items. We
 423  supply this information to jEdit through entries in the plugin's
 424  properties file. A call to
 425  <function>GUIUtilities.loadMenu()</function> or
 426  <function>GUIUtilities.loadMenuItem()</function> will read and extract
 427  the necessary labels from the contents of a properties file.
 428</para>
 429
 430
 431
 432<para>
 433  The following excerpt from <filename>QuickNotepad.props</filename>
 434  illustrates the format required for action labels and menu items:
 435</para>
 436
 437<informalexample><programlisting># action labels
 438quicknotepad.toggle.label=QuickNotepad
 439quicknotepad-to-front.label=Bring QuickNotepad to front
 440quicknotepad.choose-file.label=Choose notepad file
 441quicknotepad.save-file.label=Save notepad file
 442quicknotepad.copy-to-buffer.label=Copy notepad to buffer
 443
 444# application menu items
 445quicknotepad.menu.label=QuickNotepad
 446quicknotepad.menu=quicknotepad.toggle - quicknotepad.choose-file \
 447    quicknotepad.save-file quicknotepad.copy-to-buffer
 448</programlisting></informalexample>
 449
 450<para>
 451  <function>GUIUtilities.loadMenuItem()</function> and
 452  <function>GUIUtilites.loadMenu()</function> use special conventions
 453  for the value of a menu property to specify menu layout. In
 454  <function>loadMenu()</function>, the use of the dash, as in the second
 455  item in the example menu list, signifies the placement of a separator.
 456  In addition,
 457  the character <userinput>'%'</userinput> used as a prefix on a
 458  label causes <function>loadMenu()</function> to call itself recursively
 459  with the prefixed label as the source of submenu data. Most plugins
 460  will not need to define menus that contain other submenus.
 461</para>
 462
 463<para>
 464  Note also that <function>quicknotepad-to-front</function> is not
 465  included in the menu listing. It will appear, however, on the
 466  <guilabel>Shortcuts</guilabel> pane of the <guimenuitem>Global
 467  Options</guimenuitem> dialog,
 468  so that the action can be associated with a keyboard shortcut.
 469</para>
 470
 471</sect3>
 472
 473</sect2>
 474
 475</sect1>
 476
 477<sect1 id="window-implement"><title>Implementing a Dockable Window Class</title>
 478
 479<para>
 480 Now we must provide the actual implementation of the dockable window
 481 referenced in <filename>dockables.xml</filename> (see
 482 <xref linkend="resources-dockables" /> and
 483 <xref linkend="resources-implement" />). Here is the
 484 <filename>QuickNotepad.java</filename> source file, with some details
 485 not related to the dockable window API trimmed:
 486</para>
 487
 488<informalexample><programlisting>public class QuickNotepad extends JPanel
 489    implements EBComponent
 490{
 491    private View view;
 492    private String position;
 493    ...
 494    public QuickNotepad(View view, String position) {
 495      this.view = view;
 496      this.position = position;
 497      ...
 498    }
 499
 500    ...
 501
 502    public void handleMessage(EBMessage message) {
 503        if (message instanceof PropertiesChanged) {
 504            propertiesChanged();
 505        }
 506    }
 507
 508    ...
 509
 510}</programlisting></informalexample>
 511
 512<para>
 513  This excerpt does not set forth the layout of the plugin's visible
 514  components, nor does it show how our user actions will be implemented.
 515  Both these matters are covered in the full source code.
 516</para>
 517
 518</sect1>
 519
 520<sect1 id="window-visible-implement">
 521
 522<title>The Plugin's Visible Window</title>
 523
 524<sect2 id="example-window-class"><title>Class QuickNotepad</title>
 525
 526<para>
 527  Here is where most of the features of the plugin will be implemented.
 528  To work with the dockable window API, the top level window will be a
 529  <classname>JPanel</classname>.  The visible components reflect a
 530  simple layout. Inside the top-level panel we will place a scroll pane with
 531  a text area. Above the scroll pane we will place a panel containing a small
 532  tool bar and a label displaying the path of the current notepad file.
 533</para>
 534
 535<para>
 536  We have identified three user actions that need
 537  implementation here: <function>chooseFile()</function>,
 538  <function>saveFile()</function>, and
 539  <function>copyToBuffer()</function>. As noted earlier, we also want the
 540  text area to change its appearance in immediate response to a change in
 541  user options settings. In order to do that, the window class must
 542  respond to a <classname>PropertiesChanged</classname> message from
 543  the EditBus.
 544</para>
 545
 546<!-- <para>
 547  We could have the plugin core class receive and delegate
 548  <classname>PropertiesChanged</classname> messages to the window class.
 549  However, this would require the plugin core class to hold a reference
 550  to either the plugin window class or the visible window class and to
 551  update that reference when the user activates or deactivates the
 552  plugin.  It is simpler to have the plugin window class subscribe to the
 553  EditBus directly; many plugins take this approach.  This means that
 554  <classname>QuickNotepad</classname> must implement the
 555  <classname>EBComponent</classname> interface.
 556</para> -->
 557
 558<para>
 559  Unlike the <classname>EBPlugin</classname> class, the
 560  <classname>EBComponent</classname> interface does not deal with the
 561  component's actual subscribing and unsubscribing to the EditBus.  To
 562  accomplish this, we use a pair of methods inherited from the
 563  Java platform's <classname>JComponent</classname> class
 564  that are called when the window is made visible, and when it is hidden.
 565  These two methods,
 566  <function>addNotify()</function> and
 567  <function>removeNotify()</function>, are overridden to add and remove
 568  the visible window from the list of EditBus subscribers.
 569</para>
 570
 571<para>
 572  We will provide for two minor features when the notepad is
 573  displayed in the floating window.  First, when a floating plugin window
 574  is created, we will give the notepad text area input focus.  Second,
 575  when the notepad if floating and has input focus, we will have the
 576  <keycap>Escape</keycap> key dismiss the notepad window.  An
 577  <classname>AncestorListener</classname> and a
 578  <classname>KeyListener</classname> will implement these details.
 579</para>
 580
 581<para>
 582  Here is the listing for the data members, the constructor, and the
 583  implementation of the <classname>EBComponent</classname> interface:
 584</para>
 585
 586
 587<informalexample><programlisting>public class QuickNotepad extends JPanel
 588    implements EBComponent
 589{
 590    private String filename;
 591    private String defaultFilename;
 592    private View view;
 593    private boolean floating;
 594
 595    private QuickNotepadTextArea textArea;
 596    private QuickNotepadToolPanel toolPanel;
 597
 598    //
 599    // Constructor
 600    //
 601
 602    public QuickNotepad(View view, String position)
 603    {
 604        super(new BorderLayout());
 605
 606        this.view = view;
 607        this.floating = position.equals(
 608            DockableWindowManager.FLOATING);
 609
 610        this.filename = jEdit.getProperty(
 611            QuickNotepadPlugin.OPTION_PREFIX
 612            + "filepath");
 613        if(this.filename == null || this.filename.length() == 0)
 614        {
 615            this.filename = new String(jEdit.getSettingsDirectory()
 616                + File.separator + "qn.txt");
 617            jEdit.setProperty(QuickNotepadPlugin.OPTION_PREFIX
 618                + "filepath",this.filename);
 619        }
 620        this.defaultFilename = new String(this.filename);
 621
 622        this.toolPanel = new QuickNotepadToolPanel(this);
 623        add(BorderLayout.NORTH, this.toolPanel);
 624
 625        if(floating)
 626            this.setPreferredSize(new Dimension(500, 250));
 627
 628        textArea = new QuickNotepadTextArea();
 629        textArea.setFont(QuickNotepadOptionPane.makeFont());
 630        textArea.addKeyListener(new KeyHandler());
 631        textArea.addAncestorListener(new AncestorHandler());
 632        JScrollPane pane = new JScrollPane(textArea);
 633        add(BorderLayout.CENTER, pane);
 634
 635        readFile();
 636    }
 637
 638    //
 639    // Attribute methods
 640    //
 641
 642    // for toolBar display
 643    public String getFilename()
 644    {
 645        return filename;
 646    }
 647
 648    //
 649    // EBComponent implementation
 650    //
 651
 652    public void handleMessage(EBMessage message)
 653    {
 654        if (message instanceof PropertiesChanged)
 655        {
 656            propertiesChanged();
 657        }
 658    }
 659
 660
 661    private void propertiesChanged()
 662    {
 663        String propertyFilename = jEdit.getProperty(
 664            QuickNotepadPlugin.OPTION_PREFIX + "filepath");
 665        if(!defaultFilename.equals(propertyFilename))
 666        {
 667            saveFile();
 668            toolPanel.propertiesChanged();
 669            defaultFilename = propertyFilename.clone();
 670            filename = defaultFilename.clone();
 671            readFile();
 672        }
 673        Font newFont = QuickNotepadOptionPane.makeFont();
 674        if(!newFont.equals(textArea.getFont()))
 675        {
 676            textArea.setFont(newFont);
 677            textArea.invalidate();
 678        }
 679    }
 680
 681    // These JComponent methods provide the appropriate points
 682    // to subscribe and unsubscribe this object to the EditBus
 683
 684    public void addNotify()
 685    {
 686        super.addNotify();
 687        EditBus.addToBus(this);
 688    }
 689
 690
 691    public void removeNotify()
 692    {
 693        saveFile();
 694        super.removeNotify();
 695        EditBus.removeFromBus(this);
 696    }
 697
 698    ...
 699
 700}</programlisting></informalexample>
 701
 702<para>
 703  This listing refers to a <classname>QuickNotebookTextArea</classname>
 704  object. It is currently implemented as a <classname>JTextArea</classname> with
 705  word wrap and tab sizes hard-coded.  Placing the object in a separate
 706  class will simply future modifications.
 707</para>
 708
 709</sect2>
 710
 711<sect2 id="example-window-toolbar"><title>Class QuickNotepadToolBar</title>
 712
 713<para>
 714  There is nothing remarkable about the toolbar panel that is placed
 715  inside the <classname>QuickNotepad</classname> object. The constructor
 716  shows the continued use of items from the plugin's properties file.
 717</para>
 718
 719<informalexample><programlisting>public class QuickNotepadToolPanel extends JPanel
 720{
 721    private QuickNotepad pad;
 722    private JLabel label;
 723
 724    public QuickNotepadToolPanel(QuickNotepad qnpad)
 725    {
 726        pad = qnpad;
 727        JToolBar toolBar = new JToolBar();
 728        toolBar.setFloatable(false);
 729
 730        toolBar.add(makeCustomButton("quicknotepad.choose-file",
 731            new ActionListener() {
 732                public void actionPerformed(ActionEvent evt) {
 733                    QuickNotepadToolPanel.this.pad.chooseFile();
 734                }
 735            }));
 736        toolBar.add(makeCustomButton("quicknotepad.save-file",
 737            new ActionListener() {
 738                public void actionPerformed(ActionEvent evt) {
 739                    QuickNotepadToolPanel.this.pad.saveFile();
 740                }
 741            }));
 742        toolBar.add(makeCustomButton("quicknotepad.copy-to-buffer",
 743            new ActionListener() {
 744                public void actionPerformed(ActionEvent evt) {
 745                    QuickNotepadToolPanel.this.pad.copyToBuffer();
 746                }
 747            }));
 748        label = new JLabel(pad.getFilename(),
 749            SwingConstants.RIGHT);
 750        label.setForeground(Color.black);
 751        label.setVisible(jEdit.getProperty(
 752            QuickNotepadPlugin.OPTION_PREFIX
 753            + "show-filepath").equals("true"));
 754        this.setLayout(new BorderLayout(10, 0));
 755        this.add(BorderLayout.WEST, toolBar);
 756        this.add(BorderLayout.CENTER, label);
 757        this.setBorder(BorderFactory.createEmptyBorder(0, 0, 3, 10));
 758    }
 759
 760    ...
 761
 762}</programlisting></informalexample>
 763
 764<para>
 765  The method <classname>makeCustomButton()</classname> provides uniform
 766  attributes for the three toolbar buttons corresponding to three of the
 767  plugin's use actions.  The menu titles for the user actions serve double
 768  duty as tooltip text for the buttons. There is also a
 769  <function>propertiesChanged()</function> method for the toolbar that
 770  sets the text and visibility of the label containing the notepad file path.
 771</para>
 772
 773</sect2>
 774
 775</sect1>
 776
 777<sect1 id="option-implement"><title>Designing an Option Pane</title>
 778
 779<para>
 780  Using the default implementation provided by
 781  <classname>AbstractOptionPane</classname> reduces the preparation of an
 782  option pane to two principal tasks: writing a
 783  <function>_init()</function> method to layout and initialize the pane,
 784  and writing a <function>_save()</function> method to commit any settings
 785  changed by user input. If a button on the option pane should trigger
 786  another dialog, such as a <classname>JFileChooser</classname> or jEdit's
 787  own enhanced <classname>VFSFileChooserDialog</classname>, the option
 788  pane will also have to implement the
 789  <classname>ActionListener</classname> interface to display additional
 790  components.
 791</para>
 792
 793<para>
 794  The QuickNotepad plugin has only three options to set: the path name of
 795  the file that will store the notepad text, the visibility of the
 796  path name on the tool bar, and the notepad's display font.
 797  Using the shortcut methods of the plugin API, the implementation of
 798  <function>_init()</function> looks like this:
 799</para>
 800
 801<informalexample><programlisting>public class QuickNotepadOptionPane extends AbstractOptionPane
 802      implements ActionListener
 803{
 804    private JTextField pathName;
 805    private JButton pickPath;
 806    private FontSelector font;
 807
 808    ...
 809
 810    public void _init()
 811    {
 812        showPath = new JCheckBox(jEdit.getProperty(
 813            QuickNotepadPlugin.OPTION_PREFIX
 814            + "show-filepath.title"),
 815        jEdit.getProperty(
 816            QuickNotepadPlugin.OPTION_PREFIX +  "show-filepath")
 817            .equals("true"));
 818        addComponent(showPath);
 819
 820        pathName = new JTextField(jEdit.getProperty(
 821            QuickNotepadPlugin.OPTION_PREFIX
 822            + "filepath"));
 823        JButton pickPath = new JButton(jEdit.getProperty(
 824            QuickNotepadPlugin.OPTION_PREFIX
 825            + "choose-file"));
 826        pickPath.addActionListener(this);
 827
 828        JPanel pathPanel = new JPanel(new BorderLayout(0, 0));
 829        pathPanel.add(pathName, BorderLayout.CENTER);
 830        pathPanel.add(pickPath, BorderLayout.EAST);
 831
 832        addComponent(jEdit.getProperty(
 833            QuickNotepadPlugin.OPTION_PREFIX + "file"),
 834            pathPanel);
 835
 836        font = new FontSelector(makeFont());
 837        addComponent(jEdit.getProperty(
 838            QuickNotepadPlugin.OPTION_PREFIX + "choose-font"),
 839            font);
 840    }
 841
 842    ...
 843
 844}</programlisting></informalexample>
 845
 846<para>
 847  Here we adopt the vertical arrangement offered by use of the
 848  <function>addComponent()</function> method with one embellishment.
 849  We want the first <quote>row</quote> of the option pane to contain
 850  a text field with the current notepad file path and a button that will
 851  trigger a file chooser dialog when pressed.  To place both of them on
 852  the same line (along with an identifying label for the file option),
 853  we create a <classname>JPanel</classname> to contain both components and
 854  pass the configured panel to <function>addComponent()</function>.
 855</para>
 856
 857<para>
 858  The <function>_init()</function> method uses properties from the plugin's
 859  property file to provide the names of label for the components placed
 860  in the option pane.  It also uses a property whose name begins with
 861  <function>PROPERTY_PREFIX</function> as a persistent data item - the
 862  path of the current notepad file.  The elements of the notepad's font
 863  are also extracted from properties using a static method of the option
 864  pane class.
 865</para>
 866
 867<para>
 868  The <function>_save()</function> method extracts data from the user
 869  input components and
 870  assigns them to the plugin's properties.  The implementation is
 871  straightforward:
 872</para>
 873
 874<informalexample><programlisting>public void _save()
 875{
 876    jEdit.setProperty(QuickNotepadPlugin.OPTION_PREFIX
 877        + "filepath", pathName.getText());
 878    Font _font = font.getFont();
 879
 880    jEdit.setProperty(QuickNotepadPlugin.OPTION_PREFIX
 881        + "font", _font.getFamily());
 882    jEdit.setProperty(QuickNotepadPlugin.OPTION_PREFIX
 883        + "fontsize", String.valueOf(_font.getSize()));
 884    jEdit.setProperty(QuickNotepadPlugin.OPTION_PREFIX
 885        + "fontstyle", String.valueOf(_font.getStyle()));
 886    jEdit.setProperty(QuickNotepadPlugin.OPTION_PREFIX
 887        + "show-filepath", String.valueOf(showPath.isSelected()));
 888}</programlisting></informalexample>
 889
 890<para>
 891  The class has only two other methods, one to display a file chooser
 892  dialog in response to user action, and the other
 893  to construct a <classname>Font</classname> object from the plugin's font
 894  properties. They do not require discussion here.
 895</para>
 896
 897
 898</sect1>
 899
 900<sect1 id="resources-implement"><title>Creating Other Plugin Resources</title>
 901
 902<para>
 903  We have already covered in some detail one of the three types of resources
 904  that plugins include with their class files - the user action catalog - and
 905  the need for help documentation does not require extended discussion.  The
 906  remaining resource is the properties file.
 907</para>
 908
 909<para>
 910  The first type of property data is information about the plugin itself.
 911  The first few entries from the QuickNotepad plugin's properties file
 912  fulfills this requirement:
 913</para>
 914
 915<informalexample><programlisting># general plugin information
 916plugin.QuickNotepadPlugin.name=QuickNotepad
 917plugin.QuickNotepadPlugin.author=John Gellene
 918plugin.QuickNotepadPlugin.version=2.0
 919plugin.QuickNotepadPlugin.docs=QuickNotepad.html
 920plugin.QuickNotepadPlugin.depend.0=jedit 04.00.01.00</programlisting></informalexample>
 921<para>
 922  These properties are described in detail in <xref
 923  linkend="resources-properties" /> and do not require further
 924  discussion here.
 925</para>
 926
 927<para>
 928  Next in the file comes a property that sets the title of the
 929  plugin in docked or frame windows.  The use of the suffix
 930  <literal>.title</literal> in the property's key name is
 931  required by the plugin API.
 932</para>
 933
 934<informalexample><programlisting># dockable window name
 935quicknotepad.title=QuickNotepad</programlisting></informalexample>
 936
 937<para>
 938  The next sections, consisting of the action label and menu item
 939  properties, have been discussed earlier in
 940  <xref linkend="plugin-implement-menu" />.
 941</para>
 942
 943<informalexample><programlisting># action labels
 944quicknotepad.toggle.label=QuickNotepad
 945quicknotepad-to-front.label=Bring QuickNotepad to front
 946quicknotepad.choose-file.label=Choose notepad file
 947quicknotepad.save-file.label=Save notepad file
 948quicknotepad.copy-to-buffer.label=Copy notepad to buffer
 949
 950# application menu items
 951quicknotepad.menu=quicknotepad.toggle - quicknotepad.choose-file \
 952  quicknotepad.save-file quicknotepad.copy-to-buffer</programlisting></informalexample>
 953
 954<para>
 955  We have created a small toolbar as a component of QuickNotepad, so
 956  file names for the button icons follow:
 957</para>
 958
 959<informalexample><programlisting># plugin toolbar buttons
 960quicknotepad.choose-file.icon=Open.gif
 961quicknotepad.save-file.icon=Save.gif
 962quicknotepad.copy-to-buffer.icon=Edit.gif</programlisting></informalexample>
 963
 964<para>
 965  The menu labels corresponding to these icons will also serve as tooltip
 966  text.
 967</para>
 968
 969<para>
 970  Finally, the properties file set forth the labels and settings
 971  used by the option pane:
 972</para>
 973
 974<informalexample><programlisting># Option pane labels
 975options.quicknotepad.label=QuickNotepad
 976options.quicknotepad.file=File:
 977options.quicknotepad.choose-file=Choose
 978options.quicknotepad.choose-file.title=Choose a notepad file
 979options.quicknotepad.choose-font=Font:
 980options.quicknotepad.show-filepath.title=Display notepad file path
 981
 982# Initial default font settings
 983options.quicknotepad.show-filepath=true
 984options.quicknotepad.font=Monospaced
 985options.quicknotepad.fontstyle=0
 986options.quicknotepad.fontsize=14
 987
 988# Setting not defined but supplied for completeness
 989options.quicknotepad.filepath=</programlisting></informalexample>
 990
 991<para>
 992  We do not define a default setting for the <literal>filepath</literal>
 993  property because of
 994  differences among operating systems.  We will define a default file
 995  programatically that will reside in the directory jEdit designates for
 996  user settings.
 997</para>
 998
 999</sect1>
1000
1001<sect1 id="example-building"><title>Compiling the Plugin</title>
1002
1003<para>
1004  We have already outlined the contents of the user action catalog, the
1005  properties file and the documentation file in our earlier discussion.
1006  The final step is to compile the source file and build the archive file
1007  that will hold the class files and the plugin's other resources.
1008</para>
1009
1010<para>
1011  Publicly released plugins include with their source a makefile
1012  in XML format for the
1013  <application>Ant</application> utility.  The format for this file
1014  requires few changes from plugin to plugin.  Here is the version of
1015  <filename>build.xml</filename> used by QuickNotepad and many other
1016  plugins:
1017</para>
1018
1019<informalexample><programlisting><![CDATA[<project name="QuickNotepad" default="dist" basedir=".">
1020
1021  <property name="jedit.install.dir"  value="../.."/>
1022  <property name="jar.name"  value="QuickNotepad.jar"/>
1023
1024  <property name="install.dir"  value=".."/>
1025
1026
1027  <path id="project.class.path">
1028    <pathelement location="${jedit.install.dir}/jedit.jar"/>
1029    <pathelement location="."/>
1030  </path>
1031
1032
1033  <target name="compile">
1034    <javac
1035      srcdir="."
1036      deprecation="on"
1037      includeJavaRuntime="yes"
1038    >
1039      <classpath refid="project.class.path"/>
1040    </javac>
1041  </target>
1042
1043
1044  <target name="dist" depends="compile">
1045    <mkdir dir="${install.dir}"/>
1046    <jar jarfile="${install.dir}/${jar.name}">
1047      <fileset dir=".">
1048        <include name="**/*.class"/>
1049        <include name="**/*.props"/>
1050        <include name="**/*.html"/>
1051        <include name="**/*.gif"/>
1052        <include name="actions.xml"/>
1053        <include name="dockables.xml"/>
1054      </fileset>
1055    </jar>
1056  </target>
1057</project>]]></programlisting></informalexample>
1058
1059<para>
1060  For a full discussion of the <filename>Ant</filename> file format and
1061  command syntax, you should consult the <ulink
1062  url="http://jakarta.apache.org/ant/manual/index.html">Ant
1063  documentation site</ulink>. Modifying this makefile for a different
1064  plugin will likely only require three changes:
1065</para>
1066
1067<itemizedlist>
1068  <listitem><para>
1069    the name of the plugin;
1070  </para></listitem>
1071  <listitem><para>
1072    the choice of compiler (made by inserting and deleting the comment character
1073    <userinput>'#'</userinput>); and
1074  </para> </listitem>
1075  <listitem><para>
1076    the classpath variables for <filename>jedit.jar</filename>
1077    any plugins this one depends on.
1078  </para></listitem>
1079</itemizedlist>
1080
1081<para>
1082  If you have reached this point in the text, you are probably serious
1083  about writing a plugin for jEdit.  Good luck with your efforts, and
1084  thank you for contributing to the jEdit project.
1085</para>
1086
1087</sect1>
1088
1089</chapter>