PageRenderTime 46ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/www/tags/NOV_07_2009/htdocs/42docs/users-guide/dialog-macro.xml.bak

#
Unknown | 668 lines | 586 code | 82 blank | 0 comment | 0 complexity | bfbbc475ac3a6a85b09ea5a2c5cb2bd6 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. <!-- :xml.root=users-guide.xml: -->
  5. <!-- This file contains an extended discussion of a -->
  6. <!-- dialog-based macro example "Add_Prefix_and_Suffix.bsh" -->
  7. <!-- $Id: dialog-macro.xml,v 1.11 2004/03/19 19:16:35 spestov Exp $ -->
  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
  254. <ulink
  255. url="../api/org/gjt/sp/jedit/gui/HistoryTextField.html">HistoryTextField</ulink>
  256. class. It is derived from the Java Swing class
  257. <classname>JTextField</classname>. This class offers the enhancement of a stored
  258. list of prior values used as text input. When the component has input focus, the
  259. up and down keys scroll through the prior values for the variable.
  260. <!-- The prior values are stored in a file named
  261. <filename>history</filename> located in the directory in which jEdit stores
  262. various user data. -->
  263. </para>
  264. <para>
  265. To create the <ulink
  266. url="../api/org/gjt/sp/jedit/gui/HistoryTextField.html">HistoryTextField</ulink> objects we use a
  267. constructor method that takes a single parameter: the name of the tag
  268. under which history values will be stored. Here we choose names that are
  269. not likely to conflict with existing jEdit history items.
  270. </para>
  271. <para>
  272. The labels that accompany the text fields are
  273. <classname>JLabel</classname> objects from the Java Swing
  274. package. The constructor we use for both labels takes the label text
  275. as a single <classname>String</classname> parameter.
  276. </para>
  277. <para>
  278. We wish to arrange these four components from top to bottom,
  279. one after the other. To achieve that, we use a
  280. <classname>JPanel</classname> container object named
  281. <varname>fieldPanel</varname> that
  282. will be nested inside the dialog's content pane that we have
  283. already created. In the constructor for <varname>fieldPanel</varname>,
  284. we assign a new <classname>GridLayout</classname> with the indicated
  285. parameters: four rows, one column, zero spacing between columns (a
  286. meaningless element of a grid with only one column, but
  287. nevertheless a required parameter) and spacing of six pixels between
  288. rows. The spacing between rows spreads out the four <quote>grid</quote>
  289. elements. After the components, the panel and the layout are
  290. specified, the components are added to <varname>fieldPanel</varname>
  291. top to bottom, one <quote>grid cell</quote> at a time. Finally, the complete
  292. <varname>fieldPanel</varname> is added to the dialog's content pane to
  293. occupy the <quote>Center</quote> section of the content pane.
  294. </para>
  295. </sect2>
  296. <sect2 id="explain-button-panel"><title>
  297. Create the Buttons</title>
  298. <informalexample><programlisting>// add the buttons
  299. buttonPanel = new JPanel();
  300. buttonPanel.setLayout(new BoxLayout(buttonPanel,
  301. BoxLayout.X_AXIS));
  302. buttonPanel.setBorder(new EmptyBorder(12, 50, 0, 50));
  303. buttonPanel.add(Box.createGlue());
  304. ok = new JButton(<quote>OK</quote>);
  305. cancel = new JButton(<quote>Cancel</quote>);
  306. ok.setPreferredSize(cancel.getPreferredSize());
  307. dialog.getRootPane().setDefaultButton(ok);
  308. buttonPanel.add(ok);
  309. buttonPanel.add(Box.createHorizontalStrut(6));
  310. buttonPanel.add(cancel);
  311. buttonPanel.add(Box.createGlue());
  312. content.add(buttonPanel, <quote>South</quote>);</programlisting></informalexample>
  313. <para>
  314. To create the dialog's buttons, we follow repeat the <quote>nested container</quote>
  315. pattern we used in creating the text fields.
  316. First, we create a new, nested panel. This time we use a <classname>BoxLayout</classname>
  317. that places components either in a single row or
  318. column, depending on the parameter passed to its constructor. This layout object
  319. is more flexible than a <classname>GridLayout</classname> in that variable spacing
  320. between elements can be specified easily. We put an
  321. <classname>EmptyBorder</classname> in the new panel to set margins for placing
  322. the buttons. Then we create the buttons, using a <classname>JButton</classname>
  323. constructor that specifies the button text. After setting the size of the
  324. <guilabel>OK</guilabel> button to equal the size of the
  325. <guilabel>Cancel</guilabel> button, we designate the <guilabel>OK</guilabel>
  326. button as the default button in the dialog. This causes the
  327. <guilabel>OK</guilabel> button to be outlined when the dialog if first displayed.
  328. Finally, we place the buttons side by side with a 6 pixel gap between them (for aesthetic
  329. reasons), and place the completed <varname>buttonPanel</varname> in the
  330. <quote>South</quote> section of the dialog's content pane.
  331. </para>
  332. </sect2>
  333. <sect2 id="explain-add-listeners">
  334. <title>Register the Action Listeners</title>
  335. <informalexample><programlisting>// register this method as an ActionListener for
  336. // the buttons and text fields
  337. ok.addActionListener(this);
  338. cancel.addActionListener(this);
  339. prefixField.addActionListener(this);
  340. suffixField.addActionListener(this);</programlisting></informalexample>
  341. <para>
  342. In order to specify the action to be taken upon clicking a
  343. button or pressing the <keycap>Enter</keycap> key, we must register
  344. an <classname>ActionListener</classname> for each of the four active
  345. components of the dialog - the two
  346. <ulink
  347. url="../api/org/gjt/sp/jedit/HistoryTextField.html">HistoryTextField</ulink>
  348. components and the two buttons. In Java, an
  349. <classname>ActionListener</classname> is an <glossterm>interface</glossterm> - an
  350. abstract specification for a derived class to implement. The
  351. <classname>ActionListener</classname> interface contains a single method to
  352. be implemented:
  353. </para>
  354. <funcsynopsis>
  355. <funcprototype>
  356. <funcdef>public void <function>actionPerformed</function></funcdef>
  357. <paramdef>ActionEvent <parameter>e</parameter></paramdef>
  358. </funcprototype>
  359. </funcsynopsis>
  360. <para>
  361. BeanShell does not permit a script to create derived classes.
  362. However, BeanShell offers a useful substitute: a method can be
  363. used as a scripted object that can include nested methods implementing a
  364. number of Java interfaces. The method
  365. <function>prefixSuffixDialog()</function> that we are writing can thus be
  366. treated as an <classname>ActionListener</classname> object. To accomplish this, we
  367. call <function>addActionListener()</function> on each of the four
  368. components specifying <varname>this</varname> as the
  369. <classname>ActionListener</classname>. We still need to implement the
  370. interface. We will do that shortly.
  371. </para>
  372. </sect2>
  373. <sect2 id="explain-set-visible">
  374. <title>Make the Dialog Visible</title>
  375. <informalexample><programlisting>// locate the dialog in the center of the
  376. // editing pane and make it visible
  377. dialog.pack();
  378. dialog.setLocationRelativeTo(view);
  379. dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
  380. dialog.setVisible(true);</programlisting></informalexample>
  381. <para>
  382. Here we do three things. First, we activate all the layout routines we have
  383. established by calling the <function>pack()</function> method for the dialog as
  384. the top-level window. Next we center the dialog's position in the active jEdit
  385. <varname>view</varname> by calling <function>setLocationRelativeTo()</function>
  386. on the dialog. We also call the <function>setDefaultCloseOperation()</function>
  387. function to specify that the dialog box should be immediately disposed if the
  388. user clicks the close box. Finally, we activate the dialog by calling
  389. <function>setVisible()</function>with the state parameter set to
  390. <constant>true</constant>.
  391. </para>
  392. <para>
  393. At this point we have a decent looking dialog window that
  394. doesn't do anything. Without more code, it will not respond to
  395. user input and will not accomplish any text manipulation. The
  396. remainder of the script deals with these two requirements.
  397. </para>
  398. </sect2>
  399. <sect2 id="explain-action-listener">
  400. <title>The Action Listener</title>
  401. <informalexample><programlisting>// this method will be called when a button is clicked
  402. // or when ENTER is pressed
  403. void actionPerformed(e)
  404. {
  405. if(e.getSource() != cancel)
  406. {
  407. processText();
  408. }
  409. dialog.dispose();
  410. }</programlisting></informalexample>
  411. <para>
  412. The method <function>actionPerformed()</function> nested inside
  413. <function>prefixSuffixDialog()</function> implements the implicit
  414. <classname>ActionListener</classname> interface. It looks at the source
  415. of the <classname>ActionEvent</classname>, determined by a call to
  416. <function>getSource()</function>. What we do with this return value is
  417. straightforward: if the source is not the <guibutton>Cancel</guibutton> button, we
  418. call the <function>processText()</function> method to insert the prefix
  419. and suffix text. Then the dialog is closed by calling its
  420. <function>dispose()</function> method.
  421. </para>
  422. <para>
  423. The ability to implement interfaces like
  424. <classname>ActionListener</classname> inside a BeanShell script is
  425. one of the more powerful features of the BeanShell package.
  426. this technique is discussed in
  427. the next chapter; see <xref linkend="macro-tips-BeanShell-class" />.
  428. </para>
  429. </sect2>
  430. <sect2 id="explain-process-text">
  431. <title>Get the User's Input</title>
  432. <informalexample><programlisting>// this is where the work gets done to insert
  433. // the prefix and suffix
  434. void processText()
  435. {
  436. prefix = prefixField.getText();
  437. suffix = suffixField.getText();
  438. if(prefix.length() == 0 &amp;&amp; suffix.length() == 0)
  439. return;
  440. prefixField.addCurrentToHistory();
  441. suffixField.addCurrentToHistory();</programlisting></informalexample>
  442. <para>
  443. The method <function>processText()</function> does the work of our
  444. macro. First we obtain the input from the two text fields with a
  445. call to their <function>getText()</function> methods. If they are both
  446. empty, there is nothing to do, so the method returns. If there is
  447. input, any text in the field is added to that field's stored
  448. history list by calling <function>addCurrentToHistory()</function>.
  449. We do not need to test the <varname>prefixField</varname> or
  450. <varname>suffixField</varname> controls for <constant>null</constant>
  451. or empty values because <function>addCurrentToHistory()</function>
  452. does that internally.
  453. </para>
  454. </sect2>
  455. <sect2 id="explain-jedit-calls">
  456. <title>Call jEdit Methods to Manipulate Text</title>
  457. <informalexample><programlisting> // text manipulation begins here using calls
  458. // to jEdit methods
  459. buffer.beginCompoundEdit();
  460. selectedLines = textArea.getSelectedLines();
  461. for(i = 0; i &lt; selectedLines.length; ++i)
  462. {
  463. offsetBOL = textArea.getLineStartOffset(
  464. selectedLines[i]);
  465. textArea.setCaretPosition(offsetBOL);
  466. textArea.goToStartOfWhiteSpace(false);
  467. textArea.goToEndOfWhiteSpace(true);
  468. text = textArea.getSelectedText();
  469. if(text == null) text = &quot;&quot;;
  470. textArea.setSelectedText(prefix + text + suffix);
  471. }
  472. buffer.endCompoundEdit();
  473. }</programlisting></informalexample>
  474. <para>
  475. The text manipulation routine loops through each selected line
  476. in the text buffer. We get the loop parameters by calling
  477. <function>textArea.getSelectedLines()</function>, which returns an array
  478. consisting of the line numbers of every selected line. The array includes the
  479. number of the current line, whether or not it is selected, and the line numbers
  480. are sorted in increasing order. We iterate through each member of the
  481. <varname>selectedLines</varname> array, which represents the number of a
  482. selected line, and apply the following routine:
  483. </para>
  484. <itemizedlist>
  485. <listitem>
  486. <para>
  487. Get the buffer position of the start of the line (expressed
  488. as a zero-based index from the start of the buffer) by calling
  489. <function>textArea.getLineStartOffset(selectedLines[i])</function>;
  490. </para>
  491. </listitem>
  492. <listitem>
  493. <para>
  494. Move the caret to that position by calling
  495. <function>textArea.setCaretPosition()</function>;
  496. </para>
  497. </listitem>
  498. <listitem>
  499. <para>
  500. Find the first and last non-whitespace characters on the line
  501. by calling <function>textArea.goToStartOfWhiteSpace()</function> and
  502. <function>textArea.goToEndOfWhiteSpace()</function>;
  503. </para>
  504. <para>
  505. The <function>goTo...</function> methods in
  506. <ulink
  507. url="../api/org/gjt/sp/jedit/textarea/JEditTextArea.html">JEditTextArea</ulink> take a single parameter which
  508. tells jEdit whether the text between the current caret position and
  509. the desired position should be selected. Here, we call
  510. <function>textArea.goToStartOfWhiteSpace(false)</function> so that
  511. no text is selected, then call
  512. <function>textArea.goToEndOfWhiteSpace(true)</function> so that all of
  513. the text between the beginning and ending whitespace is
  514. selected.
  515. </para>
  516. </listitem>
  517. <listitem>
  518. <para>
  519. Retrieve the selected text by storing the return value of
  520. <function>textArea.getSelectedText()</function> in a new variable
  521. <function>text</function>.
  522. </para>
  523. <para>
  524. If the line is empty, <function>getSelectedText()</function> will
  525. return <constant>null</constant>. In that case, we assign an empty
  526. string to <varname>text</varname> to avoid calling methods on a
  527. null object.
  528. </para>
  529. </listitem>
  530. <listitem>
  531. <para>
  532. Change the selected text to <varname>prefix + text +
  533. suffix</varname> by calling
  534. <function>textArea.setSelectedText()</function>.
  535. If there is no selected text (for example, if the line is empty),
  536. the prefix and suffix will be inserted without any intervening
  537. characters.
  538. </para>
  539. </listitem>
  540. </itemizedlist>
  541. <sidebar><title>Compound edits</title>
  542. <para>
  543. Note the <function>beginCompoundEdit()</function> and
  544. <function>endCompoundEdit()</function> calls. These ensure that all edits
  545. performed between the two calls can be undone in one step. Normally,
  546. jEdit automatically wraps a macro call in these methods; however if
  547. the macro shows a non-modal dialog box, as far as jEdit is concerned
  548. the macro has finished executing by the time the dialog is shown,
  549. since control returns to the event dispatch thread.
  550. </para>
  551. <para>
  552. If you do not understand this, don't worry; just keep it in mind if
  553. your macro needs to show a non-modal dialog box for some reason;
  554. Most macros won't.
  555. </para>
  556. </sidebar>
  557. <!-- <para>
  558. The loop routine is bracketed by calls to
  559. <function>buffer.beginCompoundEdit()</function> and
  560. <function>buffer.endCompoundEdit()</function>. These methods
  561. modify the way in which jEdit's undo facility performs. Text
  562. operations done between calls to these functions will be
  563. treated as a single operation for undo purposes. A single undo
  564. command issued immediately after the macro completes will thus remove
  565. the prefix and suffix text from all of the previously selected lines.
  566. </para> -->
  567. </sect2>
  568. <sect2 id="explain-main">
  569. <title>The Main Routine</title>
  570. <informalexample><programlisting>// this single line of code is the script's main routine
  571. prefixSuffixDialog();</programlisting>
  572. </informalexample>
  573. <para>
  574. The call to <function>prefixSuffixDialog()</function>is the only line
  575. in the macro that is not inside an enclosing block. BeanShell
  576. treats such code as a top-level <function>main</function> method and
  577. begins execution with it.
  578. </para>
  579. <para>
  580. Our analysis of <filename>Add_Prefix_and_Suffix.bsh</filename> is now
  581. complete. In the next section, we look at other ways in which a macro
  582. can obtain user input, as well as other macro writing techniques.
  583. </para>
  584. </sect2>
  585. </sect1>
  586. </chapter>