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( | Component | parentComponent, |
Object | message, | |
String | title, | |
int | optionType, | |
int | 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( | Component | parentComponent, |
Object | message, | |
String | title, | |
int | messageType, | |
Icon | icon, | |
Object[] | selectionValues, | |
Object | 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( | String | prompt, |
String | 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.