PageRenderTime 55ms CodeModel.GetById 47ms app.highlight 4ms RepoModel.GetById 0ms app.codeStats 1ms


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