/www/tags/NOV_07_2009/htdocs/users-guide/macro-analysis.html
HTML | 280 lines | 280 code | 0 blank | 0 comment | 0 complexity | 36d6df2c1981f3cf7681a6517c1b6291 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
- <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
- import javax.swing.border.*;</pre></div><p>This macro makes use of classes in the
- <code class="literal">javax.swing.border</code> package, which is not
- automatically imported. As we mentioned previously (see <a class="xref" href="first-example.html" title="The Mandatory First Example">the section called “The Mandatory First Example”</a>), jEdit's implementation of BeanShell
- causes a number of classes to be automatically imported. Classes
- that are not automatically imported must be identified by a full
- qualified name or be the subject of an <code class="function">import</code>
- 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
- title = “<span class="quote">Add prefix and suffix to selected lines</span>”;
- dialog = new JDialog(view, title, false);
- content = new JPanel(new BorderLayout());
- content.setBorder(new EmptyBorder(12, 12, 12, 12));
- dialog.setContentPane(content);</pre></div><p>To get input for the macro, we need a dialog that provides for
- input of the prefix and suffix strings, an <span class="guibutton"><strong>OK</strong></span>
- button to perform text insertion, and a
- <span class="guibutton"><strong>Cancel</strong></span> button in case we change our mind. We
- have decided to make the dialog window non-modal. This will allow us
- to move around in the text buffer to find things we may need
- (including text to cut and paste) while the macro is running and the
- dialog is visible.</p><p>The Java object we need is a <code class="classname">JDialog</code>
- object from the Swing package. To construct one, we use the
- <code class="function">new</code> keyword and call a
- <em class="glossterm">constructor</em> function. The constructor we use
- takes three parameters: the owner of the new dialog, the title to be
- displayed in the dialog frame, and a <code class="classname">boolean</code>
- parameter (<code class="constant">true</code> or <code class="constant">false</code>)
- that specifies whether the dialog will be modal or non-modal. We
- define the variable <code class="varname">title</code> using a string literal,
- then use it immediately in the <code class="classname">JDialog</code>
- constructor.</p><p>A <code class="classname">JDialog</code> object is a window containing
- a single object called a <em class="glossterm">content pane</em>. The
- content pane in turn contains the various visible components of the
- dialog. A <code class="classname">JDialog</code> creates an empty content
- pane for itself as during its construction. However, to control the
- dialog's appearance as much as possible, we will separately create
- our own content pane and attach it to the
- <code class="classname">JDialog</code>. We do this by creating a
- <code class="classname">JPanel</code> object. A
- <code class="classname">JPanel</code> is a lightweight container for other
- components that can be set to a given size and color. It also
- contains a <em class="glossterm">layout</em> scheme for arranging the
- size and position of its components. Here we are constructing a
- <code class="classname">JPanel</code> as a content pane with a
- <code class="classname">BorderLayout</code>. We put a
- <code class="classname">EmptyBorder</code> inside it to serve as a margin
- between the edge of the window and the components inside. We then
- attach the <code class="classname">JPanel</code> as the dialog's content
- pane, replacing the dialog's home-grown version.</p><p>A <code class="classname">BorderLayout</code> is one of the simpler
- layout schemes available for container objects like
- <code class="classname">JPanel</code>. A <code class="classname">BorderLayout</code>
- divides the container into five sections: “<span class="quote">North</span>”,
- “<span class="quote">South</span>”, “<span class="quote">East</span>”, “<span class="quote">West</span>” and
- “<span class="quote">Center</span>”. Components are added to the layout using the
- container's <code class="function">add</code> method, specifying the
- component to be added and the section to which it is assigned.
- Building a component like our dialog window involves building a set
- of nested containers and specifying the location of each of their
- member components. We have taken the first step by creating a
- <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
- fieldPanel = new JPanel(new GridLayout(4, 1, 0, 6));
- prefixField = new HistoryTextField("macro.add-prefix");
- prefixLabel = new JLabel(“<span class="quote">Prefix to add</span>”:);
- suffixField = new HistoryTextField(“<span class="quote">macro.add-suffix</span>”);
- suffixLabel = new JLabel(“<span class="quote">Suffix to add:</span>”);
- fieldPanel.add(prefixLabel);
- fieldPanel.add(prefixField);
- fieldPanel.add(suffixLabel);
- fieldPanel.add(suffixField);
- content.add(fieldPanel, “<span class="quote">Center</span>”);</pre></div><p>Next we shall create a smaller panel containing two fields for
- entering the prefix and suffix text and two labels identifying the
- 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>
- class. It is derived from the Java Swing class
- <code class="classname">JTextField</code>. This class offers the enhancement
- of a stored list of prior values used as text input. When the
- component has input focus, the up and down keys scroll through the
- 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>
- objects we use a constructor method that takes a single parameter:
- the name of the tag under which history values will be stored. Here
- we choose names that are not likely to conflict with existing jEdit
- history items.</p><p>The labels that accompany the text fields are
- <code class="classname">JLabel</code> objects from the Java Swing package.
- The constructor we use for both labels takes the label text as a
- single <code class="classname">String</code> parameter.</p><p>We wish to arrange these four components from top to bottom,
- one after the other. To achieve that, we use a
- <code class="classname">JPanel</code> container object named
- <code class="varname">fieldPanel</code> that will be nested inside the
- dialog's content pane that we have already created. In the
- constructor for <code class="varname">fieldPanel</code>, we assign a new
- <code class="classname">GridLayout</code> with the indicated parameters:
- four rows, one column, zero spacing between columns (a meaningless
- element of a grid with only one column, but nevertheless a required
- parameter) and spacing of six pixels between rows. The spacing
- between rows spreads out the four “<span class="quote">grid</span>” elements.
- After the components, the panel and the layout are specified, the
- components are added to <code class="varname">fieldPanel</code> top to bottom,
- one “<span class="quote">grid cell</span>” at a time. Finally, the complete
- <code class="varname">fieldPanel</code> is added to the dialog's content pane
- to occupy the “<span class="quote">Center</span>” section of the content
- 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
- buttonPanel = new JPanel();
- buttonPanel.setLayout(new BoxLayout(buttonPanel,
- BoxLayout.X_AXIS));
- buttonPanel.setBorder(new EmptyBorder(12, 50, 0, 50));
- buttonPanel.add(Box.createGlue());
- ok = new JButton(“<span class="quote">OK</span>”);
- cancel = new JButton(“<span class="quote">Cancel</span>”);
- ok.setPreferredSize(cancel.getPreferredSize());
- dialog.getRootPane().setDefaultButton(ok);
- buttonPanel.add(ok);
- buttonPanel.add(Box.createHorizontalStrut(6));
- buttonPanel.add(cancel);
- buttonPanel.add(Box.createGlue());
- content.add(buttonPanel, “<span class="quote">South</span>”);</pre></div><p>To create the dialog's buttons, we follow repeat the
- “<span class="quote">nested container</span>” pattern we used in creating the text
- fields. First, we create a new, nested panel. This time we use a
- <code class="classname">BoxLayout</code> that places components either in a
- single row or column, depending on the parameter passed to its
- constructor. This layout object is more flexible than a
- <code class="classname">GridLayout</code> in that variable spacing between
- elements can be specified easily. We put an
- <code class="classname">EmptyBorder</code> in the new panel to set margins
- for placing the buttons. Then we create the buttons, using a
- <code class="classname">JButton</code> constructor that specifies the button
- text. After setting the size of the <span class="guilabel"><strong>OK</strong></span> button
- to equal the size of the <span class="guilabel"><strong>Cancel</strong></span> button, we
- designate the <span class="guilabel"><strong>OK</strong></span> button as the default button
- in the dialog. This causes the <span class="guilabel"><strong>OK</strong></span> button to be
- outlined when the dialog if first displayed. Finally, we place the
- buttons side by side with a 6 pixel gap between them (for aesthetic
- reasons), and place the completed <code class="varname">buttonPanel</code> in
- the “<span class="quote">South</span>” section of the dialog's content
- 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
- // the buttons and text fields
- ok.addActionListener(this);
- cancel.addActionListener(this);
- prefixField.addActionListener(this);
- suffixField.addActionListener(this);</pre></div><p>In order to specify the action to be taken upon clicking a
- button or pressing the <code class="keycap">Enter</code> key, we must register
- an <code class="classname">ActionListener</code> for each of the four active
- components of the dialog - the two <a class="ulink" href="../api/org/gjt/sp/jedit/HistoryTextField.html" target="_top">HistoryTextField</a>
- components and the two buttons. In Java, an
- <code class="classname">ActionListener</code> is an
- <em class="glossterm">interface</em> - an abstract specification for a
- derived class to implement. The
- <code class="classname">ActionListener</code> interface contains a single
- 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
- <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.
- However, BeanShell offers a useful substitute: a method can be used
- as a scripted object that can include nested methods implementing a
- number of Java interfaces. The method
- <code class="function">prefixSuffixDialog()</code> that we are writing can
- thus be treated as an <code class="classname">ActionListener</code> object.
- To accomplish this, we call <code class="function">addActionListener()</code>
- on each of the four components specifying <code class="varname">this</code> as
- the <code class="classname">ActionListener</code>. We still need to
- 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
- // editing pane and make it visible
- dialog.pack();
- dialog.setLocationRelativeTo(view);
- dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
- dialog.setVisible(true);</pre></div><p>Here we do three things. First, we activate all the layout
- routines we have established by calling the
- <code class="function">pack()</code> method for the dialog as the top-level
- window. Next we center the dialog's position in the active jEdit
- <code class="varname">view</code> by calling
- <code class="function">setLocationRelativeTo()</code> on the dialog. We also
- call the <code class="function">setDefaultCloseOperation()</code> function to
- specify that the dialog box should be immediately disposed if the
- user clicks the close box. Finally, we activate the dialog by
- calling <code class="function">setVisible()</code>with the state parameter
- set to <code class="constant">true</code>.</p><p>At this point we have a decent looking dialog window that
- doesn't do anything. Without more code, it will not respond to user
- input and will not accomplish any text manipulation. The remainder
- 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
- // or when ENTER is pressed
- void actionPerformed(e)
- {
- if(e.getSource() != cancel)
- {
- processText();
- }
- dialog.dispose();
- }</pre></div><p>The method <code class="function">actionPerformed()</code> nested
- inside <code class="function">prefixSuffixDialog()</code> implements the
- implicit <code class="classname">ActionListener</code> interface. It looks
- at the source of the <code class="classname">ActionEvent</code>, determined
- by a call to <code class="function">getSource()</code>. What we do with this
- return value is straightforward: if the source is not the
- <span class="guibutton"><strong>Cancel</strong></span> button, we call the
- <code class="function">processText()</code> method to insert the prefix and
- suffix text. Then the dialog is closed by calling its
- <code class="function">dispose()</code> method.</p><p>The ability to implement interfaces like
- <code class="classname">ActionListener</code> inside a BeanShell script is
- one of the more powerful features of the BeanShell package. this
- 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 “Implementing Classes and Interfaces”</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
- // the prefix and suffix
- void processText()
- {
- prefix = prefixField.getText();
- suffix = suffixField.getText();
- if(prefix.length() == 0 && suffix.length() == 0)
- return;
- prefixField.addCurrentToHistory();
- suffixField.addCurrentToHistory();</pre></div><p>The method <code class="function">processText()</code> does the work of
- our macro. First we obtain the input from the two text fields with a
- call to their <code class="function">getText()</code> methods. If they are
- both empty, there is nothing to do, so the method returns. If there
- is input, any text in the field is added to that field's stored
- history list by calling <code class="function">addCurrentToHistory()</code>.
- We do not need to test the <code class="varname">prefixField</code> or
- <code class="varname">suffixField</code> controls for
- <code class="constant">null</code> or empty values because
- <code class="function">addCurrentToHistory()</code> does that
- 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
- // to jEdit methods
- buffer.beginCompoundEdit();
- selectedLines = textArea.getSelectedLines();
- for(i = 0; i < selectedLines.length; ++i)
- {
- offsetBOL = textArea.getLineStartOffset(
- selectedLines[i]);
- textArea.setCaretPosition(offsetBOL);
- textArea.goToStartOfWhiteSpace(false);
- textArea.goToEndOfWhiteSpace(true);
- text = textArea.getSelectedText();
- if(text == null) text = "";
- textArea.setSelectedText(prefix + text + suffix);
- }
- buffer.endCompoundEdit();
- }</pre></div><p>The text manipulation routine loops through each selected line
- in the text buffer. We get the loop parameters by calling
- <code class="function">textArea.getSelectedLines()</code>, which returns an
- array consisting of the line numbers of every selected line. The
- array includes the number of the current line, whether or not it is
- selected, and the line numbers are sorted in increasing order. We
- iterate through each member of the <code class="varname">selectedLines</code>
- array, which represents the number of a selected line, and apply the
- following routine:</p><div class="itemizedlist"><ul type="disc"><li><p>Get the buffer position of the start of the line
- (expressed as a zero-based index from the start of the
- buffer) by calling
- <code class="function">textArea.getLineStartOffset(selectedLines[i])</code>;</p></li><li><p>Move the caret to that position by calling
- <code class="function">textArea.setCaretPosition()</code>;</p></li><li><p>Find the first and last non-whitespace characters on
- the line by calling
- <code class="function">textArea.goToStartOfWhiteSpace()</code> and
- <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>
- take a single parameter which tells jEdit whether the text
- between the current caret position and the desired position
- should be selected. Here, we call
- <code class="function">textArea.goToStartOfWhiteSpace(false)</code>
- so that no text is selected, then call
- <code class="function">textArea.goToEndOfWhiteSpace(true)</code> so
- that all of the text between the beginning and ending
- whitespace is selected.</p></li><li><p>Retrieve the selected text by storing the return value
- of <code class="function">textArea.getSelectedText()</code> in a new
- variable <code class="function">text</code>.</p><p>If the line is empty,
- <code class="function">getSelectedText()</code> will return
- <code class="constant">null</code>. In that case, we assign an empty
- string to <code class="varname">text</code> to avoid calling methods
- on a null object.</p></li><li><p>Change the selected text to <code class="varname">prefix + text +
- suffix</code> by calling
- <code class="function">textArea.setSelectedText()</code>. If there is
- no selected text (for example, if the line is empty), the
- prefix and suffix will be inserted without any intervening
- 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
- <code class="function">endCompoundEdit()</code> calls. These ensure that
- all edits performed between the two calls can be undone in one
- step. Normally, jEdit automatically wraps a macro call in these
- methods; however if the macro shows a non-modal dialog box, as
- far as jEdit is concerned the macro has finished executing by
- the time the dialog is shown, since control returns to the event
- dispatch thread.</p><p>If you do not understand this, don't worry; just keep it
- in mind if your macro needs to show a non-modal dialog box for
- 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
- prefixSuffixDialog();</pre></div><p>The call to <code class="function">prefixSuffixDialog()</code>is the
- only line in the macro that is not inside an enclosing block.
- BeanShell treats such code as a top-level <code class="function">main</code>
- method and begins execution with it.</p><p>Our analysis of <code class="filename">Add_Prefix_and_Suffix.bsh</code>
- is now complete. In the next section, we look at other ways in which
- a macro can obtain user input, as well as other macro writing
- 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>