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