/www/tags/NOV_07_2009/htdocs/42docs/users-guide/macro-analysis.html
HTML | 322 lines | 321 code | 1 blank | 0 comment | 0 complexity | 2501bb67b10b46d2cc6168c28656f49f 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.65.1"><link rel="home" href="index.html" title="jEdit 4.2 User's Guide"><link rel="up" href="dialog-macro.html" title="Chapter 14. A Dialog-Based Macro"><link rel="previous" 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></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></div><div class="informalexample"><table border="0" bgcolor="#E0E0E0"><tr><td><pre class="programlisting">// import statement
- import javax.swing.border.*;</pre></td></tr></table></div><p>
- This macro makes use of classes in the
- <tt class="literal">javax.swing.border</tt> package, which is not
- automatically imported. As we mentioned
- previously (see <a 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 <tt class="function">import</tt> 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></div><div class="informalexample"><table border="0" bgcolor="#E0E0E0"><tr><td><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></td></tr></table></div><p>
- To get input for the macro, we need a dialog that provides for input of the prefix
- and suffix strings, an <span><b class="guibutton">OK</b></span> button to perform text
- insertion, and a <span><b class="guibutton">Cancel</b></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 <tt class="classname">JDialog</tt> object from
- the Swing package. To construct one, we use the <tt class="function">new</tt>
- keyword and call a <i class="glossterm">constructor</i> 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
- <tt class="classname">boolean</tt> parameter (<tt class="constant">true</tt> or
- <tt class="constant">false</tt>) that specifies whether the dialog will be
- modal or non-modal. We define the variable <tt class="varname">title</tt>
- using a string literal, then use it immediately in the
- <tt class="classname">JDialog</tt> constructor.
- </p><p>
- A <tt class="classname">JDialog</tt> object is a window containing a single object
- called a <i class="glossterm">content pane</i>. The content pane in turn contains
- the various visible components of the dialog. A
- <tt class="classname">JDialog</tt> 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 <tt class="classname">JDialog</tt>. We do this by creating a
- <tt class="classname">JPanel</tt> object. A <tt class="classname">JPanel</tt> is a
- lightweight container for other components that can be set to a given size and
- color. It also contains a <i class="glossterm">layout</i> scheme for arranging the
- size and position of its components. Here we are constructing a
- <tt class="classname">JPanel</tt> as a content pane with a
- <tt class="classname">BorderLayout</tt>. We put a <tt class="classname">EmptyBorder</tt>
- inside it to serve as a margin between the edge of the window and the components
- inside. We then attach the <tt class="classname">JPanel</tt> as the dialog's content
- pane, replacing the dialog's home-grown version.
- </p><p>
- A <tt class="classname">BorderLayout</tt> is one of the simpler layout
- schemes available for container objects like <tt class="classname">JPanel</tt>.
- A <tt class="classname">BorderLayout</tt>
- 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 <tt class="function">add</tt> 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
- <tt class="classname">JPanel</tt> 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></div><div class="informalexample"><table border="0" bgcolor="#E0E0E0"><tr><td><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></td></tr></table></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 href="../api/org/gjt/sp/jedit/gui/HistoryTextField.html" target="_top">HistoryTextField</a>
- class. It is derived from the Java Swing class
- <tt class="classname">JTextField</tt>. 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 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
- <tt class="classname">JLabel</tt> objects from the Java Swing
- package. The constructor we use for both labels takes the label text
- as a single <tt class="classname">String</tt> parameter.
- </p><p>
- We wish to arrange these four components from top to bottom,
- one after the other. To achieve that, we use a
- <tt class="classname">JPanel</tt> container object named
- <tt class="varname">fieldPanel</tt> that
- will be nested inside the dialog's content pane that we have
- already created. In the constructor for <tt class="varname">fieldPanel</tt>,
- we assign a new <tt class="classname">GridLayout</tt> 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 <tt class="varname">fieldPanel</tt>
- top to bottom, one “<span class="quote">grid cell</span>” at a time. Finally, the complete
- <tt class="varname">fieldPanel</tt> 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></div><div class="informalexample"><table border="0" bgcolor="#E0E0E0"><tr><td><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></td></tr></table></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 <tt class="classname">BoxLayout</tt>
- 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 <tt class="classname">GridLayout</tt> in that variable spacing
- between elements can be specified easily. We put an
- <tt class="classname">EmptyBorder</tt> in the new panel to set margins for placing
- the buttons. Then we create the buttons, using a <tt class="classname">JButton</tt>
- constructor that specifies the button text. After setting the size of the
- <span><b class="guilabel">OK</b></span> button to equal the size of the
- <span><b class="guilabel">Cancel</b></span> button, we designate the <span><b class="guilabel">OK</b></span>
- button as the default button in the dialog. This causes the
- <span><b class="guilabel">OK</b></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 <tt class="varname">buttonPanel</tt> 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></div><div class="informalexample"><table border="0" bgcolor="#E0E0E0"><tr><td><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></td></tr></table></div><p>
- In order to specify the action to be taken upon clicking a
- button or pressing the <tt class="keycap">Enter</tt> key, we must register
- an <tt class="classname">ActionListener</tt> for each of the four active
- components of the dialog - the two
- <a href="../api/org/gjt/sp/jedit/HistoryTextField.html" target="_top">HistoryTextField</a>
- components and the two buttons. In Java, an
- <tt class="classname">ActionListener</tt> is an <i class="glossterm">interface</i> - an
- abstract specification for a derived class to implement. The
- <tt class="classname">ActionListener</tt> interface contains a single method to
- be implemented:
- </p><div class="funcsynopsis"><p><code class="funcdef">public void <b class="fsfunc">actionPerformed</b>(</code>ActionEvent <var class="pdparam">e</var><code>)</code>;</p></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
- <tt class="function">prefixSuffixDialog()</tt> that we are writing can thus be
- treated as an <tt class="classname">ActionListener</tt> object. To accomplish this, we
- call <tt class="function">addActionListener()</tt> on each of the four
- components specifying <tt class="varname">this</tt> as the
- <tt class="classname">ActionListener</tt>. 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></div><div class="informalexample"><table border="0" bgcolor="#E0E0E0"><tr><td><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></td></tr></table></div><p>
- Here we do three things. First, we activate all the layout routines we have
- established by calling the <tt class="function">pack()</tt> method for the dialog as
- the top-level window. Next we center the dialog's position in the active jEdit
- <tt class="varname">view</tt> by calling <tt class="function">setLocationRelativeTo()</tt>
- on the dialog. We also call the <tt class="function">setDefaultCloseOperation()</tt>
- 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
- <tt class="function">setVisible()</tt>with the state parameter set to
- <tt class="constant">true</tt>.
- </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></div><div class="informalexample"><table border="0" bgcolor="#E0E0E0"><tr><td><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></td></tr></table></div><p>
- The method <tt class="function">actionPerformed()</tt> nested inside
- <tt class="function">prefixSuffixDialog()</tt> implements the implicit
- <tt class="classname">ActionListener</tt> interface. It looks at the source
- of the <tt class="classname">ActionEvent</tt>, determined by a call to
- <tt class="function">getSource()</tt>. What we do with this return value is
- straightforward: if the source is not the <span><b class="guibutton">Cancel</b></span> button, we
- call the <tt class="function">processText()</tt> method to insert the prefix
- and suffix text. Then the dialog is closed by calling its
- <tt class="function">dispose()</tt> method.
- </p><p>
- The ability to implement interfaces like
- <tt class="classname">ActionListener</tt> 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 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></div><div class="informalexample"><table border="0" bgcolor="#E0E0E0"><tr><td><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></td></tr></table></div><p>
- The method <tt class="function">processText()</tt> does the work of our
- macro. First we obtain the input from the two text fields with a
- call to their <tt class="function">getText()</tt> 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 <tt class="function">addCurrentToHistory()</tt>.
- We do not need to test the <tt class="varname">prefixField</tt> or
- <tt class="varname">suffixField</tt> controls for <tt class="constant">null</tt>
- or empty values because <tt class="function">addCurrentToHistory()</tt>
- 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></div><div class="informalexample"><table border="0" bgcolor="#E0E0E0"><tr><td><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></td></tr></table></div><p>
- The text manipulation routine loops through each selected line
- in the text buffer. We get the loop parameters by calling
- <tt class="function">textArea.getSelectedLines()</tt>, 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
- <tt class="varname">selectedLines</tt> 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
- <tt class="function">textArea.getLineStartOffset(selectedLines[i])</tt>;
- </p></li><li><p>
- Move the caret to that position by calling
- <tt class="function">textArea.setCaretPosition()</tt>;
- </p></li><li><p>
- Find the first and last non-whitespace characters on the line
- by calling <tt class="function">textArea.goToStartOfWhiteSpace()</tt> and
- <tt class="function">textArea.goToEndOfWhiteSpace()</tt>;
- </p><p>
- The <tt class="function">goTo...</tt> methods in
- <a 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
- <tt class="function">textArea.goToStartOfWhiteSpace(false)</tt> so that
- no text is selected, then call
- <tt class="function">textArea.goToEndOfWhiteSpace(true)</tt> 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
- <tt class="function">textArea.getSelectedText()</tt> in a new variable
- <tt class="function">text</tt>.
- </p><p>
- If the line is empty, <tt class="function">getSelectedText()</tt> will
- return <tt class="constant">null</tt>. In that case, we assign an empty
- string to <tt class="varname">text</tt> to avoid calling methods on a
- null object.
- </p></li><li><p>
- Change the selected text to <tt class="varname">prefix + text +
- suffix</tt> by calling
- <tt class="function">textArea.setSelectedText()</tt>.
- 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 <tt class="function">beginCompoundEdit()</tt> and
- <tt class="function">endCompoundEdit()</tt> 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></div><div class="informalexample"><table border="0" bgcolor="#E0E0E0"><tr><td><pre class="programlisting">// this single line of code is the script's main routine
- prefixSuffixDialog();</pre></td></tr></table></div><p>
- The call to <tt class="function">prefixSuffixDialog()</tt>is the only line
- in the macro that is not inside an enclosing block. BeanShell
- treats such code as a top-level <tt class="function">main</tt> method and
- begins execution with it.
- </p><p>
- Our analysis of <tt class="filename">Add_Prefix_and_Suffix.bsh</tt> 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>