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

# · XML · 1231 lines · 941 code · 218 blank · 72 comment · 0 complexity · 860f8a9d07a1a9274af002942682b863 MD5 · raw file

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