001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.core;
043:
044: import java.awt.event.KeyEvent;
045: import java.util.ArrayList;
046: import java.util.Arrays;
047: import java.util.Collections;
048: import java.util.Comparator;
049: import java.util.HashMap;
050: import java.util.HashSet;
051: import java.util.Iterator;
052: import java.util.List;
053: import java.util.Map;
054: import java.util.Map.Entry;
055: import java.util.Observable;
056: import java.util.Set;
057: import javax.swing.Action;
058: import javax.swing.KeyStroke;
059: import javax.swing.text.Keymap;
060: import org.openide.awt.StatusDisplayer;
061: import org.openide.util.Mutex;
062:
063: /** Implementation of standard key - action mappings.
064: *
065: * @author Dafe Simonek
066: */
067: public final class NbKeymap extends Observable implements Keymap,
068: Comparator<KeyStroke> {
069: /** Name of this keymap */
070: String name;
071: /** Parent keymap */
072: Keymap parent;
073: /** Hashtable holding KeyStroke > Action mappings */
074: Map<KeyStroke, Action> bindings;
075: /** Default action */
076: Action defaultAction;
077: /** hash table to map (Action -> ArrayList of KeyStrokes) */
078: Map<Action, List<KeyStroke>> actions;
079:
080: private static List<KeyStroke> context = new ArrayList<KeyStroke>();
081:
082: public static void resetContext() {
083: context.clear();
084: StatusDisplayer.getDefault().setStatusText("");
085: }
086:
087: public static KeyStroke[] getContext() {
088: return (KeyStroke[]) context.toArray(new KeyStroke[context
089: .size()]);
090: }
091:
092: public static void shiftContext(KeyStroke stroke) {
093: context.add(stroke);
094:
095: StringBuilder text = new StringBuilder();
096: for (KeyStroke ks : context) {
097: text.append(getKeyText(ks)).append(' ');
098: }
099: StatusDisplayer.getDefault().setStatusText(text.toString());
100: }
101:
102: private static String getKeyText(KeyStroke keyStroke) {
103: if (keyStroke == null)
104: return "";
105: String modifText = KeyEvent.getKeyModifiersText(keyStroke
106: .getModifiers());
107: if ("".equals(modifText))
108: return KeyEvent.getKeyText(keyStroke.getKeyCode());
109: return modifText + "+" + // NOI18N
110: KeyEvent.getKeyText(keyStroke.getKeyCode());
111: }
112:
113: private final Action NO_ACTION = new KeymapAction(null, null);
114:
115: public Action createMapAction(Keymap k, KeyStroke stroke) {
116: return new KeymapAction(k, stroke);
117: }
118:
119: /** Default constructor
120: */
121: public NbKeymap() {
122: this ("Default", null); // NOI18N
123: }
124:
125: NbKeymap(final String name, final Keymap parent) {
126: this .name = name;
127: this .parent = parent;
128: bindings = new HashMap<KeyStroke, Action>();
129: }
130:
131: public Action getDefaultAction() {
132: if (defaultAction != null) {
133: return defaultAction;
134: }
135: return (parent != null) ? parent.getDefaultAction() : null;
136: }
137:
138: public void setDefaultAction(Action a) {
139: defaultAction = a;
140: setChanged();
141: notifyObservers();
142: }
143:
144: public String getName() {
145: return name;
146: }
147:
148: public Action getAction(KeyStroke key) {
149: Action a;
150:
151: KeyStroke[] ctx = getContext();
152: Keymap activ = this ;
153: for (int i = 0; i < ctx.length; i++) {
154: if (activ == this ) {
155: a = bindings.get(ctx[i]);
156: if ((a == null) && (parent != null)) {
157: a = parent.getAction(ctx[i]);
158: }
159: } else {
160: a = activ.getAction(ctx[i]);
161: }
162:
163: if (a instanceof KeymapAction) {
164: activ = ((KeymapAction) a).keymap;
165: } else { // unknown ctx
166: int code = key.getKeyCode();
167: if (code != KeyEvent.VK_CONTROL
168: && code != KeyEvent.VK_ALT
169: && code != KeyEvent.VK_ALT_GRAPH
170: && code != KeyEvent.VK_SHIFT
171: && code != KeyEvent.VK_META)
172: resetContext();
173: return null;
174: }
175: }
176:
177: if (activ == this ) {
178: a = bindings.get(key);
179: if ((a == null) && (parent != null)) {
180: a = parent.getAction(key);
181: }
182: return a;
183: } else {
184: a = activ.getAction(key);
185: }
186:
187: if (a != null) {
188: if (!(a instanceof KeymapAction)) {
189: resetContext();
190: }
191: return a;
192: }
193:
194: // no action, should we reset?
195: if (key.isOnKeyRelease()
196: || (key.getKeyChar() != 0 && key.getKeyChar() != KeyEvent.CHAR_UNDEFINED)) {
197: return null;
198: }
199:
200: switch (key.getKeyCode()) {
201: case KeyEvent.VK_SHIFT:
202: case KeyEvent.VK_CONTROL:
203: case KeyEvent.VK_ALT:
204: case KeyEvent.VK_META:
205: return null;
206: default:
207: resetContext();
208: return NO_ACTION;
209: }
210: }
211:
212: public KeyStroke[] getBoundKeyStrokes() {
213: int i = 0;
214: KeyStroke[] keys = null;
215: synchronized (this ) {
216: keys = new KeyStroke[bindings.size()];
217: for (KeyStroke ks : bindings.keySet()) {
218: keys[i++] = ks;
219: }
220: }
221: return keys;
222: }
223:
224: public Action[] getBoundActions() {
225: int i = 0;
226: Action[] actionsArray = null;
227: synchronized (this ) {
228: actionsArray = new Action[bindings.size()];
229: for (Iterator iter = bindings.values().iterator(); iter
230: .hasNext();) {
231: actionsArray[i++] = (Action) iter.next();
232: }
233: }
234: return actionsArray;
235: }
236:
237: public KeyStroke[] getKeyStrokesForAction(Action a) {
238: Map<Action, List<KeyStroke>> localActions = actions;
239: if (localActions == null) {
240: localActions = buildReverseMapping();
241: }
242:
243: List<KeyStroke> strokes = localActions.get(a);
244: if (strokes != null) {
245: return strokes.toArray(new KeyStroke[strokes.size()]);
246: } else {
247: return new KeyStroke[0];
248: }
249: }
250:
251: private Map<Action, List<KeyStroke>> buildReverseMapping() {
252: Map<Action, List<KeyStroke>> localActions = actions = new HashMap<Action, List<KeyStroke>>();
253:
254: synchronized (this ) {
255: for (Map.Entry<KeyStroke, Action> curEntry : bindings
256: .entrySet()) {
257: Action curAction = curEntry.getValue();
258: KeyStroke curKey = curEntry.getKey();
259:
260: List<KeyStroke> keysForAction = localActions
261: .get(curAction);
262: if (keysForAction == null) {
263: keysForAction = Collections
264: .synchronizedList(new ArrayList<KeyStroke>(
265: 1));
266: localActions.put(curAction, keysForAction);
267: }
268: keysForAction.add(curKey);
269: }
270: }
271:
272: return localActions;
273: }
274:
275: public synchronized boolean isLocallyDefined(KeyStroke key) {
276: return bindings.containsKey(key);
277: }
278:
279: /** Updates action accelerator. */
280: private void updateActionAccelerator(final Action a) {
281: if (a == null) {
282: return;
283: }
284:
285: Mutex.EVENT.writeAccess(new Runnable() {
286: public void run() {
287: KeyStroke[] keystrokes = getKeyStrokesForAction(a);
288: Arrays.sort(keystrokes, NbKeymap.this );
289: a.putValue(Action.ACCELERATOR_KEY,
290: keystrokes.length > 0 ? keystrokes[0] : null);
291: }
292: });
293: }
294:
295: public int compare(KeyStroke k1, KeyStroke k2) {
296: //#47024 and 32733 - "Find" should not be shown as an accelerator,
297: //nor should "Backspace" for Delete. Solution: The shorter text wins.
298: return KeyEvent.getKeyText(k1.getKeyCode()).length()
299: - KeyEvent.getKeyText(k2.getKeyCode()).length();
300: }
301:
302: public void addActionForKeyStroke(KeyStroke key, Action a) {
303: // Update reverse binding for old action too (#30455):
304: Action old;
305: synchronized (this ) {
306: old = bindings.put(key, a);
307: actions = null;
308: }
309:
310: updateActionAccelerator(a);
311: updateActionAccelerator(old);
312: setChanged();
313: notifyObservers();
314: }
315:
316: void addActionForKeyStrokeMap(Map<KeyStroke, Action> map) {
317: Set<Action> actionsSet = new HashSet<Action>();
318: synchronized (this ) {
319: for (Entry<KeyStroke, Action> entry : map.entrySet()) {
320: KeyStroke key = entry.getKey();
321: Action value = entry.getValue();
322: // Add both old and new action:
323: actionsSet.add(value);
324: actionsSet.add(bindings.put(key, value));
325: }
326: actions = null;
327: }
328:
329: for (Action a : actionsSet) {
330: updateActionAccelerator(a);
331: }
332:
333: setChanged();
334: notifyObservers();
335: }
336:
337: public void removeKeyStrokeBinding(KeyStroke key) {
338: Action a;
339: synchronized (this ) {
340: a = bindings.remove(key);
341: actions = null;
342: }
343: updateActionAccelerator(a);
344: setChanged();
345: notifyObservers();
346: }
347:
348: public void removeBindings() {
349: Set<Action> actionsSet;
350: synchronized (this ) {
351: actionsSet = new HashSet<Action>(bindings.values());
352: bindings.clear();
353: actions = null;
354: }
355:
356: for (Action a : actionsSet) {
357: updateActionAccelerator(a);
358: }
359:
360: setChanged();
361: notifyObservers();
362: }
363:
364: public Keymap getResolveParent() {
365: return parent;
366: }
367:
368: public void setResolveParent(Keymap parent) {
369: this .parent = parent;
370: setChanged();
371: notifyObservers();
372: }
373:
374: /** Returns string representation - can be looong.
375: */
376: public String toString() {
377: return "Keymap[" + name + "]" + bindings; // NOI18N
378: }
379:
380: public static class SubKeymap implements Keymap {
381: Object hold;
382: Keymap parent;
383: Map<KeyStroke, Action> bindings;
384: Action defaultAction;
385:
386: public SubKeymap(Object hold) {
387: this .hold = hold;
388: bindings = new HashMap<KeyStroke, Action>();
389: }
390:
391: public String getName() {
392: return "name";
393: }
394:
395: public void setResolveParent(Keymap parent) {
396: this .parent = parent;
397: }
398:
399: public Keymap getResolveParent() {
400: return parent;
401: }
402:
403: public void addActionForKeyStroke(KeyStroke key, Action a) {
404: bindings.put(key, a);
405: }
406:
407: public KeyStroke[] getKeyStrokesForAction(Action a) {
408: return new KeyStroke[0];
409: }
410:
411: public void setDefaultAction(Action a) {
412: defaultAction = a;
413: }
414:
415: public Action getAction(KeyStroke key) {
416: return bindings.get(key);
417: }
418:
419: public boolean isLocallyDefined(KeyStroke key) {
420: return bindings.containsKey(key);
421: }
422:
423: public void removeKeyStrokeBinding(KeyStroke keys) {
424: bindings.remove(keys);
425: }
426:
427: public Action[] getBoundActions() {
428: synchronized (this ) {
429: return bindings.values().toArray(new Action[0]);
430: }
431: }
432:
433: public KeyStroke[] getBoundKeyStrokes() {
434: synchronized (this ) {
435: return bindings.keySet().toArray(new KeyStroke[0]);
436: }
437: }
438:
439: public Action getDefaultAction() {
440: return defaultAction;
441: }
442:
443: public void removeBindings() {
444: bindings.clear();
445: }
446:
447: }
448:
449: public static class KeymapAction extends javax.swing.AbstractAction {
450: private Keymap keymap;
451: private KeyStroke stroke;
452:
453: public KeymapAction(Keymap keymap, KeyStroke stroke) {
454: this .keymap = keymap;
455: this .stroke = stroke;
456: }
457:
458: public Keymap getSubMap() {
459: return keymap;
460: }
461:
462: public void actionPerformed(java.awt.event.ActionEvent e) {
463: if (stroke == null) { // NO_ACTION -> reset
464: resetContext();
465: } else {
466: shiftContext(stroke);
467: }
468: }
469: }
470: }
|