PageRenderTime 28ms CodeModel.GetById 13ms app.highlight 9ms RepoModel.GetById 1ms app.codeStats 0ms


HTML | 280 lines | 280 code | 0 blank | 0 comment | 0 complexity | 36d6df2c1981f3cf7681a6517c1b6291 MD5 | raw file
  1<html><head><meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"><title>Analysis of the Macro</title><meta name="generator" content="DocBook XSL Stylesheets V1.73.2"><link rel="start" href="index.html" title="jEdit 4.3 User's Guide"><link rel="up" href="dialog-macro.html" title="Chapter 14. A Dialog-Based Macro"><link rel="prev" href="add-prefix-and-suffix.html" title="Listing of the Macro"><link rel="next" href="macro-tips.html" title="Chapter 15. Macro Tips and Techniques"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">Analysis of the Macro</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="add-prefix-and-suffix.html">Prev</a> </td><th width="60%" align="center">Chapter 14. A Dialog-Based Macro</th><td width="20%" align="right"> <a accesskey="n" href="macro-tips.html">Next</a></td></tr></table><hr></div><div class="sect1" lang="en"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="macro-analysis"></a>Analysis of the Macro</h2></div></div></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="explain-imports"></a>Import Statements</h3></div></div></div><div class="informalexample"><pre class="programlisting">// import statement
  2import javax.swing.border.*;</pre></div><p>This macro makes use of classes in the
  3            <code class="literal">javax.swing.border</code> package, which is not
  4            automatically imported. As we mentioned previously (see <a class="xref" href="first-example.html" title="The Mandatory First Example">the section called &#8220;The Mandatory First Example&#8221;</a>), jEdit's implementation of BeanShell
  5            causes a number of classes to be automatically imported. Classes
  6            that are not automatically imported must be identified by a full
  7            qualified name or be the subject of an <code class="function">import</code>
  8            statement.</p></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="explain-create-dialog"></a>Create the Dialog</h3></div></div></div><div class="informalexample"><pre class="programlisting">// create dialog object
  9title = &#8220;<span class="quote">Add prefix and suffix to selected lines</span>&#8221;;
 10dialog = new JDialog(view, title, false);
 11content = new JPanel(new BorderLayout());
 12content.setBorder(new EmptyBorder(12, 12, 12, 12));
 13dialog.setContentPane(content);</pre></div><p>To get input for the macro, we need a dialog that provides for
 14            input of the prefix and suffix strings, an <span class="guibutton"><strong>OK</strong></span>
 15            button to perform text insertion, and a
 16            <span class="guibutton"><strong>Cancel</strong></span> button in case we change our mind. We
 17            have decided to make the dialog window non-modal. This will allow us
 18            to move around in the text buffer to find things we may need
 19            (including text to cut and paste) while the macro is running and the
 20            dialog is visible.</p><p>The Java object we need is a <code class="classname">JDialog</code>
 21            object from the Swing package. To construct one, we use the
 22            <code class="function">new</code> keyword and call a
 23            <em class="glossterm">constructor</em> function. The constructor we use
 24            takes three parameters: the owner of the new dialog, the title to be
 25            displayed in the dialog frame, and a <code class="classname">boolean</code>
 26            parameter (<code class="constant">true</code> or <code class="constant">false</code>)
 27            that specifies whether the dialog will be modal or non-modal. We
 28            define the variable <code class="varname">title</code> using a string literal,
 29            then use it immediately in the <code class="classname">JDialog</code>
 30            constructor.</p><p>A <code class="classname">JDialog</code> object is a window containing
 31            a single object called a <em class="glossterm">content pane</em>. The
 32            content pane in turn contains the various visible components of the
 33            dialog. A <code class="classname">JDialog</code> creates an empty content
 34            pane for itself as during its construction. However, to control the
 35            dialog's appearance as much as possible, we will separately create
 36            our own content pane and attach it to the
 37            <code class="classname">JDialog</code>. We do this by creating a
 38            <code class="classname">JPanel</code> object. A
 39            <code class="classname">JPanel</code> is a lightweight container for other
 40            components that can be set to a given size and color. It also
 41            contains a <em class="glossterm">layout</em> scheme for arranging the
 42            size and position of its components. Here we are constructing a
 43            <code class="classname">JPanel</code> as a content pane with a
 44            <code class="classname">BorderLayout</code>. We put a
 45            <code class="classname">EmptyBorder</code> inside it to serve as a margin
 46            between the edge of the window and the components inside. We then
 47            attach the <code class="classname">JPanel</code> as the dialog's content
 48            pane, replacing the dialog's home-grown version.</p><p>A <code class="classname">BorderLayout</code> is one of the simpler
 49            layout schemes available for container objects like
 50            <code class="classname">JPanel</code>. A <code class="classname">BorderLayout</code>
 51            divides the container into five sections: &#8220;<span class="quote">North</span>&#8221;,
 52            &#8220;<span class="quote">South</span>&#8221;, &#8220;<span class="quote">East</span>&#8221;, &#8220;<span class="quote">West</span>&#8221; and
 53            &#8220;<span class="quote">Center</span>&#8221;. Components are added to the layout using the
 54            container's <code class="function">add</code> method, specifying the
 55            component to be added and the section to which it is assigned.
 56            Building a component like our dialog window involves building a set
 57            of nested containers and specifying the location of each of their
 58            member components. We have taken the first step by creating a
 59            <code class="classname">JPanel</code> as the dialog's content pane.</p></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="explain-fields-panel"></a>Create the Text Fields</h3></div></div></div><div class="informalexample"><pre class="programlisting">// add the text fields
 60fieldPanel = new JPanel(new GridLayout(4, 1, 0, 6));
 61prefixField = new HistoryTextField("macro.add-prefix");
 62prefixLabel = new JLabel(&#8220;<span class="quote">Prefix to add</span>&#8221;:);
 63suffixField = new HistoryTextField(&#8220;<span class="quote">macro.add-suffix</span>&#8221;);
 64suffixLabel = new JLabel(&#8220;<span class="quote">Suffix to add:</span>&#8221;);
 69content.add(fieldPanel, &#8220;<span class="quote">Center</span>&#8221;);</pre></div><p>Next we shall create a smaller panel containing two fields for
 70            entering the prefix and suffix text and two labels identifying the
 71            input fields.</p><p>For the text fields, we will use jEdit's <a class="ulink" href="../api/org/gjt/sp/jedit/gui/HistoryTextField.html" target="_top">HistoryTextField</a>
 72            class. It is derived from the Java Swing class
 73            <code class="classname">JTextField</code>. This class offers the enhancement
 74            of a stored list of prior values used as text input. When the
 75            component has input focus, the up and down keys scroll through the
 76            prior values for the variable. </p><p>To create the <a class="ulink" href="../api/org/gjt/sp/jedit/gui/HistoryTextField.html" target="_top">HistoryTextField</a>
 77            objects we use a constructor method that takes a single parameter:
 78            the name of the tag under which history values will be stored. Here
 79            we choose names that are not likely to conflict with existing jEdit
 80            history items.</p><p>The labels that accompany the text fields are
 81            <code class="classname">JLabel</code> objects from the Java Swing package.
 82            The constructor we use for both labels takes the label text as a
 83            single <code class="classname">String</code> parameter.</p><p>We wish to arrange these four components from top to bottom,
 84            one after the other. To achieve that, we use a
 85            <code class="classname">JPanel</code> container object named
 86            <code class="varname">fieldPanel</code> that will be nested inside the
 87            dialog's content pane that we have already created. In the
 88            constructor for <code class="varname">fieldPanel</code>, we assign a new
 89            <code class="classname">GridLayout</code> with the indicated parameters:
 90            four rows, one column, zero spacing between columns (a meaningless
 91            element of a grid with only one column, but nevertheless a required
 92            parameter) and spacing of six pixels between rows. The spacing
 93            between rows spreads out the four &#8220;<span class="quote">grid</span>&#8221; elements.
 94            After the components, the panel and the layout are specified, the
 95            components are added to <code class="varname">fieldPanel</code> top to bottom,
 96            one &#8220;<span class="quote">grid cell</span>&#8221; at a time. Finally, the complete
 97            <code class="varname">fieldPanel</code> is added to the dialog's content pane
 98            to occupy the &#8220;<span class="quote">Center</span>&#8221; section of the content
 99            pane.</p></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="explain-button-panel"></a>Create the Buttons</h3></div></div></div><div class="informalexample"><pre class="programlisting">// add the buttons
100buttonPanel = new JPanel();
101buttonPanel.setLayout(new BoxLayout(buttonPanel,
102    BoxLayout.X_AXIS));
103buttonPanel.setBorder(new EmptyBorder(12, 50, 0, 50));
105ok = new JButton(&#8220;<span class="quote">OK</span>&#8221;);
106cancel = new JButton(&#8220;<span class="quote">Cancel</span>&#8221;);
113content.add(buttonPanel, &#8220;<span class="quote">South</span>&#8221;);</pre></div><p>To create the dialog's buttons, we follow repeat the
114            &#8220;<span class="quote">nested container</span>&#8221; pattern we used in creating the text
115            fields. First, we create a new, nested panel. This time we use a
116            <code class="classname">BoxLayout</code> that places components either in a
117            single row or column, depending on the parameter passed to its
118            constructor. This layout object is more flexible than a
119            <code class="classname">GridLayout</code> in that variable spacing between
120            elements can be specified easily. We put an
121            <code class="classname">EmptyBorder</code> in the new panel to set margins
122            for placing the buttons. Then we create the buttons, using a
123            <code class="classname">JButton</code> constructor that specifies the button
124            text. After setting the size of the <span class="guilabel"><strong>OK</strong></span> button
125            to equal the size of the <span class="guilabel"><strong>Cancel</strong></span> button, we
126            designate the <span class="guilabel"><strong>OK</strong></span> button as the default button
127            in the dialog. This causes the <span class="guilabel"><strong>OK</strong></span> button to be
128            outlined when the dialog if first displayed. Finally, we place the
129            buttons side by side with a 6 pixel gap between them (for aesthetic
130            reasons), and place the completed <code class="varname">buttonPanel</code> in
131            the &#8220;<span class="quote">South</span>&#8221; section of the dialog's content
132            pane.</p></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="explain-add-listeners"></a>Register the Action Listeners</h3></div></div></div><div class="informalexample"><pre class="programlisting">// register this method as an ActionListener for
133// the buttons and text fields
137suffixField.addActionListener(this);</pre></div><p>In order to specify the action to be taken upon clicking a
138            button or pressing the <code class="keycap">Enter</code> key, we must register
139            an <code class="classname">ActionListener</code> for each of the four active
140            components of the dialog - the two <a class="ulink" href="../api/org/gjt/sp/jedit/HistoryTextField.html" target="_top">HistoryTextField</a>
141            components and the two buttons. In Java, an
142            <code class="classname">ActionListener</code> is an
143            <em class="glossterm">interface</em> - an abstract specification for a
144            derived class to implement. The
145            <code class="classname">ActionListener</code> interface contains a single
146            method to be implemented:</p><div class="funcsynopsis"><table border="0" summary="Function synopsis" cellspacing="0" cellpadding="0"><tr><td><code class="funcdef">public void
147                    <b class="fsfunc">actionPerformed</b>(</code></td><td>ActionEvent  </td><td><var class="pdparam">e</var><code>)</code>;</td></tr></table></div><p>BeanShell does not permit a script to create derived classes.
148            However, BeanShell offers a useful substitute: a method can be used
149            as a scripted object that can include nested methods implementing a
150            number of Java interfaces. The method
151            <code class="function">prefixSuffixDialog()</code> that we are writing can
152            thus be treated as an <code class="classname">ActionListener</code> object.
153            To accomplish this, we call <code class="function">addActionListener()</code>
154            on each of the four components specifying <code class="varname">this</code> as
155            the <code class="classname">ActionListener</code>. We still need to
156            implement the interface. We will do that shortly.</p></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="explain-set-visible"></a>Make the Dialog Visible</h3></div></div></div><div class="informalexample"><pre class="programlisting">// locate the dialog in the center of the
157// editing pane and make it visible
161dialog.setVisible(true);</pre></div><p>Here we do three things. First, we activate all the layout
162            routines we have established by calling the
163            <code class="function">pack()</code> method for the dialog as the top-level
164            window. Next we center the dialog's position in the active jEdit
165            <code class="varname">view</code> by calling
166            <code class="function">setLocationRelativeTo()</code> on the dialog. We also
167            call the <code class="function">setDefaultCloseOperation()</code> function to
168            specify that the dialog box should be immediately disposed if the
169            user clicks the close box. Finally, we activate the dialog by
170            calling <code class="function">setVisible()</code>with the state parameter
171            set to <code class="constant">true</code>.</p><p>At this point we have a decent looking dialog window that
172            doesn't do anything. Without more code, it will not respond to user
173            input and will not accomplish any text manipulation. The remainder
174            of the script deals with these two requirements.</p></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="explain-action-listener"></a>The Action Listener</h3></div></div></div><div class="informalexample"><pre class="programlisting">// this method will be called when a button is clicked
175// or when ENTER is pressed
176void actionPerformed(e)
178    if(e.getSource() != cancel)
179    {
180        processText();
181    }
182    dialog.dispose();
183}</pre></div><p>The method <code class="function">actionPerformed()</code> nested
184            inside <code class="function">prefixSuffixDialog()</code> implements the
185            implicit <code class="classname">ActionListener</code> interface. It looks
186            at the source of the <code class="classname">ActionEvent</code>, determined
187            by a call to <code class="function">getSource()</code>. What we do with this
188            return value is straightforward: if the source is not the
189            <span class="guibutton"><strong>Cancel</strong></span> button, we call the
190            <code class="function">processText()</code> method to insert the prefix and
191            suffix text. Then the dialog is closed by calling its
192            <code class="function">dispose()</code> method.</p><p>The ability to implement interfaces like
193            <code class="classname">ActionListener</code> inside a BeanShell script is
194            one of the more powerful features of the BeanShell package. this
195            technique is discussed in the next chapter; see <a class="xref" href="macro-tips-BeanShell.html#macro-tips-BeanShell-class" title="Implementing Classes and Interfaces">the section called &#8220;Implementing Classes and Interfaces&#8221;</a>.</p></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="explain-process-text"></a>Get the User's Input</h3></div></div></div><div class="informalexample"><pre class="programlisting">// this is where the work gets done to insert
196// the prefix and suffix
197void processText()
199    prefix = prefixField.getText();
200    suffix = suffixField.getText();
201    if(prefix.length() == 0 &amp;&amp; suffix.length() == 0)
202        return;
203    prefixField.addCurrentToHistory();
204    suffixField.addCurrentToHistory();</pre></div><p>The method <code class="function">processText()</code> does the work of
205            our macro. First we obtain the input from the two text fields with a
206            call to their <code class="function">getText()</code> methods. If they are
207            both empty, there is nothing to do, so the method returns. If there
208            is input, any text in the field is added to that field's stored
209            history list by calling <code class="function">addCurrentToHistory()</code>.
210            We do not need to test the <code class="varname">prefixField</code> or
211            <code class="varname">suffixField</code> controls for
212            <code class="constant">null</code> or empty values because
213            <code class="function">addCurrentToHistory()</code> does that
214            internally.</p></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="explain-jedit-calls"></a>Call jEdit Methods to Manipulate Text</h3></div></div></div><div class="informalexample"><pre class="programlisting">    // text manipulation begins here using calls
215    // to jEdit methods
216    buffer.beginCompoundEdit();
217    selectedLines = textArea.getSelectedLines();
218    for(i = 0; i &lt; selectedLines.length; ++i)
219    {
220        offsetBOL = textArea.getLineStartOffset(
221            selectedLines[i]);
222        textArea.setCaretPosition(offsetBOL);
223        textArea.goToStartOfWhiteSpace(false);
224        textArea.goToEndOfWhiteSpace(true);
225        text = textArea.getSelectedText();
226        if(text == null) text = "";
227        textArea.setSelectedText(prefix + text + suffix);
228    }
229    buffer.endCompoundEdit();
230}</pre></div><p>The text manipulation routine loops through each selected line
231            in the text buffer. We get the loop parameters by calling
232            <code class="function">textArea.getSelectedLines()</code>, which returns an
233            array consisting of the line numbers of every selected line. The
234            array includes the number of the current line, whether or not it is
235            selected, and the line numbers are sorted in increasing order. We
236            iterate through each member of the <code class="varname">selectedLines</code>
237            array, which represents the number of a selected line, and apply the
238            following routine:</p><div class="itemizedlist"><ul type="disc"><li><p>Get the buffer position of the start of the line
239                    (expressed as a zero-based index from the start of the
240                    buffer) by calling
241                    <code class="function">textArea.getLineStartOffset(selectedLines[i])</code>;</p></li><li><p>Move the caret to that position by calling
242                    <code class="function">textArea.setCaretPosition()</code>;</p></li><li><p>Find the first and last non-whitespace characters on
243                    the line by calling
244                    <code class="function">textArea.goToStartOfWhiteSpace()</code> and
245                    <code class="function">textArea.goToEndOfWhiteSpace()</code>;</p><p>The <code class="function">goTo...</code> methods in <a class="ulink" href="../api/org/gjt/sp/jedit/textarea/JEditTextArea.html" target="_top">JEditTextArea</a>
246                    take a single parameter which tells jEdit whether the text
247                    between the current caret position and the desired position
248                    should be selected. Here, we call
249                    <code class="function">textArea.goToStartOfWhiteSpace(false)</code>
250                    so that no text is selected, then call
251                    <code class="function">textArea.goToEndOfWhiteSpace(true)</code> so
252                    that all of the text between the beginning and ending
253                    whitespace is selected.</p></li><li><p>Retrieve the selected text by storing the return value
254                    of <code class="function">textArea.getSelectedText()</code> in a new
255                    variable <code class="function">text</code>.</p><p>If the line is empty,
256                    <code class="function">getSelectedText()</code> will return
257                    <code class="constant">null</code>. In that case, we assign an empty
258                    string to <code class="varname">text</code> to avoid calling methods
259                    on a null object.</p></li><li><p>Change the selected text to <code class="varname">prefix + text +
260                    suffix</code> by calling
261                    <code class="function">textArea.setSelectedText()</code>. If there is
262                    no selected text (for example, if the line is empty), the
263                    prefix and suffix will be inserted without any intervening
264                    characters.</p></li></ul></div><div class="sidebar"><p class="title"><b>Compound edits</b></p><p>Note the <code class="function">beginCompoundEdit()</code> and
265                <code class="function">endCompoundEdit()</code> calls. These ensure that
266                all edits performed between the two calls can be undone in one
267                step. Normally, jEdit automatically wraps a macro call in these
268                methods; however if the macro shows a non-modal dialog box, as
269                far as jEdit is concerned the macro has finished executing by
270                the time the dialog is shown, since control returns to the event
271                dispatch thread.</p><p>If you do not understand this, don't worry; just keep it
272                in mind if your macro needs to show a non-modal dialog box for
273                some reason; Most macros won't.</p></div></div><div class="sect2" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="explain-main"></a>The Main Routine</h3></div></div></div><div class="informalexample"><pre class="programlisting">// this single line of code is the script's main routine
274prefixSuffixDialog();</pre></div><p>The call to <code class="function">prefixSuffixDialog()</code>is the
275            only line in the macro that is not inside an enclosing block.
276            BeanShell treats such code as a top-level <code class="function">main</code>
277            method and begins execution with it.</p><p>Our analysis of <code class="filename">Add_Prefix_and_Suffix.bsh</code>
278            is now complete. In the next section, we look at other ways in which
279            a macro can obtain user input, as well as other macro writing
280            techniques.</p></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="add-prefix-and-suffix.html">Prev</a> </td><td width="20%" align="center"><a accesskey="u" href="dialog-macro.html">Up</a></td><td width="40%" align="right"> <a accesskey="n" href="macro-tips.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">Listing of the Macro </td><td width="20%" align="center"><a accesskey="h" href="index.html">Home</a></td><td width="40%" align="right" valign="top"> Chapter 15. Macro Tips and Techniques</td></tr></table></div></body></html>