PageRenderTime 442ms CodeModel.GetById 420ms app.highlight 11ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-0-pre3/doc/users-guide/plugin-implement.xml

#
XML | 1274 lines | 1053 code | 203 blank | 18 comment | 0 complexity | 4de69d8db66b3d1a0154678c16a4479f MD5 | raw file
   1<!-- jEdit 3.2 Plugin Guide, (C) 2001 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 use jEdit's dockable window API?
 111  </para>
 112  <para>
 113    If a plugin will use the dockable window API, it must handle a
 114    targeted <classname>CreateDockableWindow</classname> message.
 115  </para>
 116  </listitem>
 117  <listitem><para>
 118    Will the plugin
 119    respond to any other messages reflecting changes in the host
 120    application's state?
 121  </para></listitem>
 122  <listitem><para>
 123    Will the plugin have settings that the user can configure?
 124  </para></listitem>
 125
 126</itemizedlist>
 127
 128<sect2 id="implement-plugin-choose-base"><title>Choosing a Base Class</title>
 129
 130<para>
 131  If the plugin will respond to EditBus
 132  messages, it must be derived from <classname>EBPlugin</classname>.
 133  Otherwise, <classname>EditPlugin</classname> will suffice as a
 134  base class.
 135</para>
 136
 137<para>
 138  Knowing what types of messages are made available by the plugin API is
 139  obviously helpful is determining both the base plugin class and the
 140  contents of a <filename>handleMessage()</filename> method. The message
 141  classes derived from <classname>EBMessage</classname> cover the opening
 142  and closing of the application, changes in the status of text buffers
 143  and their container and changes in user settings, as well as changes in
 144  the state of other program features. Specific message classes of potential
 145  interest to a plugin include the following:
 146</para>
 147
 148<itemizedlist>
 149  <listitem>
 150    <para>
 151      <classname>EditorStarted</classname>, sent during the
 152      application's startup routine, just prior to the creation of the
 153      initial <classname>View</classname>;
 154    </para>
 155  </listitem>
 156  <listitem>
 157    <para>
 158      <classname>EditorExitRequested</classname>, sent when a request to
 159      exit has been made, but before saving open buffers and writing user
 160      settings to storage;
 161    </para>
 162  </listitem>
 163  <listitem>
 164    <para>
 165      <classname>EditorExiting</classname>, sent just before jEdit
 166      actually exits;
 167    </para>
 168  </listitem>
 169  <listitem>
 170    <para>
 171      <classname>EditPaneUpdate</classname>, sent when an edit pane
 172      containing a text area (including a pane created by splitting
 173      an existing window) has been created or destroyed, or when a
 174      buffer displayed in an edit pane has been changed;
 175    </para>
 176  </listitem>
 177  <listitem>
 178    <para>
 179      <classname>BufferUpdate</classname>, sent when a text buffer is
 180      created, loaded, or being saved, or when its editing mode or
 181      markers have changed;
 182    </para>
 183  </listitem>
 184  <listitem>
 185    <para>
 186      <classname>ViewUpdate</classname>, sent when a <classname>View</classname>
 187      is created or closed; and
 188    </para>
 189  </listitem>
 190  <listitem>
 191    <para>
 192      <classname>PropertiesChanged</classname>, sent when the properties
 193      of the application or a plugin has been changed through the
 194      <guimenuitem>General Options</guimenuitem> dialog;
 195    </para>
 196  </listitem>
 197</itemizedlist>
 198
 199<para>
 200  Detailed documentation for each message class can be found in <xref
 201  linkend="api-message" />.
 202</para>
 203
 204<!-- TODO: include data members of derived message classes in appendix -->
 205
 206</sect2>
 207
 208<sect2 id="plugin-implement-base-methods">
 209<title>Implementing Base Class Methods</title>
 210
 211<sect3 id="plugin-implement-base-general">
 212<title>General Considerations</title>
 213
 214<para>
 215  If <classname>EditPlugin</classname> is selected as the base plugin core
 216  class, the implementations of <function>start()</function> and
 217  <function>stop()</function> in the plugin's derived class are likely to
 218  be trivial, or not present at all (in which case they will be
 219  <quote>no-ops</quote>). If
 220  <classname>EBPlugin</classname> is selected to provide
 221  messaging capability, however, there are a few fixed requirements.
 222</para>
 223
 224<para>
 225  If the plugin is to use the dockable window API, the
 226  <quote> internal names</quote> of any dockable windows must be
 227  registered with the EditBus component.
 228  The EditBus stores such information in one of a number of
 229  <quote>named lists</quote>. Here is how the QuickNotepad plugin
 230  registers its dockable window:
 231</para>
 232
 233<informalexample>
 234<programlisting>EditBus.addToNamedList(DockableWindow.DOCKABLE_WINDOW_LIST, NAME);</programlisting>
 235</informalexample>
 236
 237<para>
 238  The first parameter is a <classname>String</classname> constant
 239  identifying the dockable window list. The second is a static
 240  <classname>String</classname> constant which is initialized in the
 241  plugin core class as the dockable window's internal name.
 242</para>
 243
 244<para>
 245  The use of <varname>NAME</varname> as the second parameter employs an
 246  idiom found in many plugins. The plugin class can include <literal>static
 247  final String</literal> data members containing information to be registered
 248  with the EditBus or key names for certain types of plugin properties.
 249  This makes it easier to refer to the
 250  information when a method such as <function>handleMessage()</function>
 251  examines the contents of a message. The kind of data that can be handled
 252  in this fashion include the following:
 253</para>
 254
 255<itemizedlist>
 256  <listitem>
 257    <para>
 258      the internal working name of dockable windows that will be used in the
 259      <classname>CreateDockableWindow</classname> message and elsewhere;
 260    </para>
 261  </listitem>
 262  <listitem>
 263    <para>
 264      a label for identifying the plugin's menu;
 265    </para>
 266  </listitem>
 267  <listitem>
 268    <para>
 269      a prefix for labelling properties required by the plugin API; and
 270    </para>
 271  </listitem>
 272  <listitem>
 273    <para>
 274      a prefix to be used in labelling items used in the plugin's option pane
 275    </para>
 276  </listitem>
 277</itemizedlist>
 278
 279</sect3>
 280
 281<sect3 id="plugin-implement-base-example">
 282<title>Example Plugin Core Class</title>
 283
 284<para>
 285  We will derive the plugin core class for QuickNotepad from
 286  <classname>EBPlugin</classname> to allow the plugin core object to
 287  subscribe to the EditBus and receive a
 288  <classname>CreateDockableWindow</classname> message. There are no other
 289  messages to which the plugin core object needs to respond, so the
 290  implementation of <function>handleMessage()</function> will only deal
 291  with one class of message. We will define a few static
 292  <classname>String</classname> data members to enforce consistent syntax
 293  for the name of properties we will use throughout the plugin. 
 294  Finally, we will use a standalone plugin
 295  window class to separate the functions of that class from the visible
 296  component class we will create.
 297</para>
 298
 299<para>
 300  The resulting plugin core class is lightweight and straightforward to implement:
 301</para>
 302
 303<informalexample><programlisting>public class QuickNotepadPlugin extends EBPlugin {
 304    public static final String NAME = "quicknotepad";
 305    public static final String MENU = "quicknotepad.menu";
 306    public static final String PROPERTY_PREFIX
 307        = "plugin.QuickNotepadPlugin.";
 308    public static final String OPTION_PREFIX
 309        = "options.quicknotepad.";
 310
 311    public void start() {
 312        EditBus.addToNamedList(DockableWindow
 313            .DOCKABLE_WINDOW_LIST, NAME);
 314    }
 315
 316    public void createMenuItems(Vector menuItems) {
 317        menuItems.addElement(GUIUtilities.loadMenu(MENU));
 318    }
 319
 320    public void createOptionPanes(OptionsDialog od) {
 321        od.addOptionPane(new QuickNotepadOptionPane());
 322    }
 323
 324    public void handleMessage(EBMessage message) {
 325        if(message instanceof CreateDockableWindow) {
 326            CreateDockableWindow cmsg = (CreateDockableWindow)
 327                message;
 328            if (cmsg.getDockableWindowName().equals(NAME)) {
 329                DockableWindow win = new QuickNotepadDockable(
 330                    cmsg.getView(), cmsg.getPosition());
 331                cmsg.setDockableWindow(win);
 332            }
 333        }
 334    }
 335}</programlisting></informalexample>
 336
 337<para>
 338  The implementations of <function>createMenuItems()</function> and
 339  <function>createOptionPane()</function>
 340  are typically trivial, because the real work will be done using other
 341  plugin elements. Menu creation is performed by a utility function in
 342  jEdit's API, using properties defined in the plugin's properties file.
 343  The option pane is constructed in its own class.
 344</para>
 345
 346<para>
 347  If the plugin only had a single menu item (for example, a checkbox item
 348  that toggled activation of a dockable window), we would call
 349  <function>GUIUtilities.loadMenuItem()</function> instead of
 350  <function>loadMenu()</function>. We will explain the use of both methods
 351  in the next section.
 352</para>
 353
 354<para>
 355  The constructor for <classname>QuickNotepadDockable</classname> takes
 356  the values of the <classname>View</classname> object and the docking
 357  position contained in the <classname>CreateDockableWindow</classname>
 358  message. This will enable the plugin to <quote>know</quote> where it is
 359  located and modify its behavior accordingly. In another plugin, it could
 360  enable the plugin to obtain and manipulate various data that are
 361  available through a <classname>View</classname> object.
 362</para>
 363
 364</sect3>
 365
 366</sect2>
 367
 368
 369<sect2 id="plugin-implement-resources">
 370<title>Resources for the Plugin Core Class</title>
 371
 372<sect3 id="plugin-implement-actions"><title>Actions</title>
 373
 374<para>
 375  The plugin's user action catalog, <filename>actions.xml</filename>, is
 376  the resource used by the plugin API to get the names and definitions of
 377  user actions. The following <filename>actions.xml</filename>
 378  file from the <application>QuickNotepad</application> plugin can
 379  provide a model:
 380</para>
 381
 382<informalexample><programlisting>&lt;!DOCTYPE ACTIONS SYSTEM "actions.dtd"&gt;
 383
 384&lt;ACTIONS&gt;
 385    &lt;ACTION NAME="quicknotepad.toggle"&gt;
 386        &lt;CODE&gt;
 387            view.getDockableWindowManager()
 388                .toggleDockableWindow(QuickNotepadPlugin.NAME);
 389        &lt;/CODE&gt;
 390        &lt;IS_SELECTED&gt;
 391            return view.getDockableWindowManager()
 392                .isDockableWindowVisible(QuickNotepadPlugin.NAME);
 393        &lt;/IS_SELECTED&gt;
 394    &lt;/ACTION&gt;
 395
 396    &lt;ACTION NAME="quicknotepad-to-front"&gt;
 397    &lt;CODE&gt;
 398      view.getDockableWindowManager()
 399                .addDockableWindow(QuickNotepadPlugin.NAME);
 400        &lt;/CODE&gt;
 401    &lt;/ACTION&gt;
 402
 403    &lt;ACTION NAME="quicknotepad.choose-file"&gt;
 404        &lt;CODE&gt;
 405          wm = view.getDockableWindowManager();
 406          wm.addDockableWindow(QuickNotepadPlugin.NAME);
 407          wm.getDockableWindow(QuickNotepadPlugin.NAME)
 408            .chooseFile();
 409        &lt;/CODE&gt;
 410    &lt;/ACTION&gt;
 411
 412    &lt;ACTION NAME="quicknotepad.save-file"&gt;
 413        &lt;CODE&gt;
 414          wm = view.getDockableWindowManager();
 415          wm.addDockableWindow(QuickNotepadPlugin.NAME);
 416          wm.getDockableWindow(QuickNotepadPlugin.NAME)
 417            .saveFile();
 418        &lt;/CODE&gt;
 419    &lt;/ACTION&gt;
 420
 421    &lt;ACTION NAME="quicknotepad.copy-to-buffer"&gt;
 422        &lt;CODE&gt;
 423          wm = view.getDockableWindowManager();
 424          wm.addDockableWindow(QuickNotepadPlugin.NAME);
 425          wm.getDockableWindow(QuickNotepadPlugin.NAME)
 426            .copyToBuffer();
 427        &lt;/CODE&gt;
 428    &lt;/ACTION&gt;
 429&lt;/ACTIONS&gt;</programlisting></informalexample>
 430
 431<para>
 432  This file defines five actions. The first action uses the QuickNotepad's
 433  internal plugin window name to toggle its visible state. The second
 434  action places QuickNotepad at the top of a stack of overlapping plugin
 435  windows. The other actions use <varname>wm</varname>, the
 436  <classname>DockableWindowManager</classname> object contained in the
 437  current view, to find the QuickNotepad plugin window and call the desired
 438  method.
 439</para>
 440
 441<para>
 442  If you are unfamiliar with BeanShell code, you may nevertheless notice
 443  that the code statements bear a strong resemblance to Java code, with
 444  two exceptions: the variable <varname>wm</varname> is not typed, and the
 445  variable <varname>view</varname> is never assigned any value.
 446</para>
 447
 448<para>
 449  For complete answers to these and other BeanShell
 450  mysteries, see <xref linkend="writing-macros-part" />; two
 451  observations will suffice here. First, the
 452  BeanShell scripting language is based upon Java syntax, but allows
 453  variables to be typed at run time, so explicit types for variables such
 454  as <varname>wm</varname> need not be declared. Second, the variable
 455  <varname>view</varname> is predefined by jEdit's implementation of
 456  BeanShell to refer to the current <classname>View</classname> object.
 457</para>
 458
 459<para>
 460  A formal description of each element of the
 461  <filename>actions.xml</filename> file can be found in
 462  <xref linkend="api-resources-action" />.
 463</para>
 464
 465</sect3>
 466
 467<sect3 id="plugin-implement-menu"><title>Action Labels and Menu Items</title>
 468<para>
 469  Now that we have named and defined actions for the plugin, we have to
 470  put them to work. To do so, we must first give them labels that can be
 471  used in menu items and in the sections of jEdit's options dialog that
 472  deal with toolbar buttons, keyboard shortcuts and context menu items. We
 473  supply this information to jEdit through entries in the plugin's
 474  properties file. A call to
 475  <function>GUIUtilities.loadMenu()</function> or
 476  <function>GUIUtilities.loadMenuItem()</function> will read and extract
 477  the necessary labels from the contents of a properties file.
 478</para>
 479
 480<para>
 481  The following excerpt from <filename>QuickNotepad.props</filename>
 482  illustrates the format required for action labels and menu items:
 483</para>
 484
 485<informalexample><programlisting># action labels
 486quicknotepad.toggle.label=QuickNotepad
 487quicknotepad-to-front.label=Bring QuickNotepad to front
 488quicknotepad.choose-file.label=Choose notepad file
 489quicknotepad.save-file.label=Save notepad file
 490quicknotepad.copy-to-buffer.label=Copy notepad to buffer
 491
 492# application menu items
 493quicknotepad.menu.label=QuickNotepad
 494quicknotepad.menu=quicknotepad.toggle - quicknotepad.choose-file \
 495    quicknotepad.save-file quicknotepad.copy-to-buffer
 496</programlisting></informalexample>
 497
 498<para>
 499  <function>GUIUtilities.loadMenuItem()</function> and
 500  <function>GUIUtilites.loadMenu()</function> use syntatical conventions
 501  for the value of a menu property that simplifies menu layout. In
 502  <function>loadMenu()</function>, the use of the dash, as in the second
 503  item in the example menu list, signifies the placement of a separator.
 504  Finally, the character <userinput>'%'</userinput> used as a prefix on a
 505  label causes <function>loadMenu()</function> to call itself recursively
 506  with the prefixed label as the source of submenu data. Most plugins
 507  will not need to define menus that contain other submenus.
 508</para>
 509
 510<para>
 511  Note also that <function>quicknotepad-to-front</function> is not
 512  included in the menu listing. It will appear, however, on the
 513  <guilabel>Shortcuts</guilabel> pane of the <guimenuitem>Global
 514  Options</guimenuitem> dialog,
 515  so that the action can be associated with a keyboard shortcut.
 516</para>
 517
 518</sect3>
 519
 520</sect2>
 521
 522</sect1>
 523
 524
 525<sect1 id="window-implement"><title>Implementing a Dockable Window Class</title>
 526
 527<para>
 528  The <application>QuickNotepad</application> plugin uses the dockable
 529  window API and provides one dockable window. Dockable window classes
 530  must implement the
 531  <classname>DockableWindow</classname> interface. There are basically two
 532  approaches to doing this. One is to have the top-level visible component
 533  also serve as the plugin window. The other is to derive a lightweight
 534  class that will create and hold the top-level window component. We will
 535  ilustrate both approaches.
 536</para>
 537
 538<sect2 id="window-implement-single"><title>Using a Single Window Class</title>
 539
 540<para>
 541  A single window class must implement the
 542  <classname>DockableWindow</classname> interface as well as provide for
 543  the creation and layout of the plugin's visible components, and
 544  execution of user actions. The window for QuickNotepad will also
 545  implement the <classname>EBComponent</classname> so it can receive
 546  messages from the EditBus whenever the user has changed the plugin's
 547  settings in the <guimenuitem>Global Options</guimenuitem> dialog. Here
 548  is an excerpt from a class
 549  definition showing the implementation of both interfaces:
 550</para>
 551
 552<informalexample><programlisting>public class QuickNotepad extends JPanel
 553    implements EBComponent, DockableWindow
 554{
 555    private View view;
 556    private String position;
 557    ...
 558    public QuickNotepad(View view, String position) {
 559      this.view = view;
 560      this.position = position;
 561      ...
 562    }
 563
 564    public String getName() {
 565        return QuickNotepadPlugin.NAME;
 566    }
 567
 568    public Component getComponent() {
 569        return this;
 570    }
 571    ...
 572    public void handleMessage(EBMessage message) {
 573        if (message instanceof PropertiesChanged) {
 574            propertiesChanged();
 575        }
 576    }
 577    ...
 578}</programlisting></informalexample>
 579
 580<para>
 581  This excerpt  does not set forth the layout of the plugin's visible
 582  components, nor does it show how our user actions will be implemented.
 583  To provide more structure to the code, we will implement the
 584  <classname>DockableWindow</classname> interface in a separate,
 585  lightweight class.
 586</para>
 587
 588</sect2>
 589
 590<sect2 id="window-implement-actions"><title>An Action Interface</title>
 591
 592<para>
 593  When an action is invoked, program control must pass to the
 594  component responsible for executing the action. The use of an internal
 595  table of BeanShell scripts that implement actions avoids the need for
 596  plugins to implement <classname>ActionListener</classname> or similar
 597  objects to respond to actions. Instead, the BeanShell scripts address
 598  the plugin through static methods, or if instance data is needed, the
 599  current <classname>View</classname>, its
 600  <classname>DockableWindowManager</classname>, and the plugin's
 601  <classname>DockableWindow</classname> object. When the plugin window
 602  class is separated from the class containing its visible components,
 603  there is one more link in the chain of control. This means that the
 604  plugin window should either execute the action itself or delgte it to
 605  the visible component.
 606</para>
 607
 608<para>
 609  We will use delegation between QuickNotepad's plugin window, which we
 610  will call <classname>QuickNotepadDockable</classname>, and the revised,
 611  slimmer <classname>QuickNotepad</classname> object. To represent that
 612  delegation we will employ a <classname>QuickNotepadActions</classname>
 613  interface as an organizational tool:
 614</para>
 615
 616<informalexample><programlisting>public interface QuickNotepadActions {
 617    void chooseFile();
 618    void saveFile();
 619    void copyToBuffer();
 620}</programlisting></informalexample>
 621
 622</sect2>
 623
 624<sect2 id="window-implement-lightweight">
 625<title>A Lightweight Dockable Window Class</title>
 626
 627<para>
 628  Here is the complete definition of the
 629  <classname>QuickNotepadDockable</classname> class:
 630</para>
 631
 632<informalexample><programlisting>public class QuickNotepadDockable
 633    implements DockableWindow, QuickNotepadActions
 634{
 635    private QuickNotepad notepad;
 636
 637    public QuickNotepadDockable(View view, String position) {
 638        notepad = new QuickNotepad(view, position);
 639    }
 640
 641    public String getName() {
 642        return QuickNotepadPlugin.NAME;
 643    }
 644
 645    public Component getComponent() {
 646        return notepad;
 647    }
 648
 649    public void chooseFile() {
 650      notepad.chooseFile();
 651    }
 652
 653    public void saveFile() {
 654      notepad.saveFile();
 655    }
 656
 657    public void copyToBuffer() {
 658      notepad.copyToBuffer();
 659    }
 660
 661}</programlisting></informalexample>
 662
 663<para>
 664  Once again we use a static data member of the plugin core class
 665  to provide a name for the plugin window to its
 666  <classname>DockableWindowManager</classname>. The last three
 667  methods implement the <classname>QuickNotepadActions</classname>
 668  interface.
 669</para>
 670
 671</sect2>
 672
 673</sect1>
 674
 675<sect1 id="window-visible-implement">
 676<title>The Plugin's Visible Window</title>
 677
 678<sect2 id="example-window-class"><title>Class QuickNotepad</title>
 679
 680<para>
 681  Here is where most of the features of the plugin will be implemented.
 682  To work with the dockable window API, the top level window will be a
 683  <classname>JPanel</classname>.  The visible components reflect a
 684  simple layout. Inside the top-level panel we will place a scroll pane with
 685  a text area. Above the scroll pane we will place a panel containing a small
 686  tool bar and a label displaying the path of the current notepad file.
 687</para>
 688
 689<para>
 690  We have identified three user actions in the
 691  <classname>QuickNotepadActions</classname> interface that need
 692  implementation here: <function>chooseFile()</function>,
 693  <function>saveFile()</function>, and
 694  <function>copyToBuffer()</function>. As noted earlier, we also want the
 695  text area to change its appearance in immediate response to a change in
 696  user options settings. In order to do that, the window class must
 697  respond to a <classname>PropertiesChanged</classname> message from
 698  the EditBus.
 699</para>
 700
 701<!-- <para>
 702  We could have the plugin core class receive and delegate
 703  <classname>PropertiesChanged</classname> messages to the window class.
 704  However, this would require the plugin core class to hold a reference
 705  to either the plugin window class or the visible window class and to
 706  update that reference when the user activates or deactivates the
 707  plugin.  It is simpler to have the plugin window class subscribe to the
 708  EditBus directly; many plugins take this approach.  This means that
 709  <classname>QuickNotepad</classname> must implement the
 710  <classname>EBComponent</classname> interface.
 711</para> -->
 712
 713<para>
 714  Unlike the <classname>EBPlugin</classname> class, the
 715  <classname>EBComponent</classname> interface does not deal with the
 716  component's actual subscribing and unsubscribing to the EditBus.  To
 717  accomplish this, we use a pair of methods inherited from the
 718  Java platform's <classname>JComponent</classname> that are called when
 719  the visible window becomes is assigned and unassigned to its
 720  <classname>DockableWindowContainer</classname>.  These two methods,
 721  <function>addNotify()</function> and
 722  <function>removeNotify()</function>, are overridden to add and remove
 723  the visible window from the list of EditBus subscribers.
 724</para>
 725
 726<para>
 727  We will provide for two minor features when the notepad is
 728  displayed in the floating window.  First, when a floating plugin window
 729  is created, we will give the notepad text area input focus.  Second,
 730  when the notepad if floating and has input focus, we will have the
 731  <keycap>Escape</keycap> key dismiss the notepad window.  An
 732  <classname>AncestorListener</classname> and a
 733  <classname>KeyListener</classname> will implement these details.
 734</para>
 735
 736<para>
 737  Here is the listing for the data members, the constructor, and the
 738  implementation of the <classname>EBComponent</classname> interface:
 739</para>
 740
 741<informalexample><programlisting>public class QuickNotepad extends JPanel
 742implements EBComponent, QuickNotepadActions
 743{
 744    private String filename;
 745    private String defaultFilename;
 746    private View view;
 747    private boolean floating;
 748
 749    private QuickNotepadTextArea textArea;
 750    private QuickNotepadToolPanel toolPanel;
 751
 752    //
 753    // Constructor
 754    //
 755
 756    public QuickNotepad(View view, String position)
 757    {
 758        super(new BorderLayout());
 759
 760        this.view = view;
 761        this.floating = position.equals(
 762            DockableWindowManager.FLOATING);
 763
 764        this.filename = jEdit.getProperty(
 765            QuickNotepadPlugin.OPTION_PREFIX
 766            + "filepath");
 767        if(this.filename == null || this.filename.length() == 0)
 768        {
 769            this.filename = new String(jEdit.getSettingsDirectory()
 770                + File.separator + "qn.txt");
 771            jEdit.setProperty(QuickNotepadPlugin.OPTION_PREFIX
 772                + "filepath",this.filename);
 773        }
 774        this.defaultFilename = new String(this.filename);
 775
 776        this.toolPanel = new QuickNotepadToolPanel(this);
 777        add(BorderLayout.NORTH, this.toolPanel);
 778
 779        if(floating)
 780            this.setPreferredSize(new Dimension(500, 250));
 781
 782        textArea = new QuickNotepadTextArea();
 783        textArea.setFont(QuickNotepadOptionPane.makeFont());
 784        textArea.addKeyListener(new KeyHandler());
 785        textArea.addAncestorListener(new AncestorHandler());
 786
 787        JScrollPane pane = new JScrollPane(textArea);
 788        add(BorderLayout.CENTER, pane);
 789
 790        readFile();
 791    }
 792
 793    //
 794    // Attribute methods
 795    //
 796
 797    // for toolBar display
 798    public String getFilename()
 799    {
 800        return filename;
 801    }
 802
 803    //
 804    // EBComponent implementation
 805    //
 806
 807    public void handleMessage(EBMessage message)
 808    {
 809        if (message instanceof PropertiesChanged)
 810        {
 811            propertiesChanged();
 812        }
 813    }
 814
 815
 816    private void propertiesChanged()
 817    {
 818        String propertyFilename = jEdit.getProperty(
 819            QuickNotepadPlugin.OPTION_PREFIX + "filepath");
 820        if(!defaultFilename.equals(propertyFilename))
 821        {
 822            saveFile();
 823            toolPanel.propertiesChanged();
 824            defaultFilename = propertyFilename.clone();
 825            filename = defaultFilename.clone();
 826            readFile();
 827        }
 828        Font newFont = QuickNotepadOptionPane.makeFont();
 829        if(!newFont.equals(textArea.getFont()))
 830        {
 831            textArea.setFont(newFont);
 832            textArea.invalidate();
 833        }
 834    }
 835
 836    // These JComponent methods provide the appropriate points
 837    // to subscribe and unsubscribe this object to the EditBus
 838
 839    public void addNotify()
 840    {
 841        super.addNotify();
 842        EditBus.addToBus(this);
 843    }
 844
 845
 846    public void removeNotify()
 847    {
 848        saveFile();
 849        super.removeNotify();
 850        EditBus.removeFromBus(this);
 851    }
 852
 853    ...
 854
 855}</programlisting></informalexample>
 856
 857<para>
 858  This listing refers to a <classname>QuickNotebookTextArea</classname>
 859  object. It is currently implemented as a <classname>JTextArea</classname> with
 860  word wrap and tab sizes hard-coded.  Placing the object in a separate
 861  class will simply future modifications.
 862</para>
 863
 864</sect2>
 865
 866<sect2 id="example-window-toolbar"><title>Class QuickNotepadToolBar</title>
 867
 868<para>
 869  There is nothing remarkable about the toolbar panel that is placed
 870  inside the <classname>QuickNotepad</classname> object. The constructor
 871  shows the continued use of items from the plugin's properties file.
 872</para>
 873
 874<informalexample><programlisting>public class QuickNotepadToolPanel extends JPanel
 875{
 876    private QuickNotepad pad;
 877    private JLabel label;
 878
 879    public QuickNotepadToolPanel(QuickNotepad qnpad)
 880    {
 881        pad = qnpad;
 882        JToolBar toolBar = new JToolBar();
 883        toolBar.setFloatable(false);
 884
 885        toolBar.add(makeCustomButton("quicknotepad.choose-file",
 886            new ActionListener() {
 887                public void actionPerformed(ActionEvent evt) {
 888                    QuickNotepadToolPanel.this.pad.chooseFile();
 889                }
 890            }));
 891        toolBar.add(makeCustomButton("quicknotepad.save-file",
 892            new ActionListener() {
 893                public void actionPerformed(ActionEvent evt) {
 894                    QuickNotepadToolPanel.this.pad.saveFile();
 895                }
 896            }));
 897        toolBar.add(makeCustomButton("quicknotepad.copy-to-buffer",
 898            new ActionListener() {
 899                public void actionPerformed(ActionEvent evt) {
 900                    QuickNotepadToolPanel.this.pad.copyToBuffer();
 901                }
 902            }));
 903        label = new JLabel(pad.getFilename(),
 904            SwingConstants.RIGHT);
 905        label.setForeground(Color.black);
 906        label.setVisible(jEdit.getProperty(
 907            QuickNotepadPlugin.OPTION_PREFIX
 908            + "show-filepath").equals("true"));
 909        this.setLayout(new BorderLayout(10, 0));
 910        this.add(BorderLayout.WEST, toolBar);
 911        this.add(BorderLayout.CENTER, label);
 912        this.setBorder(BorderFactory.createEmptyBorder(0, 0, 3, 10));
 913    }
 914
 915    ...
 916
 917}</programlisting></informalexample>
 918
 919<para>
 920  The method <classname>makeCustomButton()</classname> provides uniform
 921  attributes for the three toolbar buttons corresponding to three of the
 922  plugin's use actions.  The menu titles for the user actions serve double
 923  duty as tooltip text for the buttons. There is also a
 924  <function>propertiesChanged()</function> method for the toolbar that
 925  sets the text and visibility of the label containing the notepad file path.
 926</para>
 927
 928</sect2>
 929
 930</sect1>
 931
 932<sect1 id="option-implement"><title>Designing an Option Pane</title>
 933
 934<para>
 935  Using the default implementation provided by
 936  <classname>AbstractOptionPane</classname> reduces the preparation of an
 937  option pane to two principal tasks: writing a
 938  <function>_init()</function> method to layout and initialize the pane,
 939  and writing a <function>_save()</function> method to commit any settings
 940  changed by user input. If a button on the option pane should trigger
 941  another dialog, such as a <classname>JFileChooser</classname> or jEdit's
 942  own enhanced <classname>VFSFileChooserDialog</classname>, the option
 943  pane will also have to implement the
 944  <classname>ActionListener</classname> interface to display additional
 945  components.
 946</para>
 947
 948<para>
 949  The QuickNotepad plugin has only three options to set: the path name of
 950  the file that will store the notepad text, the visibility of the
 951  path name on the tool bar, and the notepad's display font.
 952  Using the shortcut methods of the plugin API, the implementation of
 953  <function>_init()</function> looks like this:
 954</para>
 955
 956<informalexample><programlisting>public class QuickNotepadOptionPane extends AbstractOptionPane
 957      implements ActionListener
 958{
 959    private JTextField pathName;
 960    private JButton pickPath;
 961    private FontSelector font;
 962
 963    ...
 964
 965    public void _init()
 966    {
 967        showPath = new JCheckBox(jEdit.getProperty(
 968            QuickNotepadPlugin.OPTION_PREFIX
 969            + "show-filepath.title"),
 970        jEdit.getProperty(
 971            QuickNotepadPlugin.OPTION_PREFIX +  "show-filepath")
 972            .equals("true"));
 973        addComponent(showPath);
 974
 975        pathName = new JTextField(jEdit.getProperty(
 976            QuickNotepadPlugin.OPTION_PREFIX
 977            + "filepath"));
 978        JButton pickPath = new JButton(jEdit.getProperty(
 979            QuickNotepadPlugin.OPTION_PREFIX
 980            + "choose-file"));
 981        pickPath.addActionListener(this);
 982
 983        JPanel pathPanel = new JPanel(new BorderLayout(0, 0));
 984        pathPanel.add(pathName, BorderLayout.CENTER);
 985        pathPanel.add(pickPath, BorderLayout.EAST);
 986
 987        addComponent(jEdit.getProperty(
 988            QuickNotepadPlugin.OPTION_PREFIX + "file"),
 989            pathPanel);
 990
 991        font = new FontSelector(makeFont());
 992        addComponent(jEdit.getProperty(
 993            QuickNotepadPlugin.OPTION_PREFIX + "choose-font"),
 994            font);
 995    }
 996
 997    ...
 998
 999}</programlisting></informalexample>
