PageRenderTime 62ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/www/tags/NOV_07_2009/htdocs/42docs/users-guide/plugin-implement.xml

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