package com.vitco.layout.content.shortcut;
import com.jidesoft.docking.DialogFloatingContainer;
import com.jidesoft.docking.DockableFrame;
import com.jidesoft.docking.DockingManager;
import com.jidesoft.docking.event.DockableFrameAdapter;
import com.jidesoft.docking.event.DockableFrameEvent;
import com.vitco.manager.action.ActionManager;
import com.vitco.manager.action.types.SwitchActionPrototype;
import com.vitco.manager.async.AsyncAction;
import com.vitco.manager.async.AsyncActionManager;
import com.vitco.manager.error.ErrorHandlerInterface;
import com.vitco.manager.help.FrameHelpOverlay;
import com.vitco.manager.lang.LangSelectorInterface;
import com.vitco.manager.pref.PreferencesInterface;
import com.vitco.settings.VitcoSettings;
import com.vitco.util.file.FileTools;
import com.vitco.util.misc.SaveResourceLoader;
import gnu.trove.map.hash.TIntObjectHashMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.swing.*;
import javax.swing.FocusManager;
import javax.swing.text.JTextComponent;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.util.*;
/**
* Handles shortcut linking (logic)
*/
public class ShortcutManager implements ShortcutManagerInterface {
protected AsyncActionManager asyncActionManager;
@Autowired
public final void setAsyncActionManager(AsyncActionManager asyncActionManager) {
this.asyncActionManager = asyncActionManager;
}
// what shortcuts we allow
private final ArrayList<Integer> VALID_KEYS_WITH_MODIFIER =
new ArrayList<Integer>(Arrays.asList(new Integer[]{
// A-Z
65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77,
78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90,
// 0-9
48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
// f1 - f12
112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123,
// delete, escape, space, enter
127, 27, 32, 10,
// up, down, left, right, pgup, pgdown
KeyEvent.VK_UP, KeyEvent.VK_DOWN, KeyEvent.VK_LEFT, KeyEvent.VK_RIGHT,
KeyEvent.VK_PAGE_DOWN, KeyEvent.VK_PAGE_UP,
// minus, [, ], =
45, KeyEvent.VK_OPEN_BRACKET, KeyEvent.VK_CLOSE_BRACKET, KeyEvent.VK_EQUALS
}));
private final ArrayList<Integer> VALID_KEYS_WITHOUT_MODIFIER =
new ArrayList<Integer>(Arrays.asList(new Integer[]{
// A-Z
65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77,
78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90,
// 0-9
48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
// f1 - f12
112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123,
// delete, escape, space, enter
127, 27, 32, 10,
// up, down, left, right, pgup, pgdown
KeyEvent.VK_UP, KeyEvent.VK_DOWN, KeyEvent.VK_LEFT, KeyEvent.VK_RIGHT,
KeyEvent.VK_PAGE_DOWN, KeyEvent.VK_PAGE_UP,
// minus, [, ], =
45, KeyEvent.VK_OPEN_BRACKET, KeyEvent.VK_CLOSE_BRACKET, KeyEvent.VK_EQUALS
}));
// global setting for all activatable actions
private boolean enableAllActivatableActions = true;
// actions currently active b/c a key is pressed
private static final TIntObjectHashMap<AbstractAction> activeKeyActions = new TIntObjectHashMap<AbstractAction>();
// handle release key event and switch off registered actions
private void releaseKey(int keyCode) {
synchronized (activeKeyActions) {
AbstractAction action = activeKeyActions.remove(keyCode);
if (action instanceof SwitchActionPrototype) {
((SwitchActionPrototype) action).switchOff();
}
}
}
public void rememberPressedKey(int keyCode, AbstractAction action) {
if (activeKeyActions.containsKey(keyCode)) {
releaseKey(keyCode);
}
activeKeyActions.put(keyCode, action);
}
// prototype of an action that can be disabled
private final class ActivatableKeyStrokeAction extends AbstractAction {
private final AbstractAction action;
public final int keyCode;
private ActivatableKeyStrokeAction(KeyStroke keyStroke, AbstractAction action) {
this.keyCode = keyStroke.getKeyCode();
this.action = action;
}
@Override
public void actionPerformed(ActionEvent e) {
if (enableAllActivatableActions) {
synchronized (activeKeyActions) {
rememberPressedKey(keyCode, action);
action.actionPerformed(e);
}
}
}
}
// hold the currently active frame
private DockableFrame activeFrame = null;
// holds the mapping: frame -> (KeyStroke, actionName)
private final HashMap<String, ArrayList<ShortcutObject>> map = new HashMap<String, ArrayList<ShortcutObject>>();
// holds the global shortcuts (KeyStroke, actionName)
private final ArrayList<ShortcutObject> global = new ArrayList<ShortcutObject>();
// updated when global changes
private final Map<KeyStroke, ShortcutObject> globalByKeyStroke = new HashMap<KeyStroke, ShortcutObject>();
private final Map<String, ShortcutObject> globalByAction = new HashMap<String, ShortcutObject>();
// notified when shortcut change
private final ArrayList<ShortcutChangeListener> shortcutChangeListeners = new ArrayList<ShortcutChangeListener>();
// hook that catches KeyStroke if this is registered as global
private final KeyEventDispatcher globalProcessor = new KeyEventDispatcher() {
@Override
public boolean dispatchKeyEvent(final KeyEvent e) {
final int eventId = e.getID();
final int keyCode = e.getKeyCode();
final KeyStroke keyStroke = KeyStroke.getKeyStrokeForEvent(e);
// ensure the release is triggered if this is a release event
if (eventId == KeyEvent.KEY_RELEASED) {
asyncActionManager.addAsyncAction(new AsyncAction() {
@Override
public void performAction() {
releaseKey(keyCode);
}
});
}
// do not process if there is an active frame shortcut that will match
if (activeFrame != null && activeFrame.getActionForKeyStroke(keyStroke) != null) {
return keyCode == 18;
}
if (globalByKeyStroke.containsKey(keyStroke)
// only fire if all actions are activated
&& enableAllActivatableActions) {
asyncActionManager.addAsyncAction(new AsyncAction() {
@Override
public void performAction() {
// fire new action
new ActivatableKeyStrokeAction(keyStroke, actionManager.getAction(globalByKeyStroke.get(keyStroke).actionName)).actionPerformed(
new ActionEvent(e.getSource(), eventId, e.paramString(), e.getWhen(), e.getModifiers())
);
}
});
e.consume(); // no-one else needs to handle this now
return true; // no further action
}
// might need further action (but disable if alt key)
return keyCode == 18;
}
};
// register global shortcuts and make sure all shortcuts are correctly enabled
@Override
public void registerShortcuts(final Frame frame, final DockingManager dockingManager) {
// register global actions
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(globalProcessor);
// listen to frame activation / deactivation
dockingManager.addDockableFrameListener(new DockableFrameAdapter() {
@Override
public void dockableFrameActivated(DockableFrameEvent dockableFrameEvent) {
activeFrame = dockableFrameEvent.getDockableFrame();
}
});
// update activity / inactivity of shortcuts
FocusManager.getCurrentManager().addPropertyChangeListener(new PropertyChangeListener() {
private Object activeWindow = null;
private Object focusOwner = null;
private void update() {
// the current frame or a dockableFrame popup, or a component where we edit
if ((activeWindow != frame && !(activeWindow instanceof DialogFloatingContainer))
|| (focusOwner instanceof JTextComponent && ((JTextComponent) focusOwner).isEditable())
// disable hotkeys if help overlay is showing
|| (focusOwner instanceof FrameHelpOverlay)) {
deactivateShortcuts();
} else {
activateShortcuts();
}
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals("focusOwner")) {
focusOwner = evt.getNewValue();
update();
}
if (evt.getPropertyName().equals("activeWindow")) {
activeWindow = evt.getNewValue();
update();
}
}
});
}
// executed when "global" shortcut change
private void handleGlobalUpdate() {
// rewrite the fast access data for global
globalByKeyStroke.clear();
globalByAction.clear();
for (ShortcutObject shortcutObject : global) {
globalByKeyStroke.put(shortcutObject.keyStroke, shortcutObject);
globalByAction.put(shortcutObject.actionName, shortcutObject);
}
// notify all listeners
for (ShortcutChangeListener shortcutChangeListener : shortcutChangeListeners) {
shortcutChangeListener.onChange();
}
}
@Override
public void addShortcutChangeListener(ShortcutChangeListener shortcutChangeListener) {
shortcutChangeListeners.add(shortcutChangeListener);
}
@Override
public void removeShortcutChangeListener(ShortcutChangeListener shortcutChangeListener) {
shortcutChangeListeners.remove(shortcutChangeListener);
}
// var & setter
private PreferencesInterface preferences;
@Override
public final void setPreferences(PreferencesInterface preferences) {
this.preferences = preferences;
}
// var & setter
private String xmlFile;
@Override
public final void setConfigFile(String filename) {
xmlFile = filename;
}
// var & setter
private ActionManager actionManager;
@Override
public final void setActionManager(ActionManager actionManager) {
this.actionManager = actionManager;
}
// var & setter
private ErrorHandlerInterface errorHandler;
@Override
public final void setErrorHandler(ErrorHandlerInterface errorHandler) {
this.errorHandler = errorHandler;
}
// var & setter
private LangSelectorInterface langSel;
@Override
public final void setLangSelector(LangSelectorInterface langSel) {
this.langSel = langSel;
}
// get all frames as string array (frameKey, localized frameCaption)
@Override
public final String[][] getFrames() {
String[][] result = new String[map.size()][];
// fetch and sort frames (note: global is created separate!)
ArrayList<String> list = new ArrayList<String>(map.keySet());
Collections.sort(list);
// loop over all the frames
int i = 0;
for (String key : list) {
result[i++] = new String[]{
key,
langSel.getString(key + "_caption")
};
}
return result;
}
@Override
public final KeyStroke getShortcutByAction(String frame, String actionName) {
if (frame == null) { // check global shortcuts
if (globalByAction.containsKey(actionName)) {
return globalByAction.get(actionName).keyStroke;
}
} else { // check frame shortcuts
ArrayList<ShortcutObject> frameAction = map.get(frame);
for (ShortcutObject shortcutObject : frameAction) {
if (shortcutObject.actionName.equals(actionName)) {
return shortcutObject.keyStroke;
}
}
}
return null;
}
// get shortcuts as string array (localized caption, str representation)
// for null this will return the global shortcuts
@Override
public final String[][] getShortcuts(String frameKey) {
if (frameKey != null) { // frame shortcuts
if (map.containsKey(frameKey)) {
ArrayList<ShortcutObject> shortcuts = map.get(frameKey);
String[][] result = new String[shortcuts.size()][];
for (int i = 0, len = shortcuts.size(); i < len; i++) {
result[i] = new String[]{
langSel.getString(shortcuts.get(i).caption),
asString(shortcuts.get(i).keyStroke)
};
}
return result;
} else {
System.err.println("Error: Shortcuts for the non-existing frame \"" + frameKey + "\" were requested.");
return null;
}
} else { // global shortcuts
String[][] result = new String[global.size()][];
for (int i = 0, len = global.size(); i < len; i++) {
result[i] = new String[]{
langSel.getString(global.get(i).caption),
asString(global.get(i).keyStroke)
};
}
return result;
}
}
/* Check if keyStroke is local and collides with global or the other way around */
private boolean hasSoftCollision(String frame, KeyStroke keyStroke) {
if (frame == null) {
// check frame shortcuts for collision
for (Collection<ShortcutObject> shortcutObjects : map.values()) {
for (ShortcutObject shortcutObject : shortcutObjects) {
if (shortcutObject.keyStroke != null && shortcutObject.keyStroke.equals(keyStroke)) {
return true;
}
}
}
} else {
// check global shortcuts for collision
for (ShortcutObject shortcutObject : global) {
if (shortcutObject.keyStroke != null && shortcutObject.keyStroke.equals(keyStroke)) {
return true;
}
}
}
return false;
}
/* Obtain BG color for a shortcut field that is being edited */
@Override
public final Color getEditBgColor(String frame, int id) {
KeyStroke keyStroke = frame == null ? global.get(id).keyStroke : map.get(frame).get(id).keyStroke;
if (hasSoftCollision(frame, keyStroke)) {
return VitcoSettings.EDIT_BG_COLOR_HIGHLIGHT;
} else {
return VitcoSettings.EDIT_BG_COLOR;
}
}
// update (keystroke,action) registration for a frame and shortcut id
// if frame == null this will update a global shortcut
@Override
public final boolean updateShortcutObject(KeyStroke keyStroke, String frame, int id) {
boolean result = false;
if (frame != null) { // update frame shortcut
// for frame
if (map.containsKey(frame)) {
if (map.get(frame).size() > id) {
final ShortcutObject shortcutObject = map.get(frame).get(id);
if (shortcutObject.keyStroke != null) {
shortcutObject.linkedFrame.unregisterKeyboardAction(shortcutObject.keyStroke); // un-register old
}
shortcutObject.keyStroke = keyStroke;
if (shortcutObject.keyStroke != null) {
// remember *this* action name
final String actionName = shortcutObject.actionName;
// lazy shortcut registration (the action might not be ready!)
actionManager.performWhenActionIsReady(actionName, new Runnable() {
@Override
public void run() {
for (ShortcutChangeListener shortcutChangeListener : shortcutChangeListeners) {
shortcutChangeListener.onChange();
}
shortcutObject.linkedFrame.registerKeyboardAction(
new ActivatableKeyStrokeAction(shortcutObject.keyStroke, actionManager.getAction(actionName)),
shortcutObject.keyStroke,
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT
);
}
});
}
result = true;
}
} else {
System.err.println("Error: Can not set KeyStroke for frame \"" + frame + "\" and id \"" + id + "\".");
}
} else { // update global shortcut
if (global.size() > id) {
global.get(id).keyStroke = keyStroke;
handleGlobalUpdate(); // need to rewrite the fast access
result = true;
} else {
System.err.println("Error: Can not set global KeyStroke for id \"" + id + "\".");
}
}
return result;
}
// convert KeyStroke to string representation
@Override
public final String asString(KeyStroke keyStroke) {
return keyStroke == null ? "-" : keyStroke.toString()
.replaceFirst("^pressed ", "") // remove if at very beginning (e.g. for f-keys)
.replace("pressed", "+")
.toUpperCase();
}
// check if this shortcut is not already used
// if frame == null this will treat it as a global shortcut
// otherwise as a shortcut for the frame
// null is always a valid keyStroke
@Override
public final boolean isFreeShortcut(String frame, KeyStroke keyStroke) {
boolean result = true;
if (keyStroke != null) { // null is valid
if (frame != null) { // check for this frame
// check that this shortcut is not already set for an action in this frame
if (map.containsKey(frame)) {
for (ShortcutObject shortcutObject : map.get(frame)) {
if (shortcutObject.keyStroke != null && shortcutObject.keyStroke.equals(keyStroke)) {
result = false;
}
}
} else {
System.err.println("Error: Can not find frame \"" + frame + "\".");
}
} else {
// check for global shortcuts
for (ShortcutObject shortcutObject : global) {
if (shortcutObject.keyStroke != null && shortcutObject.keyStroke.equals(keyStroke)) {
result = false;
}
}
}
}
return result;
}
// check if this is a KeyStroke that has a valid format
// null is a valid shortcut
@Override
public final boolean isValidShortcut(KeyStroke keyStroke) {
// check that this is a format that is allowed as shortcut
int altmask = (InputEvent.ALT_DOWN_MASK | InputEvent.ALT_MASK);
int ctrlmask = (InputEvent.CTRL_DOWN_MASK | InputEvent.CTRL_MASK);
int shiftmask = (InputEvent.SHIFT_DOWN_MASK | InputEvent.SHIFT_MASK);
int modifier = keyStroke == null ? 0 : (keyStroke.getModifiers()
& ( ctrlmask | altmask | shiftmask ));
return keyStroke == null || ((modifier == ctrlmask) ||
(modifier == altmask) ||
(modifier == shiftmask) ||
(modifier == (ctrlmask | altmask)) ||
(modifier == (ctrlmask | shiftmask)) ||
(modifier == (altmask | shiftmask)))
// allow only certain keys as trigger keys
&& VALID_KEYS_WITH_MODIFIER.contains(keyStroke.getKeyCode()) ||
// allow some keys without modifiers
((modifier == 0)
&& VALID_KEYS_WITHOUT_MODIFIER.contains(keyStroke.getKeyCode()));
}
// internal - check that this action is not yet in this frame
// if frameName == null it will check this for global shortcut actions
private boolean usedAction(String frameName, String actionName) {
boolean result = false;
if (frameName != null) {
// check that this shortcut is not already set for an action
if (map.containsKey(frameName)) {
for (ShortcutObject shortcutObject : map.get(frameName)) {
if (shortcutObject.actionName.equals(actionName)) {
result = true;
}
}
} else {
System.err.println("Error: Can not find frame \"" + frameName + "\".");
}
} else { // check for global shortcuts
for (ShortcutObject shortcutObject : global) {
if (shortcutObject.actionName.equals(actionName)) {
result = true;
}
}
}
return result;
}
// internal - check that this caption (pre)localization is not yet used in this frame
// if frameName == null it will check this for global shortcut captions
private boolean usedCaption(String frameName, String caption) {
boolean result = false;
if (frameName != null) {
// check that this shortcut is not already set for an action
if (map.containsKey(frameName)) {
for (ShortcutObject shortcutObject : map.get(frameName)) {
if (shortcutObject.caption.equals(caption)) {
result = true;
}
}
} else {
System.err.println("Error: Can not find frame \"" + frameName + "\".");
}
} else { // check for global shortcuts
for (ShortcutObject shortcutObject : global) {
if (shortcutObject.caption.equals(caption)) {
result = true;
}
}
}
return result;
}
// store shortcuts in preferences
@PreDestroy
public void onDestruct() {
preferences.storeObject("all_shortcuts_as_map", map);
preferences.storeObject("global_shortcuts_as_map", global);
}
// check if a keystroke exists
private boolean isShortcutSet(KeyStroke keyStroke, String frame) {
// check if exists in global shortcuts
for (ShortcutObject so : global) {
if (so.keyStroke != null && so.keyStroke.equals(keyStroke)) {
return true;
}
}
if (frame != null) {
// check if this is a shortcut in provided frame
for (ShortcutObject so : map.get(frame)) {
if (so.keyStroke != null && so.keyStroke.equals(keyStroke)) {
return true;
}
}
} else {
// check if this is a shortcut in any frame
for (String key : map.keySet()) {
for (ShortcutObject so : map.get(key)) {
if (so.keyStroke != null && so.keyStroke.equals(keyStroke)) {
return true;
}
}
}
}
return false;
}
// load the xml files that maps (shortcut, action)
// and then load custom user defined shortcuts
@PostConstruct
@Override
public void loadConfig() {
// load and parse the xml file so we
// can access it to hook the key bindings
try {
// load the xml document
DocumentBuilderFactory factory = DocumentBuilderFactory
.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(
new SaveResourceLoader(xmlFile).asInputStream()
);
// load the mapping
Node node = doc.getFirstChild(); // head node
if (node.getNodeName().equals("head")) {
NodeList list = node.getChildNodes();
// loop over all the windows (and global)
for (int i = 0, len = list.getLength(); i < len; i++) {
addShortcuts(list.item(i));
}
}
} catch (ParserConfigurationException e) {
errorHandler.handle(e); // should not happen
} catch (SAXException e) {
errorHandler.handle(e); // should not happen
} catch (IOException e) {
errorHandler.handle(e); // should not happen
}
// check if we have global shortcuts stored
if (preferences.contains("global_shortcuts_as_map")) {
ArrayList tmp = (ArrayList)preferences.loadObject("global_shortcuts_as_map");
for (ShortcutObject so1 : global) {
for (Object so2 : tmp) {
// only update existing actions
if (so1.actionName.equals(((ShortcutObject)so2).actionName)) {
// check if there is a collision
if (!isShortcutSet(((ShortcutObject)so2).keyStroke, null)) {
so1.keyStroke = ((ShortcutObject)so2).keyStroke;
}
}
}
}
}
// check if we have frame shortcuts stored
if (preferences.contains("all_shortcuts_as_map")) {
Map<String, ArrayList> tmp = FileTools.castHash(
(HashMap) preferences.loadObject("all_shortcuts_as_map"),
String.class,
ArrayList.class
);
for (String key : map.keySet()) {
if (tmp.containsKey(key)) {
for (ShortcutObject so1 : map.get(key)) {
for (Object so2 : tmp.get(key)) {
// only update existing actions
if (so1.actionName.equals(((ShortcutObject)so2).actionName)) {
// check if there is a collision
if (!isShortcutSet(((ShortcutObject)so2).keyStroke, key)) {
so1.keyStroke = ((ShortcutObject) so2).keyStroke;
}
}
}
}
}
}
}
// make sure the list updated
handleGlobalUpdate();
}
// check if there are duplicate shortcuts
public final void doSanityCheck(boolean debug) {
// check global keystrokes for duplicates
HashMap<String, ShortcutObject> globalShortcuts = new HashMap<String, ShortcutObject>();
for (ShortcutObject so : global) {
if (so.keyStroke != null) {
String stroke = asString(so.keyStroke);
if (globalShortcuts.containsKey(stroke)) {
ShortcutObject dup = globalShortcuts.get(stroke);
System.err.println("Warning: The two actions \"" +
(debug ? so.actionName : langSel.getString(so.caption)) + "\" and \"" +
(debug ? dup.actionName : langSel.getString(dup.caption)) +
"\" have the same global shortcut (" + stroke + ").");
}
globalShortcuts.put(stroke, so);
}
}
// check frame keystrokes for duplicates
HashMap<String, ShortcutObject> shortcuts = new HashMap<String, ShortcutObject>();
for (String key : map.keySet()) {
shortcuts.clear();
for (ShortcutObject so : map.get(key)) {
if (so.keyStroke != null) {
String stroke = asString(so.keyStroke);
if (shortcuts.containsKey(stroke)) {
ShortcutObject dup = shortcuts.get(stroke);
System.err.println("Warning: The two actions \"" +
(debug ? so.actionName : langSel.getString(so.caption)) + "\" and \"" +
(debug ? dup.actionName : langSel.getString(dup.caption)) +
"\" for frame \"" + key + "\" have the same shortcut (" + stroke + ").");
}
shortcuts.put(stroke, so);
}
}
}
}
// convert string into KeyCode
// returns zero if no conversion found
private int strToKeyCode(String str) {
int result = 0;
if (str.length() == 1) {
result = str.toCharArray()[0];
} else {
if (str.equals("DEL")) {
result = 127;
} else if (str.equals("ESC")) {
result = 27;
} else if (str.equals("SPACE")) {
result = 32;
} else if (str.equals("ENTER")) {
result = 10;
} else if (str.equals("UP")) {
result = KeyEvent.VK_UP;
} else if (str.equals("DOWN")) {
result = KeyEvent.VK_DOWN;
} else if (str.equals("LEFT")) {
result = KeyEvent.VK_LEFT;
} else if (str.equals("RIGHT")) {
result = KeyEvent.VK_RIGHT;
} else if (str.equals("PAGE_DOWN")) {
result = KeyEvent.VK_PAGE_DOWN;
} else if (str.equals("PAGE_UP")) {
result = KeyEvent.VK_PAGE_UP;
} else
if (str.startsWith("F")) {
str = str.substring(1);
try {
int tmp = Integer.valueOf(str);
if ((tmp >= 1) && (tmp <= 12)) {
result = 111 + tmp;
}
} catch (NumberFormatException e) {
errorHandler.handle(e);
}
}
}
return result;
}
// internal - build a ShortcutObject from xml element
// performs some sanity checks, frameName is only needed for error reporting
// frameName should be null for global shortcuts
private ShortcutObject buildShortcut(Element e, String frameName) {
ShortcutObject shortcutObject = new ShortcutObject();
// store
shortcutObject.actionName = e.getAttribute("action");
shortcutObject.caption = e.getAttribute("caption");
// build the keystroke
KeyStroke keyStroke = null;
if (e.hasAttribute("key")) {
int controller = 0;
if (e.getAttribute("ctrl").equals("yes")) {
controller = controller | InputEvent.CTRL_DOWN_MASK | InputEvent.CTRL_MASK;
}
if (e.getAttribute("alt").equals("yes")) {
controller = controller | InputEvent.ALT_DOWN_MASK | InputEvent.ALT_MASK;
}
if (e.getAttribute("shift").equals("yes")) {
controller = controller | InputEvent.SHIFT_DOWN_MASK | InputEvent.SHIFT_MASK;
}
keyStroke = KeyStroke.getKeyStroke(strToKeyCode(e.getAttribute("key")), controller);
}
shortcutObject.keyStroke = keyStroke;
// check if this keystroke is valid
if (!isValidShortcut(keyStroke)) {
System.err.println(
"Error: Invalid shortcut \"" + asString(keyStroke) +
"\" in xml file for action \"" + shortcutObject.actionName + "\" " +
(frameName == null
? "(global)."
: "and frame \"" + frameName + "\".")
);
}
// check if KeyStroke is free
if (!isFreeShortcut(frameName, keyStroke)) {
System.err.println(
"Error: Duplicate shortcut \"" + asString(keyStroke) +
"\" in xml file for action \"" + shortcutObject.actionName + "\" " +
"and frame \"" + frameName + "\"."
);
}
// check if action is free (only allow one shortcut / action / frame)
if (usedAction(frameName, shortcutObject.actionName)) {
System.err.println(
"Error: Duplicate action \"" + shortcutObject.actionName +
"\" in xml file for shortcut \"" + asString(keyStroke) + "\" " +
"and frame \"" + frameName + "\"."
);
}
// check if caption is free (only allow one shortcut / caption / frame)
if (usedCaption(frameName, shortcutObject.caption)) {
System.err.println(
"Error: Duplicate caption id \"" + shortcutObject.caption +
"\" in xml file for action \"" + shortcutObject.actionName + "\" " +
"and frame \"" + frameName + "\"."
);
}
return shortcutObject;
}
// internal - add the shortcuts for this frame to the mapping
private void addShortcuts(Node frame) {
if (frame.getNodeName().equals("global")) { // this is the global shortcut list
NodeList list = frame.getChildNodes();
for (int i = 0, len = list.getLength(); i < len; i++) {
if (list.item(i).getNodeName().equals("shortcut")) {
Element e = (Element) list.item(i);
global.add(buildShortcut(e, null)); // add to global
}
}
} else if (frame.getNodeName().equals("frame")) { // this is a frame shortcut list
// get the frame name
String frameName = ((Element) frame).getAttribute("name");
NodeList list = frame.getChildNodes();
ArrayList<ShortcutObject> shortcutObjectArray = new ArrayList<ShortcutObject>();
map.put(frameName, shortcutObjectArray); // add to mapping
for (int i = 0, len = list.getLength(); i < len; i++) {
if (list.item(i).getNodeName().equals("shortcut")) {
Element e = (Element) list.item(i);
// add to this frame
shortcutObjectArray.add(buildShortcut(e, frameName));
}
}
}
}
// register all actions of global shortcuts, to perform validity check
@Override
public void registerGlobalShortcutActions() {
for (ShortcutObject shortcutObject : global) {
actionManager.registerActionIsUsed(shortcutObject.actionName);
}
}
// activate shortcuts
@Override
public void activateShortcuts() {
enableAllActivatableActions = true;
}
// deactivate shortcuts
@Override
public void deactivateShortcuts() {
enableAllActivatableActions = false;
}
// initial registration of frames
@Override
public void registerFrame(final JComponent frame) {
if (map.containsKey(frame.getName())) {
ArrayList<ShortcutObject> shortcutObjectArray = map.get(frame.getName());
for (final ShortcutObject entry : shortcutObjectArray) {
// to perform validity check we need to register this name
actionManager.registerActionIsUsed(entry.actionName);
if (entry.keyStroke != null) {
// lazy shortcut registration (the action might not be ready!)
actionManager.performWhenActionIsReady(entry.actionName, new Runnable() {
@Override
public void run() {
for (ShortcutChangeListener shortcutChangeListener : shortcutChangeListeners) {
shortcutChangeListener.onChange();
}
frame.registerKeyboardAction(
new ActivatableKeyStrokeAction(entry.keyStroke, actionManager.getAction(entry.actionName)),
entry.keyStroke,
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT
);
}
});
}
// store reference to frame
entry.linkedFrame = frame;
}
} else {
System.err.println(
"Warning: No shortcut map defined for frame \"" + frame.getName() + "\"."
);
}
}
}