1000
1001<para>
1002  Here we adopt the vertical arrangement offered by use of the
1003  <function>addComponent()</function> method with one embellishment.
1004  We want the first <quote>row</quote> of the option pane to contain
1005  a text field with the current notepad file path and a button that will
1006  trigger a file chooser dialog when pressed.  To place both of them on
1007  the same line (along with an identifying label for the file option),
1008  we create a <classname>JPanel</classname> to contain both components and
1009  pass the configured panel to <function>addComponent()</function>.
1010</para>
1011
1012<para>
1013  The <function>_init()</function> method uses properties from the plugin's
1014  property file to provide the names of label for the components placed
1015  in the option pane.  It also uses a property whose name begins with
1016  <function>PROPERTY_PREFIX</function> as a persistent data item - the
1017  path of the current notepad file.  The elements of the notepad's font
1018  are also extracted from properties using a static method of the option
1019  pane class.
1020</para>
1021
1022<para>
1023  The <function>_save()</function> method extracts data from the user
1024  input components and
1025  assigns them to the plugin's properties.  The implementation is
1026  straighforward:
1027</para>
1028
1029<informalexample><programlisting>public void _save()
1030{
1031    jEdit.setProperty(QuickNotepadPlugin.OPTION_PREFIX
1032        + "filepath", pathName.getText());
1033    Font _font = font.getFont();
1034    jEdit.setProperty(QuickNotepadPlugin.OPTION_PREFIX
1035        + "font", _font.getFamily());
1036    jEdit.setProperty(QuickNotepadPlugin.OPTION_PREFIX
1037        + "fontsize", String.valueOf(_font.getSize()));
1038    jEdit.setProperty(QuickNotepadPlugin.OPTION_PREFIX
1039        + "fontstyle", String.valueOf(_font.getStyle()));
1040    jEdit.setProperty(QuickNotepadPlugin.OPTION_PREFIX
1041        + "show-filepath", String.valueOf(showPath.isSelected()));
1042}</programlisting></informalexample>
1043
1044<para>
1045  The class has only two other methods, one to display a file chooser
1046  dialog in response to user action, and the other
1047  to construct a <classname>Font</classname> object from the plugin's font
1048  properties. They do not require discussion here.
1049</para>
1050
1051
1052</sect1>
1053
1054<sect1 id="resources-implement"><title>Creating Other Plugin Resources</title>
1055
1056<para>
1057  We have already covered in some detail one of the three types of resources
1058  that plugins include with their class files - the user action catalog - and
1059  the need for help documentation does not require extended discussion.  The
1060  remaining resource is the properties file.
1061</para>
1062
1063<para>
1064  The first type of property data is information about the plugin itself.
1065  The first few entries from the QuickNotepad plugin's properties file
1066  fulfills this requirement:
1067</para>
1068
1069<informalexample><programlisting># general plugin information
1070plugin.QuickNotepadPlugin.name=QuickNotepad
1071plugin.QuickNotepadPlugin.author=John Gellene
1072plugin.QuickNotepadPlugin.version=1.0
1073plugin.QuickNotepadPlugin.docs=QuickNotepad.html
1074plugin.QuickNotepadPlugin.depend.0=jdk 1.2
1075plugin.QuickNotepadPlugin.depend.1=jedit 03.01.99.00</programlisting></informalexample>
1076
1077<para>
1078  These properties are described in detail in <xref
1079  linkend="api-resource-properties" /> and do not require further
1080  discussion here.
1081</para>
1082
1083<para>
1084  Next in the file comes a property that sets the title of the
1085  plugin in docked or frame windows.  The use of the suffix
1086  <literal>.title</literal> in the property's key name is
1087  required by the plugin API.
1088</para>
1089
1090<informalexample><programlisting># dockable window name
1091quicknotepad.title=QuickNotepad</programlisting></informalexample>
1092
1093<para>
1094  The next sections, consisting of the action label and menu item
1095  properties, have been discussed earlier in
1096  <xref linkend="plugin-implement-menu" />.
1097</para>
1098
1099<informalexample><programlisting># action labels
1100quicknotepad.toggle.label=QuickNotepad
1101quicknotepad-to-front.label=Bring QuickNotepad to front
1102quicknotepad.choose-file.label=Choose notepad file
1103quicknotepad.save-file.label=Save notepad file
1104quicknotepad.copy-to-buffer.label=Copy notepad to buffer
1105
1106# application menu items
1107quicknotepad.menu=quicknotepad.toggle - quicknotepad.choose-file \
1108  quicknotepad.save-file quicknotepad.copy-to-buffer</programlisting></informalexample>
1109
1110<para>
1111  We have created a small toolbar as a component of QuickNotepad, so
1112  file names for the button icons follow:
1113</para>
1114
1115<informalexample><programlisting># plugin toolbar buttons
1116quicknotepad.choose-file.icon=Open.gif
1117quicknotepad.save-file.icon=Save.gif
1118quicknotepad.copy-to-buffer.icon=Edit.gif</programlisting></informalexample>
1119
1120<para>
1121  The menu labels corresponding to these icons will also serve as tooltip
1122  text.
1123</para>
1124
1125<para>
1126  Finally, the properties file set forth the labels and settings
1127  used by the option pane:
1128</para>
1129
1130<informalexample><programlisting># Option pane labels
1131options.quicknotepad.label=QuickNotepad
1132options.quicknotepad.file=File:
1133options.quicknotepad.choose-file=Choose
1134options.quicknotepad.choose-file.title=Choose a notepad file
1135options.quicknotepad.choose-font=Font:
1136options.quicknotepad.show-filepath.title=Display notepad file path
1137
1138# Initial default font settings
1139options.quicknotepad.show-filepath=true
1140options.quicknotepad.font=Monospaced
1141options.quicknotepad.fontstyle=0
1142options.quicknotepad.fontsize=14
1143
1144# Setting not defined but supplied for completeness
1145options.quicknotepad.filepath=</programlisting></informalexample>
1146
1147<para>
1148  We do not define a default setting for the <literal>filepath</literal>
1149  property because of
1150  differences among operating systems.  We will define a default file
1151  programatically that will reside in the directory jEdit designates for
1152  user settings.
1153</para>
1154
1155</sect1>
1156
1157<sect1 id="example-building"><title>Compiling the Plugin</title>
1158
1159<para>
1160  We have already outlined the contents of the user action catalog, the
1161  properties file and the documentation file in our earlier dicusssion.
1162  The final step is to compile the source file and build the archive file
1163  that will hold the class files and the plugin's other resources.
1164</para>
1165
1166<para>
1167  Publicly released plugins include with their source a makefile for the
1168  <application>jmk</application> utility.  The format for this file
1169  requires few changes from plugin to plugin.  Here is the version of
1170  <filename>makefile.jmk</filename> used by QuickNotepad and many other
1171  plugins:
1172</para>
1173
1174<informalexample><programlisting><![CDATA[# A plugin makefile
1175#
1176# To recompile this plugin, start jmk
1177# in the plugin's source directory.
1178#
1179
1180jar_name = "QuickNotepad";
1181
1182##
1183# javac executable and args
1184##
1185#javac_bin = "javac";
1186#javac_opts = "-deprecation";
1187
1188javac_bin = "jikes";
1189javac_opts = "-g" "-deprecation" "+E";
1190
1191# set up the class path
1192new_class_path = "../../jedit.jar;.";
1193old_class_path = (getprop "java.class.path");
1194
1195# concatenate the old and new class paths
1196if (equal "", old_class_path) then class_path = new_class_path;
1197else class_path = (cat old_class_path ";" new_class_path); end
1198
1199cmd_javac = javac_bin "-classpath" class_path javac_opts;
1200
1201##
1202# jar executable and args
1203##
1204jar_bin = "jar";
1205jar_opts = "cf0";
1206cmd_jar = jar_bin jar_opts;
1207
1208srcs = (subst ".java", ".class",
1209  (glob (join (join (dirs "."), "/"), "*Plugin.java" "*.java"))
1210);
1211jar = (cat "../" jar_name ".jar");
1212
1213get_files = function (dummy)
1214{
1215  extensions = "class" "gif" "html" "props";
1216  file_globs = (join "/*.", extensions);
1217  other_files = "actions.xml";
1218
1219  (glob (join (dirs "."), file_globs)) other_files
1220}
1221end;
1222
1223"all": jar;
1224
1225"%.class" : "%.java";
1226{
1227    exec cmd_javac <;
1228}
1229
1230jar: srcs;
1231{
1232    exec cmd_jar @ (get_files "1");
1233}
1234
1235"clean":;
1236{
1237  delete (glob (join (dirs "."), "/*.class"));
1238}
1239
1240".PHONY": "all";
1241]]></programlisting></informalexample>
1242
1243<para>
1244  For a full discussion of the <filename>jmk</filename> file format and
1245  command syntax, you should consult the <ulink
1246  url="http://jmk.sourceforge.net/edu/neu/ccs/jmk/jmk.html">jmk
1247  documentation site</ulink>. Modifying this makefile for a different
1248  plugin will likely only require three changes:
1249</para>
1250
1251<itemizedlist>
1252  <listitem><para>
1253    the name of the plugin;
1254  </para></listitem>
1255  <listitem><para>
1256    the choice of compiler (made by inserting and deleting the comment character
1257    <userinput>'#'</userinput>); and
1258  </para> </listitem>
1259  <listitem><para>
1260    the classpath variables for <filename>jedit.jar</filename>
1261    any plugins this one depends on.
1262  </para></listitem>
1263</itemizedlist>
1264
1265<para>
1266  If you have reached this point in the text, you are probably serious
1267  about writing a plugin for jEdit.  Good luck with your efforts, and
1268  thank you for contributing to the jEdit project.
1269</para>
1270
1271</sect1>
1272
1273</chapter>
1274