A keymap provides a structured way to bind keystrokes to actions. It allows a keystroke to be registered with a corresponding action, and the keymap then ensures that the action fires when the user enters the proper keystroke. For all this to happen, the keymap must be the currently registered keymap in the component that has the focus.
You'll find it helpful to study some JDK classes to understand how keymaps are implemented in Swing. This is the list of classes to study:
Keymap
class located in javax/Swing/text/Keymap.java
KeyStroke
class located in javax/Swing/KeyStroke.java
Action
class located in javax/Swing/Action.java
Event
class located in java/awt/Event.java
EditorManager
class does most of the basic keymap handling.
The
getKeymap()
method returns the current keymap, and the
getKeymapName()
method returns the name of the current keymap. Use the
getKeymap(String)
method to retrieve an instance of a specific keymap by passing a String parameter such as CUA or Emacs. You can install a new keymap either by name or with a keymap instance with the
setKeymapName(String)
and
setKeymap(Keymap)
methods.
EditorManager
class also fires a change event when you install a new keymap. To install a class as a change event listener of the EditorManager
class, use code such as this:
EditorManager.addPropertyChangeListener(myClass);
myClass
is an instance of a class that implements the the java.beans.PropertyChangeListener
interface; therefore, you should implement the
propertyChange()
method. Here is a simple code example:
public class ModifyKeyBinding implements PropertyChangeListener { public ModifyKeyBinding() { } public static void initOpenTool(byte majorVersion, byte minorVersion) { // Make sure the OpenTools API is compatible if (majorVersion != PrimeTime.CURRENT_MAJOR_VERSION) return; // Create our class and add it as a listener of any EditorManager changes // This will allow us to catch keymap changes ModifyKeyBinding m = new ModifyKeyBinding(); EditorManager.addPropertyChangeListener(m); } // The EditorManager will call this function anytime it fires a // property change public void propertyChange(PropertyChangeEvent e) { String propertyName = e.getPropertyName(); // We are only interested in keymap changes if (propertyName.equals(EditorManager.keymapAttribute)) { // We need a keymap to change Keymap keymap = EditorManager.getKeymap(); if (keymap == null) return; // Change the keymap...... } }
keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_0, Event.CTRL_MASK|Event.SHIFT_MASK), EditorActions.ACTION_MatchBrace);The code is binding the Ctrl-Shift '0' (zero) key event (on English keyboards this is Ctrl-right parenthesis) to the editor action that finds the matching brace when the cursor is positioned at a brace (see the
EditorActions.MatchBraceAction
class).
The
EditorActions
class
lists the static instances of all the actions the JBuilder editor supports. The names of all those
actions start with ACTION_, such as
ACTION_Backward
. The KeyEvent
class of the JDK lists the key codes for all recognized keys. These key codes
all start with VK_. Most of them are easy to remember. The Event
class of the JDK lists all the modifiers of a key event, such as Control, Alt, Meta, and Shift. By studying the JDK Keymap
, KeyStroke
, KeyEvent
, and Event
classes, you should have enough information to tie any key combination to any EditorActions
action.
Keymap.addActionForKeyStroke()
, because things will work fine. If, however, you try to
change the binding of keystroke with no modifiers, you might have get unexpected results. For example, you might want to tie the y key to the Cut action, so you might write this:
keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_Y, 0), EditorActions.ACTION_Cut);When you run your program, you'll find that the Cut action is indeed executed, but you also still get the y character added to your document, which is not what you wanted. This happens because the JDK fires three events for each key pressed: a pressed event, a typed event, and a released event. When you use any of the VK_ key codes in your
addActionForKeyStroke()
method call, you are hooking into the pressed event and overriding its default action.
The typed event affects keystrokes that translate into printable characters only. The default action for typed events is to emit the character into the document. The released event is usually not important.
Why can we add actions for most keystrokes and have things work well? Because we usually want to add an action to a keystroke with the Alt, Ctrl, or Meta modifier, and those key events usually aren't printable, so they don't generate a typed event. You simply override the pressed event and you're done. For printable keystrokes, you'll get what you want if you hook into the typed event with code like this:
keymap.addActionForKeyStroke(KeyStroke.getKeyStroke('y'), EditorActions.ACTION_Cut);The code still calls the cut action, but it doesn't emit the y character into the document.
Derive each new action class from the appropriate Action
class.
The
EditorAction
class can be the parent class of most new actions. If you're writing a Brief- or Emacs-specific action, derive your action from the
BriefAction
class or the
EmacsAction
class.
This is what a basic action class should look like:
class MyActions { public static class DoSomethingAction extends EditorAction { // Every action should be initialized with an appropriate name public DoSomethingAction(String nm) { super(nm); } // This is called when the JDK fires a key event public void actionPerformed(ActionEvent e) { // Action specific code, for instance: EditorPane target = getEditorTarget(e); if (target != null) { // Do something with the editor pane } } } // Add a static instance so anybody can use it public static EditorAction ACTION_DoSomething = new DoSomethingAction("do_something"); }Now you can put it to use with the following code:
keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_Y, Event.CTRL_MASK), MyActions.ACTION_DoSomething);
public static void initOpenTool(byte majorVersion, byte minorVersion) { // Register our action so it will show up in the keymap editor. // We can register it as an Editor action, which is an action that // makes sense if the focus is in the editor, or as an IDE action // which makes sense no matter where the focus is. // // This is how to add an action as an Editor action EditorActions.addBindableEditorAction(ACTION_DoSomething); // If you want to add your action as an IDE action, you would // use the following code: // // EditorActions.addBindableIDEAction(ACTION_DoSomething); // If you want to add an action as an Editor action, but have // it only show up in a particular keymap, use the following // code which makes the action only show up in the Brief keymap // // EditorActions.addBindableEditorAction(ACTION_DoSomething, "Brief"); // Similar if it is an IDE action for the CUA keymap // // EditorActions.addBindableIDEAction(ACTION_DoSomething, "CUA"); } public static class DoSomethingAction extends EditorAction { // Every action should be initialized with an appropriate name public DoSomethingAction(String nm) { super(nm); // The long description property is used in the keymap editor // to fill up the description memo. this.putValue(Action.LONG_DESCRIPTION, "This is a function that does something."); // The ActionGroup property is used to put this action // in the specified group of actions. this.putValue("ActionGroup", "Miscellaneous"); } // This is called when the JDK fires a key event public void actionPerformed(ActionEvent e) { // Action specific code, for instance: EditorPane target = getEditorTarget(e); if (target != null) { // Do something with the editor pane } } } // Add a static instance so anybody can use it public static EditorAction ACTION_DoSomething = new DoSomethingAction("do_something");
This is useful if you want to save the current action bound to a keystroke before you bind a new action with addActionForKeyStroke()
. You can then restore the original action later.
The default action is called when there is no binding for the current keystroke. These methods are handy if you want to handle most keystrokes in one event.
Use this if you want to remove a binding you added through addActionForKeyStroke()
.
Removes all the bindings in the current keymap. It's especially useful when you switch keymaps.
PageUpAction
. If JBuilder removes that binding to PageUpAction
, the Page Up key
triggers the default JDK action again.
To simulate removing a key binding completely so that the keystroke fires no event, you can tie it an action that does nothing. For example,
public static class DoNothingAction extends EditorAction { // Every action should be initialized with an appropriate name public DoNothingAction(String nm) { super(nm); } // This is called when the JDK fires a key event public void actionPerformed(ActionEvent e) { } }
EditorActions
. If you need to create a keymap with subKeymaps, the UserEmacs keymap works well. The UserBrief keymap contains a lot of editor manipulation code, so it's a good one to learn from.
initOpenTool()
method, and that same name is what shows up in the Tools|IDE Options
dialog where you change keymaps.
EditorPane
. Through the EditorPane
you can access the
document and the
caret. You should find enough code in those samples to see how to perform most basic editor manipulations.