PageRenderTime 45ms CodeModel.GetById 9ms RepoModel.GetById 0ms app.codeStats 1ms

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

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