PageRenderTime 53ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/www/tags/NOV_07_2009/htdocs/users-guide/macro-analysis.html

#
HTML | 280 lines | 280 code | 0 blank | 0 comment | 0 complexity | 36d6df2c1981f3cf7681a6517c1b6291 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. <html><head><meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"><title>Analysis of the Macro</title><meta name="generator" content="DocBook XSL Stylesheets V1.73.2"><link rel="start" href="index.html" title="jEdit 4.3 User's Guide"><link rel="up" href="dialog-macro.html" title="Chapter 14. A Dialog-Based Macro"><link rel="prev" href="add-prefix-and-suffix.html" title="Listing of the Macro"><link rel="next" href="macro-tips.html" title="Chapter 15. Macro Tips and Techniques"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">Analysis of the Macro</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="add-prefix-and-suffix.html">Prev</a> </td><th width="60%" align="center">Chapter 14. A Dialog-Based Macro</th><td width="20%" align="right"> <a accesskey="n" href="macro-tips.html">Next</a></td></tr></table><hr></div><div class="sect1" lang="en"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="macro-analysis"></a>Analysis of the Macro</h2></div></div></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="explain-imports"></a>Import Statements</h3></div></div></div><div class="informalexample"><pre class="programlisting">// import statement
  2. import javax.swing.border.*;</pre></div><p>This macro makes use of classes in the
  3. <code class="literal">javax.swing.border</code> package, which is not
  4. automatically imported. As we mentioned previously (see <a class="xref" href="first-example.html" title="The Mandatory First Example">the section called &#8220;The Mandatory First Example&#8221;</a>), jEdit's implementation of BeanShell
  5. causes a number of classes to be automatically imported. Classes
  6. that are not automatically imported must be identified by a full
  7. qualified name or be the subject of an <code class="function">import</code>
  8. statement.</p></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="explain-create-dialog"></a>Create the Dialog</h3></div></div></div><div class="informalexample"><pre class="programlisting">// create dialog object
  9. title = &#8220;<span class="quote">Add prefix and suffix to selected lines</span>&#8221;;
  10. dialog = new JDialog(view, title, false);
  11. content = new JPanel(new BorderLayout());
  12. content.setBorder(new EmptyBorder(12, 12, 12, 12));
  13. dialog.setContentPane(content);</pre></div><p>To get input for the macro, we need a dialog that provides for
  14. input of the prefix and suffix strings, an <span class="guibutton"><strong>OK</strong></span>
  15. button to perform text insertion, and a
  16. <span class="guibutton"><strong>Cancel</strong></span> button in case we change our mind. We
  17. have decided to make the dialog window non-modal. This will allow us
  18. to move around in the text buffer to find things we may need
  19. (including text to cut and paste) while the macro is running and the
  20. dialog is visible.</p><p>The Java object we need is a <code class="classname">JDialog</code>
  21. object from the Swing package. To construct one, we use the
  22. <code class="function">new</code> keyword and call a
  23. <em class="glossterm">constructor</em> function. The constructor we use
  24. takes three parameters: the owner of the new dialog, the title to be
  25. displayed in the dialog frame, and a <code class="classname">boolean</code>
  26. parameter (<code class="constant">true</code> or <code class="constant">false</code>)
  27. that specifies whether the dialog will be modal or non-modal. We
  28. define the variable <code class="varname">title</code> using a string literal,
  29. then use it immediately in the <code class="classname">JDialog</code>
  30. constructor.</p><p>A <code class="classname">JDialog</code> object is a window containing
  31. a single object called a <em class="glossterm">content pane</em>. The
  32. content pane in turn contains the various visible components of the
  33. dialog. A <code class="classname">JDialog</code> creates an empty content
  34. pane for itself as during its construction. However, to control the
  35. dialog's appearance as much as possible, we will separately create
  36. our own content pane and attach it to the
  37. <code class="classname">JDialog</code>. We do this by creating a
  38. <code class="classname">JPanel</code> object. A
  39. <code class="classname">JPanel</code> is a lightweight container for other
  40. components that can be set to a given size and color. It also
  41. contains a <em class="glossterm">layout</em> scheme for arranging the
  42. size and position of its components. Here we are constructing a
  43. <code class="classname">JPanel</code> as a content pane with a
  44. <code class="classname">BorderLayout</code>. We put a
  45. <code class="classname">EmptyBorder</code> inside it to serve as a margin
  46. between the edge of the window and the components inside. We then
  47. attach the <code class="classname">JPanel</code> as the dialog's content
  48. pane, replacing the dialog's home-grown version.</p><p>A <code class="classname">BorderLayout</code> is one of the simpler
  49. layout schemes available for container objects like
  50. <code class="classname">JPanel</code>. A <code class="classname">BorderLayout</code>
  51. divides the container into five sections: &#8220;<span class="quote">North</span>&#8221;,
  52. &#8220;<span class="quote">South</span>&#8221;, &#8220;<span class="quote">East</span>&#8221;, &#8220;<span class="quote">West</span>&#8221; and
  53. &#8220;<span class="quote">Center</span>&#8221;. Components are added to the layout using the
  54. container's <code class="function">add</code> method, specifying the
  55. component to be added and the section to which it is assigned.
  56. Building a component like our dialog window involves building a set
  57. of nested containers and specifying the location of each of their
  58. member components. We have taken the first step by creating a
  59. <code class="classname">JPanel</code> as the dialog's content pane.</p></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="explain-fields-panel"></a>Create the Text Fields</h3></div></div></div><div class="informalexample"><pre class="programlisting">// add the text fields
  60. fieldPanel = new JPanel(new GridLayout(4, 1, 0, 6));
  61. prefixField = new HistoryTextField("macro.add-prefix");
  62. prefixLabel = new JLabel(&#8220;<span class="quote">Prefix to add</span>&#8221;:);
  63. suffixField = new HistoryTextField(&#8220;<span class="quote">macro.add-suffix</span>&#8221;);
  64. suffixLabel = new JLabel(&#8220;<span class="quote">Suffix to add:</span>&#8221;);
  65. fieldPanel.add(prefixLabel);
  66. fieldPanel.add(prefixField);
  67. fieldPanel.add(suffixLabel);
  68. fieldPanel.add(suffixField);
  69. content.add(fieldPanel, &#8220;<span class="quote">Center</span>&#8221;);</pre></div><p>Next we shall create a smaller panel containing two fields for
  70. entering the prefix and suffix text and two labels identifying the
  71. input fields.</p><p>For the text fields, we will use jEdit's <a class="ulink" href="../api/org/gjt/sp/jedit/gui/HistoryTextField.html" target="_top">HistoryTextField</a>
  72. class. It is derived from the Java Swing class
  73. <code class="classname">JTextField</code>. This class offers the enhancement
  74. of a stored list of prior values used as text input. When the
  75. component has input focus, the up and down keys scroll through the
  76. prior values for the variable. </p><p>To create the <a class="ulink" href="../api/org/gjt/sp/jedit/gui/HistoryTextField.html" target="_top">HistoryTextField</a>
  77. objects we use a constructor method that takes a single parameter:
  78. the name of the tag under which history values will be stored. Here
  79. we choose names that are not likely to conflict with existing jEdit
  80. history items.</p><p>The labels that accompany the text fields are
  81. <code class="classname">JLabel</code> objects from the Java Swing package.
  82. The constructor we use for both labels takes the label text as a
  83. single <code class="classname">String</code> parameter.</p><p>We wish to arrange these four components from top to bottom,
  84. one after the other. To achieve that, we use a
  85. <code class="classname">JPanel</code> container object named
  86. <code class="varname">fieldPanel</code> that will be nested inside the
  87. dialog's content pane that we have already created. In the
  88. constructor for <code class="varname">fieldPanel</code>, we assign a new
  89. <code class="classname">GridLayout</code> with the indicated parameters:
  90. four rows, one column, zero spacing between columns (a meaningless
  91. element of a grid with only one column, but nevertheless a required
  92. parameter) and spacing of six pixels between rows. The spacing
  93. between rows spreads out the four &#8220;<span class="quote">grid</span>&#8221; elements.
  94. After the components, the panel and the layout are specified, the
  95. components are added to <code class="varname">fieldPanel</code> top to bottom,
  96. one &#8220;<span class="quote">grid cell</span>&#8221; at a time. Finally, the complete
  97. <code class="varname">fieldPanel</code> is added to the dialog's content pane
  98. to occupy the &#8220;<span class="quote">Center</span>&#8221; section of the content
  99. pane.</p></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="explain-button-panel"></a>Create the Buttons</h3></div></div></div><div class="informalexample"><pre class="programlisting">// add the buttons
  100. buttonPanel = new JPanel();
  101. buttonPanel.setLayout(new BoxLayout(buttonPanel,
  102. BoxLayout.X_AXIS));
  103. buttonPanel.setBorder(new EmptyBorder(12, 50, 0, 50));
  104. buttonPanel.add(Box.createGlue());
  105. ok = new JButton(&#8220;<span class="quote">OK</span>&#8221;);
  106. cancel = new JButton(&#8220;<span class="quote">Cancel</span>&#8221;);
  107. ok.setPreferredSize(cancel.getPreferredSize());
  108. dialog.getRootPane().setDefaultButton(ok);
  109. buttonPanel.add(ok);
  110. buttonPanel.add(Box.createHorizontalStrut(6));
  111. buttonPanel.add(cancel);
  112. buttonPanel.add(Box.createGlue());
  113. content.add(buttonPanel, &#8220;<span class="quote">South</span>&#8221;);</pre></div><p>To create the dialog's buttons, we follow repeat the
  114. &#8220;<span class="quote">nested container</span>&#8221; pattern we used in creating the text
  115. fields. First, we create a new, nested panel. This time we use a
  116. <code class="classname">BoxLayout</code> that places components either in a
  117. single row or column, depending on the parameter passed to its
  118. constructor. This layout object is more flexible than a
  119. <code class="classname">GridLayout</code> in that variable spacing between
  120. elements can be specified easily. We put an
  121. <code class="classname">EmptyBorder</code> in the new panel to set margins
  122. for placing the buttons. Then we create the buttons, using a
  123. <code class="classname">JButton</code> constructor that specifies the button
  124. text. After setting the size of the <span class="guilabel"><strong>OK</strong></span> button
  125. to equal the size of the <span class="guilabel"><strong>Cancel</strong></span> button, we
  126. designate the <span class="guilabel"><strong>OK</strong></span> button as the default button
  127. in the dialog. This causes the <span class="guilabel"><strong>OK</strong></span> button to be
  128. outlined when the dialog if first displayed. Finally, we place the
  129. buttons side by side with a 6 pixel gap between them (for aesthetic
  130. reasons), and place the completed <code class="varname">buttonPanel</code> in
  131. the &#8220;<span class="quote">South</span>&#8221; section of the dialog's content
  132. pane.</p></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="explain-add-listeners"></a>Register the Action Listeners</h3></div></div></div><div class="informalexample"><pre class="programlisting">// register this method as an ActionListener for
  133. // the buttons and text fields
  134. ok.addActionListener(this);
  135. cancel.addActionListener(this);
  136. prefixField.addActionListener(this);
  137. suffixField.addActionListener(this);</pre></div><p>In order to specify the action to be taken upon clicking a
  138. button or pressing the <code class="keycap">Enter</code> key, we must register
  139. an <code class="classname">ActionListener</code> for each of the four active
  140. components of the dialog - the two <a class="ulink" href="../api/org/gjt/sp/jedit/HistoryTextField.html" target="_top">HistoryTextField</a>
  141. components and the two buttons. In Java, an
  142. <code class="classname">ActionListener</code> is an
  143. <em class="glossterm">interface</em> - an abstract specification for a
  144. derived class to implement. The
  145. <code class="classname">ActionListener</code> interface contains a single
  146. method to be implemented:</p><div class="funcsynopsis"><table border="0" summary="Function synopsis" cellspacing="0" cellpadding="0"><tr><td><code class="funcdef">public void
  147. <b class="fsfunc">actionPerformed</b>(</code></td><td>ActionEvent  </td><td><var class="pdparam">e</var><code>)</code>;</td></tr></table></div><p>BeanShell does not permit a script to create derived classes.
  148. However, BeanShell offers a useful substitute: a method can be used
  149. as a scripted object that can include nested methods implementing a
  150. number of Java interfaces. The method
  151. <code class="function">prefixSuffixDialog()</code> that we are writing can
  152. thus be treated as an <code class="classname">ActionListener</code> object.
  153. To accomplish this, we call <code class="function">addActionListener()</code>
  154. on each of the four components specifying <code class="varname">this</code> as
  155. the <code class="classname">ActionListener</code>. We still need to
  156. implement the interface. We will do that shortly.</p></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="explain-set-visible"></a>Make the Dialog Visible</h3></div></div></div><div class="informalexample"><pre class="programlisting">// locate the dialog in the center of the
  157. // editing pane and make it visible
  158. dialog.pack();
  159. dialog.setLocationRelativeTo(view);
  160. dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
  161. dialog.setVisible(true);</pre></div><p>Here we do three things. First, we activate all the layout
  162. routines we have established by calling the
  163. <code class="function">pack()</code> method for the dialog as the top-level
  164. window. Next we center the dialog's position in the active jEdit
  165. <code class="varname">view</code> by calling
  166. <code class="function">setLocationRelativeTo()</code> on the dialog. We also
  167. call the <code class="function">setDefaultCloseOperation()</code> function to
  168. specify that the dialog box should be immediately disposed if the
  169. user clicks the close box. Finally, we activate the dialog by
  170. calling <code class="function">setVisible()</code>with the state parameter
  171. set to <code class="constant">true</code>.</p><p>At this point we have a decent looking dialog window that
  172. doesn't do anything. Without more code, it will not respond to user
  173. input and will not accomplish any text manipulation. The remainder
  174. of the script deals with these two requirements.</p></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="explain-action-listener"></a>The Action Listener</h3></div></div></div><div class="informalexample"><pre class="programlisting">// this method will be called when a button is clicked
  175. // or when ENTER is pressed
  176. void actionPerformed(e)
  177. {
  178. if(e.getSource() != cancel)
  179. {
  180. processText();
  181. }
  182. dialog.dispose();
  183. }</pre></div><p>The method <code class="function">actionPerformed()</code> nested
  184. inside <code class="function">prefixSuffixDialog()</code> implements the
  185. implicit <code class="classname">ActionListener</code> interface. It looks
  186. at the source of the <code class="classname">ActionEvent</code>, determined
  187. by a call to <code class="function">getSource()</code>. What we do with this
  188. return value is straightforward: if the source is not the
  189. <span class="guibutton"><strong>Cancel</strong></span> button, we call the
  190. <code class="function">processText()</code> method to insert the prefix and
  191. suffix text. Then the dialog is closed by calling its
  192. <code class="function">dispose()</code> method.</p><p>The ability to implement interfaces like
  193. <code class="classname">ActionListener</code> inside a BeanShell script is
  194. one of the more powerful features of the BeanShell package. this
  195. technique is discussed in the next chapter; see <a class="xref" href="macro-tips-BeanShell.html#macro-tips-BeanShell-class" title="Implementing Classes and Interfaces">the section called &#8220;Implementing Classes and Interfaces&#8221;</a>.</p></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="explain-process-text"></a>Get the User's Input</h3></div></div></div><div class="informalexample"><pre class="programlisting">// this is where the work gets done to insert
  196. // the prefix and suffix
  197. void processText()
  198. {
  199. prefix = prefixField.getText();
  200. suffix = suffixField.getText();
  201. if(prefix.length() == 0 &amp;&amp; suffix.length() == 0)
  202. return;
  203. prefixField.addCurrentToHistory();
  204. suffixField.addCurrentToHistory();</pre></div><p>The method <code class="function">processText()</code> does the work of
  205. our macro. First we obtain the input from the two text fields with a
  206. call to their <code class="function">getText()</code> methods. If they are
  207. both empty, there is nothing to do, so the method returns. If there
  208. is input, any text in the field is added to that field's stored
  209. history list by calling <code class="function">addCurrentToHistory()</code>.
  210. We do not need to test the <code class="varname">prefixField</code> or
  211. <code class="varname">suffixField</code> controls for
  212. <code class="constant">null</code> or empty values because
  213. <code class="function">addCurrentToHistory()</code> does that
  214. internally.</p></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="explain-jedit-calls"></a>Call jEdit Methods to Manipulate Text</h3></div></div></div><div class="informalexample"><pre class="programlisting"> // text manipulation begins here using calls
  215. // to jEdit methods
  216. buffer.beginCompoundEdit();
  217. selectedLines = textArea.getSelectedLines();
  218. for(i = 0; i &lt; selectedLines.length; ++i)
  219. {
  220. offsetBOL = textArea.getLineStartOffset(
  221. selectedLines[i]);
  222. textArea.setCaretPosition(offsetBOL);
  223. textArea.goToStartOfWhiteSpace(false);
  224. textArea.goToEndOfWhiteSpace(true);
  225. text = textArea.getSelectedText();
  226. if(text == null) text = "";
  227. textArea.setSelectedText(prefix + text + suffix);
  228. }
  229. buffer.endCompoundEdit();
  230. }</pre></div><p>The text manipulation routine loops through each selected line
  231. in the text buffer. We get the loop parameters by calling
  232. <code class="function">textArea.getSelectedLines()</code>, which returns an
  233. array consisting of the line numbers of every selected line. The
  234. array includes the number of the current line, whether or not it is
  235. selected, and the line numbers are sorted in increasing order. We
  236. iterate through each member of the <code class="varname">selectedLines</code>
  237. array, which represents the number of a selected line, and apply the
  238. following routine:</p><div class="itemizedlist"><ul type="disc"><li><p>Get the buffer position of the start of the line
  239. (expressed as a zero-based index from the start of the
  240. buffer) by calling
  241. <code class="function">textArea.getLineStartOffset(selectedLines[i])</code>;</p></li><li><p>Move the caret to that position by calling
  242. <code class="function">textArea.setCaretPosition()</code>;</p></li><li><p>Find the first and last non-whitespace characters on
  243. the line by calling
  244. <code class="function">textArea.goToStartOfWhiteSpace()</code> and
  245. <code class="function">textArea.goToEndOfWhiteSpace()</code>;</p><p>The <code class="function">goTo...</code> methods in <a class="ulink" href="../api/org/gjt/sp/jedit/textarea/JEditTextArea.html" target="_top">JEditTextArea</a>
  246. take a single parameter which tells jEdit whether the text
  247. between the current caret position and the desired position
  248. should be selected. Here, we call
  249. <code class="function">textArea.goToStartOfWhiteSpace(false)</code>
  250. so that no text is selected, then call
  251. <code class="function">textArea.goToEndOfWhiteSpace(true)</code> so
  252. that all of the text between the beginning and ending
  253. whitespace is selected.</p></li><li><p>Retrieve the selected text by storing the return value
  254. of <code class="function">textArea.getSelectedText()</code> in a new
  255. variable <code class="function">text</code>.</p><p>If the line is empty,
  256. <code class="function">getSelectedText()</code> will return
  257. <code class="constant">null</code>. In that case, we assign an empty
  258. string to <code class="varname">text</code> to avoid calling methods
  259. on a null object.</p></li><li><p>Change the selected text to <code class="varname">prefix + text +
  260. suffix</code> by calling
  261. <code class="function">textArea.setSelectedText()</code>. If there is
  262. no selected text (for example, if the line is empty), the
  263. prefix and suffix will be inserted without any intervening
  264. characters.</p></li></ul></div><div class="sidebar"><p class="title"><b>Compound edits</b></p><p>Note the <code class="function">beginCompoundEdit()</code> and
  265. <code class="function">endCompoundEdit()</code> calls. These ensure that
  266. all edits performed between the two calls can be undone in one
  267. step. Normally, jEdit automatically wraps a macro call in these
  268. methods; however if the macro shows a non-modal dialog box, as
  269. far as jEdit is concerned the macro has finished executing by
  270. the time the dialog is shown, since control returns to the event
  271. dispatch thread.</p><p>If you do not understand this, don't worry; just keep it
  272. in mind if your macro needs to show a non-modal dialog box for
  273. some reason; Most macros won't.</p></div></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="explain-main"></a>The Main Routine</h3></div></div></div><div class="informalexample"><pre class="programlisting">// this single line of code is the script's main routine
  274. prefixSuffixDialog();</pre></div><p>The call to <code class="function">prefixSuffixDialog()</code>is the
  275. only line in the macro that is not inside an enclosing block.
  276. BeanShell treats such code as a top-level <code class="function">main</code>
  277. method and begins execution with it.</p><p>Our analysis of <code class="filename">Add_Prefix_and_Suffix.bsh</code>
  278. is now complete. In the next section, we look at other ways in which
  279. a macro can obtain user input, as well as other macro writing
  280. techniques.</p></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="add-prefix-and-suffix.html">Prev</a> </td><td width="20%" align="center"><a accesskey="u" href="dialog-macro.html">Up</a></td><td width="40%" align="right"> <a accesskey="n" href="macro-tips.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">Listing of the Macro </td><td width="20%" align="center"><a accesskey="h" href="index.html">Home</a></td><td width="40%" align="right" valign="top"> Chapter 15. Macro Tips and Techniques</td></tr></table></div></body></html>