The dialog-based macro discussed in Chapter 14, A Dialog-Based Macro reflects a conventional approach to obtaining input in a Java program. Nevertheless, it can be too lengthy or tedious for someone trying to write a macro quickly. Not every macro needs a user interface specified in such detail; some macros require only a single keystroke or no input at all. In this section we outline some other techniques for obtaining input that will help you write macros quickly.
As mentioned earlier in the section called “Helpful Methods in the
Macros Class”, the method
Macros.input()
offers a convenient way to obtain
a single line of text input. Here is an example that inserts a pair
of HTML markup tags specified by the user.
// Insert_Tag.bsh void insertTag() { caret = textArea.getCaretPosition(); tag = Macros.input(view, “Enter name of tag:”); if( tag == null || tag.length() == 0) return; text = textArea.getSelectedText(); if(text == null) text = “”; sb = new StringBuffer(); sb.append(“<”).append(tag).append(“>”); sb.append(text); sb.append(“</”).append(tag).append(“>”); textArea.setSelectedText(sb.toString()); if(text.length() == 0) textArea.setCaretPosition(caret + tag.length() + 2); } insertTag(); // end Insert_Tag.bsh
Here the call to Macros.input()
seeks the name
of the markup tag. This method sets the message box title to a fixed string,
“Macro input”, but the specific message Enter name
of tag provides all the information necessary. The return value
tag
must be tested to see if it is null. This would
occur if the user presses the Cancel button or
closes the dialog window displayed by Macros.input()
.
If more than one item of input is needed, a succession of calls to
Macros.input()
is a possible, but awkward approach,
because it would not be possible to correct early input after the
corresponding message box is dismissed. Where more is required,
but a full dialog layout is either unnecessary or too much work,
the Java method JOptionPane.showConfirmDialog()
is available. The version to use has the following prototype:
public static int showConfirmDialog( | parentComponent, | |
parentComponent, | ||
message, | ||
title, | ||
optionType, | ||
messageType) ; |
The usefulness of this method arises from the fact that the
message
parameter can be an object of any
Java class (since all classes are derived from
Object
), or any array of objects. The
following example shows how this feature can be used.
// excerpt from Write_File_Header.bsh title = “Write file header”; currentName = buffer.getName(); nameField = new JTextField(currentName); authorField = new JTextField(“Your name here”); descField = new JTextField(“”, 25); namePanel = new JPanel(new GridLayout(1, 2)); nameLabel = new JLabel(“Name of file:”, SwingConstants.LEFT); saveField = new JCheckBox(“Save file when done”, !buffer.isNewFile()); namePanel.add(nameLabel); namePanel.add(saveField); message = new Object[9]; message[0] = namePanel; message[1] = nameField; message[2] = Box.createVerticalStrut(10); message[3] = “Author's name:”; message[4] = authorField; message[5] = Box.createVerticalStrut(10); message[6] = “Enter description:”; message[7] = descField; message[8] = Box.createVerticalStrut(5); if( JOptionPane.OK_OPTION != JOptionPane.showConfirmDialog(view, message, title, JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE)) return null; // *****remainder of macro script omitted***** // end excerpt from Write_File_Header.bsh
This macro takes several items of user input and produces a formatted file header at the beginning of the buffer. The full macro is included in the set of macros installed by jEdit. There are a number of input features of this excerpt worth noting.
The macro uses a total of seven visible components. Two of them are
created behind the scenes by showConfirmDialog()
,
the rest are made by the macro. To arrange them, the script creates an
array of Object
objects and assigns components to
each location in the array. This translates to a fixed, top-to-bottom
arrangement in the message box created by
showConfirmDialog()
.
The macro uses JTextField
objects to
obtain most of the input data. The fields nameField
and authorField
are created with constructors
that take the initial, default text to be displayed in the field as a
parameter. When the message box is displayed, the default text
will appear and can be altered or deleted by the user.
The text field descField
uses an empty string for its
initial value. The second parameter in its constructor sets the width of
the text field component, expressed as the number of characters of
“average” width. When
showConfirmDialog()
prepares the layout of the
message box, it sets the width wide enough to accommodate the
designated with of descField
. This technique produces
a message box and input text fields that are wide enough for your data
with one line of code.
The displayed message box includes a JCheckBox
component that determines whether the buffer will be saved to disk
immediately after the file header is written. To conserve space
in the message box, we want to display the check box to the
right of the label Name of file:. To do that,
we create a JPanel
object and populate it with
the label and the checkbox in a left-to-right
GridLayout
. The JPanel
containing the two components is then added to the beginning of
message
array.
The two visible components created by
showConfirmDialog()
appear at positions 3 and 6 of
the message
array. Only the text is required; they
are rendered as text labels.
There are three invisible components created by
showConfirmDialog()
. Each of them involves
a call to Box.createVerticalStrut()
. The
Box
class is a sophisticated layout class
that gives the user great flexibility in sizing and positioning
components. Here we use a static
method of
the Box
class that produces a vertical
strut. This is a transparent component
whose width expands to fill its parent component (in this case,
the message box). The single parameter indicates the height
of the strut in pixels. The last call to
createVerticalStrut()
separates the
description text field from the OK and
Cancel buttons that are automatically added
by showConfirmDialog()
.
Finally, the call to showConfirmDialog()
uses
defined constants for the option type and the message type. The
constants are the same as those used with the
Macros.confirm()
method; see
the section called “Helpful Methods in the
Macros Class”. The
option type signifies the use of OK
and Cancel buttons. The
QUERY_MESSAGE
message type causes the message box
to display a question mark icon.
The return value of the method
is tested against the value OK_OPTION
. If
the return value is something else (because the
Cancel button was pressed or because the
message box window was closed without a button press), a
null
value is returned to a calling function,
signaling that the user canceled macro execution. If the return
value is OK_OPTION
, each of the input components
can yield their contents for further processing by calls to
JTextField.getText()
(or, in the case of
the check box, JCheckBox.isSelected()
).
Another useful way to get user input for a macro is to use a combo box
containing a number of pre-set options. If this is the only input
required, one of the versions of showInputDialog()
in the JOptionPane
class provides a shortcut.
Here is its prototype:
public static Object showInputDialog( | parentComponent, | |
parentComponent, | ||
message, | ||
title, | ||
messageType, | ||
icon, | ||
selectionValues, | ||
initialSelectionValue) ; |
This method creates a message box containing a drop-down list of the
options specified in the method's parameters, along with
OK and Cancel buttons.
Compared to showConfirmDialog()
, this method lacks
an optionType
parameter and has three additional
parameters: an icon
to display in the dialog
(which can be set to null
), an array of
selectionValues
objects, and a reference to one
of the options as the initialSelectionValue
to be
displayed. In addition, instead of returning an int
representing the user's action, showInputDialog()
returns the Object
corresponding to the user's
selection, or null
if the selection is canceled.
The following macro fragment illustrates the use of this method.
// fragment illustrating use of showInputDialog() options = new Object[5]; options[0] = "JLabel"; options[1] = "JTextField"; options[2] = "JCheckBox"; options[3] = "HistoryTextField"; options[4} = "-- other --"; result = JOptionPane.showInputDialog(view, "Choose component class", "Select class for input component", JOptionPane.QUESTION_MESSAGE, null, options, options[0]);
The return value result
will contain either the
String
object representing the selected text
item or null
representing no selection. Any
further use of this fragment would have to test the value of
result
and likely exit from the macro if the value
equaled null
.
A set of options can be similarly placed in a
JComboBox
component created as part of a larger
dialog or showMessageDialog()
layout. Here are some
code fragments showing this approach:
// fragments from Display_Abbreviations.bsh // import statements and other code omitted // from main routine, this method call returns an array // of Strings representing the names of abbreviation sets abbrevSets = getActiveSets(); ... // from showAbbrevs() method combo = new JComboBox(abbrevSets); // set width to uniform size regardless of combobox contents Dimension dim = combo.getPreferredSize(); dim.width = Math.max(dim.width, 120); combo.setPreferredSize(dim); combo.setSelectedItem(STARTING_SET); // defined as "global" // end fragments
Some macros may choose to emulate the style of character-based text editors such as emacs or vi. They will require only a single keypress as input that would be handled by the macro but not displayed on the screen. If the keypress corresponds to a character value, jEdit can pass that value as a parameter to a BeanShell script.
The jEdit class InputHandler is an abstract class that that manages associations between keyboard input and editing actions, along with the recording of macros. Keyboard input in jEdit is normally managed by the derived class DefaultInputHandler. One of the methods in the InputHandler class handles input from a single keypress:
public void readNextChar( | prompt, | |
prompt, | ||
code) ; |
When this method is called, the contents of the prompt
parameter is shown in the view's status
bar. The method then waits for a key press, after which
the contents of the code
parameter will be run as
a BeanShell script, with one important modification. Each time the
string __char__
appears in the parameter script, it
will be substituted by the character pressed. The key press
is “consumed” by readNextChar()
.
It will not be displayed on the screen or otherwise processed by jEdit.
Using readNextChar()
requires a macro within the
macro, formatted as a single, potentially lengthy string literal. The
following macro illustrates this technique. It selects a line of text
from the current caret position to the first occurrence of the character
next typed by the user. If the character does not appear on the line, no
new selection occurs and the display remains unchanged.
// Next_Char.bsh script = new StringBuffer(512); script.append( "start = textArea.getCaretPosition();" ); script.append( "line = textArea.getCaretLine();" ); script.append( "end = textArea.getLineEndOffset(line) + 1;" ); script.append( "text = buffer.getText(start, end - start);" ); script.append( "match = text.indexOf(__char__, 1);" ); script.append( "if(match != -1) {" ); script.append( "if(__char__ != '\\n') ++match;" ); script.append( "textArea.select(start, start + match - 1);" ); script.append( "}" ); view.getInputHandler().readNextChar("Enter a character", script.toString()); // end Next_Char.bsh
Once again, here are a few comments on the macro's design.
A StringBuffer
object is used for efficiency; it
obviates multiple creation of fixed-length String
objects. The parameter to the constructor of script
specifies the initial size of the buffer that will receive the contents
of the child script.
Besides the quoting of the script code, the formatting of the macro is entirely optional but (hopefully) makes it easier to read.
It is important that the child script be self-contained. It does
not run in the same namespace as the “parent” macro
Next_Char.bsh
and therefore does not share
variables, methods, or scripted objects defined in the parent
macro.
Finally, access to the InputHandler object used
by jEdit is available by calling
getInputHandler()
on the current view.