com.sun.electric.tool.user.KeyBindingManager Maven / Gradle / Ivy
/* -*- tab-width: 4 -*-
*
* Electric(tm) VLSI Design System
*
* File: KeyBindingManager.java
*
* Copyright (c) 2003, Oracle and/or its affiliates. All rights reserved.
*
* 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.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.help.ManualViewer;
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.TopLevel;
import com.sun.electric.tool.user.ui.WindowFrame;
import com.sun.electric.tool.user.waveform.WaveSignal;
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.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 KeyListener
so it can be added as a key listener
* to any component.
*
* The inputMap uses KeyStrokes
as it's keys, and stores Objects
* of type Set. The Set contains Strings and the set will guarantee they are not repeated.
*
* Each String is then used as a key into the HashMap actionMap to retrieve
* a KeyBindings object. Each key bindings object has a list of actions which can then be
* performed.
*
* 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.
*
* Two-stroke bindings:
* The KeyBindingManager has a HashMap prefixedInputMapMaps. 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 actionMap to get a KeyBindings object and
* perform the associated action. There is only one actionMap.
*
*
* @author gainsley
*/
public class KeyBindingManager implements KeyEventDispatcher
{
// ----------------------------- object stuff ---------------------------------
/** Hash table of lists all key bindings */ private Map> inputMap;
/** Hash table of all actions */ private Map actionMap;
/** last prefix key pressed */ private KeyStroke lastPrefix;
/** Hash table of hash of lists of prefixed key bindings */ private Map>> prefixedInputMapMaps;
/** action to take on prefix key hit */ private PrefixAction prefixAction;
/** where to store Preferences */ private Preferences prefs;
/** prefix on preference key, if desired */ private String prefPrefix;
// ----------------------------- global stuff ----------------------------------
/** Listener to register for catching keys */ //public static KeyBindingListener listener = new KeyBindingListener();
/** All key binding managers */ private static List allManagers = new ArrayList();
/** 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>();
actionMap = new HashMap();
prefixedInputMapMaps = new HashMap>>();
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 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
{
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 standard output for help
*/
public void printKeyBindings()
{
Map>> set = new HashMap>>();
List keyList = new ArrayList(); // has to be a list so it could be sorted.
List columnList = new ArrayList(); // has to be a list so it could be sorted.
KeyBindingColumn row = new KeyBindingColumn("Keys");
Set tmpSet = new HashSet();
columnList.add(row); // inserting the first row with key names as column
for (Map.Entry> map : inputMap.entrySet())
{
KeyStroke keyS = map.getKey();
String key = KeyStrokePair.getStringFromKeyStroke(keyS);
Map> m = set.get(key);
if (m == null)
{
m = new HashMap>();
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 analogous to KeyBinding */ public static final String actionDesc = "KeyBindingManager prefix action";
/** the key binding manager using this action */ 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 TopLevel) { 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; }
if (c instanceof ManualViewer) { lastPrefix = null; return false; }
if (c instanceof ManualViewer.EditHTML) { lastPrefix = null; return false; }
if (c instanceof WaveSignal.EditSignalName) { 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 = wf.getFrame().getJMenuBar();
for(int i=0; ipair as an active binding for action actionDesc.
* @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 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> inputMapToUse = inputMap;
if (prefixStroke != null) {
// find HashMap based on prefixAction
inputMapToUse = prefixedInputMapMaps.get(prefixStroke);
if (inputMapToUse == null) {
inputMapToUse = new HashMap>();
prefixedInputMapMaps.put(prefixStroke, inputMapToUse);
}
// add prefix action to primary input map
Set set = inputMap.get(prefixStroke);
if (set == null) {
set = new HashSet();
inputMap.put(prefixStroke, set);
}
set.add(PrefixAction.actionDesc);
}
// add stroke to input map to use
Set set = inputMapToUse.get(stroke);
if (set == null) {
set = new HashSet();
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 actionDesc
from Preferences.
* Returns null if actionDesc not present in preferences.
* @param actionDesc the action description associated with these bindings
* @return a list of KeyStrokePairs
*/
private synchronized List 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 bindings = new ArrayList();
for (Iterator 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 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 preferences
List keyPairs = getBindingsFromPrefs(bindings.getActionDesc());
if (keyPairs == null) {
// no entry found, use default settings
bindings.setUsingDefaultKeys(true);
for (Iterator 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);
}
}