001: /*
002: * Sun Public License Notice
003: *
004: * The contents of this file are subject to the Sun Public License
005: * Version 1.0 (the "License"). You may not use this file except in
006: * compliance with the License. A copy of the License is available at
007: * http://www.sun.com/
008: *
009: * The Original Code is NetBeans. The Initial Developer of the Original
010: * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun
011: * Microsystems, Inc. All Rights Reserved.
012: */
013:
014: package org.netbeans.editor;
015:
016: import java.awt.event.ActionEvent;
017: import java.awt.event.InputEvent;
018: import java.awt.event.KeyEvent;
019: import java.util.HashMap;
020: import java.util.Map;
021:
022: import javax.swing.AbstractAction;
023: import javax.swing.Action;
024: import javax.swing.KeyStroke;
025: import javax.swing.text.DefaultEditorKit;
026: import javax.swing.text.JTextComponent;
027: import javax.swing.text.Keymap;
028:
029: /**
030: * Keymap that is capable to work with MultiKeyBindings
031: *
032: * @author Miloslav Metelka
033: * @version 0.10
034: */
035:
036: public class MultiKeymap implements Keymap {
037:
038: /** Action that does nothing */
039: public static final Action EMPTY_ACTION = new AbstractAction() {
040: public void actionPerformed(ActionEvent evt) {
041: }
042: };
043:
044: /** Action that beeps. Used for wrong shortcut by default */
045: public static final Action BEEP_ACTION = new DefaultEditorKit.BeepAction();
046:
047: /** JTextComponent.DefaultKeymap to be used for processing by this keymap */
048: private Keymap delegate;
049:
050: /** Context keymap or null for base context */
051: private Keymap context;
052:
053: /** Ignore possible keyTyped events after context reset */
054: private boolean ignoreNextTyped = false;
055:
056: /**
057: * Action to return when there's no action for incoming key in some context.
058: * This action doesn't occur when no action is found in base context.
059: */
060: private Action contextKeyNotFoundAction = BEEP_ACTION;
061:
062: /**
063: * Construct new keymap.
064: *
065: * @param name
066: * name of new keymap
067: */
068: public MultiKeymap(String name) {
069: delegate = JTextComponent.addKeymap(name, null);
070: }
071:
072: /** Set the context keymap */
073: void setContext(Keymap contextKeymap) {
074: context = contextKeymap;
075: }
076:
077: /** Reset keymap to base context */
078: public void resetContext() {
079: context = null;
080: }
081:
082: /** What to do when key is not resolved for context */
083: public void setContextKeyNotFoundAction(Action a) {
084: contextKeyNotFoundAction = a;
085: }
086:
087: /**
088: * Loads the key to action mappings into this keymap in similar way as
089: * JTextComponent.loadKeymap() does. This method is able to handle
090: * MultiKeyBindings but for compatibility it expects
091: * JTextComponent.KeyBinding array.
092: */
093: public void load(JTextComponent.KeyBinding[] bindings,
094: Action[] actions) {
095: Map h = new HashMap(bindings.length);
096: // add actions to map to resolve by names quickly
097: for (int i = 0; i < actions.length; i++) {
098: Action a = actions[i];
099: String value = (String) a.getValue(Action.NAME);
100: h.put((value != null ? value : ""), a); // NOI18N
101: }
102: load(bindings, h);
103: }
104:
105: /**
106: * Loads key to action mappings into this keymap
107: *
108: * @param bindings
109: * array of bindings
110: * @param actions
111: * map of [action_name, action] pairs
112: */
113: public void load(JTextComponent.KeyBinding[] bindings, Map actions) {
114: // now create bindings in keymap(s)
115: for (int i = 0; i < bindings.length; i++) {
116: Action a = (Action) actions.get(bindings[i].actionName);
117: if (a != null) {
118: boolean added = false;
119: if (bindings[i] instanceof MultiKeyBinding) {
120: MultiKeyBinding mb = (MultiKeyBinding) bindings[i];
121: if (mb.keys != null) {
122: Keymap cur = delegate;
123: for (int j = 0; j < mb.keys.length; j++) {
124: if (j == mb.keys.length - 1) { // last keystroke in
125: // sequence
126: cur
127: .addActionForKeyStroke(
128: mb.keys[j], a);
129: } else { // not the last keystroke
130: Action sca = cur.getAction(mb.keys[j]);
131: if (!(sca instanceof KeymapSetContextAction)) {
132: sca = new KeymapSetContextAction(
133: JTextComponent.addKeymap(
134: null, null));
135: cur.addActionForKeyStroke(
136: mb.keys[j], sca);
137: }
138: cur = ((KeymapSetContextAction) sca).contextKeymap;
139: }
140: }
141: added = true;
142: }
143: }
144: if (!added) {
145: if (bindings[i].key != null) {
146: delegate.addActionForKeyStroke(bindings[i].key,
147: a);
148: } else { // key is null -> set default action
149: setDefaultAction(a);
150: }
151: }
152: }
153: }
154: }
155:
156: public String getName() {
157: return (context != null) ? context.getName() : delegate
158: .getName();
159: }
160:
161: /**
162: * Get default action of this keymap or parent keymap if this one doesn't
163: * have one. Context keymap can have default action but it will be not used.
164: */
165: public Action getDefaultAction() {
166: return delegate.getDefaultAction();
167: }
168:
169: public void setDefaultAction(Action a) {
170: if (context != null) {
171: context.setDefaultAction(a);
172: } else {
173: delegate.setDefaultAction(a);
174: }
175: }
176:
177: Action getActionImpl(KeyStroke key) {
178: Action a = null;
179: if (context != null) {
180: a = context.getAction(key);
181: if (a == null) { // possibly ignore modifier keystrokes
182: switch (key.getKeyCode()) {
183: case KeyEvent.VK_SHIFT:
184: case KeyEvent.VK_CONTROL:
185: case KeyEvent.VK_ALT:
186: case KeyEvent.VK_META:
187: return EMPTY_ACTION;
188: }
189: if (key.isOnKeyRelease()
190: || (key.getKeyChar() != 0 && key.getKeyChar() != KeyEvent.CHAR_UNDEFINED)) {
191: return EMPTY_ACTION; // ignore releasing and typed events
192: }
193: }
194: } else {
195: a = delegate.getAction(key);
196: }
197:
198: return a;
199: }
200:
201: public Action getAction(KeyStroke key) {
202:
203: Action ret = null;
204:
205: // Explicit patches of the keyboard problems
206: if (ignoreNextTyped) {
207: if (key.isOnKeyRelease()) { // ignore releasing here
208: ret = EMPTY_ACTION;
209: } else { // either pressed or typed
210: ignoreNextTyped = false;
211: }
212: if (key.getKeyChar() != 0
213: && key.getKeyChar() != KeyEvent.CHAR_UNDEFINED) {
214: ret = EMPTY_ACTION; // prevent using defaultAction
215: }
216: }
217:
218: if (ret == null) {
219: ret = getActionImpl(key);
220: if (ret != EMPTY_ACTION) { // key that should be ignored
221: if (!(ret instanceof KeymapSetContextAction)) {
222: if (context != null) {
223: ignoreNextTyped = true;
224: } else if ( // Explicit patch for the keyTyped sent after
225: // Alt+key
226: (key.getModifiers() & InputEvent.ALT_MASK) != 0 // Alt
227: // pressed
228: && (key.getModifiers() & InputEvent.CTRL_MASK) == 0 // Ctrl
229: // not
230: // pressed
231: ) {
232: boolean patch = true;
233: if (key.getKeyChar() == 0
234: || key.getKeyChar() == KeyEvent.CHAR_UNDEFINED) {
235: switch (key.getKeyCode()) {
236: case KeyEvent.VK_ALT: // don't patch single Alt
237: case KeyEvent.VK_KANJI:
238: case KeyEvent.VK_KATAKANA:
239: case KeyEvent.VK_HIRAGANA:
240: case KeyEvent.VK_JAPANESE_KATAKANA:
241: case KeyEvent.VK_JAPANESE_HIRAGANA:
242: case 0x0107: // KeyEvent.VK_INPUT_METHOD_ON_OFF:
243: // - in 1.3 only
244: case KeyEvent.VK_NUMPAD0: // Alt+NumPad keys
245: case KeyEvent.VK_NUMPAD1:
246: case KeyEvent.VK_NUMPAD2:
247: case KeyEvent.VK_NUMPAD3:
248: case KeyEvent.VK_NUMPAD4:
249: case KeyEvent.VK_NUMPAD5:
250: case KeyEvent.VK_NUMPAD6:
251: case KeyEvent.VK_NUMPAD7:
252: case KeyEvent.VK_NUMPAD8:
253: case KeyEvent.VK_NUMPAD9:
254: patch = false;
255: break;
256: }
257: }
258:
259: if (patch) {
260: ignoreNextTyped = true;
261: }
262: } else if ((key.getModifiers() & InputEvent.META_MASK) != 0) { // Explicit
263: // patch
264: // for
265: // the
266: // keyTyped
267: // sent
268: // after
269: // Meta+key
270: // for
271: // Mac
272: // OS X
273: ignoreNextTyped = true;
274: }
275: resetContext(); // reset context when resolved
276: }
277:
278: if (context != null && ret == null) { // no action found when
279: // in context
280: ret = contextKeyNotFoundAction;
281: }
282: }
283: }
284:
285: // Explicit patch for Ctrl+Space - eliminating the additional KEY_TYPED
286: // sent
287: if (key == KeyStroke.getKeyStroke(KeyEvent.VK_SPACE,
288: InputEvent.CTRL_MASK)) {
289: ignoreNextTyped = true;
290: }
291:
292: /*
293: * System.out.println("key=" + key + ", keyChar=" +
294: * (int)key.getKeyChar() + ", keyCode=" + key.getKeyCode() + ",
295: * keyModifiers=" + key.getModifiers() // NOI18N + ", ignoreNextTyped=" +
296: * ignoreNextTyped + ", context=" + context // NOI18N + ", returning
297: * action=" + ((ret == EMPTY_ACTION) ? "EMPTY_ACTION" : ((ret == null) ?
298: * "null" : ((ret instanceof javax.swing.text.TextAction) // NOI18N ?
299: * ret.getValue(javax.swing.Action.NAME) : ret.getClass()))));
300: */
301:
302: return ret;
303: }
304:
305: public KeyStroke[] getBoundKeyStrokes() {
306: return (context != null) ? context.getBoundKeyStrokes()
307: : delegate.getBoundKeyStrokes();
308: }
309:
310: public Action[] getBoundActions() {
311: return (context != null) ? context.getBoundActions() : delegate
312: .getBoundActions();
313: }
314:
315: public KeyStroke[] getKeyStrokesForAction(Action a) {
316: return (context != null) ? context.getKeyStrokesForAction(a)
317: : delegate.getKeyStrokesForAction(a);
318: }
319:
320: public boolean isLocallyDefined(KeyStroke key) {
321: return (context != null) ? context.isLocallyDefined(key)
322: : delegate.isLocallyDefined(key);
323: }
324:
325: public void addActionForKeyStroke(KeyStroke key, Action a) {
326: if (context != null) {
327: context.addActionForKeyStroke(key, a);
328: } else {
329: delegate.addActionForKeyStroke(key, a);
330: }
331: }
332:
333: public void removeKeyStrokeBinding(KeyStroke key) {
334: if (context != null) {
335: context.removeKeyStrokeBinding(key);
336: } else {
337: delegate.removeKeyStrokeBinding(key);
338: }
339: }
340:
341: public void removeBindings() {
342: if (context != null) {
343: context.removeBindings();
344: } else {
345: delegate.removeBindings();
346: }
347: }
348:
349: public Keymap getResolveParent() {
350: return (context != null) ? context.getResolveParent()
351: : delegate.getResolveParent();
352: }
353:
354: public void setResolveParent(Keymap parent) {
355: if (context != null) {
356: context.setResolveParent(parent);
357: } else {
358: delegate.setResolveParent(parent);
359: }
360: }
361:
362: public String toString() {
363: return "MK: name=" + getName(); // NOI18N
364: }
365:
366: /** Internal class used to set the context */
367: class KeymapSetContextAction extends AbstractAction {
368:
369: Keymap contextKeymap;
370:
371: static final long serialVersionUID = 1034848289049566148L;
372:
373: KeymapSetContextAction(Keymap contextKeymap) {
374: this .contextKeymap = contextKeymap;
375: }
376:
377: public void actionPerformed(ActionEvent evt) {
378: setContext(contextKeymap);
379: }
380:
381: }
382:
383: }
|