PageRenderTime 35ms CodeModel.GetById 1ms RepoModel.GetById 0ms app.codeStats 0ms

/jEdit/tags/jedit-4-1-pre5/doc/users-guide/dialog-macro.xml

#
XML | 666 lines | 563 code | 82 blank | 21 comment | 0 complexity | 0cc25e7dfbc8be9de4e5287c0f2b20dd 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 Macro Guide, (C) 2001, 2002 John Gellene -->
  2. <!-- jEdit buffer-local properties: -->
  3. <!-- :indentSize=1:noTabs=yes:maxLineLen=72:tabSize=2: -->
  4. <!-- This file contains an extended discussion of a -->
  5. <!-- dialog-based macro example "Add_Prefix_and_Suffix.bsh" -->
  6. <!-- $Id: dialog-macro.xml 4097 2002-03-17 10:21:56Z spestov $
  7. -->
  8. <chapter id="dialog-macro"><title>A Dialog-Based Macro</title>
  9. <para>
  10. Now we will look at a more complicated macro which will demonstrate
  11. some useful techniques and BeanShell features.
  12. </para>
  13. <sect1 id="dialog-macro-intro"><title>Use of the Macro</title>
  14. <para>
  15. Our new example adds prefix and suffix text to a series of selected lines. This
  16. macro can be used to reduce typing for a series of text items that must be
  17. preceded and following by identical text. In Java, for example, if we are
  18. interested in making a series of calls to
  19. <function>StringBuffer.append()</function> to construct a lengthy, formatted
  20. string, we could type the parameter for each call on successive lines as follows:
  21. </para>
  22. <screen>profileString_1
  23. secretThing.toString()
  24. name
  25. address
  26. addressSupp
  27. city
  28. <quote>state/province</quote>
  29. country</screen>
  30. <para>
  31. Our macro would ask for input for the common <quote>prefix</quote> and
  32. <quote>suffix</quote> to be applied to each line; in this case, the prefix is
  33. <userinput>ourStringBuffer.append(</userinput> and the suffix is
  34. <userinput>);</userinput>. After selecting these lines and
  35. running the macro, the resulting text would look like this:
  36. </para>
  37. <screen>ourStringBuffer.append(profileString_1);
  38. ourStringBuffer.append(secretThing.toString());
  39. ourStringBuffer.append(name);
  40. ourStringBuffer.append(address);
  41. ourStringBuffer.append(addressSupp);
  42. ourStringBuffer.append(city);
  43. ourStringBuffer.append(<quote>state/province</quote>);
  44. ourStringBuffer.append(country);</screen>
  45. </sect1>
  46. <sect1 id="add-prefix-and-suffix"><title>Listing of the Macro</title>
  47. <para>
  48. The macro script follows. You can find it in the jEdit
  49. distribution in the <filename>Text</filename> subdirectory of the
  50. <filename>macros</filename> directory. You can also try it out by invoking
  51. <guimenu>Macros</guimenu>&gt;<guisubmenu>Text</guisubmenu>&gt;<guimenuitem>Add
  52. Prefix and Suffix</guimenuitem>.
  53. </para>
  54. <informalexample>
  55. <!-- <title>Add_Prefix_and_Suffix.bsh</title> -->
  56. <programlisting>// beginning of Add_Prefix_and_Suffix.bsh
  57. <anchor id="imports"/>// import statement (see <xref linkend=
  58. "explain-imports"/>)
  59. import javax.swing.border.*;
  60. <anchor id="main-routine"/>// main routine
  61. void prefixSuffixDialog()
  62. {
  63. <anchor id="create-dialog"/> // create dialog object (see <xref
  64. linkend="explain-create-dialog"/>)
  65. title = <quote>Add prefix and suffix to selected lines</quote>;
  66. dialog = new JDialog(view, title, false);
  67. content = new JPanel(new BorderLayout());
  68. content.setBorder(new EmptyBorder(12, 12, 12, 12));
  69. content.setPreferredSize(new Dimension(320, 160));
  70. dialog.setContentPane(content);
  71. <anchor id="fields-panel"/> // add the text fields (see <xref linkend=
  72. "explain-fields-panel"/>)
  73. fieldPanel = new JPanel(new GridLayout(4, 1, 0, 6));
  74. prefixField = new HistoryTextField(<quote>macro.add-prefix</quote>);
  75. prefixLabel = new JLabel(<quote>Prefix to add:</quote>);
  76. suffixField = new HistoryTextField(<quote>macro.add-suffix</quote>);
  77. suffixLabel = new JLabel(<quote>Suffix to add:</quote>);
  78. fieldPanel.add(prefixLabel);
  79. fieldPanel.add(prefixField);
  80. fieldPanel.add(suffixLabel);
  81. fieldPanel.add(suffixField);
  82. content.add(fieldPanel, <quote>Center</quote>);
  83. <anchor id="button-panel"/> // add a panel containing the buttons (see <xref
  84. linkend="explain-button-panel"/>)
  85. buttonPanel = new JPanel();
  86. buttonPanel.setLayout(new BoxLayout(buttonPanel,
  87. BoxLayout.X_AXIS));
  88. buttonPanel.setBorder(new EmptyBorder(12, 50, 0, 50));
  89. buttonPanel.add(Box.createGlue());
  90. ok = new JButton(<quote>OK</quote>);
  91. cancel = new JButton(<quote>Cancel</quote>);
  92. ok.setPreferredSize(cancel.getPreferredSize());
  93. dialog.getRootPane().setDefaultButton(ok);
  94. buttonPanel.add(ok);
  95. buttonPanel.add(Box.createHorizontalStrut(6));
  96. buttonPanel.add(cancel);
  97. buttonPanel.add(Box.createGlue());
  98. content.add(buttonPanel, <quote>South</quote>);
  99. <anchor id="add-listeners"/> // register this method as an ActionListener for
  100. // the buttons and text fields (see <xref linkend=
  101. "explain-add-listeners"/>)
  102. ok.addActionListener(this);
  103. cancel.addActionListener(this);
  104. prefixField.addActionListener(this);
  105. suffixField.addActionListener(this);
  106. <anchor id="set-visible"/> // locate the dialog in the center of the
  107. // editing pane and make it visible (see <xref linkend=
  108. "explain-set-visible"/>)
  109. dialog.pack();
  110. dialog.setLocationRelativeTo(view);
  111. dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
  112. dialog.setVisible(true);
  113. <anchor id="action-listener"/> // this method will be called when a button is clicked
  114. // or when ENTER is pressed (see <xref linkend=
  115. "explain-action-listener"/>)
  116. void actionPerformed(e)
  117. {
  118. if(e.getSource() != cancel)
  119. {
  120. processText();
  121. }
  122. dialog.dispose();
  123. }
  124. <anchor id="process-text"/> // this is where the work gets done to insert
  125. // the prefix and suffix (see <xref linkend=
  126. "explain-process-text"/>)
  127. void processText()
  128. {
  129. prefix = prefixField.getText();
  130. suffix = suffixField.getText();
  131. if(prefix.length() == 0 &amp;&amp; suffix.length() == 0)
  132. return;
  133. prefixField.addCurrentToHistory();
  134. suffixField.addCurrentToHistory();
  135. <anchor id="jEdit-calls"/> // text manipulation begins here using calls
  136. // to jEdit methods (see <xref linkend="explain-jedit-calls"/>)
  137. buffer.beginCompoundEdit();
  138. selectedLines = textArea.getSelectedLines();
  139. for(i = 0; i &lt; selectedLines.length; ++i)
  140. {
  141. offsetBOL = textArea.getLineStartOffset(
  142. selectedLines[i]);
  143. textArea.setCaretPosition(offsetBOL);
  144. textArea.goToStartOfWhiteSpace(false);
  145. textArea.goToEndOfWhiteSpace(true);
  146. text = textArea.getSelectedText();
  147. if(text == null) text = &quot;&quot;;
  148. textArea.setSelectedText(prefix + text + suffix);
  149. }
  150. buffer.endCompoundEdit();
  151. }
  152. }
  153. <anchor id="main"/>// this single line of code is the script's main routine
  154. // (see <xref linkend=
  155. "explain-main"/>)
  156. prefixSuffixDialog();
  157. // end of Add_Prefix_and_Suffix.bsh</programlisting>
  158. </informalexample>
  159. </sect1>
  160. <sect1 id="macro-analysis"><title>Analysis of the Macro</title>
  161. <sect2 id="explain-imports"><title>Import Statements</title>
  162. <informalexample><programlisting>// import statement
  163. import javax.swing.border.*;</programlisting></informalexample>
  164. <para>
  165. This macro makes use of classes in the
  166. <literal>javax.swing.border</literal> package, which is not
  167. automatically imported. As we mentioned
  168. previously (see <xref linkend="first-example" />), jEdit's implementation of
  169. BeanShell causes a number of classes to be automatically imported. Classes
  170. that are not automatically imported must be identified by a full qualified
  171. name or be the subject of an <function>import</function> statement.
  172. </para>
  173. </sect2>
  174. <sect2 id="explain-create-dialog"><title>Create the Dialog</title>
  175. <informalexample><programlisting>// create dialog object
  176. title = <quote>Add prefix and suffix to selected lines</quote>;
  177. dialog = new JDialog(view, title, false);
  178. content = new JPanel(new BorderLayout());
  179. content.setBorder(new EmptyBorder(12, 12, 12, 12));
  180. dialog.setContentPane(content);</programlisting></informalexample>
  181. <para>
  182. To get input for the macro, we need a dialog that provides for input of the prefix
  183. and suffix strings, an <guibutton>OK</guibutton> button to perform text
  184. insertion, and a <guibutton>Cancel</guibutton> button in case we change our
  185. mind. We have decided to make the dialog window non-modal. This will allow us to
  186. move around in the text buffer to find things we may need (including text to cut
  187. and paste) while the macro is running and the dialog is visible.
  188. </para>
  189. <para>
  190. The Java object we need is a <classname>JDialog</classname> object from
  191. the Swing package. To construct one, we use the <function>new</function>
  192. keyword and call a <glossterm>constructor</glossterm> function. The
  193. constructor we use takes three parameters: the owner of the new dialog,
  194. the title to be displayed in the dialog frame, and a
  195. <classname>boolean</classname> parameter (<constant>true</constant> or
  196. <constant>false</constant>) that specifies whether the dialog will be
  197. modal or non-modal. We define the variable <varname>title</varname>
  198. using a string literal, then use it immediately in the
  199. <classname>JDialog</classname> constructor.
  200. </para>
  201. <para>
  202. A <classname>JDialog</classname> object is a window containing a single object
  203. called a <glossterm>content pane</glossterm>. The content pane in turn contains
  204. the various visible components of the dialog. A
  205. <classname>JDialog</classname> creates an empty content pane for itself as
  206. during its construction. However, to control the dialog's appearance
  207. as much as possible, we will separately create our own content pane and
  208. attach it to the <classname>JDialog</classname>. We do this by creating a
  209. <classname>JPanel</classname> object. A <classname>JPanel</classname> is a
  210. lightweight container for other components that can be set to a given size and
  211. color. It also contains a <glossterm>layout</glossterm> scheme for arranging the
  212. size and position of its components. Here we are constructing a
  213. <classname>JPanel</classname> as a content pane with a
  214. <classname>BorderLayout</classname>. We put a <classname>EmptyBorder</classname>
  215. inside it to serve as a margin between the edge of the window and the components
  216. inside. We then attach the <classname>JPanel</classname> as the dialog's content
  217. pane, replacing the dialog's home-grown version.
  218. </para>
  219. <para>
  220. A <classname>BorderLayout</classname> is one of the simpler layout
  221. schemes available for container objects like <classname>JPanel</classname>.
  222. A <classname>BorderLayout</classname>
  223. divides the container into five sections: <quote>North</quote>,
  224. <quote>South</quote>, <quote>East</quote>, <quote>West</quote> and
  225. <quote>Center</quote>. Components are added to the layout using the
  226. container's <function>add</function> method, specifying the component to
  227. be added and the section to which it is assigned. Building a
  228. component like our dialog window involves building a set of
  229. nested containers and specifying the location of each of their
  230. member components. We have taken the first step by creating a
  231. <classname>JPanel</classname> as the dialog's content pane.
  232. </para>
  233. </sect2>
  234. <sect2 id="explain-fields-panel"><title>
  235. Create the Text Fields</title>
  236. <informalexample><programlisting>// add the text fields
  237. fieldPanel = new JPanel(new GridLayout(4, 1, 0, 6));
  238. prefixField = new HistoryTextField(&quot;macro.add-prefix&quot;);
  239. prefixLabel = new JLabel(<quote>Prefix to add</quote>:);
  240. suffixField = new HistoryTextField(<quote>macro.add-suffix</quote>);
  241. suffixLabel = new JLabel(<quote>Suffix to add:</quote>);
  242. fieldPanel.add(prefixLabel);
  243. fieldPanel.add(prefixField);
  244. fieldPanel.add(suffixLabel);
  245. fieldPanel.add(suffixField);
  246. content.add(fieldPanel, <quote>Center</quote>);</programlisting></informalexample>
  247. <para>
  248. Next we shall create a smaller panel containing two fields for
  249. entering the prefix and suffix text and two labels identifying the
  250. input fields.
  251. </para>
  252. <para>
  253. For the text fields, we will use jEdit's <classname>HistoryTextField</classname>
  254. class. It is derived from the Java Swing class
  255. <classname>JTextField</classname>. This class offers the enhancement of a stored
  256. list of prior values used as text input. When the component has input focus, the
  257. up and down keys scroll through the prior values for the variable.
  258. <!-- The prior values are stored in a file named
  259. <filename>history</filename> located in the directory in which jEdit stores
  260. various user data. -->
  261. </para>
  262. <para>
  263. To create the <classname>HistoryTextField</classname> objects we use a
  264. constructor method that takes a single parameter: the name of the tag
  265. under which history values will be stored. Here we choose names that are
  266. not likely to conflict with existing jEdit history items.
  267. </para>
  268. <para>
  269. The labels that accompany the text fields are
  270. <classname>JLabel</classname> objects from the Java Swing
  271. package. The constructor we use for both labels takes the label text
  272. as a single <classname>String</classname> parameter.
  273. </para>
  274. <para>
  275. We wish to arrange these four components from top to bottom,
  276. one after the other. To achieve that, we use a
  277. <classname>JPanel</classname> container object named
  278. <varname>fieldPanel</varname> that
  279. will be nested inside the dialog's content pane that we have
  280. already created. In the constructor for <varname>fieldPanel</varname>,
  281. we assign a new <classname>GridLayout</classname> with the indicated
  282. parameters: four rows, one column, zero spacing between columns (a
  283. meaningless element of a grid with only one column, but
  284. nevertheless a required parameter) and spacing of six pixels between
  285. rows. The spacing between rows spreads out the four <quote>grid</quote>
  286. elements. After the components, the panel and the layout are
  287. specified, the components are added to <varname>fieldPanel</varname>
  288. top to bottom, one <quote>grid cell</quote> at a time. Finally, the complete
  289. <varname>fieldPanel</varname> is added to the dialog's content pane to
  290. occupy the <quote>Center</quote> section of the content pane.
  291. </para>
  292. </sect2>
  293. <sect2 id="explain-button-panel"><title>
  294. Create the Buttons</title>
  295. <informalexample><programlisting>// add the buttons
  296. buttonPanel = new JPanel();
  297. buttonPanel.setLayout(new BoxLayout(buttonPanel,
  298. BoxLayout.X_AXIS));
  299. buttonPanel.setBorder(new EmptyBorder(12, 50, 0, 50));
  300. buttonPanel.add(Box.createGlue());
  301. ok = new JButton(<quote>OK</quote>);
  302. cancel = new JButton(<quote>Cancel</quote>);
  303. ok.setPreferredSize(cancel.getPreferredSize());
  304. dialog.getRootPane().setDefaultButton(ok);
  305. buttonPanel.add(ok);
  306. buttonPanel.add(Box.createHorizontalStrut(6));
  307. buttonPanel.add(cancel);
  308. buttonPanel.add(Box.createGlue());
  309. content.add(buttonPanel, <quote>South</quote>);</programlisting></informalexample>
  310. <para>
  311. To create the dialog's buttons, we follow repeat the <quote>nested container</quote>
  312. pattern we used in creating the text fields.
  313. First, we create a new, nested panel. This time we use a <classname>BoxLayout</classname>
  314. that places components either in a single row or
  315. column, depending on the parameter passed to its constructor. This layout object
  316. is more flexible than a <classname>GridLayout</classname> in that variable spacing
  317. between elements can be specified easily. We put an
  318. <classname>EmptyBorder</classname> in the new panel to set margins for placing
  319. the buttons. Then we create the buttons, using a <classname>JButton</classname>
  320. constructor that specifies the button text. After setting the size of the
  321. <guilabel>OK</guilabel> button to equal the size of the
  322. <guilabel>Cancel</guilabel> button, we designate the <guilabel>OK</guilabel>
  323. button as the default button in the dialog. This causes the
  324. <guilabel>OK</guilabel> button to be outlined when the dialog if first displayed.
  325. Finally, we place the buttons side by side with a 6 pixel gap between them (for aesthetic
  326. reasons), and place the completed <varname>buttonPanel</varname> in the
  327. <quote>South</quote> section of the dialog's content pane.
  328. </para>
  329. </sect2>
  330. <sect2 id="explain-add-listeners">
  331. <title>Register the Action Listeners</title>
  332. <informalexample><programlisting>// register this method as an ActionListener for
  333. // the buttons and text fields
  334. ok.addActionListener(this);
  335. cancel.addActionListener(this);
  336. prefixField.addActionListener(this);
  337. suffixField.addActionListener(this);</programlisting></informalexample>
  338. <para>
  339. In order to specify the action to be taken upon clicking a
  340. button or pressing the <keycap>Enter</keycap> key, we must register
  341. an <classname>ActionListener</classname> for each of the four active
  342. components of the dialog - the two <classname>HistoryTextField</classname>
  343. components and the two buttons. In Java, an
  344. <classname>ActionListener</classname> is an <glossterm>interface</glossterm> - an
  345. abstract specification for a derived class to implement. The
  346. <classname>ActionListener</classname> interface contains a single method to
  347. be implemented:
  348. </para>
  349. <funcsynopsis>
  350. <funcprototype>
  351. <funcdef>public void <function>actionPerformed</function></funcdef>
  352. <paramdef>ActionEvent <parameter>e</parameter></paramdef>
  353. </funcprototype>
  354. </funcsynopsis>
  355. <para>
  356. BeanShell does not permit a script to create derived classes.
  357. However, BeanShell offers a useful substitute: a method can be
  358. used as a scripted object that can include nested methods implementing a
  359. number of Java interfaces. The method
  360. <function>prefixSuffixDialog()</function> that we are writing can thus be
  361. treated as an <classname>ActionListener</classname> object. To accomplish this, we
  362. call <function>addActionListener()</function> on each of the four
  363. components specifying <varname>this</varname> as the
  364. <classname>ActionListener</classname>. We still need to implement the
  365. interface. We will do that shortly.
  366. </para>
  367. </sect2>
  368. <sect2 id="explain-set-visible">
  369. <title>Make the Dialog Visible</title>
  370. <informalexample><programlisting>// locate the dialog in the center of the
  371. // editing pane and make it visible
  372. dialog.pack();
  373. dialog.setLocationRelativeTo(view);
  374. dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
  375. dialog.setVisible(true);</programlisting></informalexample>
  376. <para>
  377. Here we do three things. First, we activate all the layout routines we have
  378. established by calling the <function>pack()</function> method for the dialog as
  379. the top-level window. Next we center the dialog's position in the active jEdit
  380. <varname>view</varname> by calling <function>setLocationRelativeTo()</function>
  381. on the dialog. We also call the <function>setDefaultCloseOperation()</function>
  382. function to specify that the dialog box should be immediately disposed if the
  383. user clicks the close box. Finally, we activate the dialog by calling
  384. <function>setVisible()</function>with the state parameter set to
  385. <constant>true</constant>.
  386. </para>
  387. <para>
  388. At this point we have a decent looking dialog window that
  389. doesn't do anything. Without more code, it will not respond to
  390. user input and will not accomplish any text manipulation. The
  391. remainder of the script deals with these two requirements.
  392. </para>
  393. </sect2>
  394. <sect2 id="explain-action-listener">
  395. <title>The Action Listener</title>
  396. <informalexample><programlisting>// this method will be called when a button is clicked
  397. // or when ENTER is pressed
  398. void actionPerformed(e)
  399. {
  400. if(e.getSource() != cancel)
  401. {
  402. processText();
  403. }
  404. dialog.dispose();
  405. }</programlisting></informalexample>
  406. <para>
  407. The method <function>actionPerformed()</function> nested inside
  408. <function>prefixSuffixDialog()</function> implements the implicit
  409. <classname>ActionListener</classname> interface. It looks at the source
  410. of the <classname>ActionEvent</classname>, determined by a call to
  411. <function>getSource()</function>. What we do with this return value is
  412. straightforward: if the source is not the <guibutton>Cancel</guibutton> button, we
  413. call the <function>processText()</function> method to insert the prefix
  414. and suffix text. Then the dialog is closed by calling its
  415. <function>dispose()</function> method.
  416. </para>
  417. <para>
  418. The ability to implement interfaces like
  419. <classname>ActionListener</classname> inside a BeanShell script is
  420. one of the more powerful features of the BeanShell package. With an
  421. <classname>ActionListener</classname> interface, which has only a
  422. single method, implementation is simple. When using other
  423. interfaces with multiple methods, however, there are some details to
  424. deal with that will vary depending on the version of the Java
  425. platform that you are running. These techniques are discussed in
  426. the next chapter; see <xref linkend="macro-tips-BeanShell-interface" />.
  427. </para>
  428. </sect2>
  429. <sect2 id="explain-process-text">
  430. <title>Get the User's Input</title>
  431. <informalexample><programlisting>// this is where the work gets done to insert
  432. // the prefix and suffix
  433. void processText()
  434. {
  435. prefix = prefixField.getText();
  436. suffix = suffixField.getText();
  437. if(prefix.length() == 0 &amp;&amp; suffix.length() == 0)
  438. return;
  439. prefixField.addCurrentToHistory();
  440. suffixField.addCurrentToHistory();</programlisting></informalexample>
  441. <para>
  442. The method <function>processText()</function> does the work of our
  443. macro. First we obtain the input from the two text fields with a
  444. call to their <function>getText()</function> methods. If they are both
  445. empty, there is nothing to do, so the method returns. If there is
  446. input, any text in the field is added to that field's stored
  447. history list by calling <function>addCurrentToHistory()</function>.
  448. We do not need to test the <varname>prefixField</varname> or
  449. <varname>suffixField</varname> controls for <constant>null</constant>
  450. or empty values because <function>addCurrentToHistory()</function>
  451. does that internally.
  452. </para>
  453. </sect2>
  454. <sect2 id="explain-jedit-calls">
  455. <title>Call jEdit Methods to Manipulate Text</title>
  456. <informalexample><programlisting> // text manipulation begins here using calls
  457. // to jEdit methods
  458. buffer.beginCompoundEdit();
  459. selectedLines = textArea.getSelectedLines();
  460. for(i = 0; i &lt; selectedLines.length; ++i)
  461. {
  462. offsetBOL = textArea.getLineStartOffset(
  463. selectedLines[i]);
  464. textArea.setCaretPosition(offsetBOL);
  465. textArea.goToStartOfWhiteSpace(false);
  466. textArea.goToEndOfWhiteSpace(true);
  467. text = textArea.getSelectedText();
  468. if(text == null) text = &quot;&quot;;
  469. textArea.setSelectedText(prefix + text + suffix);
  470. }
  471. buffer.endCompoundEdit();
  472. }</programlisting></informalexample>
  473. <para>
  474. The text manipulation routine loops through each selected line
  475. in the text buffer. We get the loop parameters by calling
  476. <function>textArea.getSelectedLines()</function>, which returns an array
  477. consisting of the line numbers of every selected line. The array includes the
  478. number of the current line, whether or not it is selected, and the line numbers
  479. are sorted in increasing order. We iterate through each member of the
  480. <varname>selectedLines</varname> array, which represents the number of a
  481. selected line, and apply the following routine:
  482. </para>
  483. <itemizedlist>
  484. <listitem>
  485. <para>
  486. Get the buffer position of the start of the line (expressed
  487. as a zero-based index from the start of the buffer) by calling
  488. <function>textArea.getLineStartOffset(selectedLines[i])</function>;
  489. </para>
  490. </listitem>
  491. <listitem>
  492. <para>
  493. Move the caret to that position by calling
  494. <function>textArea.setCaretPosition()</function>;
  495. </para>
  496. </listitem>
  497. <listitem>
  498. <para>
  499. Find the first and last non-whitespace characters on the line
  500. by calling <function>textArea.goToStartOfWhiteSpace()</function> and
  501. <function>textArea.goToEndOfWhiteSpace()</function>;
  502. </para>
  503. <para>
  504. The <function>goTo...</function> methods in
  505. <classname>JEditTextArea</classname> take a single parameter which
  506. tells jEdit whether the text between the current caret position and
  507. the desired position should be selected. Here, we call
  508. <function>textArea.goToStartOfWhiteSpace(false)</function> so that
  509. no text is selected, then call
  510. <function>textArea.goToEndOfWhiteSpace(true)</function> so that all of
  511. the text between the beginning and ending whitespace is
  512. selected.
  513. </para>
  514. </listitem>
  515. <listitem>
  516. <para>
  517. Retrieve the selected text by storing the return value of
  518. <function>textArea.getSelectedText()</function> in a new variable
  519. <function>text</function>.
  520. </para>
  521. <para>
  522. If the line is empty, <function>getSelectedText()</function> will
  523. return <constant>null</constant>. In that case, we assign an empty
  524. string to <varname>text</varname> to avoid calling methods on a
  525. null object.
  526. </para>
  527. </listitem>
  528. <listitem>
  529. <para>
  530. Change the selected text to <varname>prefix + text +
  531. suffix</varname> by calling
  532. <function>textArea.setSelectedText()</function>.
  533. If there is no selected text (for example, if the line is empty),
  534. the prefix and suffix will be inserted without any intervening
  535. characters.
  536. </para>
  537. </listitem>
  538. </itemizedlist>
  539. <sidebar><title>Compound edits</title>
  540. <para>
  541. Note the <function>beginCompoundEdit()</function> and
  542. <function>endCompoundEdit()</function> calls. These ensure that all edits
  543. performed between the two calls can be undone in one step. Normally,
  544. jEdit automatically wraps a macro call in these methods; however if
  545. the macro shows a non-modal dialog box, as far as jEdit is concerned
  546. the macro has finished executing by the time the dialog is shown,
  547. since control returns to the event dispatch thread.
  548. </para>
  549. <para>
  550. If you do not understand this, don't worry; just keep it in mind if
  551. your macro needs to show a non-modal dialog box for some reason;
  552. Most macros won't.
  553. </para>
  554. </sidebar>
  555. <!-- <para>
  556. The loop routine is bracketed by calls to
  557. <function>buffer.beginCompoundEdit()</function> and
  558. <function>buffer.endCompoundEdit()</function>. These methods
  559. modify the way in which jEdit's undo facility performs. Text
  560. operations done between calls to these functions will be
  561. treated as a single operation for undo purposes. A single undo
  562. command issued immediately after the macro completes will thus remove
  563. the prefix and suffix text from all of the previously selected lines.
  564. </para> -->
  565. </sect2>
  566. <sect2 id="explain-main">
  567. <title>The Main Routine</title>
  568. <informalexample><programlisting>// this single line of code is the script's main routine
  569. prefixSuffixDialog();</programlisting>
  570. </informalexample>
  571. <para>
  572. The call to <function>prefixSuffixDialog()</function>is the only line
  573. in the macro that is not inside an enclosing block. BeanShell
  574. treats such code as a top-level <function>main</function> method and
  575. begins execution with it.
  576. </para>
  577. <para>
  578. Our analysis of <filename>Add_Prefix_and_Suffix.bsh</filename> is now
  579. complete. In the next section, we look at other ways in which a macro
  580. can obtain user input, as well as other macro writing techniques.
  581. </para>
  582. </sect2>
  583. </sect1>
  584. </chapter>