PageRenderTime 53ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

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

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