PageRenderTime 92ms CodeModel.GetById 81ms app.highlight 5ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-2-pre4/doc/users-guide/plugin-implement.xml

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