2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
6
* The contents of this file are subject to the terms of either the GNU
7
* General Public License Version 2 only ("GPL") or the Common
8
* Development and Distribution License("CDDL") (collectively, the
9
* "License"). You may not use this file except in compliance with the
10
* License. You can obtain a copy of the License at
11
* http://www.netbeans.org/cddl-gplv2.html
12
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
* specific language governing permissions and limitations under the
14
* License. When distributing the software, include this License Header
15
* Notice in each file and include the License file at
16
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
17
* particular file as subject to the "Classpath" exception as provided
18
* by Sun in the GPL Version 2 section of the License file that
19
* accompanied this code. If applicable, add the following below the
20
* License Header, with the fields enclosed by brackets [] replaced by
21
* your own identifying information:
22
* "Portions Copyrighted [year] [name of copyright owner]"
26
* The Original Software is NetBeans. The Initial Developer of the Original
27
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
28
* Microsystems, Inc. All Rights Reserved.
30
* If you wish your version of this file to be governed by only the CDDL
31
* or only the GPL Version 2, indicate your decision by adding
32
* "[Contributor] elects to include this software in this distribution
33
* under the [CDDL or GPL Version 2] license." If you do not indicate a
34
* single choice of license, a recipient has the option to distribute
35
* your version of this file under either the CDDL, the GPL Version 2 or
36
* to extend the choice of license to its licensees as provided above.
37
* However, if you add GPL Version 2 code and therefore, elected the GPL
38
* Version 2 license, then the option applies only if the new code is
39
* made subject to such option by the copyright holder.
42
package org.netbeans.core;
44
import java.awt.event.KeyEvent;
45
import java.util.ArrayList;
46
import java.util.Arrays;
47
import java.util.Collections;
48
import java.util.Comparator;
49
import java.util.HashMap;
50
import java.util.HashSet;
51
import java.util.Iterator;
52
import java.util.List;
54
import java.util.Map.Entry;
55
import java.util.Observable;
57
import javax.swing.Action;
58
import javax.swing.KeyStroke;
59
import javax.swing.text.Keymap;
60
import org.openide.awt.StatusDisplayer;
61
import org.openide.util.Mutex;
63
/** Implementation of standard key - action mappings.
65
* @author Dafe Simonek
67
public final class NbKeymap extends Observable implements Keymap, Comparator<KeyStroke> {
68
/** Name of this keymap */
72
/** Hashtable holding KeyStroke > Action mappings */
73
Map<KeyStroke,Action> bindings;
76
/** hash table to map (Action -> ArrayList of KeyStrokes) */
77
Map<Action,List<KeyStroke>> actions;
79
private static List<KeyStroke> context = new ArrayList<KeyStroke>();
81
public static void resetContext() {
83
StatusDisplayer.getDefault().setStatusText("");
86
public static KeyStroke[] getContext() {
87
return (KeyStroke[]) context.toArray(new KeyStroke[context.size()]);
90
public static void shiftContext(KeyStroke stroke) {
93
StringBuilder text = new StringBuilder();
94
for (KeyStroke ks: context) {
95
text.append(getKeyText(ks)).append(' ');
97
StatusDisplayer.getDefault().setStatusText(text.toString());
100
private static String getKeyText (KeyStroke keyStroke) {
101
if (keyStroke == null) return "";
102
String modifText = KeyEvent.getKeyModifiersText
103
(keyStroke.getModifiers ());
104
if ("".equals (modifText))
105
return KeyEvent.getKeyText (keyStroke.getKeyCode ());
106
return modifText + "+" + // NOI18N
107
KeyEvent.getKeyText (keyStroke.getKeyCode ());
110
private final Action NO_ACTION = new KeymapAction(null, null);
112
public Action createMapAction(Keymap k, KeyStroke stroke) {
113
return new KeymapAction(k, stroke);
116
/** Default constructor
119
this("Default", null); // NOI18N
122
NbKeymap(final String name, final Keymap parent) {
124
this.parent = parent;
125
bindings = new HashMap<KeyStroke,Action>();
128
public Action getDefaultAction() {
129
if (defaultAction != null) {
130
return defaultAction;
132
return (parent != null) ? parent.getDefaultAction() : null;
135
public void setDefaultAction(Action a) {
141
public String getName() {
145
public Action getAction(KeyStroke key) {
148
KeyStroke[] ctx = getContext();
150
for (int i=0; i<ctx.length; i++) {
152
a = bindings.get(ctx[i]);
153
if ((a == null) && (parent != null)) {
154
a = parent.getAction(ctx[i]);
157
a = activ.getAction(ctx[i]);
160
if (a instanceof KeymapAction) {
161
activ = ((KeymapAction)a).keymap;
162
} else { // unknown ctx
163
int code = key.getKeyCode();
164
if (code != KeyEvent.VK_CONTROL &&
165
code != KeyEvent.VK_ALT &&
166
code != KeyEvent.VK_ALT_GRAPH &&
167
code != KeyEvent.VK_SHIFT &&
168
code != KeyEvent.VK_META) resetContext();
174
a = bindings.get(key);
175
if ((a == null) && (parent != null)) {
176
a = parent.getAction(key);
180
a = activ.getAction(key);
184
if (!(a instanceof KeymapAction)) {
190
// no action, should we reset?
191
if (key.isOnKeyRelease() ||
192
(key.getKeyChar() != 0 && key.getKeyChar() != KeyEvent.CHAR_UNDEFINED)) {
196
switch (key.getKeyCode()) {
197
case KeyEvent.VK_SHIFT:
198
case KeyEvent.VK_CONTROL:
199
case KeyEvent.VK_ALT:
200
case KeyEvent.VK_META:
208
public KeyStroke[] getBoundKeyStrokes() {
210
KeyStroke[] keys = null;
211
synchronized (this) {
212
keys = new KeyStroke[bindings.size()];
213
for (KeyStroke ks: bindings.keySet()) {
220
public Action[] getBoundActions() {
222
Action[] actionsArray = null;
223
synchronized (this) {
224
actionsArray = new Action[bindings.size()];
225
for (Iterator iter = bindings.values().iterator(); iter.hasNext(); ) {
226
actionsArray[i++] = (Action) iter.next();
232
public KeyStroke[] getKeyStrokesForAction(Action a) {
233
Map<Action,List<KeyStroke>> localActions = actions;
234
if (localActions == null) {
235
localActions = buildReverseMapping ();
238
List<KeyStroke> strokes = localActions.get (a);
239
if (strokes != null) {
240
return strokes.toArray(new KeyStroke[strokes.size ()]);
242
return new KeyStroke[0];
246
private Map<Action,List<KeyStroke>> buildReverseMapping () {
247
Map<Action,List<KeyStroke>> localActions = actions = new HashMap<Action,List<KeyStroke>> ();
249
synchronized (this) {
250
for (Map.Entry<KeyStroke,Action> curEntry: bindings.entrySet()) {
251
Action curAction = curEntry.getValue();
252
KeyStroke curKey = curEntry.getKey();
254
List<KeyStroke> keysForAction = localActions.get (curAction);
255
if (keysForAction == null) {
256
keysForAction = Collections.synchronizedList (new ArrayList<KeyStroke> (1));
257
localActions.put (curAction, keysForAction);
259
keysForAction.add (curKey);
266
public synchronized boolean isLocallyDefined(KeyStroke key) {
267
return bindings.containsKey(key);
270
/** Updates action accelerator. */
271
private void updateActionAccelerator(final Action a) {
276
Mutex.EVENT.writeAccess(new Runnable() {
278
KeyStroke[] keystrokes = getKeyStrokesForAction(a);
279
Arrays.sort (keystrokes, NbKeymap.this);
280
a.putValue(Action.ACCELERATOR_KEY, keystrokes.length > 0 ? keystrokes[0] : null);
285
public int compare(KeyStroke k1, KeyStroke k2) {
286
//#47024 and 32733 - "Find" should not be shown as an accelerator,
287
//nor should "Backspace" for Delete. Solution: The shorter text wins.
288
return KeyEvent.getKeyText(k1.getKeyCode()).length() -
289
KeyEvent.getKeyText(k2.getKeyCode()).length();
293
public void addActionForKeyStroke(KeyStroke key, Action a) {
294
// Update reverse binding for old action too (#30455):
296
synchronized (this) {
297
old = bindings.put(key, a);
301
updateActionAccelerator(a);
302
updateActionAccelerator(old);
307
void addActionForKeyStrokeMap(Map<KeyStroke,Action> map) {
308
Set<Action> actionsSet = new HashSet<Action>();
309
synchronized (this) {
310
for (Entry<KeyStroke,Action> entry: map.entrySet ()) {
311
KeyStroke key = entry.getKey();
312
Action value = entry.getValue();
313
// Add both old and new action:
314
actionsSet.add(value);
315
actionsSet.add(bindings.put(key, value));
320
for(Action a: actionsSet) {
321
updateActionAccelerator(a);
328
public void removeKeyStrokeBinding(KeyStroke key) {
330
synchronized (this) {
331
a = bindings.remove(key);
334
updateActionAccelerator(a);
339
public void removeBindings() {
340
Set<Action> actionsSet;
341
synchronized (this) {
342
actionsSet = new HashSet<Action>(bindings.values());
347
for(Action a: actionsSet) {
348
updateActionAccelerator(a);
355
public Keymap getResolveParent() {
359
public void setResolveParent(Keymap parent) {
360
this.parent = parent;
365
/** Returns string representation - can be looong.
367
public String toString() {
368
return "Keymap[" + name + "]" + bindings; // NOI18N
371
public static class SubKeymap implements Keymap {
374
Map<KeyStroke, Action> bindings;
375
Action defaultAction;
377
public SubKeymap(Object hold) {
379
bindings = new HashMap<KeyStroke, Action>();
382
public String getName() {
386
public void setResolveParent(Keymap parent) {
387
this.parent = parent;
390
public Keymap getResolveParent() {
394
public void addActionForKeyStroke(KeyStroke key, Action a) {
395
bindings.put(key, a);
398
public KeyStroke[] getKeyStrokesForAction(Action a) {
399
return new KeyStroke[0];
402
public void setDefaultAction(Action a) {
406
public Action getAction(KeyStroke key) {
407
return bindings.get(key);
410
public boolean isLocallyDefined(KeyStroke key) {
411
return bindings.containsKey(key);
414
public void removeKeyStrokeBinding(KeyStroke keys) {
415
bindings.remove(keys);
418
public Action[] getBoundActions() {
419
synchronized (this) {
420
return bindings.values().toArray(new Action[0]);
424
public KeyStroke[] getBoundKeyStrokes() {
425
synchronized (this) {
426
return bindings.keySet().toArray(new KeyStroke[0]);
430
public Action getDefaultAction() {
431
return defaultAction;
434
public void removeBindings() {
440
public static class KeymapAction extends javax.swing.AbstractAction {
441
private Keymap keymap;
442
private KeyStroke stroke;
444
public KeymapAction(Keymap keymap, KeyStroke stroke) {
445
this.keymap = keymap;
446
this.stroke = stroke;
449
public Keymap getSubMap() {
453
public void actionPerformed(java.awt.event.ActionEvent e) {
454
if (stroke == null) { // NO_ACTION -> reset
457
shiftContext(stroke);