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

/jEdit/tags/jedit-4-5-pre1/doc/users-guide/dialog-macro.xml

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