21.3 Keymaps


A Keymap contains mappings from KeyStrokes[2] to Actions and provides a variety of methods for accessing and updating these mappings.

[2] KeyStroke is discussed in more detail in Chapter 27. Basically, it's just a representation of a key being typed, containing both the key code and any key modifiers (Ctrl, Alt, etc.).

21.3.1 The Keymap Interface

One last interface you can use to customize your application without implementing your own L&F is Keymap.

Normally, an L&F defines a set of meaningful keystrokes. For example, Windows users expect Ctrl-C to copy text and Ctrl-V to paste while the Mac uses the Command key instead. Ctrl-Insert and Shift-Insert perform the same tasks for Motif users. These key sequences work as expected in Swing text components because of the Keymap installed by the L&F. This interface lets you change or augment this behavior.

The Keymap interface has been available to Swing text components since the beginning. SDK 1.3 introduced the more flexible and universal keyboard event system based on InputMaps and ActionMaps (described in Chapter 3). The techniques illustrated in this section remain useful: Keymap support was reimplemented using the new mechanism for backward compatibility. This approach is still quite convenient if all you need is to add a couple of keyboard commands to a text component. Also be sure to learn how to use InputMap and ActionMap directly they give you more capabilities, in many more situations.

21.3.1.1 Properties

The Keymap interface defines the properties shown in Table 21-8. The boundAction and boundKeyStrokes properties contain the Actions and KeyStrokes (both part of the javax.swing package) local to the Keymap.

Table 21-8. Keymap properties

Property

Data type

get

is

set

Default value

boundActions

Action[]

·

     

boundKeyStrokes

KeyStroke[]

·

     

defaultAction

Action

·

 

·

 

name

String

·

     

resolveParent

Keymap

·

 

·

 

The defaultAction property represents the action to be used when there is no action defined for a key. It is typically set to an instance of the DefaultKeyTypedAction inner class from the DefaultEditorKit. However, if you want to catch all keystrokes (perhaps to implement an editor like vi that inserts text only when it is in insert mode), you could define your own default action. defaultAction may be null, but the getDefaultAction( ) method should consult the resolveParent before returning null.

name is an arbitrary name given to the map. It may be null, but then the keymap will not be found by JTextComponent methods that refer to keymaps by name.

Finally, the resolveParent is another Keymap used to resolve any keys that are not defined locally in the map, thus creating a linear hierarchy of Keymaps. If an appropriate Action is not defined locally, the parent keymap is consulted (unless the parent is null).

21.3.1.2 Methods

Several methods defined in the Keymap interface allow mappings from KeyStrokes to Actions to be added, removed, and queried:

public abstract void addActionForKeyStroke(KeyStroke key, Action a)

Add the Action to be performed for the given KeyStroke.

public abstract Action getAction(KeyStroke key)

