All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy