PageRenderTime 37ms CodeModel.GetById 27ms app.highlight 5ms RepoModel.GetById 0ms app.codeStats 1ms

/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
  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>&gt;<guisubmenu>Text</guisubmenu>&gt;<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 &amp;&amp; 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 &lt; 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 &amp;&amp; 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 &lt; 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>