Determine the action for the given keystroke. If the action is not found in the Keymap, the resolveParent (if any) should be consulted before returning null. (The defaultAction should not be consulted. If the caller wishes to fall back on the keymap's defaultAction, it must call the getDefaultAction( ) method manually.)

public abstract KeyStroke[] getKeyStrokesForAction(Action a)

Return all KeyStrokes that map to the specified Action. This includes KeyStrokes maintained by the resolveParent, unless mapped elsewhere locally.

public abstract boolean isLocallyDefined(KeyStroke key)

Signal whether the specified KeyStroke is defined in the Keymap. The resolveParent should not be checked.

public abstract void removeBindings( )

Remove all KeyStroke to Action bindings from the Keymap. (Should not affect the resolveParent.)

public abstract void removeKeyStrokeBinding(KeyStroke keys)

Remove the binding for the specified KeyStroke. (Should not affect the resolveParent.)

21.3.2 Keymap Implementation

Unlike Caret and Highlighter, the Keymap interface does not have a public default implementation. The JTextComponent class defines DefaultKeymap as a package-private inner class. This implementation uses a Hashtable to map from KeyStrokes to Actions. (So each KeyStroke maps to no more than one Action.)

There are a few ways to change the Keymap used by your text components. One option is to call getKeymap( ) on the component and add any new actions directly to that map. Doing this may change the mappings for all JTextComponents in the application if the L&F is sharing a single Keymap for every component (which is what the Swing L&Fs do). This is not a big problem if the actions you add work with all types of text components, but it is probably not the best approach.

A better approach is to define a new Keymap that uses the default Keymap (installed by the L&F) as its parent. The new Keymap contains only the mappings you define and passes any other keystrokes up to the default map. As the next example shows, this is done by calling JTextComponent.addKeymap( ). This method takes the name of your new map and the parent map to be used and returns a new Keymap for you to add mappings to. Since this is a static method, once you've made this call and added to the new map, you can use the new map for any other JTextComponents by calling:

myComponent.setKeymap(JTextComponent.getKeymap("MyKeymapName"))

21.3.3 Adding Keyboard Actions

Here's a quick example showing how easy it is to add keyboard functionality to Swing text components. In this example, we'll enable Ctrl-W to select a word, Ctrl-L to select a line, and Ctrl-U to convert words to uppercase.[3]

[3] Of course, a robust implementation would want to be sensitive to the modifier key that's commonly used in the current L&F rather than blindly using Ctrl.

JTextArea provides Actions for select-word and select-line via the getActions( ) method, but we create our own upcase-word Action as an inner class. All we have to do is create a Keymap containing the mappings we want, and we'll be able to make selections or upcase words with a simple key press. Here's the code to do this:

// KeymapExample.java // import javax.swing.*; import javax.swing.text.*; import java.util.Hashtable; import java.awt.event.*; import java.awt.BorderLayout; // A simple example showing how to add Actions for KeyStrokes public class KeymapExample {   public static void main(String[] args) {     // Start with a simple JTextArea, get its Keymap to use as our parent,     // and create a new map called "KeymapExampleMap".     JTextArea area = new JTextArea(6, 32);     Keymap parent = area.getKeymap( );     Keymap newmap = JTextComponent.addKeymap("KeymapExampleMap", parent);     // Add Ctrl-U: change current word to uppercase (our own action).     KeyStroke u = KeyStroke.getKeyStroke(KeyEvent.VK_U, InputEvent.CTRL_MASK);     Action actionU = new UpWord( ); // An inner class (defined below)     newmap.addActionForKeyStroke(u, actionU);     // Get all the actions JTextArea provides for us.     Action actionList[] = area.getActions( );     // Put them in a Hashtable so that we can retreive them by Action.NAME.     Hashtable lookup = new Hashtable( );     for (int j=0; j < actionList.length; j+=1)       lookup.put(actionList[j].getValue(Action.NAME), actionList[j]);     // Add Ctrl-L: select current line (action provided for us).     KeyStroke L = KeyStroke.getKeyStroke(KeyEvent.VK_L, InputEvent.CTRL_MASK);     Action actionL = (Action)lookup.get(DefaultEditorKit.selectLineAction);     newmap.addActionForKeyStroke(L, actionL);     // Add Ctrl-W: select current word (action provided for us).     KeyStroke W = KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.CTRL_MASK);     Action actionW = (Action)lookup.get(DefaultEditorKit.selectWordAction);     newmap.addActionForKeyStroke(W, actionW);     // Set the JTextArea's Keymap to be our new map.     area.setKeymap(newmap);     // Show the TextPane.     JFrame f = new JFrame("KeymapExample");     f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);     f.getContentPane( ).add(new JScrollPane(area), BorderLayout.CENTER);     area.setText("This is the story\nof the hare who\nlost his spectacles.");     f.pack( );     f.setVisible(true);   }   // Begin inner class.   public static class UpWord extends TextAction {     public UpWord( ) {       super("uppercase-word-action");     }     public void actionPerformed(ActionEvent e) {       // Change current word (or selected words) to uppercase.       JTextComponent comp = getTextComponent(e);       if (comp == null) return;       Document doc = comp.getDocument( );       int start = comp.getSelectionStart( );       int end = comp.getSelectionEnd( );       try {         int left = javax.swing.text.Utilities.getWordStart(comp, start);         int right = javax.swing.text.Utilities.getWordEnd(comp, end);         String word = doc.getText(left, right-left);         doc.remove(left, right-left);         doc.insertString(left, word.toUpperCase( ), null);         comp.setSelectionStart(start); // Restore previous position/selection.         comp.setSelectionEnd(end);       } catch (BadLocationException ble) { return; }     }   } // End inner class. }

If you're sure your code won't need to be compatible with SDKs prior to 1.4, you should consider adding code to check whether the document extends AbstractDocument and, if it does, use its replace( ) method rather than separate calls to delete( ) and insert( ). This makes the Action work better with any JFormattedTextFields or DocumentFilters that accept the final text but not the intermediate state.

Don't worry about understanding everything about the actions we added here. (Text actions are covered in detail in Chapter 23.) The important things to understand are the following basic steps:

  1. Get the current map (the default set by the L&F).

  2. Create a new map with the old map as its parent by calling the static addKeymap( ) method in JTextComponent.

  3. Get the desired KeyStrokes using the KeyStroke.getKeyStroke( ) method, along with a few constants defined in the java.awt.event package. The java.awt.event.KeyEvent class defines constant values for many common keys while java.awt.event.InputEvent defines masks for Shift, Ctrl, Alt, and Meta.

  4. Add the KeyStroke/Action pair to the new Keymap.

  5. Set the new map as the map fo r our JTextComponent.



Java Swing
Graphic Java 2: Mastering the Jfc, By Geary, 3Rd Edition, Volume 2: Swing
ISBN: 0130796670
EAN: 2147483647
Year: 2001
Pages: 289
Authors: David Geary

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net