blob: 0049be8aa7deb8523b956c559f25155e6ceef568 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.openapi.keymap.impl;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.keymap.Keymap;
import com.intellij.openapi.keymap.KeymapUtil;
import com.intellij.openapi.keymap.ex.KeymapManagerEx;
import com.intellij.openapi.options.ExternalInfo;
import com.intellij.openapi.options.ExternalizableScheme;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashMap;
import gnu.trove.THashMap;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.lang.reflect.Field;
import java.util.*;
/**
* @author Eugene Belyaev
* @author Anton Katilin
* @author Vladimir Kondratyev
*/
public class KeymapImpl implements Keymap, ExternalizableScheme {
private static final Logger LOG = Logger.getInstance("#com.intellij.keymap.KeymapImpl");
@NonNls private static final String KEY_MAP = "keymap";
@NonNls private static final String KEYBOARD_SHORTCUT = "keyboard-shortcut";
@NonNls private static final String KEYBOARD_GESTURE_SHORTCUT = "keyboard-gesture-shortcut";
@NonNls private static final String KEYBOARD_GESTURE_KEY = "keystroke";
@NonNls private static final String KEYBOARD_GESTURE_MODIFIER = "modifier";
@NonNls private static final String KEYSTROKE_ATTRIBUTE = "keystroke";
@NonNls private static final String FIRST_KEYSTROKE_ATTRIBUTE = "first-keystroke";
@NonNls private static final String SECOND_KEYSTROKE_ATTRIBUTE = "second-keystroke";
@NonNls private static final String ABBREVIATION = "abbreviation";
@NonNls private static final String ACTION = "action";
@NonNls private static final String VERSION_ATTRIBUTE = "version";
@NonNls private static final String PARENT_ATTRIBUTE = "parent";
@NonNls private static final String NAME_ATTRIBUTE = "name";
@NonNls private static final String ID_ATTRIBUTE = "id";
@NonNls private static final String MOUSE_SHORTCUT = "mouse-shortcut";
@NonNls private static final String SHIFT = "shift";
@NonNls private static final String CONTROL = "control";
@NonNls private static final String META = "meta";
@NonNls private static final String ALT = "alt";
@NonNls private static final String ALT_GRAPH = "altGraph";
@NonNls private static final String DOUBLE_CLICK = "doubleClick";
@NonNls private static final String VIRTUAL_KEY_PREFIX = "VK_";
@NonNls private static final String EDITOR_ACTION_PREFIX = "Editor";
private String myName;
private KeymapImpl myParent;
private boolean myCanModify = true;
private final Map<String, LinkedHashSet<Shortcut>> myActionId2ListOfShortcuts = new THashMap<String, LinkedHashSet<Shortcut>>();
/**
* Don't use this field directly! Use it only through <code>getKeystroke2ListOfIds</code>.
*/
private Map<KeyStroke, List<String>> myKeystroke2ListOfIds = null;
private Map<KeyboardModifierGestureShortcut, List<String>> myGesture2ListOfIds = null;
// TODO[vova,anton] it should be final member
/**
* Don't use this field directly! Use it only through <code>getMouseShortcut2ListOfIds</code>.
*/
private Map<MouseShortcut, List<String>> myMouseShortcut2ListOfIds = null;
// TODO[vova,anton] it should be final member
private static final Map<Integer, String> ourNamesForKeycodes;
private static final Shortcut[] ourEmptyShortcutsArray = new Shortcut[0];
private final List<Listener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList();
private KeymapManagerEx myKeymapManager;
private final ExternalInfo myExternalInfo = new ExternalInfo();
static {
ourNamesForKeycodes = new HashMap<Integer, String>();
try {
Field[] fields = KeyEvent.class.getDeclaredFields();
for (Field field : fields) {
String fieldName = field.getName();
if (fieldName.startsWith(VIRTUAL_KEY_PREFIX)) {
int keyCode = field.getInt(KeyEvent.class);
ourNamesForKeycodes.put(keyCode, fieldName.substring(3));
}
}
}
catch (Exception e) {
LOG.error(e);
}
}
@NotNull
@Override
public String getName() {
return myName;
}
@Override
public String getPresentableName() {
return getName();
}
@Override
public void setName(@NotNull String name) {
myName = name;
}
public KeymapImpl deriveKeymap() {
if (canModify()) {
return copy(false);
}
else {
KeymapImpl newKeymap = new KeymapImpl();
newKeymap.myParent = this;
newKeymap.myName = null;
newKeymap.myCanModify = canModify();
return newKeymap;
}
}
public KeymapImpl copy(boolean copyExternalInfo) {
KeymapImpl newKeymap = new KeymapImpl();
newKeymap.myParent = myParent;
newKeymap.myName = myName;
newKeymap.myCanModify = canModify();
newKeymap.cleanShortcutsCache();
for (Map.Entry<String, LinkedHashSet<Shortcut>> entry : myActionId2ListOfShortcuts.entrySet()) {
LinkedHashSet<Shortcut> list = entry.getValue();
String key = entry.getKey();
newKeymap.myActionId2ListOfShortcuts.put(key, new LinkedHashSet<Shortcut>(list));
}
if (copyExternalInfo) {
newKeymap.myExternalInfo.copy(myExternalInfo);
}
return newKeymap;
}
public boolean equals(Object object) {
if (!(object instanceof Keymap)) return false;
KeymapImpl secondKeymap = (KeymapImpl)object;
if (!Comparing.equal(myName, secondKeymap.myName)) return false;
if (myCanModify != secondKeymap.myCanModify) return false;
if (!Comparing.equal(myParent, secondKeymap.myParent)) return false;
if (!Comparing.equal(myActionId2ListOfShortcuts, secondKeymap.myActionId2ListOfShortcuts)) return false;
return true;
}
public int hashCode() {
int hashCode = 0;
if (myName != null) {
hashCode += myName.hashCode();
}
return hashCode;
}
@Override
public Keymap getParent() {
return myParent;
}
@Override
public boolean canModify() {
return myCanModify;
}
public void setCanModify(boolean val) {
myCanModify = val;
}
protected Shortcut[] getParentShortcuts(String actionId) {
return myParent.getShortcuts(actionId);
}
@Override
public void addShortcut(String actionId, Shortcut shortcut) {
addShortcutSilently(actionId, shortcut, true);
fireShortcutChanged(actionId);
}
private void addShortcutSilently(String actionId, Shortcut shortcut, final boolean checkParentShortcut) {
LinkedHashSet<Shortcut> list = myActionId2ListOfShortcuts.get(actionId);
if (list == null) {
list = new LinkedHashSet<Shortcut>();
myActionId2ListOfShortcuts.put(actionId, list);
Shortcut[] boundShortcuts = getBoundShortcuts(actionId);
if (boundShortcuts != null) {
ContainerUtil.addAll(list, boundShortcuts);
}
else if (myParent != null) {
ContainerUtil.addAll(list, getParentShortcuts(actionId));
}
}
list.add(shortcut);
if (checkParentShortcut && myParent != null && areShortcutsEqual(getParentShortcuts(actionId), getShortcuts(actionId))) {
myActionId2ListOfShortcuts.remove(actionId);
}
cleanShortcutsCache();
}
private void cleanShortcutsCache() {
myKeystroke2ListOfIds = null;
myMouseShortcut2ListOfIds = null;
}
@Override
public void removeAllActionShortcuts(String actionId) {
Shortcut[] allShortcuts = getShortcuts(actionId);
for (Shortcut shortcut : allShortcuts) {
removeShortcut(actionId, shortcut);
}
}
@Override
public void removeShortcut(String actionId, Shortcut toDelete) {
LinkedHashSet<Shortcut> list = myActionId2ListOfShortcuts.get(actionId);
if (list != null) {
Iterator<Shortcut> it = list.iterator();
while (it.hasNext()) {
Shortcut each = it.next();
if (toDelete.equals(each)) {
it.remove();
if ((myParent != null && areShortcutsEqual(getParentShortcuts(actionId), getShortcuts(actionId)))
|| (myParent == null && list.isEmpty())) {
myActionId2ListOfShortcuts.remove(actionId);
}
break;
}
}
}
else {
Shortcut[] inherited = getBoundShortcuts(actionId);
if (inherited == null && myParent != null) {
inherited = getParentShortcuts(actionId);
}
if (inherited != null) {
boolean affected = false;
LinkedHashSet<Shortcut> newShortcuts = new LinkedHashSet<Shortcut>(inherited.length);
for (Shortcut eachInherited : inherited) {
if (toDelete.equals(eachInherited)) {
// skip this one
affected = true;
}
else {
newShortcuts.add(eachInherited);
}
}
if (affected) {
myActionId2ListOfShortcuts.put(actionId, newShortcuts);
}
}
}
cleanShortcutsCache();
fireShortcutChanged(actionId);
}
private Map<KeyStroke, List<String>> getKeystroke2ListOfIds() {
if (myKeystroke2ListOfIds != null) return myKeystroke2ListOfIds;
myKeystroke2ListOfIds = new THashMap<KeyStroke, List<String>>();
for (String id : ContainerUtil.concat(myActionId2ListOfShortcuts.keySet(), getKeymapManager().getBoundActions())) {
addKeystrokesMap(id, myKeystroke2ListOfIds);
}
return myKeystroke2ListOfIds;
}
private Map<KeyboardModifierGestureShortcut, List<String>> getGesture2ListOfIds() {
if (myGesture2ListOfIds == null) {
myGesture2ListOfIds = new THashMap<KeyboardModifierGestureShortcut, List<String>>();
fillShortcut2ListOfIds(myGesture2ListOfIds, KeyboardModifierGestureShortcut.class);
}
return myGesture2ListOfIds;
}
private <T extends Shortcut>void fillShortcut2ListOfIds(final Map<T,List<String>> map, final Class<T> shortcutClass) {
for (String id : ContainerUtil.concat(myActionId2ListOfShortcuts.keySet(), getKeymapManager().getBoundActions())) {
addAction2ShortcutsMap(id, map, shortcutClass);
}
}
private Map<MouseShortcut, List<String>> getMouseShortcut2ListOfIds() {
if (myMouseShortcut2ListOfIds == null) {
myMouseShortcut2ListOfIds = new THashMap<MouseShortcut, List<String>>();
fillShortcut2ListOfIds(myMouseShortcut2ListOfIds, MouseShortcut.class);
}
return myMouseShortcut2ListOfIds;
}
private <T extends Shortcut>void addAction2ShortcutsMap(final String actionId, final Map<T, List<String>> strokesMap, final Class<T> shortcutClass) {
LinkedHashSet<Shortcut> listOfShortcuts = _getShortcuts(actionId);
for (Shortcut shortcut : listOfShortcuts) {
if (!shortcutClass.isAssignableFrom(shortcut.getClass())) {
continue;
}
@SuppressWarnings({"unchecked"})
T t = (T)shortcut;
List<String> listOfIds = strokesMap.get(t);
if (listOfIds == null) {
listOfIds = new ArrayList<String>();
strokesMap.put(t, listOfIds);
}
// action may have more that 1 shortcut with same first keystroke
if (!listOfIds.contains(actionId)) {
listOfIds.add(actionId);
}
}
}
private void addKeystrokesMap(final String actionId, final Map<KeyStroke, List<String>> strokesMap) {
LinkedHashSet<Shortcut> listOfShortcuts = _getShortcuts(actionId);
for (Shortcut shortcut : listOfShortcuts) {
if (!(shortcut instanceof KeyboardShortcut)) {
continue;
}
KeyStroke firstKeyStroke = ((KeyboardShortcut)shortcut).getFirstKeyStroke();
List<String> listOfIds = strokesMap.get(firstKeyStroke);
if (listOfIds == null) {
listOfIds = new ArrayList<String>();
strokesMap.put(firstKeyStroke, listOfIds);
}
// action may have more that 1 shortcut with same first keystroke
if (!listOfIds.contains(actionId)) {
listOfIds.add(actionId);
}
}
}
private LinkedHashSet<Shortcut> _getShortcuts(final String actionId) {
KeymapManagerEx keymapManager = getKeymapManager();
LinkedHashSet<Shortcut> listOfShortcuts = myActionId2ListOfShortcuts.get(actionId);
if (listOfShortcuts != null) {
return listOfShortcuts;
}
else {
listOfShortcuts = new LinkedHashSet<Shortcut>();
}
final String actionBinding = keymapManager.getActionBinding(actionId);
if (actionBinding != null) {
listOfShortcuts.addAll(_getShortcuts(actionBinding));
}
return listOfShortcuts;
}
protected String[] getParentActionIds(KeyStroke firstKeyStroke) {
return myParent.getActionIds(firstKeyStroke);
}
protected String[] getParentActionIds(KeyboardModifierGestureShortcut gesture) {
return myParent.getActionIds(gesture);
}
private String[] getActionIds(KeyboardModifierGestureShortcut shortcut) {
// first, get keystrokes from own map
final Map<KeyboardModifierGestureShortcut, List<String>> map = getGesture2ListOfIds();
List<String> list = new ArrayList<String>();
for (Map.Entry<KeyboardModifierGestureShortcut, List<String>> entry : map.entrySet()) {
if (shortcut.startsWith(entry.getKey())) {
list.addAll(entry.getValue());
}
}
if (myParent != null) {
String[] ids = getParentActionIds(shortcut);
if (ids.length > 0) {
for (String id : ids) {
// add actions from parent keymap only if they are absent in this keymap
if (!myActionId2ListOfShortcuts.containsKey(id)) {
list.add(id);
}
}
}
}
return sortInOrderOfRegistration(ArrayUtil.toStringArray(list));
}
@Override
public String[] getActionIds(KeyStroke firstKeyStroke) {
// first, get keystrokes from own map
List<String> list = getKeystroke2ListOfIds().get(firstKeyStroke);
if (myParent != null) {
String[] ids = getParentActionIds(firstKeyStroke);
if (ids.length > 0) {
boolean originalListInstance = true;
for (String id : ids) {
// add actions from parent keymap only if they are absent in this keymap
// do not add parent bind actions, if bind-on action is overwritten in the child
if (!myActionId2ListOfShortcuts.containsKey(id) &&
!myActionId2ListOfShortcuts.containsKey(getActionBinding(id))) {
if (list == null) {
list = new ArrayList<String>();
originalListInstance = false;
}
else if (originalListInstance) {
list = new ArrayList<String>(list);
originalListInstance = false;
}
if (!list.contains(id)) list.add(id);
}
}
}
}
if (list == null) return ArrayUtil.EMPTY_STRING_ARRAY;
return sortInOrderOfRegistration(ArrayUtil.toStringArray(list));
}
@Override
public String[] getActionIds(KeyStroke firstKeyStroke, KeyStroke secondKeyStroke) {
String[] ids = getActionIds(firstKeyStroke);
ArrayList<String> actualBindings = new ArrayList<String>();
for (String id : ids) {
Shortcut[] shortcuts = getShortcuts(id);
for (Shortcut shortcut : shortcuts) {
if (!(shortcut instanceof KeyboardShortcut)) {
continue;
}
if (Comparing.equal(firstKeyStroke, ((KeyboardShortcut)shortcut).getFirstKeyStroke()) &&
Comparing.equal(secondKeyStroke, ((KeyboardShortcut)shortcut).getSecondKeyStroke())) {
actualBindings.add(id);
break;
}
}
}
return ArrayUtil.toStringArray(actualBindings);
}
@Override
public String[] getActionIds(final Shortcut shortcut) {
if (shortcut instanceof KeyboardShortcut) {
final KeyboardShortcut kb = (KeyboardShortcut)shortcut;
final KeyStroke first = kb.getFirstKeyStroke();
final KeyStroke second = kb.getSecondKeyStroke();
return second != null ? getActionIds(first, second) : getActionIds(first);
}
else if (shortcut instanceof MouseShortcut) {
return getActionIds((MouseShortcut)shortcut);
}
else if (shortcut instanceof KeyboardModifierGestureShortcut) {
return getActionIds((KeyboardModifierGestureShortcut)shortcut);
}
else {
return ArrayUtil.EMPTY_STRING_ARRAY;
}
}
protected String[] getParentActionIds(MouseShortcut shortcut) {
return myParent.getActionIds(shortcut);
}
@Override
public String[] getActionIds(MouseShortcut shortcut) {
// first, get shortcuts from own map
List<String> list = getMouseShortcut2ListOfIds().get(shortcut);
if (myParent != null) {
String[] ids = getParentActionIds(shortcut);
if (ids.length > 0) {
boolean originalListInstance = true;
for (String id : ids) {
// add actions from parent keymap only if they are absent in this keymap
if (!myActionId2ListOfShortcuts.containsKey(id)) {
if (list == null) {
list = new ArrayList<String>();
originalListInstance = false;
}
else if (originalListInstance) {
list = new ArrayList<String>(list);
}
list.add(id);
}
}
}
}
if (list == null) {
return ArrayUtil.EMPTY_STRING_ARRAY;
}
return sortInOrderOfRegistration(ArrayUtil.toStringArray(list));
}
private static String[] sortInOrderOfRegistration(String[] ids) {
Arrays.sort(ids, ActionManagerEx.getInstanceEx().getRegistrationOrderComparator());
return ids;
}
public boolean isActionBound(@NotNull final String actionId) {
return getKeymapManager().getBoundActions().contains(actionId);
}
@Nullable
public String getActionBinding(@NotNull final String actionId) {
return getKeymapManager().getActionBinding(actionId);
}
@Override
public Shortcut[] getShortcuts(String actionId) {
LinkedHashSet<Shortcut> shortcuts = myActionId2ListOfShortcuts.get(actionId);
if (shortcuts == null) {
Shortcut[] boundShortcuts = getBoundShortcuts(actionId);
if (boundShortcuts!= null) return boundShortcuts;
}
if (shortcuts == null) {
if (myParent != null) {
return getParentShortcuts(actionId);
}
else {
return ourEmptyShortcutsArray;
}
}
return shortcuts.isEmpty() ? ourEmptyShortcutsArray : shortcuts.toArray(new Shortcut[shortcuts.size()]);
}
@Nullable
private Shortcut[] getOwnShortcuts(String actionId) {
LinkedHashSet<Shortcut> own = myActionId2ListOfShortcuts.get(actionId);
if (own == null) return null;
return own.isEmpty() ? ourEmptyShortcutsArray : own.toArray(new Shortcut[own.size()]);
}
@Nullable
private Shortcut[] getBoundShortcuts(String actionId) {
KeymapManagerEx keymapManager = getKeymapManager();
boolean hasBoundedAction = keymapManager.getBoundActions().contains(actionId);
if (hasBoundedAction) {
return getOwnShortcuts(keymapManager.getActionBinding(actionId));
}
return null;
}
private KeymapManagerEx getKeymapManager() {
if (myKeymapManager == null) {
myKeymapManager = KeymapManagerEx.getInstanceEx();
}
return myKeymapManager;
}
/**
* @param keymapElement element which corresponds to "keymap" tag.
*/
public void readExternal(Element keymapElement, Keymap[] existingKeymaps) throws InvalidDataException {
// Check and convert parameters
if (!KEY_MAP.equals(keymapElement.getName())) {
throw new InvalidDataException("unknown element: " + keymapElement);
}
if (keymapElement.getAttributeValue(VERSION_ATTRIBUTE) == null) {
Converter01.convert(keymapElement);
}
//
String parentName = keymapElement.getAttributeValue(PARENT_ATTRIBUTE);
if (parentName != null) {
for (Keymap existingKeymap : existingKeymaps) {
if (parentName.equals(existingKeymap.getName())) {
myParent = (KeymapImpl)existingKeymap;
myCanModify = true;
break;
}
}
}
myName = keymapElement.getAttributeValue(NAME_ATTRIBUTE);
Map<String, ArrayList<Shortcut>> id2shortcuts = new HashMap<String, ArrayList<Shortcut>>();
final boolean skipInserts = SystemInfo.isMac && !ApplicationManager.getApplication().isUnitTestMode();
for (final Object o : keymapElement.getChildren()) {
Element actionElement = (Element)o;
if (ACTION.equals(actionElement.getName())) {
String id = actionElement.getAttributeValue(ID_ATTRIBUTE);
if (id == null) {
throw new InvalidDataException("Attribute 'id' cannot be null; Keymap's name=" + myName);
}
id2shortcuts.put(id, new ArrayList<Shortcut>(1));
for (final Object o1 : actionElement.getChildren()) {
Element shortcutElement = (Element)o1;
if (KEYBOARD_SHORTCUT.equals(shortcutElement.getName())) {
// Parse first keystroke
KeyStroke firstKeyStroke;
String firstKeyStrokeStr = shortcutElement.getAttributeValue(FIRST_KEYSTROKE_ATTRIBUTE);
if (skipInserts && firstKeyStrokeStr.contains("INSERT")) continue;
if (firstKeyStrokeStr != null) {
firstKeyStroke = ActionManagerEx.getKeyStroke(firstKeyStrokeStr);
if (firstKeyStroke == null) {
throw new InvalidDataException(
"Cannot parse first-keystroke: '" + firstKeyStrokeStr + "'; " + "Action's id=" + id + "; Keymap's name=" + myName);
}
}
else {
throw new InvalidDataException("Attribute 'first-keystroke' cannot be null; Action's id=" + id + "; Keymap's name=" + myName);
}
// Parse second keystroke
KeyStroke secondKeyStroke = null;
String secondKeyStrokeStr = shortcutElement.getAttributeValue(SECOND_KEYSTROKE_ATTRIBUTE);
if (secondKeyStrokeStr != null) {
secondKeyStroke = ActionManagerEx.getKeyStroke(secondKeyStrokeStr);
if (secondKeyStroke == null) {
throw new InvalidDataException(
"Wrong second-keystroke: '" + secondKeyStrokeStr + "'; Action's id=" + id + "; Keymap's name=" + myName);
}
}
Shortcut shortcut = new KeyboardShortcut(firstKeyStroke, secondKeyStroke);
ArrayList<Shortcut> shortcuts = id2shortcuts.get(id);
shortcuts.add(shortcut);
}
else if (KEYBOARD_GESTURE_SHORTCUT.equals(shortcutElement.getName())) {
KeyStroke stroke = null;
final String strokeText = shortcutElement.getAttributeValue(KEYBOARD_GESTURE_KEY);
if (strokeText != null) {
stroke = ActionManagerEx.getKeyStroke(strokeText);
}
final String modifierText = shortcutElement.getAttributeValue(KEYBOARD_GESTURE_MODIFIER);
KeyboardGestureAction.ModifierType modifier = null;
if (KeyboardGestureAction.ModifierType.dblClick.toString().equalsIgnoreCase(modifierText)) {
modifier = KeyboardGestureAction.ModifierType.dblClick;
}
else if (KeyboardGestureAction.ModifierType.hold.toString().equalsIgnoreCase(modifierText)) {
modifier = KeyboardGestureAction.ModifierType.hold;
}
if (stroke == null) {
throw new InvalidDataException("Wrong keystroke=" + strokeText + " action id=" + id + " keymap=" + myName);
}
if (modifier == null) {
throw new InvalidDataException("Wrong modifier=" + modifierText + " action id=" + id + " keymap=" + myName);
}
Shortcut shortcut = KeyboardModifierGestureShortcut.newInstance(modifier, stroke);
final ArrayList<Shortcut> shortcuts = id2shortcuts.get(id);
shortcuts.add(shortcut);
}
else if (MOUSE_SHORTCUT.equals(shortcutElement.getName())) {
String keystrokeString = shortcutElement.getAttributeValue(KEYSTROKE_ATTRIBUTE);
if (keystrokeString == null) {
throw new InvalidDataException("Attribute 'keystroke' cannot be null; Action's id=" + id + "; Keymap's name=" + myName);
}
try {
MouseShortcut shortcut = KeymapUtil.parseMouseShortcut(keystrokeString);
ArrayList<Shortcut> shortcuts = id2shortcuts.get(id);
shortcuts.add(shortcut);
}
catch (InvalidDataException exc) {
throw new InvalidDataException(
"Wrong mouse-shortcut: '" + keystrokeString + "'; Action's id=" + id + "; Keymap's name=" + myName);
}
}
else {
throw new InvalidDataException("unknown element: " + shortcutElement + "; Keymap's name=" + myName);
}
}
}
else {
throw new InvalidDataException("unknown element: " + actionElement + "; Keymap's name=" + myName);
}
}
// Add read shortcuts
for (String id : id2shortcuts.keySet()) {
myActionId2ListOfShortcuts.put(id, new LinkedHashSet<Shortcut>(2)); // It's a trick! After that parent's shortcuts are not added to the keymap
ArrayList<Shortcut> shortcuts = id2shortcuts.get(id);
for (Shortcut shortcut : shortcuts) {
addShortcutSilently(id, shortcut, false);
}
}
}
public Element writeExternal() {
Element keymapElement = new Element(KEY_MAP);
keymapElement.setAttribute(VERSION_ATTRIBUTE, Integer.toString(1));
keymapElement.setAttribute(NAME_ATTRIBUTE, myName);
if (myParent != null) {
keymapElement.setAttribute(PARENT_ATTRIBUTE, myParent.getName());
}
writeOwnActionIds(keymapElement);
return keymapElement;
}
private void writeOwnActionIds(final Element keymapElement) {
String[] ownActionIds = getOwnActionIds();
Arrays.sort(ownActionIds);
for (String actionId : ownActionIds) {
Element actionElement = new Element(ACTION);
actionElement.setAttribute(ID_ATTRIBUTE, actionId);
// Save keyboad shortcuts
Shortcut[] shortcuts = getShortcuts(actionId);
for (Shortcut shortcut : shortcuts) {
if (shortcut instanceof KeyboardShortcut) {
KeyboardShortcut keyboardShortcut = (KeyboardShortcut)shortcut;
Element element = new Element(KEYBOARD_SHORTCUT);
element.setAttribute(FIRST_KEYSTROKE_ATTRIBUTE, getKeyShortcutString(keyboardShortcut.getFirstKeyStroke()));
if (keyboardShortcut.getSecondKeyStroke() != null) {
element.setAttribute(SECOND_KEYSTROKE_ATTRIBUTE, getKeyShortcutString(keyboardShortcut.getSecondKeyStroke()));
}
actionElement.addContent(element);
}
else if (shortcut instanceof MouseShortcut) {
MouseShortcut mouseShortcut = (MouseShortcut)shortcut;
Element element = new Element(MOUSE_SHORTCUT);
element.setAttribute(KEYSTROKE_ATTRIBUTE, getMouseShortcutString(mouseShortcut));
actionElement.addContent(element);
}
else if (shortcut instanceof KeyboardModifierGestureShortcut) {
final KeyboardModifierGestureShortcut gesture = (KeyboardModifierGestureShortcut)shortcut;
final Element element = new Element(KEYBOARD_GESTURE_SHORTCUT);
element.setAttribute(KEYBOARD_GESTURE_SHORTCUT, getKeyShortcutString(gesture.getStroke()));
element.setAttribute(KEYBOARD_GESTURE_MODIFIER, gesture.getType().name());
actionElement.addContent(element);
}
else {
throw new IllegalStateException("unknown shortcut class: " + shortcut);
}
}
keymapElement.addContent(actionElement);
}
}
private static boolean areShortcutsEqual(Shortcut[] shortcuts1, Shortcut[] shortcuts2) {
if (shortcuts1.length != shortcuts2.length) {
return false;
}
for (Shortcut shortcut : shortcuts1) {
Shortcut parentShortcutEqual = null;
for (Shortcut parentShortcut : shortcuts2) {
if (shortcut.equals(parentShortcut)) {
parentShortcutEqual = parentShortcut;
break;
}
}
if (parentShortcutEqual == null) {
return false;
}
}
return true;
}
/**
* @return string representation of passed keystroke.
*/
public static String getKeyShortcutString(KeyStroke keyStroke) {
StringBuffer buf = new StringBuffer();
int modifiers = keyStroke.getModifiers();
if ((modifiers & InputEvent.SHIFT_MASK) != 0) {
buf.append(SHIFT);
buf.append(' ');
}
if ((modifiers & InputEvent.CTRL_MASK) != 0) {
buf.append(CONTROL);
buf.append(' ');
}
if ((modifiers & InputEvent.META_MASK) != 0) {
buf.append(META);
buf.append(' ');
}
if ((modifiers & InputEvent.ALT_MASK) != 0) {
buf.append(ALT);
buf.append(' ');
}
if ((modifiers & InputEvent.ALT_GRAPH_MASK) != 0) {
buf.append(ALT_GRAPH);
buf.append(' ');
}
buf.append(ourNamesForKeycodes.get(keyStroke.getKeyCode()));
return buf.toString();
}
/**
* @return string representation of passed mouse shortcut. This method should
* be used only for serializing of the <code>MouseShortcut</code>
*/
private static String getMouseShortcutString(MouseShortcut shortcut) {
StringBuffer buffer = new StringBuffer();
// modifiers
int modifiers = shortcut.getModifiers();
if ((MouseEvent.SHIFT_DOWN_MASK & modifiers) > 0) {
buffer.append(SHIFT);
buffer.append(' ');
}
if ((MouseEvent.CTRL_DOWN_MASK & modifiers) > 0) {
buffer.append(CONTROL);
buffer.append(' ');
}
if ((MouseEvent.META_DOWN_MASK & modifiers) > 0) {
buffer.append(META);
buffer.append(' ');
}
if ((MouseEvent.ALT_DOWN_MASK & modifiers) > 0) {
buffer.append(ALT);
buffer.append(' ');
}
if ((MouseEvent.ALT_GRAPH_DOWN_MASK & modifiers) > 0) {
buffer.append(ALT_GRAPH);
buffer.append(' ');
}
// button
buffer.append("button").append(shortcut.getButton()).append(' ');
if (shortcut.getClickCount() > 1) {
buffer.append(DOUBLE_CLICK);
}
return buffer.toString().trim(); // trim trailing space (if any)
}
/**
* @return IDs of the action which are specified in the keymap. It doesn't
* return IDs of action from parent keymap.
*/
public String[] getOwnActionIds() {
return myActionId2ListOfShortcuts.keySet().toArray(new String[myActionId2ListOfShortcuts.size()]);
}
public void clearOwnActionsIds() {
myActionId2ListOfShortcuts.clear();
cleanShortcutsCache();
}
public boolean hasOwnActionId(String actionId) {
return myActionId2ListOfShortcuts.containsKey(actionId);
}
public void clearOwnActionsId(String actionId) {
myActionId2ListOfShortcuts.remove(actionId);
cleanShortcutsCache();
}
@Override
public String[] getActionIds() {
ArrayList<String> ids = new ArrayList<String>();
if (myParent != null) {
String[] parentIds = getParentActionIds();
ContainerUtil.addAll(ids, parentIds);
}
String[] ownActionIds = getOwnActionIds();
for (String id : ownActionIds) {
if (!ids.contains(id)) {
ids.add(id);
}
}
return ArrayUtil.toStringArray(ids);
}
protected String[] getParentActionIds() {
return myParent.getActionIds();
}
@Override
public HashMap<String, ArrayList<KeyboardShortcut>> getConflicts(String actionId, KeyboardShortcut keyboardShortcut) {
HashMap<String, ArrayList<KeyboardShortcut>> result = new HashMap<String, ArrayList<KeyboardShortcut>>();
String[] actionIds = getActionIds(keyboardShortcut.getFirstKeyStroke());
for (String id : actionIds) {
if (id.equals(actionId)) {
continue;
}
if (actionId.startsWith(EDITOR_ACTION_PREFIX) && id.equals("$" + actionId.substring(6))) {
continue;
}
if (StringUtil.startsWithChar(actionId, '$') && id.equals(EDITOR_ACTION_PREFIX + actionId.substring(1))) {
continue;
}
final String useShortcutOf = myKeymapManager.getActionBinding(id);
if (useShortcutOf != null && useShortcutOf.equals(actionId)) {
continue;
}
Shortcut[] shortcuts = getShortcuts(id);
for (Shortcut shortcut1 : shortcuts) {
if (!(shortcut1 instanceof KeyboardShortcut)) {
continue;
}
KeyboardShortcut shortcut = (KeyboardShortcut)shortcut1;
if (!shortcut.getFirstKeyStroke().equals(keyboardShortcut.getFirstKeyStroke())) {
continue;
}
if (keyboardShortcut.getSecondKeyStroke() != null &&
shortcut.getSecondKeyStroke() != null &&
!keyboardShortcut.getSecondKeyStroke().equals(shortcut.getSecondKeyStroke())) {
continue;
}
ArrayList<KeyboardShortcut> list = result.get(id);
if (list == null) {
list = new ArrayList<KeyboardShortcut>();
result.put(id, list);
}
list.add(shortcut);
}
}
return result;
}
@Override
public void addShortcutChangeListener(Listener listener) {
myListeners.add(listener);
}
@Override
public void removeShortcutChangeListener(Listener listener) {
myListeners.remove(listener);
}
private void fireShortcutChanged(String actionId) {
for (Listener listener : myListeners) {
listener.onShortcutChanged(actionId);
}
}
@Override
public String[] getAbbreviations() {
return new String[0];
}
@Override
public void addAbbreviation(String actionId, String abbreviation) {
}
@Override
public void removeAbbreviation(String actionId, String abbreviation) {
}
@Override
@NotNull
public ExternalInfo getExternalInfo() {
return myExternalInfo;
}
@Override
public String toString() {
return getPresentableName();
}
}