KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > editor > MultiKeymap


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.editor;
21
22 import java.awt.event.KeyEvent JavaDoc;
23 import java.awt.event.ActionEvent JavaDoc;
24 import java.awt.event.InputEvent JavaDoc;
25 import java.util.ArrayList JavaDoc;
26 import java.util.Iterator JavaDoc;
27 import java.util.List JavaDoc;
28 import java.util.Map JavaDoc;
29 import java.util.HashMap JavaDoc;
30 import javax.swing.text.Keymap JavaDoc;
31 import javax.swing.text.JTextComponent JavaDoc;
32 import javax.swing.text.DefaultEditorKit JavaDoc;
33 import javax.swing.KeyStroke JavaDoc;
34 import javax.swing.Action JavaDoc;
35 import javax.swing.AbstractAction JavaDoc;
36 import org.openide.awt.StatusDisplayer;
37 import org.openide.util.Lookup;
38
39 /**
40 * Keymap that is capable to work with MultiKeyBindings
41 *
42 * @author Miloslav Metelka
43 * @version 0.10
44 */

45
46 public class MultiKeymap implements Keymap JavaDoc {
47     
48     private static final boolean compatibleIgnoreNextTyped
49         = Boolean.getBoolean("netbeans.editor.keymap.compatible");
50
51     /** Action that does nothing */
52     public static final Action JavaDoc EMPTY_ACTION = new AbstractAction JavaDoc() {
53         public void actionPerformed(ActionEvent JavaDoc evt) {
54         }
55     };
56
57     /** Action that beeps. Used for wrong shortcut by default */
58     public static final Action JavaDoc BEEP_ACTION = new DefaultEditorKit.BeepAction JavaDoc();
59
60     /** JTextComponent.DefaultKeymap to be used for processing by this keymap */
61     private Keymap JavaDoc delegate;
62
63     /** Context keymap or null for base context */
64     private Keymap JavaDoc context;
65
66     /** Ignore possible keyTyped events after context reset */
67     private boolean ignoreNextTyped = false;
68
69     /** Action to return when there's no action for incoming key
70     * in some context. This action doesn't occur when no action
71     * is found in base context.
72     */

73     private Action JavaDoc contextKeyNotFoundAction = BEEP_ACTION;
74     
75     /**
76      * List of key strokes that form the present context.
77      * If this list differs from the global context maintained in the status displayer
78      * then the keymap must be reset and attempted to be put
79      * into the global context before attempting to process the given keystroke.
80      * If the keymap cannot be put into such a context then
81      * it returns null action for the given keystroke.
82      */

83     private List JavaDoc contextKeys;
84
85     /** Construct new keymap.
86     * @param name name of new keymap
87     */

88     public MultiKeymap(String JavaDoc name) {
89         delegate = JTextComponent.addKeymap(name, null);
90         contextKeys = new ArrayList JavaDoc();
91     }
92
93     /** Set the context keymap */
94     void setContext(Keymap JavaDoc contextKeymap) {
95         context = contextKeymap;
96     }
97
98     private static String JavaDoc getKeyText (KeyStroke JavaDoc keyStroke) {
99         if (keyStroke == null) return ""; // NOI18N
100
String JavaDoc modifText = KeyEvent.getKeyModifiersText
101             (keyStroke.getModifiers ());
102         if ("".equals (modifText)) // NOI18N
103
return KeyEvent.getKeyText (keyStroke.getKeyCode ());
104         return modifText + "+" + // NOI18N
105
KeyEvent.getKeyText (keyStroke.getKeyCode ());
106     }
107             
108     /** Reset keymap to base context */
109     public void resetContext() {
110         context = null;
111         contextKeys.clear();
112     }
113     
114     /**
115      * Add a context key to the global context maintained by the NbKeymap.
116      *
117      * @param key a key to be added to the global context.
118      */

119     private void shiftGlobalContext(KeyStroke JavaDoc key) {
120         List JavaDoc globalContextList = getGlobalContextList();
121         if (globalContextList != null) {
122             globalContextList.add(key);
123             
124             StringBuffer JavaDoc text = new StringBuffer JavaDoc();
125             for (Iterator JavaDoc it = globalContextList.iterator(); it.hasNext();) {
126                 text.append(getKeyText((KeyStroke JavaDoc)it.next())).append(' ');
127             }
128             StatusDisplayer.getDefault().setStatusText(text.toString());
129         }
130         // Shift the locally maintained mirror context as well
131
contextKeys.add(key);
132     }
133
134     /**
135      * Reset the global context in case there is a reason for it.
136      */

137     private void resetGlobalContext() {
138         List JavaDoc globalContextList = getGlobalContextList();
139         if (globalContextList != null) {
140             globalContextList.clear();
141             StatusDisplayer.getDefault().setStatusText("");
142         }
143     }
144     
145     private List JavaDoc getGlobalContextList() {
146         // Retrieve the list from NbKeymap by reflection
147
// Get system classloader
148
List JavaDoc globalContextList;
149         try {
150             ClassLoader JavaDoc sysCL = (ClassLoader JavaDoc)Lookup.getDefault().lookup(ClassLoader JavaDoc.class);
151             Class JavaDoc nbKeymapClass = Class.forName("org.netbeans.core.NbKeymap", true, sysCL); // NOI18N
152
java.lang.reflect.Field JavaDoc contextField = nbKeymapClass.getDeclaredField("context"); // NOI18N
153
contextField.setAccessible(true);
154             globalContextList = (List JavaDoc)contextField.get(null);
155         } catch (Exception JavaDoc e) {
156             // Ignore the exception
157
globalContextList = null;
158         }
159         return globalContextList;
160     }
161
162     /** What to do when key is not resolved for context */
163     public void setContextKeyNotFoundAction(Action JavaDoc a) {
164         contextKeyNotFoundAction = a;
165     }
166
167     /** Loads the key to action mappings into this keymap in similar way
168     * as JTextComponent.loadKeymap() does. This method is able to handle
169     * MultiKeyBindings but for compatibility it expects
170     * JTextComponent.KeyBinding array.
171     */

172     public void load(JTextComponent.KeyBinding JavaDoc[] bindings, Action JavaDoc[] actions) {
173         Map JavaDoc h = new HashMap JavaDoc(bindings.length);
174         // add actions to map to resolve by names quickly
175
for (int i = 0; i < actions.length; i++) {
176             Action JavaDoc a = actions[i];
177             String JavaDoc value = (String JavaDoc)a.getValue(Action.NAME);
178             h.put((value != null ? value : ""), a); // NOI18N
179
}
180         load(bindings, h);
181     }
182
183     /** Loads key to action mappings into this keymap
184     * @param bindings array of bindings
185     * @param actions map of [action_name, action] pairs
186     */

187     public void load(JTextComponent.KeyBinding JavaDoc[] bindings, Map JavaDoc actions) {
188         // now create bindings in keymap(s)
189
for (int i = 0; i < bindings.length; i++) {
190             Action JavaDoc a = (Action JavaDoc)actions.get(bindings[i].actionName);
191             if (a != null) {
192                 boolean added = false;
193                 if (bindings[i] instanceof MultiKeyBinding) {
194                     MultiKeyBinding mb = (MultiKeyBinding)bindings[i];
195                     if (mb.keys != null) {
196                         Keymap JavaDoc cur = delegate;
197                         for (int j = 0; j < mb.keys.length; j++) {
198                             if (j == mb.keys.length - 1) { // last keystroke in sequence
199
cur.addActionForKeyStroke(mb.keys[j], a);
200                             } else { // not the last keystroke
201
Action JavaDoc sca = cur.getAction(mb.keys[j]);
202                                 if (!(sca instanceof KeymapSetContextAction)) {
203                                     sca = new KeymapSetContextAction(JTextComponent.addKeymap(null, null));
204                                     cur.addActionForKeyStroke(mb.keys[j], sca);
205                                 }
206                                 cur = ((KeymapSetContextAction)sca).contextKeymap;
207                             }
208                         }
209                         added = true;
210                     }
211                 }
212                 if (!added) {
213                     if (bindings[i].key != null) {
214                         delegate.addActionForKeyStroke(bindings[i].key, a);
215                     } else { // key is null -> set default action
216
setDefaultAction(a);
217                     }
218                 }
219             }
220         }
221     }
222
223     public String JavaDoc getName() {
224         return (context != null) ? context.getName()
225                : delegate.getName();
226     }
227
228     /** Get default action of this keymap or parent keymap if this
229     * one doesn't have one. Context keymap can have default action
230     * but it will be not used.
231     */

232     public Action JavaDoc getDefaultAction() {
233         return delegate.getDefaultAction();
234     }
235
236     public void setDefaultAction(Action JavaDoc a) {
237         if (context != null) {
238             context.setDefaultAction(a);
239         } else {
240             delegate.setDefaultAction(a);
241         }
242     }
243
244     private Action JavaDoc getActionImpl(KeyStroke JavaDoc key) {
245         Action JavaDoc a = null;
246         if (context != null) {
247             a = context.getAction(key);
248             // Commented out the next part to allow the other
249
// keystroke processors to work when the editor does not have an action
250
// for the particular keystroke.
251
/* if (a == null) { // possibly ignore modifier keystrokes
252                 switch (key.getKeyCode()) {
253                 case KeyEvent.VK_SHIFT:
254                 case KeyEvent.VK_CONTROL:
255                 case KeyEvent.VK_ALT:
256                 case KeyEvent.VK_META:
257                     return EMPTY_ACTION;
258                 }
259                 if (key.isOnKeyRelease()
260                     || (key.getKeyChar() != 0 && key.getKeyChar() != KeyEvent.CHAR_UNDEFINED)
261                 ) {
262                     return EMPTY_ACTION; // ignore releasing and typed events
263                 }
264             }
265  */

266         } else {
267             a = delegate.getAction(key);
268         }
269
270         return a;
271     }
272     
273     private boolean contextKeysEqual(List JavaDoc keys) {
274         if (keys.size() != contextKeys.size()) {
275             return false;
276         }
277         for (int i = keys.size() - 1; i >= 0; i--) {
278             if (!contextKeys.get(i).equals(keys.get(i))) {
279                 return false;
280             }
281         }
282         return true;
283     }
284
285     public Action JavaDoc getAction(KeyStroke JavaDoc key) {
286         Action JavaDoc ret = null;
287
288         // Check whether the context in status displayer corresponds to the keymap's context
289
// If there would be a non-empty SD context that differs from the editor's one
290
// then do not return any action for this keystroke.
291
List JavaDoc globalContextList = getGlobalContextList();
292         if (globalContextList != null && globalContextList.size() > 0 && !contextKeysEqual(globalContextList)) {
293             resetContext();
294             int i;
295             for (i = 0; i < globalContextList.size(); i++) {
296                 Action JavaDoc a = getActionImpl((KeyStroke JavaDoc)globalContextList.get(i));
297                 if (a instanceof KeymapSetContextAction) {
298                     a.actionPerformed(null);
299                 } else {
300                     // no multi-keystrokes for such context in editor
301
resetContext();
302                     break;
303                 }
304             }
305             if (i != globalContextList.size()) { // unsuccessful context switch
306
return null;
307             }
308         }
309         
310         // Explicit patches of the keyboard problems
311
if (ignoreNextTyped) {
312             if (key.isOnKeyRelease()) { // ignore releasing here
313
ret = EMPTY_ACTION;
314             } else { // either pressed or typed
315
ignoreNextTyped = false;
316             }
317             if (key.getKeyChar() != 0 && key.getKeyChar() != KeyEvent.CHAR_UNDEFINED) {
318                 ret = EMPTY_ACTION; // prevent using defaultAction
319
}
320         }
321
322         if (ret == null) {
323             ret = getActionImpl(key);
324             if (ret instanceof KeymapSetContextAction) { //
325
// Mark the context shifting
326
shiftGlobalContext(key);
327
328             } else { // not a context shift action
329
if (context != null) { // Already in a non-empty context
330
ignoreNextTyped = true;
331
332                 } else if (compatibleIgnoreNextTyped) {
333                     // #44307 = disabled extra ignoreNextTyped patches for past JDKs
334
if ( // Explicit patch for the keyTyped sent after Alt+key
335
(key.getModifiers() & InputEvent.ALT_MASK) != 0 // Alt pressed
336
&& (key.getModifiers() & InputEvent.CTRL_MASK) == 0 // Ctrl not pressed
337
) {
338                         boolean patch = true;
339                         if (key.getKeyChar() == 0 || key.getKeyChar() == KeyEvent.CHAR_UNDEFINED) {
340                             switch (key.getKeyCode()) {
341                             case KeyEvent.VK_ALT: // don't patch single Alt
342
case KeyEvent.VK_KANJI:
343                             case KeyEvent.VK_KATAKANA:
344                             case KeyEvent.VK_HIRAGANA:
345                             case KeyEvent.VK_JAPANESE_KATAKANA:
346                             case KeyEvent.VK_JAPANESE_HIRAGANA:
347                             case 0x0107: // KeyEvent.VK_INPUT_METHOD_ON_OFF: - in 1.3 only
348
case KeyEvent.VK_NUMPAD0: // Alt+NumPad keys
349
case KeyEvent.VK_NUMPAD1:
350                             case KeyEvent.VK_NUMPAD2:
351                             case KeyEvent.VK_NUMPAD3:
352                             case KeyEvent.VK_NUMPAD4:
353                             case KeyEvent.VK_NUMPAD5:
354                             case KeyEvent.VK_NUMPAD6:
355                             case KeyEvent.VK_NUMPAD7:
356                             case KeyEvent.VK_NUMPAD8:
357                             case KeyEvent.VK_NUMPAD9:
358                                 patch = false;
359                                 break;
360                             }
361                         }
362
363                         if (patch) {
364                            ignoreNextTyped = true;
365                         }
366                     } else if ((key.getModifiers() & InputEvent.META_MASK) != 0) { // Explicit patch for the keyTyped sent after Meta+key for Mac OS X
367
ignoreNextTyped = true;
368                     } else if ((key.getModifiers() & InputEvent.CTRL_MASK) != 0 &&
369                                (key.getModifiers() & InputEvent.SHIFT_MASK) != 0 &&
370                                (key.getKeyCode() == KeyEvent.VK_ADD || key.getKeyCode() == KeyEvent.VK_SUBTRACT)) {
371                         // Explicit patch for keyTyped sent without the Ctrl+Shift modifiers on Mac OS X - see issue #39835
372
ignoreNextTyped = true;
373                     }
374                 }
375
376                 resetContext(); // reset context when resolved
377
// The global context cannot be reset because
378
// this is just a situation when the editor keymap
379
// does not know but the system or other
380
}
381
382             if (context != null && ret == null) { // no action found when in context
383
// Letting to return null in order to give chance to other keymaps
384
// ret = contextKeyNotFoundAction;
385
}
386         }
387         
388         // Reset global context if a valid action is found
389
if (ret != null && !(ret instanceof KeymapSetContextAction) && (ret != EMPTY_ACTION)) {
390             resetGlobalContext();
391         }
392
393         if (compatibleIgnoreNextTyped) {
394             // #44307 = disabled extra ignoreNextTyped patches for past JDKs
395
// Explicit patch for Ctrl+Space - eliminating the additional KEY_TYPED sent
396
if (key == KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, InputEvent.CTRL_MASK)) {
397                 ignoreNextTyped = true;
398             }
399         }
400
401 /* System.out.println("key=" + key + ", keyChar=" + (int)key.getKeyChar() + ", keyCode=" + key.getKeyCode() + ", keyModifiers=" + key.getModifiers() // NOI18N
402                 + ", ignoreNextTyped=" + ignoreNextTyped + ", context=" + context // NOI18N
403                 + ", returning action=" + ((ret == EMPTY_ACTION) ? "EMPTY_ACTION" : ((ret == null) ? "null" : ((ret instanceof javax.swing.text.TextAction) // NOI18N
404                     ? ret.getValue(javax.swing.Action.NAME) : ret.getClass()))));
405 */

406                     
407         return ret;
408     }
409
410     public KeyStroke JavaDoc[] getBoundKeyStrokes() {
411         return (context != null) ? context.getBoundKeyStrokes()
412                : delegate.getBoundKeyStrokes();
413     }
414
415     public Action JavaDoc[] getBoundActions() {
416         return (context != null) ? context.getBoundActions()
417                : delegate.getBoundActions();
418     }
419
420     public KeyStroke JavaDoc[] getKeyStrokesForAction(Action JavaDoc a) {
421         return (context != null) ? context.getKeyStrokesForAction(a)
422                : delegate.getKeyStrokesForAction(a);
423     }
424
425     public boolean isLocallyDefined(KeyStroke JavaDoc key) {
426         return (context != null) ? context.isLocallyDefined(key)
427                : delegate.isLocallyDefined(key);
428     }
429
430     public void addActionForKeyStroke(KeyStroke JavaDoc key, Action JavaDoc a) {
431         if (context != null) {
432             context.addActionForKeyStroke(key, a);
433         } else {
434             delegate.addActionForKeyStroke(key, a);
435         }
436     }
437
438     public void removeKeyStrokeBinding(KeyStroke JavaDoc key) {
439         if (context != null) {
440             context.removeKeyStrokeBinding(key);
441         } else {
442             delegate.removeKeyStrokeBinding(key);
443         }
444     }
445
446     public void removeBindings() {
447         if (context != null) {
448             context.removeBindings();
449         } else {
450             delegate.removeBindings();
451         }
452     }
453
454     public Keymap JavaDoc getResolveParent() {
455         return (context != null) ? context.getResolveParent()
456                : delegate.getResolveParent();
457     }
458
459     public void setResolveParent(Keymap JavaDoc parent) {
460         if (context != null) {
461             context.setResolveParent(parent);
462         } else {
463             delegate.setResolveParent(parent);
464         }
465     }
466
467     public String JavaDoc toString() {
468         return "MK: name=" + getName(); // NOI18N
469
}
470
471     /** Internal class used to set the context */
472     class KeymapSetContextAction extends AbstractAction JavaDoc {
473
474         Keymap JavaDoc contextKeymap;
475
476         static final long serialVersionUID =1034848289049566148L;
477
478         KeymapSetContextAction(Keymap JavaDoc contextKeymap) {
479             this.contextKeymap = contextKeymap;
480         }
481
482         public void actionPerformed(ActionEvent JavaDoc evt) {
483             setContext(contextKeymap);
484         }
485
486     }
487
488 }
489
Popular Tags