PageRenderTime 43ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

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

#
XML | 1089 lines | 874 code | 176 blank | 39 comment | 0 complexity | 27f06d50d03eb531e87a4b2b33fe410f MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, Apache-2.0, LGPL-2.0, LGPL-3.0, GPL-2.0, CC-BY-SA-3.0, LGPL-2.1, GPL-3.0, MPL-2.0-no-copyleft-exception, IPL-1.0
  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 labeling properties required by the plugin API; and
  228. </para>
  229. </listitem>
  230. <listitem>
  231. <para>
  232. a prefix to be used in labeling 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. </sect3>
  285. </sect2>
  286. <sect2 id="plugin-implement-resources">
  287. <title>Resources for the Plugin Core Class</title>
  288. <sect3 id="plugin-implement-actions"><title>Actions</title>
  289. <para>
  290. The plugin's user action catalog, <filename>actions.xml</filename>, is
  291. the resource used by the plugin API to get the names and definitions of
  292. user actions. The following <filename>actions.xml</filename>
  293. file from the <application>QuickNotepad</application> plugin can
  294. provide a model:
  295. </para>
  296. <informalexample><programlisting>&lt;!DOCTYPE ACTIONS SYSTEM "actions.dtd"&gt;
  297. &lt;ACTIONS&gt;
  298. &lt;ACTION NAME="quicknotepad.choose-file"&gt;
  299. &lt;CODE&gt;
  300. view.getDockableWindowManager()
  301. .getDockable(QuickNotepadPlugin.NAME).chooseFile();
  302. &lt;/CODE&gt;
  303. &lt;/ACTION&gt;
  304. &lt;ACTION NAME="quicknotepad.save-file"&gt;
  305. &lt;CODE&gt;
  306. view.getDockableWindowManager()
  307. .getDockable(QuickNotepadPlugin.NAME).saveFile();
  308. &lt;/CODE&gt;
  309. &lt;/ACTION&gt;
  310. &lt;ACTION NAME="quicknotepad.copy-to-buffer"&gt;
  311. &lt;CODE&gt;
  312. view.getDockableWindowManager()
  313. .getDockable(QuickNotepadPlugin.NAME).copyToBuffer();
  314. &lt;/CODE&gt;
  315. &lt;/ACTION&gt;
  316. &lt;/ACTIONS&gt;</programlisting></informalexample>
  317. <para>
  318. This file defines three actions. They use the current view's
  319. <classname>DockableWindowManager</classname> object and the method
  320. <filename>getDockable()</filename> to find the QuickNotepad plugin
  321. window and call the desired method.
  322. </para>
  323. <para>
  324. When an action is invoked, program control must pass to the
  325. component responsible for executing the action. The use of an internal
  326. table of BeanShell scripts that implement actions avoids the need for
  327. plugins to implement <classname>ActionListener</classname> or similar
  328. objects to respond to actions. Instead, the BeanShell scripts address
  329. the plugin through static methods, or if instance data is needed, the
  330. current <classname>View</classname>, its
  331. <classname>DockableWindowManager</classname>, and the plugin
  332. object return by the <filename>getDockable()</filename> method.
  333. </para>
  334. <para>
  335. If you are unfamiliar with BeanShell code, you may nevertheless notice
  336. that the code statements bear a strong resemblance to Java code, with
  337. one exception: the
  338. variable <varname>view</varname> is never assigned any value.
  339. </para>
  340. <para>
  341. For complete answers to this and other BeanShell
  342. mysteries, see <xref linkend="writing-macros-part" />; two
  343. observations will suffice here. First, the variable
  344. <varname>view</varname> is predefined by jEdit's implementation of
  345. BeanShell to refer to the current <classname>View</classname> object.
  346. Second, the
  347. BeanShell scripting language is based upon Java syntax, but allows
  348. variables to be typed at run time, so explicit types for variables
  349. need not be declared.
  350. </para>
  351. <para>
  352. A formal description of each element of the
  353. <filename>actions.xml</filename> file can be found in
  354. <xref linkend="resources-action" />.
  355. </para>
  356. </sect3>
  357. <sect3 id="plugin-implement-menu"><title>Action Labels and Menu Items</title>
  358. <para>
  359. Now that we have named and defined actions for the plugin, we have to
  360. put them to work. To do so, we must first give them labels that can be
  361. used in menu items and in the sections of jEdit's options dialog that
  362. deal with toolbar buttons, keyboard shortcuts and context menu items. We
  363. supply this information to jEdit through entries in the plugin's
  364. properties file. A call to
  365. <function>GUIUtilities.loadMenu()</function> or
  366. <function>GUIUtilities.loadMenuItem()</function> will read and extract
  367. the necessary labels from the contents of a properties file.
  368. </para>
  369. <para>
  370. The following excerpt from <filename>QuickNotepad.props</filename>
  371. illustrates the format required for action labels and menu items:
  372. </para>
  373. <informalexample><programlisting># action labels
  374. quicknotepad.toggle.label=QuickNotepad
  375. quicknotepad-to-front.label=Bring QuickNotepad to front
  376. quicknotepad.choose-file.label=Choose notepad file
  377. quicknotepad.save-file.label=Save notepad file
  378. quicknotepad.copy-to-buffer.label=Copy notepad to buffer
  379. # application menu items
  380. quicknotepad.menu.label=QuickNotepad
  381. quicknotepad.menu=quicknotepad.toggle - quicknotepad.choose-file \
  382. quicknotepad.save-file quicknotepad.copy-to-buffer
  383. </programlisting></informalexample>
  384. <para>
  385. <function>GUIUtilities.loadMenuItem()</function> and
  386. <function>GUIUtilites.loadMenu()</function> use special conventions
  387. for the value of a menu property to specify menu layout. In
  388. <function>loadMenu()</function>, the use of the dash, as in the second
  389. item in the example menu list, signifies the placement of a separator.
  390. In addition,
  391. the character <userinput>'%'</userinput> used as a prefix on a
  392. label causes <function>loadMenu()</function> to call itself recursively
  393. with the prefixed label as the source of submenu data. Most plugins
  394. will not need to define menus that contain other submenus.
  395. </para>
  396. <para>
  397. Note also that <function>quicknotepad-to-front</function> is not
  398. included in the menu listing. It will appear, however, on the
  399. <guilabel>Shortcuts</guilabel> pane of the <guimenuitem>Global
  400. Options</guimenuitem> dialog,
  401. so that the action can be associated with a keyboard shortcut.
  402. </para>
  403. </sect3>
  404. </sect2>
  405. </sect1>
  406. <sect1 id="window-implement"><title>Implementing a Dockable Window Class</title>
  407. <para>
  408. Now we must provide the actual implementation of the dockable window
  409. referenced in <filename>dockables.xml</filename> (see
  410. <xref linkend="resources-dockables" /> and
  411. <xref linkend="resources-implement" />). Here is the
  412. <filename>QuickNotepad.java</filename> source file, with some details
  413. not related to the dockable window API trimmed:
  414. </para>
  415. <informalexample><programlisting>public class QuickNotepad extends JPanel
  416. implements EBComponent
  417. {
  418. private View view;
  419. private String position;
  420. ...
  421. public QuickNotepad(View view, String position) {
  422. this.view = view;
  423. this.position = position;
  424. ...
  425. }
  426. ...
  427. public void handleMessage(EBMessage message) {
  428. if (message instanceof PropertiesChanged) {
  429. propertiesChanged();
  430. }
  431. }
  432. ...
  433. }</programlisting></informalexample>
  434. <para>
  435. This excerpt does not set forth the layout of the plugin's visible
  436. components, nor does it show how our user actions will be implemented.
  437. Both these matters are covered in the full source code.
  438. </para>
  439. </sect1>
  440. <sect1 id="window-visible-implement">
  441. <title>The Plugin's Visible Window</title>
  442. <sect2 id="example-window-class"><title>Class QuickNotepad</title>
  443. <para>
  444. Here is where most of the features of the plugin will be implemented.
  445. To work with the dockable window API, the top level window will be a
  446. <classname>JPanel</classname>. The visible components reflect a
  447. simple layout. Inside the top-level panel we will place a scroll pane with
  448. a text area. Above the scroll pane we will place a panel containing a small
  449. tool bar and a label displaying the path of the current notepad file.
  450. </para>
  451. <para>
  452. We have identified three user actions that need
  453. implementation here: <function>chooseFile()</function>,
  454. <function>saveFile()</function>, and
  455. <function>copyToBuffer()</function>. As noted earlier, we also want the
  456. text area to change its appearance in immediate response to a change in
  457. user options settings. In order to do that, the window class must
  458. respond to a <classname>PropertiesChanged</classname> message from
  459. the EditBus.
  460. </para>
  461. <!-- <para>
  462. We could have the plugin core class receive and delegate
  463. <classname>PropertiesChanged</classname> messages to the window class.
  464. However, this would require the plugin core class to hold a reference
  465. to either the plugin window class or the visible window class and to
  466. update that reference when the user activates or deactivates the
  467. plugin. It is simpler to have the plugin window class subscribe to the
  468. EditBus directly; many plugins take this approach. This means that
  469. <classname>QuickNotepad</classname> must implement the
  470. <classname>EBComponent</classname> interface.
  471. </para> -->
  472. <para>
  473. Unlike the <classname>EBPlugin</classname> class, the
  474. <classname>EBComponent</classname> interface does not deal with the
  475. component's actual subscribing and unsubscribing to the EditBus. To
  476. accomplish this, we use a pair of methods inherited from the
  477. Java platform's <classname>JComponent</classname> class
  478. that are called when the window is made visible, and when it is hidden.
  479. These two methods,
  480. <function>addNotify()</function> and
  481. <function>removeNotify()</function>, are overridden to add and remove
  482. the visible window from the list of EditBus subscribers.
  483. </para>
  484. <para>
  485. We will provide for two minor features when the notepad is
  486. displayed in the floating window. First, when a floating plugin window
  487. is created, we will give the notepad text area input focus. Second,
  488. when the notepad if floating and has input focus, we will have the
  489. <keycap>Escape</keycap> key dismiss the notepad window. An
  490. <classname>AncestorListener</classname> and a
  491. <classname>KeyListener</classname> will implement these details.
  492. </para>
  493. <para>
  494. Here is the listing for the data members, the constructor, and the
  495. implementation of the <classname>EBComponent</classname> interface:
  496. </para>
  497. <informalexample><programlisting>public class QuickNotepad extends JPanel
  498. implements EBComponent
  499. {
  500. private String filename;
  501. private String defaultFilename;
  502. private View view;
  503. private boolean floating;
  504. private QuickNotepadTextArea textArea;
  505. private QuickNotepadToolPanel toolPanel;
  506. //
  507. // Constructor
  508. //
  509. public QuickNotepad(View view, String position)
  510. {
  511. super(new BorderLayout());
  512. this.view = view;
  513. this.floating = position.equals(
  514. DockableWindowManager.FLOATING);
  515. this.filename = jEdit.getProperty(
  516. QuickNotepadPlugin.OPTION_PREFIX
  517. + "filepath");
  518. if(this.filename == null || this.filename.length() == 0)
  519. {
  520. this.filename = new String(jEdit.getSettingsDirectory()
  521. + File.separator + "qn.txt");
  522. jEdit.setProperty(QuickNotepadPlugin.OPTION_PREFIX
  523. + "filepath",this.filename);
  524. }
  525. this.defaultFilename = new String(this.filename);
  526. this.toolPanel = new QuickNotepadToolPanel(this);
  527. add(BorderLayout.NORTH, this.toolPanel);
  528. if(floating)
  529. this.setPreferredSize(new Dimension(500, 250));
  530. textArea = new QuickNotepadTextArea();
  531. textArea.setFont(QuickNotepadOptionPane.makeFont());
  532. textArea.addKeyListener(new KeyHandler());
  533. textArea.addAncestorListener(new AncestorHandler());
  534. JScrollPane pane = new JScrollPane(textArea);
  535. add(BorderLayout.CENTER, pane);
  536. readFile();
  537. }
  538. //
  539. // Attribute methods
  540. //
  541. // for toolBar display
  542. public String getFilename()
  543. {
  544. return filename;
  545. }
  546. //
  547. // EBComponent implementation
  548. //
  549. public void handleMessage(EBMessage message)
  550. {
  551. if (message instanceof PropertiesChanged)
  552. {
  553. propertiesChanged();
  554. }
  555. }
  556. private void propertiesChanged()
  557. {
  558. String propertyFilename = jEdit.getProperty(
  559. QuickNotepadPlugin.OPTION_PREFIX + "filepath");
  560. if(!defaultFilename.equals(propertyFilename))
  561. {
  562. saveFile();
  563. toolPanel.propertiesChanged();
  564. defaultFilename = propertyFilename.clone();
  565. filename = defaultFilename.clone();
  566. readFile();
  567. }
  568. Font newFont = QuickNotepadOptionPane.makeFont();
  569. if(!newFont.equals(textArea.getFont()))
  570. {
  571. textArea.setFont(newFont);
  572. textArea.invalidate();
  573. }
  574. }
  575. // These JComponent methods provide the appropriate points
  576. // to subscribe and unsubscribe this object to the EditBus
  577. public void addNotify()
  578. {
  579. super.addNotify();
  580. EditBus.addToBus(this);
  581. }
  582. public void removeNotify()
  583. {
  584. saveFile();
  585. super.removeNotify();
  586. EditBus.removeFromBus(this);
  587. }
  588. ...
  589. }</programlisting></informalexample>
  590. <para>
  591. This listing refers to a <classname>QuickNotebookTextArea</classname>
  592. object. It is currently implemented as a <classname>JTextArea</classname> with
  593. word wrap and tab sizes hard-coded. Placing the object in a separate
  594. class will simply future modifications.
  595. </para>
  596. </sect2>
  597. <sect2 id="example-window-toolbar"><title>Class QuickNotepadToolBar</title>
  598. <para>
  599. There is nothing remarkable about the toolbar panel that is placed
  600. inside the <classname>QuickNotepad</classname> object. The constructor
  601. shows the continued use of items from the plugin's properties file.
  602. </para>
  603. <informalexample><programlisting>public class QuickNotepadToolPanel extends JPanel
  604. {
  605. private QuickNotepad pad;
  606. private JLabel label;
  607. public QuickNotepadToolPanel(QuickNotepad qnpad)
  608. {
  609. pad = qnpad;
  610. JToolBar toolBar = new JToolBar();
  611. toolBar.setFloatable(false);
  612. toolBar.add(makeCustomButton("quicknotepad.choose-file",
  613. new ActionListener() {
  614. public void actionPerformed(ActionEvent evt) {
  615. QuickNotepadToolPanel.this.pad.chooseFile();
  616. }
  617. }));
  618. toolBar.add(makeCustomButton("quicknotepad.save-file",
  619. new ActionListener() {
  620. public void actionPerformed(ActionEvent evt) {
  621. QuickNotepadToolPanel.this.pad.saveFile();
  622. }
  623. }));
  624. toolBar.add(makeCustomButton("quicknotepad.copy-to-buffer",
  625. new ActionListener() {
  626. public void actionPerformed(ActionEvent evt) {
  627. QuickNotepadToolPanel.this.pad.copyToBuffer();
  628. }
  629. }));
  630. label = new JLabel(pad.getFilename(),
  631. SwingConstants.RIGHT);
  632. label.setForeground(Color.black);
  633. label.setVisible(jEdit.getProperty(
  634. QuickNotepadPlugin.OPTION_PREFIX
  635. + "show-filepath").equals("true"));
  636. this.setLayout(new BorderLayout(10, 0));
  637. this.add(BorderLayout.WEST, toolBar);
  638. this.add(BorderLayout.CENTER, label);
  639. this.setBorder(BorderFactory.createEmptyBorder(0, 0, 3, 10));
  640. }
  641. ...
  642. }</programlisting></informalexample>
  643. <para>
  644. The method <classname>makeCustomButton()</classname> provides uniform
  645. attributes for the three toolbar buttons corresponding to three of the
  646. plugin's use actions. The menu titles for the user actions serve double
  647. duty as tooltip text for the buttons. There is also a
  648. <function>propertiesChanged()</function> method for the toolbar that
  649. sets the text and visibility of the label containing the notepad file path.
  650. </para>
  651. </sect2>
  652. </sect1>
  653. <sect1 id="option-implement"><title>Designing an Option Pane</title>
  654. <para>
  655. Using the default implementation provided by
  656. <classname>AbstractOptionPane</classname> reduces the preparation of an
  657. option pane to two principal tasks: writing a
  658. <function>_init()</function> method to layout and initialize the pane,
  659. and writing a <function>_save()</function> method to commit any settings
  660. changed by user input. If a button on the option pane should trigger
  661. another dialog, such as a <classname>JFileChooser</classname> or jEdit's
  662. own enhanced <classname>VFSFileChooserDialog</classname>, the option
  663. pane will also have to implement the
  664. <classname>ActionListener</classname> interface to display additional
  665. components.
  666. </para>
  667. <para>
  668. The QuickNotepad plugin has only three options to set: the path name of
  669. the file that will store the notepad text, the visibility of the
  670. path name on the tool bar, and the notepad's display font.
  671. Using the shortcut methods of the plugin API, the implementation of
  672. <function>_init()</function> looks like this:
  673. </para>
  674. <informalexample><programlisting>public class QuickNotepadOptionPane extends AbstractOptionPane
  675. implements ActionListener
  676. {
  677. private JTextField pathName;
  678. private JButton pickPath;
  679. private FontSelector font;
  680. ...
  681. public void _init()
  682. {
  683. showPath = new JCheckBox(jEdit.getProperty(
  684. QuickNotepadPlugin.OPTION_PREFIX
  685. + "show-filepath.title"),
  686. jEdit.getProperty(
  687. QuickNotepadPlugin.OPTION_PREFIX + "show-filepath")
  688. .equals("true"));
  689. addComponent(showPath);
  690. pathName = new JTextField(jEdit.getProperty(
  691. QuickNotepadPlugin.OPTION_PREFIX
  692. + "filepath"));
  693. JButton pickPath = new JButton(jEdit.getProperty(
  694. QuickNotepadPlugin.OPTION_PREFIX
  695. + "choose-file"));
  696. pickPath.addActionListener(this);
  697. JPanel pathPanel = new JPanel(new BorderLayout(0, 0));
  698. pathPanel.add(pathName, BorderLayout.CENTER);
  699. pathPanel.add(pickPath, BorderLayout.EAST);
  700. addComponent(jEdit.getProperty(
  701. QuickNotepadPlugin.OPTION_PREFIX + "file"),
  702. pathPanel);
  703. font = new FontSelector(makeFont());
  704. addComponent(jEdit.getProperty(
  705. QuickNotepadPlugin.OPTION_PREFIX + "choose-font"),
  706. font);
  707. }
  708. ...
  709. }</programlisting></informalexample>
  710. <para>
  711. Here we adopt the vertical arrangement offered by use of the
  712. <function>addComponent()</function> method with one embellishment.
  713. We want the first <quote>row</quote> of the option pane to contain
  714. a text field with the current notepad file path and a button that will
  715. trigger a file chooser dialog when pressed. To place both of them on
  716. the same line (along with an identifying label for the file option),
  717. we create a <classname>JPanel</classname> to contain both components and
  718. pass the configured panel to <function>addComponent()</function>.
  719. </para>
  720. <para>
  721. The <function>_init()</function> method uses properties from the plugin's
  722. property file to provide the names of label for the components placed
  723. in the option pane. It also uses a property whose name begins with
  724. <function>PROPERTY_PREFIX</function> as a persistent data item - the
  725. path of the current notepad file. The elements of the notepad's font
  726. are also extracted from properties using a static method of the option
  727. pane class.
  728. </para>
  729. <para>
  730. The <function>_save()</function> method extracts data from the user
  731. input components and
  732. assigns them to the plugin's properties. The implementation is
  733. straightforward:
  734. </para>
  735. <informalexample><programlisting>public void _save()
  736. {
  737. jEdit.setProperty(QuickNotepadPlugin.OPTION_PREFIX
  738. + "filepath", pathName.getText());
  739. Font _font = font.getFont();
  740. jEdit.setProperty(QuickNotepadPlugin.OPTION_PREFIX
  741. + "font", _font.getFamily());
  742. jEdit.setProperty(QuickNotepadPlugin.OPTION_PREFIX
  743. + "fontsize", String.valueOf(_font.getSize()));
  744. jEdit.setProperty(QuickNotepadPlugin.OPTION_PREFIX
  745. + "fontstyle", String.valueOf(_font.getStyle()));
  746. jEdit.setProperty(QuickNotepadPlugin.OPTION_PREFIX
  747. + "show-filepath", String.valueOf(showPath.isSelected()));
  748. }</programlisting></informalexample>
  749. <para>
  750. The class has only two other methods, one to display a file chooser
  751. dialog in response to user action, and the other
  752. to construct a <classname>Font</classname> object from the plugin's font
  753. properties. They do not require discussion here.
  754. </para>
  755. </sect1>
  756. <sect1 id="resources-implement"><title>Creating Other Plugin Resources</title>
  757. <para>
  758. We have already covered in some detail one of the three types of resources
  759. that plugins include with their class files - the user action catalog - and
  760. the need for help documentation does not require extended discussion. The
  761. remaining resource is the properties file.
  762. </para>
  763. <para>
  764. The first type of property data is information about the plugin itself.
  765. The first few entries from the QuickNotepad plugin's properties file
  766. fulfills this requirement:
  767. </para>
  768. <informalexample><programlisting># general plugin information
  769. plugin.QuickNotepadPlugin.name=QuickNotepad
  770. plugin.QuickNotepadPlugin.author=John Gellene
  771. plugin.QuickNotepadPlugin.version=2.0
  772. plugin.QuickNotepadPlugin.docs=QuickNotepad.html
  773. plugin.QuickNotepadPlugin.depend.0=jedit 04.00.01.00</programlisting></informalexample>
  774. <para>
  775. These properties are described in detail in <xref
  776. linkend="resources-properties" /> and do not require further
  777. discussion here.
  778. </para>
  779. <para>
  780. Next in the file comes a property that sets the title of the
  781. plugin in docked or frame windows. The use of the suffix
  782. <literal>.title</literal> in the property's key name is
  783. required by the plugin API.
  784. </para>
  785. <informalexample><programlisting># dockable window name
  786. quicknotepad.title=QuickNotepad</programlisting></informalexample>
  787. <para>
  788. The next sections, consisting of the action label and menu item
  789. properties, have been discussed earlier in
  790. <xref linkend="plugin-implement-menu" />.
  791. </para>
  792. <informalexample><programlisting># action labels
  793. quicknotepad.toggle.label=QuickNotepad
  794. quicknotepad-to-front.label=Bring QuickNotepad to front
  795. quicknotepad.choose-file.label=Choose notepad file
  796. quicknotepad.save-file.label=Save notepad file
  797. quicknotepad.copy-to-buffer.label=Copy notepad to buffer
  798. # application menu items
  799. quicknotepad.menu=quicknotepad.toggle - quicknotepad.choose-file \
  800. quicknotepad.save-file quicknotepad.copy-to-buffer</programlisting></informalexample>
  801. <para>
  802. We have created a small toolbar as a component of QuickNotepad, so
  803. file names for the button icons follow:
  804. </para>
  805. <informalexample><programlisting># plugin toolbar buttons
  806. quicknotepad.choose-file.icon=Open.gif
  807. quicknotepad.save-file.icon=Save.gif
  808. quicknotepad.copy-to-buffer.icon=Edit.gif</programlisting></informalexample>
  809. <para>
  810. The menu labels corresponding to these icons will also serve as tooltip
  811. text.
  812. </para>
  813. <para>
  814. Finally, the properties file set forth the labels and settings
  815. used by the option pane:
  816. </para>
  817. <informalexample><programlisting># Option pane labels
  818. options.quicknotepad.label=QuickNotepad
  819. options.quicknotepad.file=File:
  820. options.quicknotepad.choose-file=Choose
  821. options.quicknotepad.choose-file.title=Choose a notepad file
  822. options.quicknotepad.choose-font=Font:
  823. options.quicknotepad.show-filepath.title=Display notepad file path
  824. # Initial default font settings
  825. options.quicknotepad.show-filepath=true
  826. options.quicknotepad.font=Monospaced
  827. options.quicknotepad.fontstyle=0
  828. options.quicknotepad.fontsize=14
  829. # Setting not defined but supplied for completeness
  830. options.quicknotepad.filepath=</programlisting></informalexample>
  831. <para>
  832. We do not define a default setting for the <literal>filepath</literal>
  833. property because of
  834. differences among operating systems. We will define a default file
  835. programatically that will reside in the directory jEdit designates for
  836. user settings.
  837. </para>
  838. </sect1>
  839. <sect1 id="example-building"><title>Compiling the Plugin</title>
  840. <para>
  841. We have already outlined the contents of the user action catalog, the
  842. properties file and the documentation file in our earlier discussion.
  843. The final step is to compile the source file and build the archive file
  844. that will hold the class files and the plugin's other resources.
  845. </para>
  846. <para>
  847. Publicly released plugins include with their source a makefile
  848. in XML format for the
  849. <application>Ant</application> utility. The format for this file
  850. requires few changes from plugin to plugin. Here is the version of
  851. <filename>build.xml</filename> used by QuickNotepad and many other
  852. plugins:
  853. </para>
  854. <informalexample><programlisting><![CDATA[<project name="QuickNotepad" default="dist" basedir=".">
  855. <property name="jedit.install.dir" value="../.."/>
  856. <property name="jar.name" value="QuickNotepad.jar"/>
  857. <property name="install.dir" value=".."/>
  858. <path id="project.class.path">
  859. <pathelement location="${jedit.install.dir}/jedit.jar"/>
  860. <pathelement location="."/>
  861. </path>
  862. <target name="compile">
  863. <javac
  864. srcdir="."
  865. deprecation="on"
  866. includeJavaRuntime="yes"
  867. >
  868. <classpath refid="project.class.path"/>
  869. </javac>
  870. </target>
  871. <target name="dist" depends="compile">
  872. <mkdir dir="${install.dir}"/>
  873. <jar jarfile="${install.dir}/${jar.name}">
  874. <fileset dir=".">
  875. <include name="**/*.class"/>
  876. <include name="**/*.props"/>
  877. <include name="**/*.html"/>
  878. <include name="**/*.gif"/>
  879. <include name="actions.xml"/>
  880. <include name="dockables.xml"/>
  881. </fileset>
  882. </jar>
  883. </target>
  884. </project>]]></programlisting></informalexample>
  885. <para>
  886. For a full discussion of the <filename>Ant</filename> file format and
  887. command syntax, you should consult the <ulink
  888. url="http://jakarta.apache.org/ant/manual/index.html">Ant
  889. documentation site</ulink>. Modifying this makefile for a different
  890. plugin will likely only require three changes:
  891. </para>
  892. <itemizedlist>
  893. <listitem><para>
  894. the name of the plugin;
  895. </para></listitem>
  896. <listitem><para>
  897. the choice of compiler (made by inserting and deleting the comment character
  898. <userinput>'#'</userinput>); and
  899. </para> </listitem>
  900. <listitem><para>
  901. the classpath variables for <filename>jedit.jar</filename>
  902. any plugins this one depends on.
  903. </para></listitem>
  904. </itemizedlist>
  905. <para>
  906. If you have reached this point in the text, you are probably serious
  907. about writing a plugin for jEdit. Good luck with your efforts, and
  908. thank you for contributing to the jEdit project.
  909. </para>
  910. </sect1>
  911. </chapter>