001: /*BEGIN_COPYRIGHT_BLOCK
002: *
003: * Copyright (c) 2001-2007, JavaPLT group at Rice University (javaplt@rice.edu)
004: * All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions are met:
008: * * Redistributions of source code must retain the above copyright
009: * notice, this list of conditions and the following disclaimer.
010: * * Redistributions in binary form must reproduce the above copyright
011: * notice, this list of conditions and the following disclaimer in the
012: * documentation and/or other materials provided with the distribution.
013: * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
014: * names of its contributors may be used to endorse or promote products
015: * derived from this software without specific prior written permission.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
018: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
019: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
020: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
021: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
022: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
023: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
024: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
025: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
026: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
027: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028: *
029: * This software is Open Source Initiative approved Open Source Software.
030: * Open Source Initative Approved is a trademark of the Open Source Initiative.
031: *
032: * This file is part of DrJava. Download the current version of this project
033: * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
034: *
035: * END_COPYRIGHT_BLOCK*/
036:
037: package edu.rice.cs.drjava.ui;
038:
039: import edu.rice.cs.drjava.*;
040: import edu.rice.cs.drjava.config.*;
041: import javax.swing.*;
042: import java.util.*;
043: import java.awt.event.*;
044:
045: /** Contains Hashtables that are used in the key-binding process along with methods to build them and access their
046: * contents. Performs the assigning of keys to actions, checking for and resolving conflicts, and setting appropriate
047: * menu accelerators.
048: * TODO: fix the design of our key binding scheme. We should be able to bind multiple keys to the same action!
049: * @version $Id: KeyBindingManager.java 4255 2007-08-28 19:17:37Z mgricken $
050: */
051: public class KeyBindingManager {
052:
053: public static final KeyBindingManager Singleton = new KeyBindingManager();
054:
055: private KeyBindingManager() {
056: }
057:
058: // Key-binding configuration tables
059: private Hashtable<KeyStroke, KeyStrokeData> _keyToDataMap = new Hashtable<KeyStroke, KeyStrokeData>();
060: private Hashtable<Action, KeyStrokeData> _actionToDataMap = new Hashtable<Action, KeyStrokeData>();
061:
062: private MainFrame _mainFrame = null;
063:
064: // Needed to get the DefaultEditorKit actions from their names
065: private ActionMap _actionMap;
066:
067: /** Should only check conflicts when the keyboard configuration options are first entered into the maps. Afterwards,
068: * the GUI configuration will warn the user about actions whose key-bindings will be overwritten in the GetKeyDialog,
069: * and the preferences panel will reflect the changes. When the user hit apply, no conflicts should exist in the
070: * preferences panel, and there should be no need to check for conflicts in the configuration.
071: */
072: private boolean _shouldCheckConflict = true;
073:
074: public void setMainFrame(MainFrame mainFrame) {
075: _mainFrame = mainFrame;
076: }
077:
078: /** Sets the ActionMap
079: * @param actionMap the ActionMap to set to
080: */
081: public void setActionMap(ActionMap actionMap) {
082: _actionMap = actionMap;
083: }
084:
085: public void setShouldCheckConflict(boolean bool) {
086: _shouldCheckConflict = bool;
087: }
088:
089: public Enumeration getKeyStrokeData() {
090: return _actionToDataMap.elements();
091: }
092:
093: public void put(Option<KeyStroke> kso, Action a, JMenuItem jmi,
094: String name) {
095: KeyStroke ks = DrJava.getConfig().getSetting(kso);
096: KeyStrokeData ksd = new KeyStrokeData(ks, a, jmi, name, kso);
097: _keyToDataMap.put(ks, ksd);
098: _actionToDataMap.put(a, ksd);
099:
100: // check for shift-actions
101: if (kso != null) {
102: DrJava.getConfig().addOptionListener(kso,
103: new KeyStrokeOptionListener(jmi, a, ks));
104: }
105: }
106:
107: /** Takes a KeyStroke and gets its Action from the keyToActionMap
108: * @param ks KeyStroke to look up
109: * @return the corresponding Action or null if there is no Action associated with the KeyStroke
110: */
111: public Action get(KeyStroke ks) {
112: KeyStrokeData ksd = _keyToDataMap.get(ks);
113: if (ksd == null)
114: return null;
115: return ksd.getAction();
116: }
117:
118: public String getName(KeyStroke ks) {
119: KeyStrokeData ksd = _keyToDataMap.get(ks);
120: if (ksd == null)
121: return null;
122: return ksd.getName();
123: }
124:
125: public String getName(Action a) {
126: KeyStrokeData ksd = _actionToDataMap.get(a);
127: if (ksd == null)
128: return null;
129: return ksd.getName();
130: }
131:
132: /*
133: public void addListener(Option<KeyStroke> opt, JMenuItem jmi) {
134: KeyStroke ks = DrJava.getConfig().getSetting(opt);
135: Action a = _keyToActionMap.get(ks);
136: DrJava.getConfig().addOptionListener(opt, new KeyStrokeOptionListener(jmi, a, ks));
137: }
138: */
139: /** Assigns the selection action with the given name to the combination of the shift key and the given key stroke
140: * option. Also adds new KeyStrokOptionListeners to the non-shifted Actions
141: * @param opt the KeyStroke Option of the Action
142: * @param shiftS the name of the Selection Action
143: */
144: public void addShiftAction(Option<KeyStroke> opt, String shiftS) {
145: Action shiftA = _actionMap.get(shiftS);
146: addShiftAction(opt, shiftA);
147: }
148:
149: /** Assigns the given selection action to the combination of the shift key and the given key stroke option.
150: * Also adds new KeyStrokOptionListeners to the non-shifted Actions
151: * @param opt the KeyStroke Option of the Action
152: * @param shiftA the Selection Action
153: */
154: public void addShiftAction(Option<KeyStroke> opt, Action shiftA) {
155: KeyStroke ks = DrJava.getConfig().getSetting(opt);
156:
157: KeyStrokeData normal = _keyToDataMap.get(ks);
158: normal.setShiftAction(shiftA);
159:
160: KeyStrokeData ksd = new KeyStrokeData(addShiftModifier(ks),
161: shiftA, null, "Selection " + normal.getName(), null);
162:
163: _keyToDataMap.put(addShiftModifier(ks), ksd);
164: _actionToDataMap.put(shiftA, ksd);
165: }
166:
167: /** Takes a KeyStroke and returns a KeyStroke that is the same that has the shift modifier
168: * @param k a KeyStroke
169: * @return the same KeyStorke with the shift modifier
170: */
171: public KeyStroke addShiftModifier(KeyStroke k) {
172: return KeyStroke.getKeyStroke(k.getKeyCode(), k.getModifiers()
173: | InputEvent.SHIFT_MASK, k.isOnKeyRelease());
174: }
175:
176: /** Inserts a KeyStroke/Action pair into the _keyToActionMap. Checks for conflicts and displays an option pane if
177: * they are any.
178: * @param ks the KeyStroke
179: * @param a the Action
180: * @return whether a map insertion was done
181: */
182: //precondition ks != KeyStrokeOption.NULL_KEYSTROKE
183: private boolean shouldUpdate(KeyStroke ks, Action a) {
184: if (ks == KeyStrokeOption.NULL_KEYSTROKE) {
185: // then there should be no keystroke for this action
186: return true;
187: }
188:
189: if (!_keyToDataMap.containsKey(ks)) {
190: // the key is not in the Hashtable, put it in
191: //_keyToActionMap.put(ks, a);
192: //need to update map
193: //KeyStrokeData data = _actionToDataMap.get(a);
194: //data.setKeyStroke(ks);
195: //_keyToDataMap.put(ks,data);
196:
197: return true;
198: } else if (_keyToDataMap.get(ks).getAction().equals(a)) {
199: // this KeyStroke/Action pair is already in the Hashtable
200: return false;
201: } else { // key-binding conflict
202: if (_shouldCheckConflict) {
203: KeyStrokeOption opt = new KeyStrokeOption(null, null);
204: KeyStrokeData conflictKSD = _keyToDataMap.get(ks);
205: String key = opt.format(ks);
206: KeyStrokeData newKSD = _actionToDataMap.get(a);
207: String text = "\"" + key + "\""
208: + " is already assigned to \""
209: + conflictKSD.getName()
210: + "\".\nWould you like to assign \"" + key
211: + "\" to \"" + newKSD.getName() + "\"?";
212: int rc = JOptionPane.showConfirmDialog(_mainFrame,
213: text, "DrJava",
214: JOptionPane.YES_NO_CANCEL_OPTION);
215:
216: switch (rc) {
217: case JOptionPane.YES_OPTION:
218: return true;
219: case JOptionPane.NO_OPTION:
220: return false;
221: case JOptionPane.CLOSED_OPTION:
222: return false;
223: case JOptionPane.CANCEL_OPTION:
224: return false;
225: default:
226: throw new RuntimeException("Invalid rc: " + rc);
227: }
228: } else
229: return true;
230: }
231: }
232:
233: /** A listener that can be attached to KeyStrokeOptions that automatically updates the Hashtables in
234: * KeyBindingManager, the corresponding selection Action bindings, and the menu accelerators
235: */
236: public class KeyStrokeOptionListener implements
237: OptionListener<KeyStroke> {
238: protected JMenuItem _jmi; // the JMenuItem associated with this option
239: protected Action _a; // the Action associated with this option
240: protected KeyStroke _ks; // the old KeyStroke value
241:
242: public KeyStrokeOptionListener(JMenuItem jmi, Action a,
243: KeyStroke ks) {
244: _jmi = jmi;
245: _a = a;
246: _ks = ks;
247: }
248:
249: public KeyStrokeOptionListener(Action a, KeyStroke ks) {
250: _jmi = null;
251: _a = a;
252: _ks = ks;
253: }
254:
255: private void _updateMenuItem(KeyStrokeData data) {
256: JMenuItem jmi = data.getJMenuItem();
257:
258: //Check associated Menu Item
259: // If jmi is null, this keystroke maps to an action that isn't in the menu
260: if (jmi != null) {
261: KeyStroke ks = data.getKeyStroke();
262: if (ks != KeyStrokeOption.NULL_KEYSTROKE) {
263: // If ks is NULL_KEYSTROKE, we don't want it "active", since some
264: // Windows keys generate NULL_KEYSTROKE
265: jmi.setAccelerator(ks);
266: } else {
267: // Clear the menu item's accelerator
268: jmi.setAccelerator(null);
269: }
270: }
271: }
272:
273: public void optionChanged(OptionEvent<KeyStroke> oce) {
274: if (shouldUpdate(oce.value, _a)) {
275: KeyStrokeData data = _actionToDataMap.get(_a);
276: if (data == null) {
277: // Nothing to change
278: return;
279: }
280:
281: // Only remove the old keystroke from the map if it is currently mapped to our data. If not, our old
282: // keystroke has already been redefined and should not be removed!
283: if (data.equals(_keyToDataMap.get(_ks))) {
284: _keyToDataMap.remove(_ks);
285: }
286:
287: //check for conflicting key binding
288: if (_keyToDataMap.containsKey(oce.value)
289: && _shouldCheckConflict) {
290: //if new key in map, and shouldUpdate returns true, we are overwriting it
291: KeyStrokeData conflictKSD = _keyToDataMap
292: .get(oce.value);
293: conflictKSD
294: .setKeyStroke(KeyStrokeOption.NULL_KEYSTROKE);
295: _updateMenuItem(conflictKSD);
296: _keyToDataMap.remove(oce.value);
297: DrJava.getConfig().setSetting(
298: conflictKSD.getOption(),
299: KeyStrokeOption.NULL_KEYSTROKE);
300: }
301:
302: if (oce.value != KeyStrokeOption.NULL_KEYSTROKE) {
303: _keyToDataMap.put(oce.value, data);
304: }
305: data.setKeyStroke(oce.value);
306: _updateMenuItem(data);
307:
308: //Check associated shift-version's binding
309: Action shiftAction = data.getShiftAction();
310: if (shiftAction != null) {
311: //_keyToActionMap.remove(addShiftModifier(_ks));
312: KeyStrokeData shiftKSD = _actionToDataMap
313: .get(shiftAction);
314: _keyToDataMap.remove(shiftKSD.getKeyStroke());
315: shiftKSD.setKeyStroke(addShiftModifier(oce.value));
316: _keyToDataMap
317: .put(shiftKSD.getKeyStroke(), shiftKSD);
318: //mapInsert(addShiftModifier(oce.value), shiftAction);
319: }
320:
321: _ks = oce.value;
322: } else if (_ks != oce.value) {
323: DrJava.getConfig().setSetting(oce.option, _ks);
324: }
325: }
326: }
327:
328: public static class KeyStrokeData {
329: private KeyStroke _ks;
330: private Action _a;
331: private JMenuItem _jmi;
332: private String _name;
333: private Option<KeyStroke> _kso;
334: private Action _shiftA;
335:
336: public KeyStrokeData(KeyStroke ks, Action a, JMenuItem jmi,
337: String name, Option<KeyStroke> kso) {
338: _ks = ks;
339: _a = a;
340: _jmi = jmi;
341: _name = name;
342: _kso = kso;
343: _shiftA = null;
344: }
345:
346: public KeyStroke getKeyStroke() {
347: return _ks;
348: }
349:
350: public Action getAction() {
351: return _a;
352: }
353:
354: public JMenuItem getJMenuItem() {
355: return _jmi;
356: }
357:
358: public String getName() {
359: return _name;
360: }
361:
362: public Option<KeyStroke> getOption() {
363: return _kso;
364: }
365:
366: public Action getShiftAction() {
367: return _shiftA;
368: }
369:
370: public void setKeyStroke(KeyStroke ks) {
371: _ks = ks;
372: }
373:
374: public void setAction(Action a) {
375: _a = a;
376: }
377:
378: public void setJMenuItem(JMenuItem jmi) {
379: _jmi = jmi;
380: }
381:
382: public void setName(String name) {
383: _name = name;
384: }
385:
386: public void setOption(Option<KeyStroke> kso) {
387: _kso = kso;
388: }
389:
390: public void setShiftAction(Action shiftA) {
391: _shiftA = shiftA;
392: }
393: }
394: }
|