JBuilder keymap concepts

General description

This concept discussion describes how keymaps work in JBuilder and how you can create, change, and extend them.

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:


Detailed descriptions

Keymaps in the JBuilder editor

JBuilder installs three keymaps: CUA, Emacs, and Brief. The default keymap is CUA. The user can change to another keymap through the Tools|Ide Options menu item.

Keymaps and OpenTools

All three keymaps have been made into OpenTools examples, so you can learn how to create an entirely new keymap. Or you can use the OpenTools API to change one or more key bindings in an existing keymap.

Changing and extending keymaps

Basic keymap information

The 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.

Notification of keymap changes

The 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......
  }
}

Changing the keymap

When you install a new keymap, it's easy to insert new key bindings or modify existing ones. For example, this code changes one of the key bindings:

  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.

Multiple key events for each keystroke

The JDK fires multiple key events for each keystroke. Usually this isn't a problem when you modify key bindings with 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.

Writing your own action

You might find the editor actions JBuilder supplies insufficient. It's easy to write your own action class and bind it to any key event.

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);

Making the keymap editor recognize your new action

If you want the keymap editor to add your new action to the list of actions that can be bound to keystrokes, you must add code similar to one of the following sequence:
  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");

More advanced keymap functions

The JDK Keymap class has some additional interesting methods:

Removing key bindings

You can remove key bindings from a keymap, either individually or en masse. Removing a key binding, however, doesn't mean that a key event doesn't have an action associated with it any more. It simply means that there's no action associated with the key in the current keymap. Keymaps are stacked on top of each other; the JDK travels the stack of keymaps trying to find an action that is bound to a certain keystroke. For instance, by default the JDK might bind an action to the Page Up key; JBuilder overrides this with the 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) {
    }

  }

Creating your own keymap

Using the sample keymaps

To create a brand new keymap, it's usually easiest to take one of the keymaps in the samples and modify it. The UserCUA keymap sample is useful for basic keymaps that mostly bind keystrokes to existing 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.

Naming the keymap

Always give your keymap a unique name. Don't try to install a new keymap called Emacs, Brief, or CUA, because those names conflict with the keymaps that JBuilder installs by default. You name your new keymap in two places in the initOpenTool() method, and that same name is what shows up in the Tools|IDE Options dialog where you change keymaps.

SubKeymaps

The UserEmacs keymap implements subKeymaps. Pressing Ctrl-x in Emacs mode switches the user to a different keymap (the Ctrl-x keymap), and that new keymap is called a subKeymap. Another way to look at this is to say that Emacs has states, and that Ctrl-x switches you to a different state with a new keymap. If you follow the UserEmacs keymap example, you should have few problems implementing subKeymaps.

Manipulating the editor

Both the UserEmacs and the UserBrief samples have code that shows you how to access the 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.

Improved keymaps in JDK 1.3

In JDK 1.3, keymaps have changed. The old scheme consisted of keymaps that contain the bindings between keystrokes and actions. The new scheme has the keymap split into two parts: an input map that holds the keystrokes, and an action map that contains the actions. Note, however, that most keymap behavior won't change for developers because the keymaps wrap the old behavior around the new behavior. You can, however, go one level deeper and start using the new input and action maps. For a detailed description, see the official JavaSoft kestrel/keybindings report.