/* -*- tab-width: 4 -*-
*
* Electric(tm) VLSI Design System
*
* File: KeyBindingManager.java
*
* Copyright (c) 2003 Sun Microsystems and Static Free Software
*
* Electric(tm) is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* Electric(tm) is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Electric(tm); see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, Mass 02111-1307, USA.
*/
package com.sun.electric.tool.user;
import com.sun.electric.Main;
import com.sun.electric.tool.user.dialogs.EDialog;
import com.sun.electric.tool.user.dialogs.EModelessDialog;
import com.sun.electric.tool.user.dialogs.GetInfoText;
import com.sun.electric.tool.user.dialogs.OpenFile;
import com.sun.electric.tool.user.ui.EditWindow;
import com.sun.electric.tool.user.ui.KeyBindings;
import com.sun.electric.tool.user.ui.KeyStrokePair;
import com.sun.electric.tool.user.ui.TextWindow;
import com.sun.electric.tool.user.ui.WindowContent;
import com.sun.electric.tool.user.ui.WindowFrame;
import com.sun.electric.tool.user.waveform.WaveformWindow;
import java.awt.Component;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.prefs.Preferences;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JDialog;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane;
import javax.swing.KeyStroke;
/**
* The KeyBindingManager manages key bindings and their associated actions. It
* implements a <code>KeyListener</code> so it can be added as a key listener
* to any component.
* <p><p>
* The <i>inputMap</i> uses <code>KeyStrokes</code> as it's keys, and stores Objects
* of type Set. The Set contains Strings and the set will guarantee they are not repetead.
* <p>
* Each String is then used as a key into the HashMap <i>actionMap</i> to retrieve
* a KeyBindings object. Each key bindings object has a list of actions which can then be
* performed.
* <p>
* This model is similar to jawa.swing.InputMap and java.swing.ActionMap.
* However, secondary InputMaps allow two-stroke key bindings. Additionally,
* the KeybindingManager has been enveloped in an object which can
* then be inserted into the event hierarchy in different ways, instead of having
* to set a Component's InputMap and ActionMap.
* <p><p>
* Two-stroke bindings:<p>
* The KeyBindingManager has a HashMap <i>prefixedInputMapMaps</i>. A prefixStroke
* is used as a key to this table to obtain an inputMap (HashMap) based on the prefixStroke.
* From here it is the same as before with the inputMap and actionMap:
* A KeyStroke is then used as a key to find a List of Strings. The Strings are
* then used as a key into <i>actionMap</i> to get a KeyBindings object and
* perform the associated action. There is only one actionMap.
* <p>
*
* @author gainsley
*/
public class KeyBindingManager implements KeyEventDispatcher
{
// ----------------------------- object stuff ---------------------------------
/** Hash table of lists all key bindings */ private Map<KeyStroke,Set<String>> inputMap;
/** Hash table of all actions */ private Map<String,Object> actionMap;
/** last prefix key pressed */ private KeyStroke lastPrefix;
/** Hash table of hash of lists of prefixed key bindings */ private Map<KeyStroke,Map<KeyStroke,Set<String>>> prefixedInputMapMaps;
/** action to take on prefix key hit */ private PrefixAction prefixAction;
/** where to store Preferences */ private Preferences prefs;
/** prefix on pref key, if desired */ private String prefPrefix;
// ----------------------------- global stuff ----------------------------------
/** Listener to register for catching keys */ //public static KeyBindingListener listener = new KeyBindingListener();
/** All key binding manangers */ private static List<KeyBindingManager> allManagers = new ArrayList<KeyBindingManager>();
/** debug preference saving */ private static final boolean debugPrefs = false;
/** debug key bindings */ private static final boolean DEBUG = false;
/**
* Construct a new KeyBindingManager that can act as a KeyListener
* on a Component.
*/
public KeyBindingManager(String prefPrefix, Preferences prefs) {
inputMap = new HashMap<KeyStroke,Set<String>>();
actionMap = new HashMap<String,Object>();
prefixedInputMapMaps = new HashMap<KeyStroke,Map<KeyStroke,Set<String>>>();
lastPrefix = null;
prefixAction = new PrefixAction(this);
this.prefs = prefs;
this.prefPrefix = prefPrefix;
// add prefix action to action map
actionMap.put(PrefixAction.actionDesc, prefixAction);
// register this with KeyboardFocusManager
// so we receive all KeyEvents
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(this);
//KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventPostProcessor(this);
// add to list of all managers
synchronized(allManagers) {
allManagers.add(this);
}
initialize();
}
public boolean dispatchKeyEvent(KeyEvent e)
{
return processKeyEvent(e);
}
/**
* Called when disposing of this manager, allows memory used to
* be reclaimed by removing static references to this.
*/
public void finished() {
synchronized(allManagers) {
allManagers.remove(this);
}
//KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventPostProcessor(this);
}
private boolean initialized = false;
/**
* Initialize: Reads all stored key bindings from preferences
*/
private synchronized void initialize() {
/* String [] allKeys;
try {
allKeys = prefs.keys();
} catch (BackingStoreException e) {
e.printStackTrace();
return;
}
for (int i = 0; i < allKeys.length; i++) {
// read bindings
String key = allKeys[i].replaceFirst(prefPrefix, "");
// old binding format and new format conflict, add check to avoid duplicates
if (actionMap.containsKey(key)) continue;
if (debugPrefs) System.out.println("looking for prefs key "+key);
KeyBindings keys = new KeyBindings(key);
List pairs = getBindingsFromPrefs(key);
// if any bindings, set usingDefaults false, and add them
if (pairs != null) {
keys.setUsingDefaultKeys(false); // set usingDefaults false
actionMap.put(key, keys);
for (Iterator it = pairs.iterator(); it.hasNext(); ) {
KeyStrokePair pair = (KeyStrokePair)it.next();
if (pair == null) continue;
addKeyBinding(key, pair);
}
}
}*/
}
private static class KeyBindingColumn
{
int hits = 0; // how many key bindings use this modifier
int maxLength = 0;
String name;
KeyBindingColumn(String n)
{
name = n;
}
public String toString() { return name; }
public String getHeader()
{
String n = name;
for (int l = name.length(); l < maxLength; l++)
n += " ";
return n + " | ";
}
public String getColumn(Object value)
{
String column = "";
int fillStart = 0;
if (value != null)
{
String n = value.toString();
fillStart = n.length();
column += n;
}
// filling
for (int l = fillStart; l < maxLength; l++)
column +=" ";
return column + " | ";
}
public void addHit(Set<String> set)
{
hits++;
int len = set.toString().length();
if (len > maxLength)
maxLength = len;
}
public boolean equals(Object obj)
{
String key = obj.toString();
boolean found = key.equals(name);
return found;
}
}
private static class KeyBindingColumnSort implements Comparator<KeyBindingColumn>
{
public int compare(KeyBindingColumn s1, KeyBindingColumn s2)
{
int bb1 = s1.hits;
int bb2 = s2.hits;
if (bb1 < bb2) return 1; // sorting from max to min
else if (bb1 > bb2) return -1;
return (0); // identical
}
}
/** Method to print existing KeyStrokes in std output for help
*/
public void printKeyBindings()
{
Map<String,Map<String,Set<String>>> set = new HashMap<String,Map<String,Set<String>>>();
List<String> keyList = new ArrayList<String>(); // has to be a list so it could be sorted.
List<KeyBindingColumn> columnList = new ArrayList<KeyBindingColumn>(); // has to be a list so it could be sorted.
KeyBindingColumn row = new KeyBindingColumn("Keys");
Set<String> tmpSet = new HashSet<String>();
columnList.add(row); // inserting the first row with key names as column
for (Map.Entry<KeyStroke,Set<String>> map : inputMap.entrySet())
{
KeyStroke keyS = map.getKey();
String key = KeyStrokePair.getStringFromKeyStroke(keyS);
Map<String,Set<String>> m = set.get(key);
if (m == null)
{
m = new HashMap<String,Set<String>>();
set.put(key, m);
keyList.add(key);
tmpSet.clear();
tmpSet.add(key);
row.addHit(tmpSet);
}
String modifier = KeyEvent.getKeyModifiersText(keyS.getModifiers());
KeyBindingColumn newCol = new KeyBindingColumn(modifier);
int index = columnList.indexOf(newCol);
KeyBindingColumn col = (index > -1) ? columnList.get(index) : null;
if (col == null)
{
col = newCol;
columnList.add(col);
}
col.addHit(map.getValue());
m.put(modifier, map.getValue());
}
Collections.sort(keyList);
Collections.sort(columnList, new KeyBindingColumnSort());
// Header
String headerLine = "\n";
for (KeyBindingColumn column : columnList)
{
String header = column.getHeader();
System.out.print(header);
for (int i = 0; i < header.length(); i++)
headerLine += "-";
}
System.out.println(headerLine);
for (String key : keyList)
{
// System.out.print(key);
for (KeyBindingColumn column : columnList)
{
Object value = (column == row) ? key : (Object)set.get(key).get(column.name);
System.out.print(column.getColumn(value));
}
System.out.println();
}
}
// ---------------------------- Prefix Action Class -----------------------------
/**
* Class PrefixAction is an action performed when a prefix key is hit.
* This then registers that prefix key with the KeyBindingManager.
* This allows key bindings to consist of two-key sequences.
*/
private static class PrefixAction extends AbstractAction
{
/** The action description analagous to KeyBinding */ public static final String actionDesc = "KeyBindingManager prefix action";
/** the key binding manager using this aciton */ private KeyBindingManager manager;
public PrefixAction(KeyBindingManager manager) {
super();
this.manager = manager;
}
public void actionPerformed(ActionEvent e) {
KeyEvent keyEvent = (KeyEvent)e.getSource();
KeyStroke stroke = KeyStroke.getKeyStrokeForEvent(keyEvent);
manager.setPrefixKey(stroke);
if (DEBUG) System.out.println("prefix key '"+KeyStrokePair.keyStrokeToString(stroke)+"' hit...");
}
}
/**
* Called by the KeyBindingManager's prefixAction to register
* that a prefix key has been hit.
* @param prefix the prefix key
*/
private synchronized void setPrefixKey(KeyStroke prefix) {
this.lastPrefix = prefix;
}
// ------------------------------ Key Processing ---------------------------------
/*
public static class KeyBindingListener implements KeyListener
{
public void keyPressed(KeyEvent e) {
for (Iterator it = allManagers.iterator(); it.hasNext(); ) {
KeyBindingManager m = (KeyBindingManager)it.next();
if (m.processKeyEvent(e)) return;
}
}
public void keyReleased(KeyEvent e) {}
public void keyTyped(KeyEvent e) {}
}
public boolean postProcessKeyEvent(KeyEvent e) {
return processKeyEvent(e);
}
*/
/**
* Says whether or not KeyBindingManager will bind to this key event
* @param e the KeyEvent
* @return true if the KeyBindingManager can bind to this event
*/
public static boolean validKeyEvent(KeyEvent e) {
// only look at key pressed events
// if (e.getID() != KeyEvent.KEY_PRESSED && e.getID() != KeyEvent.KEY_TYPED) return false;
if (e.getKeyCode() == KeyEvent.VK_LEFT || e.getKeyCode() == KeyEvent.VK_RIGHT ||
e.getKeyCode() == KeyEvent.VK_UP || e.getKeyCode() == KeyEvent.VK_DOWN) return true;
if (e.getID() != KeyEvent.KEY_PRESSED) return false;
// ignore modifier only events (CTRL, SHIFT etc just by themselves)
if (e.getKeyCode() == KeyEvent.VK_CONTROL) return false;
if (e.getKeyCode() == KeyEvent.VK_SHIFT) return false;
if (e.getKeyCode() == KeyEvent.VK_ALT) return false;
if (e.getKeyCode() == KeyEvent.VK_META) return false;
return true;
}
/**
* Process a KeyEvent by finding what actionListeners should be
* activated as a result of the event. The keyBindingManager keeps
* one stroke of history so that two-stroke events can be distinguished.
* @param e the KeyEvent
* @return true if event consumed, false if not and nothing done.
*/
public synchronized boolean processKeyEvent(KeyEvent e) {
if (DEBUG) System.out.println("got event (consumed="+e.isConsumed()+") "+e);
// see if this is a valid key event
if (!validKeyEvent(e)) return false;
// ignore events that come from dialogs, or non-Control events that are not from an EditWindow/WaveformWindow
Component c = e.getComponent();
boolean valid = false;
while (c != null)
{
if (c instanceof EditWindow) { valid = true; break; }
if (c instanceof WaveformWindow.OnePanel) { valid = true; break; }
//if (c instanceof Main) { valid = true; break; }
if (c instanceof EDialog) { lastPrefix = null; return false; }
if (c instanceof EModelessDialog) { lastPrefix = null; return false; }
if (c instanceof JOptionPane) { lastPrefix = null; return false; }
if (c instanceof TextWindow.TextWindowPanel) { lastPrefix = null; return false; }
if (c instanceof OpenFile.OpenFileSwing) { lastPrefix = null; return false; }
if (c instanceof GetInfoText.EIPEditorPane) { lastPrefix = null; return false; }
if (c instanceof GetInfoText.EIPTextField) { lastPrefix = null; return false; }
c = c.getParent();
}
if (!valid && (e.getModifiers() & InputEvent.CTRL_MASK) == 0)
{ lastPrefix = null; return false; }
// see if any popup menus are visible
WindowFrame wf = WindowFrame.getCurrentWindowFrame();
JMenuBar mb = Main.menuBar;
for(int i=0; i<mb.getMenuCount(); i++)
{
JMenu m = mb.getMenu(i);
if (m == null) continue;
if (!m.isPopupMenuVisible()) continue;
lastPrefix = null; // someone did something with it, null prefix key
return false;
}
// get KeyStroke
KeyStroke stroke = KeyStroke.getKeyStrokeForEvent(e);
if (DEBUG) System.out.println(" Current key is "+stroke+", code="+e.getKeyCode()+", type="+stroke.getKeyEventType());
{
// remove shift modifier from Events. Lets KeyStrokes like '<' register correctly,
// because they are always delivered as SHIFT-'<'.
if ((e.getModifiers() & InputEvent.SHIFT_MASK) != 0 && !Character.isLetter(e.getKeyCode()) && !Character.isDigit(e.getKeyCode()))
{
if (e.getKeyCode() != KeyEvent.VK_LEFT && e.getKeyCode() != KeyEvent.VK_RIGHT &&
e.getKeyCode() != KeyEvent.VK_UP && e.getKeyCode() != KeyEvent.VK_DOWN)
stroke = KeyStroke.getKeyStroke(e.getKeyChar());
}
if (e.getKeyCode() == '[' || e.getKeyCode() == ']')
stroke = KeyStroke.getKeyStroke(e.getKeyChar());
}
// ignore if consumed
if (e.isConsumed()) {
lastPrefix = null; // someone did something with it, null prefix key
return false;
}
Map<KeyStroke,Set<String>> inputMapToUse = inputMap;
// check if we should use prefixed key map instead of regular inputMap
if (lastPrefix != null) {
// get input map based on prefix key
inputMapToUse = prefixedInputMapMaps.get(lastPrefix);
if (inputMapToUse == null) { lastPrefix = null; return false; }
}
ActionListener action = null;
ActionEvent evt = new ActionEvent(e, ActionEvent.ACTION_PERFORMED, stroke.toString(), stroke.getModifiers());
boolean actionPerformed = false;
boolean prefixActionPerformed = false;
// get set of action strings, iterate over them
Set<String> keyBindingList = inputMapToUse.get(stroke);
if (keyBindingList != null) {
for (String actionDesc : keyBindingList) {
// get KeyBinding object from action map, activate its action
// note that if this is a prefixed action, this could actually be a
// PrefixAction object instead of a KeyBinding object.
action = (ActionListener)actionMap.get(actionDesc);
if (action instanceof PrefixAction) {
if (!prefixActionPerformed) {
action.actionPerformed(evt); // only do this once
prefixActionPerformed = true;
}
} else {
action.actionPerformed(evt);
lastPrefix = null;
}
actionPerformed = true;
}
}
if (!actionPerformed) {
// if no action to perform, perhaps the user hit a prefix key, then
// decided to start another prefix-key-combo (that does not result in
// a valid binding with the first prefix, obviously). We'll be nice
// and check for this case
Map prefixMap = prefixedInputMapMaps.get(stroke);
if (prefixMap != null) {
// valid prefix key, fire prefix event
prefixAction.actionPerformed(evt);
actionPerformed = true;
} else {
lastPrefix = null; // nothing to do
}
}
if (DEBUG) System.out.println(" actionPerformed="+actionPerformed);
if (actionPerformed) {
e.consume(); // consume event if we did something useful with it
return true; // let KeyboardFocusManager know we consumed event
}
// otherwise, do not consume, and return false to let KeyboardFocusManager
// know that we did nothing with Event, and to pass it on
return false;
}
// -------------- Static Methods Applied to All KeyBindingManagers ----------------
/**
* Get a list of conflicting key bindings from all KeyBindingManagers.
* @param pair the keystrokepair
* @return a list of conflicting KeyBindings from all KeyBindingManagers
*/
public static List<KeyBindings> getConflictsAllManagers(KeyStrokePair pair) {
List<KeyBindings> conflicts = new ArrayList<KeyBindings>();
synchronized(allManagers) {
for (KeyBindingManager m : allManagers) {
conflicts.addAll(m.getConflictingKeyBindings(pair));
}
}
return conflicts;
}
// --------------------- Public Methods to Manage Bindings ----------------------
/**
* Adds a default KeyBinding. If any keyBindings are found for
* <code>k.actionDesc</code>, those are used instead. Note that <code>k</code>
* cannot be null, but it's stroke and prefixStroke can be null. However,
* it's actionDesc and action must be valid.
* @param actionDesc the action description
* @param pair a key stroke pair
*/
public synchronized void addDefaultKeyBinding(String actionDesc, KeyStrokePair pair) {
if (pair == null) return;
// add to default bindings
KeyBindings keys = (KeyBindings)actionMap.get(actionDesc);
if (keys == null) {
keys = new KeyBindings(actionDesc);
actionMap.put(actionDesc, keys);
}
keys.addDefaultKeyBinding(pair);
/*
if (keys.getUsingDefaultKeys()) {
// using default keys, add default key to active maps
addKeyBinding(actionDesc, pair);
}
*/
}
/**
* Adds a user specified KeyBindings. Also adds it to stored user preference.
* @param actionDesc the action description
* @param pair a key stroke pair
*/
public synchronized void addUserKeyBinding(String actionDesc, KeyStrokePair pair) {
if (pair == null) return;
// add to active bindings (also adds to KeyBindings object)
KeyBindings keys = addKeyBinding(actionDesc, pair);
// now using user specified key bindings, set usingDefaults false
keys.setUsingDefaultKeys(false);
// user has modified bindings, write all current bindings to prefs
setBindingsToPrefs(keys.getActionDesc());
}
/**
* Add an action listener on actionDesc
* @param actionDesc the action description
* @param action the action listener to add
*/
public synchronized void addActionListener(String actionDesc, ActionListener action) {
// add to default set of KeyBindings
KeyBindings keys = (KeyBindings)actionMap.get(actionDesc);
if (keys == null) {
keys = new KeyBindings(actionDesc);
actionMap.put(actionDesc, keys);
}
keys.addActionListener(action);
}
/**
* Removes a key binding from the active bindings, and writes new bindings
* set to preferences.
* @param actionDesc the describing action
* @param k the KeyStrokePair to remove
*/
public synchronized void removeKeyBinding(String actionDesc, KeyStrokePair k) {
Map<KeyStroke,Set<String>> inputMapToUse = inputMap;
// if prefix stroke exists, remove one prefixAction key string
// (may be more than one if more than one binding has prefixStroke as it's prefix)
if (k.getPrefixStroke() != null) {
Set<String> set = inputMap.get(k.getPrefixStroke());
if (set != null) {
for (String str : set) {
if (str.equals(PrefixAction.actionDesc)) {
set.remove(str);
break;
}
}
}
// get input map to use
inputMapToUse = prefixedInputMapMaps.get(k.getPrefixStroke());
}
// remove stroke
if (inputMapToUse != null) {
Set<String> set = inputMapToUse.get(k.getStroke());
if (set != null) set.remove(actionDesc);
}
// remove action
KeyBindings bindings = (KeyBindings)actionMap.get(actionDesc);
bindings.removeKeyBinding(k);
bindings.setUsingDefaultKeys(false);
// user has modified bindings, write all current bindings to prefs
setBindingsToPrefs(actionDesc);
}
/**
* Get list of default KeyBindings for <code>actionDesc</code>
* @param actionDesc the action description
* @return list of KeyStrokePairs.
*/
/*
public synchronized List getDefaultKeyBindingsFor(String actionDesc) {
KeyBindings keys = (KeyBindings)defaultActionMap.get(actionDesc);
List bindings = new ArrayList();
for (Iterator it = keys.getKeyStrokePairs(); it.hasNext(); ) {
bindings.add((KeyStrokePair)it.next());
}
return bindings;
}
*/
/**
* Set <code>actionDesc<code> to use default KeyBindings
* @param actionDesc the action description
*/
public synchronized void resetKeyBindings(String actionDesc) {
// remove all previous bindings
KeyBindings keys = (KeyBindings)actionMap.get(actionDesc);
if (keys != null) {
// get new iterator each time, because removeKeyStrokePair modifies the list
while(true) {
Iterator<KeyStrokePair> it = keys.getKeyStrokePairs();
if (!it.hasNext()) break;
KeyStrokePair pair = it.next();
removeKeyBinding(actionDesc, pair);
}
}
// remove any user saved preferences
//prefs.remove(actionDesc);
prefs.remove(prefPrefix+actionDesc);
// add in default key bindings
for (Iterator<KeyStrokePair> it = keys.getDefaultKeyStrokePairs(); it.hasNext(); ) {
KeyStrokePair k = it.next();
addKeyBinding(actionDesc, k);
}
keys.setUsingDefaultKeys(true);
}
/**
* Get bindings for action string
* @param actionDesc string describing action (KeyBinding.actionDesc)
* @return a KeyBindings object, or null.
*/
public synchronized KeyBindings getKeyBindings(String actionDesc) {
return (KeyBindings)actionMap.get(actionDesc);
}
/**
* Class that converts internal key mappings to InputMap and ActionMap objects.
*/
public static class KeyMaps
{
private InputMap im;
private ActionMap am;
KeyMaps(KeyBindingManager kbm, Map<KeyStroke,Set<String>> inputMap, Map<String,Object> actionMap)
{
im = new InputMap();
am = new ActionMap();
for(KeyStroke ks : inputMap.keySet())
{
Set<String> theSet = inputMap.get(ks);
if (theSet.size() > 0)
{
String actionName = theSet.iterator().next();
im.put(ks, actionName);
am.put(actionName, new MyAbstractAction(actionName, kbm));
}
}
}
public InputMap getInputMap() { return im; }
public ActionMap getActionMap() { return am; }
}
private static class MyAbstractAction extends AbstractAction
{
private String actionName;
private KeyBindingManager kbm;
MyAbstractAction(String actionName, KeyBindingManager kbm)
{
this.actionName = actionName;
this.kbm = kbm;
}
public void actionPerformed(ActionEvent event)
{
KeyBindings kb = kbm.getKeyBindings(actionName);
kb.actionPerformed(event);
}
}
/**
* Method to return an object that has real InputMap and ActionMap objects.
* @return a KeyMaps object.
*/
public KeyMaps getKeyMaps()
{
KeyMaps km = new KeyMaps(this, inputMap, actionMap);
return km;
}
/**
* Set the faked event source of the KeyBindings object. See
* KeyBindings.setEventSource() for details.
* @param actionDesc the action description used to find the KeyBindings object.
* @param source the object to use as the source of the event. (Event.getSource()).
*/
public synchronized void setEventSource(String actionDesc, Object source) {
KeyBindings keys = (KeyBindings)actionMap.get(actionDesc);
keys.setEventSource(source);
}
/**
* Returns true if KeyBindings for the action described by
* actionDesc are already present in hash tables.
* @param actionDesc the action description of the KeyBindings
* @return true if key binding found in manager, false otherwise.
*/
/*
public synchronized boolean hasKeyBindings(String actionDesc) {
KeyBindings k = (KeyBindings)actionMap.get(actionDesc);
if (k != null) return true;
return false;
}
*/
/**
* Get a list of KeyBindings that conflict with the key combo
* <code>prefixStroke, stroke</code>. A conflict is registered if:
* an existing stroke is the same as <code>prefixStroke</code>; or
* an existing stroke is the same <code>stroke</code>
* (if <code>prefixStroke</code> is null);
* or an existing prefixStroke,stroke combo is the same as
* <code>prefixStroke,stroke</code>.
* <p>
* The returned list consists of newly created KeyBindings objects, not
* KeyBindings objects that are used in the key manager database.
* This is because not all KeyStrokePairs in an existing KeyBindings
* object will necessarily conflict. However, there may be more than
* one KeyStrokePair in a returned KeyBindings object from the list if
* more than one KeyStrokePair does actually conflict.
* <p>
* Returns an empty list if there are no conflicts.
* @param pair the KeyStrokePair
* @return a list of conflicting <code>KeyBindings</code>. Empty list if no conflicts.
*/
public synchronized List<KeyBindings> getConflictingKeyBindings(KeyStrokePair pair) {
List<KeyBindings> conflicts = new ArrayList<KeyBindings>(); // list of actual KeyBindings
List<String> conflictsStrings = new ArrayList<String>(); // list of action strings
Map<KeyStroke,Set<String>> inputMapToUse = inputMap;
if (pair.getPrefixStroke() != null) {
// check if conflicts with any single key Binding
Set<String> set = inputMap.get(pair.getPrefixStroke());
if (set != null) {
for (String str : set) {
if (str.equals(PrefixAction.actionDesc)) continue;
// add to conflicts
conflictsStrings.add(str);
}
}
inputMapToUse = prefixedInputMapMaps.get(pair.getPrefixStroke());
}
// find stroke conflicts
if (inputMapToUse != null) {
Set<String> set = inputMapToUse.get(pair.getStroke());
if (set != null) {
for (String str : set) {
if (str.equals(PrefixAction.actionDesc)) {
// find all string associated with prefix in prefix map
// NOTE: this condition is never true if prefixStroke is valid
// and we are using a prefixed map...prefixActions are only in primary inputMap.
Map<KeyStroke,Set<String>> prefixMap = prefixedInputMapMaps.get(pair.getStroke());
if (prefixMap != null) {
for (Iterator<Set<String>> it2 = prefixMap.values().iterator(); it2.hasNext(); ) {
// all existing prefixStroke,stroke combos conflict, so add them all
Set<String> prefixList = it2.next(); // this is a set of strings
conflictsStrings.addAll(prefixList);
}
}
} else {
conflictsStrings.add(str); // otherwise this is a key actionDesc
}
}
}
}
// get all KeyBindings from ActionMap
for (String aln : conflictsStrings) {
ActionListener action = (ActionListener)actionMap.get(aln);
if (action == null) continue;
if (action instanceof PrefixAction) continue;
KeyBindings keys = (KeyBindings)action;
KeyBindings conflicting = new KeyBindings(keys.getActionDesc());
for (Iterator<KeyStrokePair> it2 = keys.getKeyStrokePairs(); it2.hasNext(); ) {
// Unfortunately, any keyBinding can map to this action, including
// ones that don't actually conflict. So we need to double check
// if binding really conflicts.
KeyStrokePair pair2 = it2.next();
if (pair.getPrefixStroke() != null) {
// check prefix conflict
if (pair2.getPrefixStroke() != null) {
// only conflict is if both prefix and stroke match
if (pair.getStroke() == pair2.getStroke())
conflicting.addKeyBinding(pair2);
} else {
// conflict if prefixStroke matches pair2.stroke
if (pair.getPrefixStroke() == pair2.getStroke())
conflicting.addKeyBinding(pair2);
}
} else {
// no prefixStroke
if (pair2.getPrefixStroke() != null) {
// conflict if stroke matches pair2.prefixStroke
if (pair.getStroke() == pair2.getPrefixStroke())
conflicting.addKeyBinding(pair2);
} else {
// no prefixStroke, both only have stroke
if (pair.getStroke() == pair2.getStroke())
conflicting.addKeyBinding(pair2);
}
}
}
// add conflicting KeyBindings to list if it has bindings in it
Iterator conflictingIt = conflicting.getKeyStrokePairs();
if (conflictingIt.hasNext()) conflicts.add(conflicting);
}
return conflicts;
}
/**
* Sets the enabled state of the action to 'b'. If b is false, it
* disables all events that occur when actionDesc takes place. If b is
* true, it enables all resulting events.
* @param actionDesc the describing action
* @param b true to enable, false to disable.
*/
public synchronized void setEnabled(String actionDesc, boolean b) {
ActionListener action = (ActionListener)actionMap.get(actionDesc);
if (action == null) return;
if (action instanceof PrefixAction) return;
KeyBindings k = (KeyBindings)action;
k.setEnabled(b);
}
/**
* Get the enabled state of the action described by 'actionDesc'.
* @param actionDesc the describing action.
* @return true if the action is enabled, false otherwise.
*/
public synchronized boolean getEnabled(String actionDesc) {
ActionListener action = (ActionListener)actionMap.get(actionDesc);
if (action == null) return false;
if (action instanceof PrefixAction) return false;
KeyBindings k = (KeyBindings)action;
return k.getEnabled();
}
/**
* Check if there are any bindings that do not have any
* associated actions.
*/
public synchronized void deleteEmptyBindings() {
Set<String> keys = actionMap.keySet();
for (String key : keys) {
ActionListener action = (ActionListener)actionMap.get(key);
if (action instanceof KeyBindings) {
KeyBindings bindings = (KeyBindings)action;
Iterator listenersIt = bindings.getActionListeners();
if (!listenersIt.hasNext()) {
// no listeners on the action
System.out.println("Warning: Deleting defunct binding for "+key+" [ "+bindings.bindingsToString()+ " ]...action does not exist anymore");
// delete bindings
removeBindingsFromPrefs(key);
}
}
}
}
// --------------------------------- Private -------------------------------------
/**
* Adds a KeyStrokePair <i>pair</i> as an active binding for action <i>actionDesc</i>.
* @param actionDesc the action description
* @param pair a key stroke pair
* @return the new KeyBindings object, or an existing KeyBindings object for actionDesc
*/
private synchronized KeyBindings addKeyBinding(String actionDesc, KeyStrokePair pair) {
if (pair == null) return null;
// warn if conflicting key bindings created
List<KeyBindings> conflicts = getConflictingKeyBindings(pair);
if (conflicts.size() > 0) {
System.out.println("WARNING: Key binding for "+actionDesc+" [ " +pair.toString()+" ] conflicts with:");
for (KeyBindings k : conflicts) {
System.out.println(" > "+k.getActionDesc()+" [ "+k.bindingsToString()+" ]");
}
}
if (DEBUG) System.out.println("Adding binding for "+actionDesc+": "+pair.toString());
KeyStroke prefixStroke = pair.getPrefixStroke();
KeyStroke stroke = pair.getStroke();
Map<KeyStroke,Set<String>> inputMapToUse = inputMap;
if (prefixStroke != null) {
// find HashMap based on prefixAction
inputMapToUse = prefixedInputMapMaps.get(prefixStroke);
if (inputMapToUse == null) {
inputMapToUse = new HashMap<KeyStroke,Set<String>>();
prefixedInputMapMaps.put(prefixStroke, inputMapToUse);
}
// add prefix action to primary input map
Set<String> set = inputMap.get(prefixStroke);
if (set == null) {
set = new HashSet<String>();
inputMap.put(prefixStroke, set);
}
set.add(PrefixAction.actionDesc);
}
// add stroke to input map to use
Set<String> set = inputMapToUse.get(stroke);
if (set == null) {
set = new HashSet<String>();
inputMapToUse.put(stroke, set);
}
set.add(actionDesc);
// add stroke to KeyBindings
KeyBindings keys = (KeyBindings)actionMap.get(actionDesc);
if (keys == null) {
// no bindings for actionDesc
keys = new KeyBindings(actionDesc);
actionMap.put(actionDesc, keys);
}
keys.addKeyBinding(pair);
return keys;
}
// ---------------------------- Preferences Storage ------------------------------
/**
* Add KeyBinding to stored user preferences.
* @param actionDesc the action description under which to store all the key bindings
*/
private synchronized void setBindingsToPrefs(String actionDesc) {
if (prefs == null) return;
if (actionDesc == null || actionDesc.equals("")) return;
KeyBindings keyBindings = (KeyBindings)actionMap.get(actionDesc);
if (keyBindings == null) return;
String actionDescAbbrev = actionDesc;
if ((actionDesc.length() + prefPrefix.length()) > Preferences.MAX_KEY_LENGTH) {
int start = actionDesc.length() + prefPrefix.length() - Preferences.MAX_KEY_LENGTH;
actionDescAbbrev = actionDesc.substring(start, actionDesc.length());
}
if (debugPrefs) System.out.println("Writing to pref '"+prefPrefix+actionDescAbbrev+"': "+keyBindings.bindingsToString());
prefs.put(prefPrefix+actionDescAbbrev, keyBindings.bindingsToString());
}
/**
* Get KeyBindings for <code>actionDesc</code> from Preferences.
* Returns null if actionDesc not present in prefs.
* @param actionDesc the action description associated with these bindings
* @return a list of KeyStrokePairs
*/
private synchronized List<KeyStrokePair> getBindingsFromPrefs(String actionDesc) {
if (prefs == null) return null;
if (actionDesc == null || actionDesc.equals("")) return null;
String actionDescAbbrev = actionDesc;
if ((actionDesc.length() + prefPrefix.length()) > Preferences.MAX_KEY_LENGTH) {
int start = actionDesc.length() + prefPrefix.length() - Preferences.MAX_KEY_LENGTH;
actionDescAbbrev = actionDesc.substring(start, actionDesc.length());
}
String keys = prefs.get(prefPrefix+actionDescAbbrev, null);
if (keys == null) return null;
if (debugPrefs) System.out.println("Read from prefs for "+prefPrefix+actionDescAbbrev+": "+keys);
KeyBindings k = new KeyBindings(actionDesc);
k.addKeyBindings(keys);
if (debugPrefs) System.out.println(" turned into: "+k.describe());
List<KeyStrokePair> bindings = new ArrayList<KeyStrokePair>();
for (Iterator<KeyStrokePair> it = k.getKeyStrokePairs(); it.hasNext(); ) {
bindings.add(it.next());
}
return bindings;
}
/**
* Restored saved bindings from preferences. Usually called after
* menu has been created.
*/
public synchronized void restoreSavedBindings(boolean initialCall) {
if (initialCall && initialized == true) return;
initialized = true;
if (prefs == null) return;
// try to see if binding saved in preferences for each action
for (Map.Entry<String,Object> entry : actionMap.entrySet()) {
String actionDesc = entry.getKey();
if (actionDesc == null || actionDesc.equals("")) continue;
// clear current bindings
if (entry.getValue() instanceof PrefixAction) {
continue;
}
KeyBindings bindings = (KeyBindings)entry.getValue();
bindings.clearKeyBindings();
// look up bindings in prefs
List<KeyStrokePair> keyPairs = getBindingsFromPrefs(bindings.getActionDesc());
if (keyPairs == null) {
// no entry found, use default settings
bindings.setUsingDefaultKeys(true);
for (Iterator<KeyStrokePair> it2 = bindings.getDefaultKeyStrokePairs(); it2.hasNext(); ) {
KeyStrokePair pair = it2.next();
addKeyBinding(actionDesc, pair);
}
} else {
// otherwise, add bindings found
bindings.setUsingDefaultKeys(false);
for (KeyStrokePair pair : keyPairs) {
addKeyBinding(actionDesc, pair);
}
}
}
}
/**
* Remove any bindings stored for actionDesc.
*/
private synchronized void removeBindingsFromPrefs(String actionDesc) {
if (prefs == null) return;
if (actionDesc == null || actionDesc.equals("")) return;
prefs.remove(prefPrefix+actionDesc);
}
}