This issue presents tips, techniques, and sample code for the following topics:
This issue of the JDC Tech Tips is written by Glen McCluskey.
SWING DOCUMENT LOCATIONS
In JavaTM Foundation Classes (JFC) Project
Swing, a text pane that you use to edit text is backed by a "document". For
example, a JTextPane
has, by default, a
DefaultStyledDocument
underlying it. The document is what
actually stores text and other content
that is displayed in the pane.
Suppose that you are using a Swing document in your application, and you'd
like to keep track of specific locations in the document. So you decide to
use character offsets for this purpose, for example, a location that is 47
characters into the document.
This approach does work until you edit the document. For example, suppose you
delete the first 10 characters. You'll then need to update offset 47 to 37 to
keep the location correct.
A better approach to track document locations is Swing's Position interface.
If a class implements the Position interface, it defines a location in a
document. If the document changes, for instance, after editing, the location
is updated as necessary. To see how this works, consider the following example:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.*;
public class PosDemo {
static Position lastpos;
public static void main(String args[]) {
JFrame frame = new JFrame("Position demo");
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
final JTextPane pane = new JTextPane();
pane.setPreferredSize(new Dimension(600, 400));
JButton setbutton = new JButton("Set Position");
JButton gobutton = new JButton("Go To Position");
setbutton.addActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent e) {
try {
int dot = pane.getCaret().getDot();
Document doc = pane.getDocument();
lastpos = doc.createPosition(dot);
pane.requestFocus();
}
catch (BadLocationException exc) {
System.err.println(exc);
}
}
});
gobutton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (lastpos == null) {
Toolkit.getDefaultToolkit().beep();
}
else {
int pos = Lastpos.getOffset();
pane.getCaret().setDot(pos);
pane.requestFocus();
}
}
});
JPanel buttonpanel = new JPanel();
buttonpanel.add(setbutton);
buttonpanel.add(gobutton);
JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
panel.add("North", buttonpanel);
panel.add("Center", pane);
frame.getContentPane().add(panel);
frame.pack();
frame.setVisible(true);
}
}
The demo sets up a text window, along with Set Position and Go To
Position buttons. Suppose you type some text, move the text caret
somewhere in that text, and then select Set Position. At this point
you've recorded a location in the text. Then suppose that you edit
the text, adding and deleting characters before and after the
position you selected. If you then select the Go To Position button,
the caret will move back to the original location. In other words,
the action of the Go To Position button takes into account the
changed offset of the location based on edits you've done.
To see what's going on here, enter a distinct pattern like "XYZ"
into the middle of some random text. Move the caret to that pattern
and then select Set Position. Doing it this way makes it clear that
the pattern itself is being tracked as a location, rather than
simply a specific character offset in the document.
These three lines in the example program do the actual location
saving:
int dot = pane.getCaret().getDot();
Document doc = pane.getDocument();
astpos = doc.createPosition(dot);
The first line retrieves the current caret offset (dot). The second
line gets the document. And the third line creates a Position instance
in the document from the caret offset. The process is reversed later
in the program with the lines:
int pos = lastpos.getOffset();
pane.getCaret().setDot(pos);
where getOffset retrieves the updated offset.
KEYMAPS
You may have never considered the details of what happens
when you type characters into a text area. But this process is
important to understand, especially if you're trying to customize
how an application handles input from the keyboard.
Swing text components use what are called "keymaps". A keymap maps
keyboard keys to resulting actions. For example, the Backspace key
deletes the character preceding the text caret. Or the Delete
key removes the character after the caret. By contrast, simply typing
a character such as "z" inserts that character as text content.
It's possible to override the default keymap with your own, as this
example illustrates:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.*;
public class KeyMap {
public static void main(String args[]) {
JFrame frame = new JFrame("Keymap demo");
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
final JTextPane pane = new JTextPane();
pane.setPreferredSize(new Dimension(600, 400));
Keymap km = pane.getKeymap();
KeyStroke ks = KeyStroke.getKeyStroke(
KeyEvent.VK_Z, Event.CTRL_MASK);
Action act = new TextAction("Ctrl-Z") {
public void actionPerformed(ActionEvent e) {
pane.replaceSelection("ZZZ");
}
};
km.addActionForKeyStroke(ks, act);
JPanel panel = new JPanel();
panel.add("Center", pane);
frame.getContentPane().add(panel);
frame.pack();
frame.setVisible(true);
}
}
This demo overrides the default action for Ctrl-Z (which is to do nothing).
With this override in place, Ctrl-Z inserts "ZZZ" into the text pane. In other
words, a keystroke (a combination of a key and possibly a modifier like Ctrl)
has been added to the keymap. And an action has been specified for the
keystroke such that replaceSelection
is called to insert text or
replace selected text with new text.
This feature can be used in an application such as a word processor,where the
user binds arbitrary text strings to keys.
There are several interesting points to note about keymaps. One is that a
default keymap is set up for you when you use JTextPane
. It
contains bindings for keystrokes such as Ctrl-X (cut selected text). The
example above changes this keymap by adding a keystroke/action pair to it.
This modification will be reflected in all text panes in the application. So
if you don't want to disturb the default keymap, you can create your own.
If you set a keymap to null, as in:
pane.setKeymap(null);
then keyboard input to the text pane is disabled.
There is also the concept of parent and child keymaps. If a keystroke is not
resolved in a child keymap, then it is searched for in the parent. An
application might, for example, create its own keymap, and use the default
keymap as a parent. The parent keymap is used to resolve keystrokes not found
in the application-specific keymap. In the example above, Ctrl-Z might be
resolved in a keymap that has been created in the application, with Ctrl-X
resolved in the default parent keymap.
Note
The names on the JDCSM mailing list
are used for internal Sun MicrosystemsTM
purposes only. To remove your name from the list, see Subscribe/Unsubscribe below.
Feedback
Comments? Send your feedback on the JDC Tech Tips to: jdc-webmaster
Subscribe/Unsubscribe
The JDC Tech Tips are sent to you because you elected to subscribe when you registered as a JDC member. To unsubscribe from JDC email, go to the following address and enter the email address you wish to remove from the mailing list:
http://developer.java.sun.com/unsubscribe.html
To become a JDC member and subscribe to this newsletter go to:
http://java.sun.com/jdc/