resize window action
[fedora-idea.git] / platform / platform-impl / src / com / intellij / openapi / keymap / impl / KeymapImpl.java
blobbcb93fb5a2b9da7ed2f110bb1390eb451542258b
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.openapi.keymap.impl;
18 import com.intellij.openapi.actionSystem.*;
19 import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
20 import com.intellij.openapi.diagnostic.Logger;
21 import com.intellij.openapi.keymap.Keymap;
22 import com.intellij.openapi.keymap.KeymapUtil;
23 import com.intellij.openapi.keymap.ex.KeymapManagerEx;
24 import com.intellij.openapi.options.ExternalInfo;
25 import com.intellij.openapi.options.ExternalizableScheme;
26 import com.intellij.openapi.util.Comparing;
27 import com.intellij.openapi.util.InvalidDataException;
28 import com.intellij.openapi.util.text.StringUtil;
29 import com.intellij.util.ArrayUtil;
30 import com.intellij.util.containers.HashMap;
31 import gnu.trove.THashMap;
32 import org.jdom.Element;
33 import org.jetbrains.annotations.NonNls;
34 import org.jetbrains.annotations.NotNull;
36 import javax.swing.*;
37 import java.awt.event.InputEvent;
38 import java.awt.event.KeyEvent;
39 import java.awt.event.MouseEvent;
40 import java.lang.reflect.Field;
41 import java.util.ArrayList;
42 import java.util.Arrays;
43 import java.util.Iterator;
44 import java.util.Set;
46 /**
47 * @author Eugene Belyaev
48 * @author Anton Katilin
49 * @author Vladimir Kondratyev
51 public class KeymapImpl implements Keymap, ExternalizableScheme {
52 @NonNls
53 private static final String KEY_MAP = "keymap";
54 @NonNls
55 private static final String KEYBOARD_SHORTCUT = "keyboard-shortcut";
56 @NonNls
57 private static final String KEYBOARD_GESTURE_SHORTCUT = "keyboard-gesture-shortcut";
58 @NonNls
59 private static final String KEYBOARD_GESTURE_KEY = "keystroke";
60 @NonNls
61 private static final String KEYBOARD_GESTURE_MODIFIER = "modifier";
62 @NonNls
63 private static final String KEYSTROKE_ATTRIBUTE = "keystroke";
64 @NonNls
65 private static final String FIRST_KEYSTROKE_ATTRIBUTE = "first-keystroke";
66 @NonNls
67 private static final String SECOND_KEYSTROKE_ATTRIBUTE = "second-keystroke";
68 @NonNls
69 private static final String ACTION = "action";
70 @NonNls
71 private static final String VERSION_ATTRIBUTE = "version";
72 @NonNls
73 private static final String PARENT_ATTRIBUTE = "parent";
74 @NonNls
75 private static final String NAME_ATTRIBUTE = "name";
76 @NonNls
77 private static final String ID_ATTRIBUTE = "id";
78 @NonNls
79 private static final String TRUE_WORD = "true";
80 @NonNls
81 private static final String FALSE_WORD = "false";
82 @NonNls
83 private static final String DISABLE_MNEMONICS_ATTRIBUTE = "disable-mnemonics";
84 @NonNls
85 private static final String MOUSE_SHORTCUT = "mouse-shortcut";
86 @NonNls
87 private static final String SHIFT = "shift";
88 @NonNls
89 private static final String CONTROL = "control";
90 @NonNls
91 private static final String META = "meta";
92 @NonNls
93 private static final String ALT = "alt";
94 @NonNls
95 private static final String ALT_GRAPH = "altGraph";
96 @NonNls
97 private static final String BUTTON1 = "button1";
98 @NonNls
99 private static final String BUTTON2 = "button2";
100 @NonNls
101 private static final String BUTTON3 = "button3";
102 @NonNls
103 private static final String DOUBLE_CLICK = "doubleClick";
104 @NonNls
105 private static final String VIRTUAL_KEY_PREFIX = "VK_";
106 @NonNls
107 private static final String EDITOR_ACTION_PREFIX = "Editor";
109 private static final Logger LOG = Logger.getInstance("#com.intellij.keymap.KeymapImpl");
111 private String myName;
112 private KeymapImpl myParent;
113 private boolean myCanModify = true;
116 private THashMap<String, ArrayList<Shortcut>> myActionId2ListOfShortcuts = new THashMap<String, ArrayList<Shortcut>>();
119 * Don't use this field directly! Use it only through <code>getKeystroke2ListOfIds</code>.
121 private THashMap<KeyStroke, ArrayList<String>> myKeystroke2ListOfIds = null;
122 private THashMap<KeyboardModifierGestureShortuct, ArrayList<String>> myGesture2ListOfIds = null;
123 // TODO[vova,anton] it should be final member
126 * Don't use this field directly! Use it only through <code>getMouseShortcut2ListOfIds</code>.
128 private THashMap myMouseShortcut2ListOfIds = null;
129 // TODO[vova,anton] it should be final member
131 private static HashMap<Integer,String> ourNamesForKeycodes = null;
132 private static final Shortcut[] ourEmptyShortcutsArray = new Shortcut[0];
133 private final ArrayList<Keymap.Listener> myListeners = new ArrayList<Keymap.Listener>();
134 private KeymapManagerEx myKeymapManager;
135 private final ExternalInfo myExternalInfo = new ExternalInfo();
137 static {
138 ourNamesForKeycodes = new HashMap<Integer, String>();
139 try {
140 Field[] fields = KeyEvent.class.getDeclaredFields();
141 for (Field field : fields) {
142 String fieldName = field.getName();
143 if (fieldName.startsWith(VIRTUAL_KEY_PREFIX)) {
144 int keyCode = field.getInt(KeyEvent.class);
145 ourNamesForKeycodes.put(keyCode, fieldName.substring(3));
149 catch (Exception e) {
150 LOG.error(e);
154 public String getName() {
155 return myName;
158 public String getPresentableName() {
159 return getName();
162 public void setName(String name) {
163 myName = name;
167 public KeymapImpl deriveKeymap() {
168 if (!canModify()) {
169 KeymapImpl newKeymap = new KeymapImpl();
171 newKeymap.myParent = this;
172 newKeymap.myName = null;
173 newKeymap.myCanModify = canModify();
174 return newKeymap;
176 else {
177 return copy(false);
181 public KeymapImpl copy(boolean copyExternalInfo) {
182 KeymapImpl newKeymap = new KeymapImpl();
183 newKeymap.myParent = myParent;
184 newKeymap.myName = myName;
185 newKeymap.myCanModify = canModify();
187 newKeymap.myKeystroke2ListOfIds = null;
188 newKeymap.myMouseShortcut2ListOfIds = null;
190 THashMap actionsIdsToListOfShortcuts = new THashMap();
191 for (String key : myActionId2ListOfShortcuts.keySet()) {
192 ArrayList<Shortcut> list = myActionId2ListOfShortcuts.get(key);
193 actionsIdsToListOfShortcuts.put(key, list.clone());
196 newKeymap.myActionId2ListOfShortcuts = actionsIdsToListOfShortcuts;
198 if (copyExternalInfo) {
199 newKeymap.myExternalInfo.copy(myExternalInfo);
202 return newKeymap;
205 public boolean equals(Object object) {
206 if (!(object instanceof Keymap)) return false;
207 KeymapImpl secondKeymap = (KeymapImpl)object;
208 if (!Comparing.equal(myName, secondKeymap.myName)) return false;
209 if (myCanModify != secondKeymap.myCanModify) return false;
210 if (!Comparing.equal(myParent, secondKeymap.myParent)) return false;
211 if (!Comparing.equal(myActionId2ListOfShortcuts, secondKeymap.myActionId2ListOfShortcuts)) return false;
212 return true;
215 public int hashCode(){
216 int hashCode=0;
217 if(myName!=null){
218 hashCode+=myName.hashCode();
220 return hashCode;
223 public Keymap getParent() {
224 return myParent;
227 public boolean canModify() {
228 return myCanModify;
231 public void setCanModify(boolean val) {
232 myCanModify = val;
235 protected Shortcut[] getParentShortcuts(String actionId) {
236 return myParent.getShortcuts(actionId);
239 public void addShortcut(String actionId, Shortcut shortcut) {
240 addShortcutSilently(actionId, shortcut, true);
241 fireShortcutChanged(actionId);
244 private void addShortcutSilently(String actionId, Shortcut shortcut, final boolean checkParentShortcut) {
245 ArrayList<Shortcut> list = myActionId2ListOfShortcuts.get(actionId);
246 if (list == null) {
247 list = new ArrayList<Shortcut>();
248 myActionId2ListOfShortcuts.put(actionId, list);
249 if (myParent != null) {
250 // copy parent shortcuts for this actionId
251 Shortcut[] shortcuts = getParentShortcuts(actionId);
252 for (Shortcut parentShortcut : shortcuts) {
253 // shortcuts are immutables
254 list.add(parentShortcut);
258 list.add(shortcut);
260 if (checkParentShortcut && myParent != null && areShortcutsEqual(getParentShortcuts(actionId), getShortcuts(actionId))) {
261 myActionId2ListOfShortcuts.remove(actionId);
263 myKeystroke2ListOfIds = null;
264 myMouseShortcut2ListOfIds = null;
267 public void removeAllActionShortcuts(String actionId) {
268 Shortcut[] allShortcuts = getShortcuts(actionId);
269 for (Shortcut shortcut : allShortcuts) {
270 removeShortcut(actionId, shortcut);
274 public void removeShortcut(String actionId, Shortcut shortcut) {
275 ArrayList<Shortcut> list = myActionId2ListOfShortcuts.get(actionId);
276 if (list != null) {
277 for(int i=0; i<list.size(); i++) {
278 if(shortcut.equals(list.get(i))) {
279 list.remove(i);
280 if (myParent != null && areShortcutsEqual(getParentShortcuts(actionId), getShortcuts(actionId))) {
281 myActionId2ListOfShortcuts.remove(actionId);
283 break;
287 else if (myParent != null) {
288 // put to the map the parent's bindings except for the removed binding
289 Shortcut[] parentShortcuts = getParentShortcuts(actionId);
290 ArrayList<Shortcut> listOfShortcuts = new ArrayList<Shortcut>();
291 for (Shortcut parentShortcut : parentShortcuts) {
292 if (!shortcut.equals(parentShortcut)) {
293 listOfShortcuts.add(parentShortcut);
296 myActionId2ListOfShortcuts.put(actionId, listOfShortcuts);
298 myKeystroke2ListOfIds = null;
299 myMouseShortcut2ListOfIds = null;
300 fireShortcutChanged(actionId);
303 private THashMap<KeyStroke,ArrayList<String>> getKeystroke2ListOfIds() {
304 myKeystroke2ListOfIds = null;
306 if (myKeystroke2ListOfIds == null) {
307 myKeystroke2ListOfIds = new THashMap<KeyStroke, ArrayList<String>>();
308 fillKeystroke2ListOfIds(myKeystroke2ListOfIds, KeyboardShortcut.class);
310 return myKeystroke2ListOfIds;
313 private THashMap<KeyboardModifierGestureShortuct,ArrayList<String>> getGesture2ListOfIds() {
314 if (myGesture2ListOfIds == null) {
315 myGesture2ListOfIds = new THashMap<KeyboardModifierGestureShortuct, ArrayList<String>>();
316 fillKeystroke2ListOfIds(myGesture2ListOfIds, KeyboardModifierGestureShortuct.class);
318 return myGesture2ListOfIds;
321 private void fillKeystroke2ListOfIds(final THashMap map, final Class shortcutClass) {
322 for (String id : myActionId2ListOfShortcuts.keySet()) {
323 addAction2ShortcutsMap(id, map, shortcutClass);
326 final Set<String> boundActions = getKeymapManager().getBoundActions();
327 for (String id : boundActions) {
328 addAction2ShortcutsMap(id, map, shortcutClass);
332 private THashMap getMouseShortcut2ListOfIds(){
333 if(myMouseShortcut2ListOfIds==null){
334 myMouseShortcut2ListOfIds=new THashMap();
336 for (String id : myActionId2ListOfShortcuts.keySet()) {
337 addAction2ShortcutsMap(id, myMouseShortcut2ListOfIds, MouseShortcut.class);
340 final Set<String> boundActions = getKeymapManager().getBoundActions();
341 for (String id : boundActions) {
342 addAction2ShortcutsMap(id, myMouseShortcut2ListOfIds, MouseShortcut.class);
345 return myMouseShortcut2ListOfIds;
348 private void addAction2ShortcutsMap(
349 final String actionId,
350 final THashMap strokesMap,
351 final Class shortcutClass) {
352 ArrayList<Shortcut> listOfShortcuts = _getShortcuts(actionId);
353 for (Shortcut shortcut : listOfShortcuts) {
354 if (!shortcutClass.isAssignableFrom(shortcut.getClass())) {
355 continue;
358 ArrayList<String> listOfIds = null;
359 if (shortcut instanceof KeyboardShortcut) {
360 KeyStroke firstKeyStroke = ((KeyboardShortcut)shortcut).getFirstKeyStroke();
361 listOfIds = (ArrayList<String>)strokesMap.get(firstKeyStroke);
362 if (listOfIds == null) {
363 listOfIds = new ArrayList<String>();
364 strokesMap.put(firstKeyStroke, listOfIds);
366 } else if (shortcut instanceof KeyboardModifierGestureShortuct) {
367 final KeyboardModifierGestureShortuct gesture = (KeyboardModifierGestureShortuct)shortcut;
368 if (listOfIds == null) {
369 listOfIds = new ArrayList<String>();
370 strokesMap.put(gesture, listOfIds);
372 } else {
373 listOfIds = (ArrayList)strokesMap.get(shortcut);
374 if (listOfIds == null) {
375 listOfIds = new ArrayList<String>();
376 strokesMap.put(shortcut, listOfIds);
380 // action may have more that 1 shortcut with same first keystroke
381 if (!listOfIds.contains(actionId)) {
382 listOfIds.add(actionId);
387 private ArrayList<Shortcut> _getShortcuts(final String actionId) {
388 KeymapManagerEx keymapManager = getKeymapManager();
389 ArrayList<Shortcut> listOfShortcuts = myActionId2ListOfShortcuts.get(actionId);
390 if (listOfShortcuts != null) {
391 listOfShortcuts = new ArrayList<Shortcut>(listOfShortcuts);
393 else {
394 listOfShortcuts = new ArrayList<Shortcut>();
397 final String actionBinding = keymapManager.getActionBinding(actionId);
398 if (actionBinding != null) {
399 listOfShortcuts.addAll(_getShortcuts(actionBinding));
402 return listOfShortcuts;
406 protected String[] getParentActionIds(KeyStroke firstKeyStroke) {
407 return myParent.getActionIds(firstKeyStroke);
410 protected String[] getParentActionIds(KeyboardModifierGestureShortuct gesture) {
411 return myParent.getActionIds(gesture);
414 private String[] getActionIds(KeyboardModifierGestureShortuct shortuct) {
415 // first, get keystrokes from own map
416 final THashMap<KeyboardModifierGestureShortuct, ArrayList<String>> map = getGesture2ListOfIds();
417 ArrayList<String> list = new ArrayList<String>();
419 final Iterator<KeyboardModifierGestureShortuct> all = map.keySet().iterator();
420 while (all.hasNext()) {
421 KeyboardModifierGestureShortuct each = all.next();
422 if (shortuct.startsWith(each)) {
423 list.addAll(map.get(each));
427 if (myParent != null) {
428 String[] ids = getParentActionIds(shortuct);
429 if (ids.length > 0) {
430 boolean originalListInstance = true;
431 for (String id : ids) {
432 // add actions from parent keymap only if they are absent in this keymap
433 if (!myActionId2ListOfShortcuts.containsKey(id)) {
434 if (list == null) {
435 list = new ArrayList<String>();
436 originalListInstance = false;
438 else if (originalListInstance) {
439 list = (ArrayList<String>)list.clone();
441 list.add(id);
446 if (list == null) return ArrayUtil.EMPTY_STRING_ARRAY;
447 return sortInOrderOfRegistration(ArrayUtil.toStringArray(list));
450 public String[] getActionIds(KeyStroke firstKeyStroke) {
451 // first, get keystrokes from own map
452 ArrayList<String> list = getKeystroke2ListOfIds().get(firstKeyStroke);
453 if (myParent != null) {
454 String[] ids = getParentActionIds(firstKeyStroke);
455 if (ids.length > 0) {
456 boolean originalListInstance = true;
457 for (String id : ids) {
458 // add actions from parent keymap only if they are absent in this keymap
459 if (!myActionId2ListOfShortcuts.containsKey(id)) {
460 if (list == null) {
461 list = new ArrayList<String>();
462 originalListInstance = false;
464 else if (originalListInstance) {
465 list = (ArrayList<String>)list.clone();
467 list.add(id);
472 if (list == null) return ArrayUtil.EMPTY_STRING_ARRAY;
473 return sortInOrderOfRegistration(ArrayUtil.toStringArray(list));
476 public String[] getActionIds(KeyStroke firstKeyStroke, KeyStroke secondKeyStroke) {
477 String[] ids = getActionIds(firstKeyStroke);
478 ArrayList<String> actualBindings = new ArrayList<String>();
479 for (String id : ids) {
480 Shortcut[] shortcuts = getShortcuts(id);
481 for (Shortcut shortcut : shortcuts) {
482 if (!(shortcut instanceof KeyboardShortcut)) {
483 continue;
485 if (Comparing.equal(firstKeyStroke, ((KeyboardShortcut)shortcut).getFirstKeyStroke()) &&
486 Comparing.equal(secondKeyStroke, ((KeyboardShortcut)shortcut).getSecondKeyStroke())) {
487 actualBindings.add(id);
488 break;
492 return ArrayUtil.toStringArray(actualBindings);
495 public String[] getActionIds(final Shortcut shortcut) {
496 if (shortcut instanceof KeyboardShortcut) {
497 final KeyboardShortcut kb = (KeyboardShortcut)shortcut;
498 final KeyStroke first = kb.getFirstKeyStroke();
499 final KeyStroke second = kb.getSecondKeyStroke();
500 return second != null ? getActionIds(first, second) : getActionIds(first);
501 } else if (shortcut instanceof MouseShortcut) {
502 return getActionIds(((MouseShortcut)shortcut));
503 } else if (shortcut instanceof KeyboardModifierGestureShortuct) {
504 return getActionIds(((KeyboardModifierGestureShortuct)shortcut));
505 } else {
506 return ArrayUtil.EMPTY_STRING_ARRAY;
510 protected String[] getParentActionIds(MouseShortcut shortcut) {
511 return myParent.getActionIds(shortcut);
515 public String[] getActionIds(MouseShortcut shortcut){
516 // first, get shortcuts from own map
517 ArrayList<String> list = (ArrayList<String>)getMouseShortcut2ListOfIds().get(shortcut);
518 if (myParent != null) {
519 String[] ids = getParentActionIds(shortcut);
520 if (ids.length > 0) {
521 boolean originalListInstance = true;
522 for (String id : ids) {
523 // add actions from parent keymap only if they are absent in this keymap
524 if (!myActionId2ListOfShortcuts.containsKey(id)) {
525 if (list == null) {
526 list = new ArrayList<String>();
527 originalListInstance = false;
529 else if (originalListInstance) {
530 list = (ArrayList<String>)list.clone();
532 list.add(id);
537 if (list == null){
538 return ArrayUtil.EMPTY_STRING_ARRAY;
540 return sortInOrderOfRegistration(ArrayUtil.toStringArray(list));
543 private static String[] sortInOrderOfRegistration(String[] ids) {
544 Arrays.sort(ids, ActionManagerEx.getInstanceEx().getRegistrationOrderComparator());
545 return ids;
548 public Shortcut[] getShortcuts(String actionId) {
549 KeymapManagerEx keymapManager = getKeymapManager();
550 if (keymapManager.getBoundActions().contains(actionId)) {
551 return getShortcuts(keymapManager.getActionBinding(actionId));
554 ArrayList<Shortcut> shortcuts = myActionId2ListOfShortcuts.get(actionId);
556 if (shortcuts == null) {
557 if (myParent != null) {
558 return getParentShortcuts(actionId);
559 }else{
560 return ourEmptyShortcutsArray;
563 return shortcuts.toArray(new Shortcut[shortcuts.size()]);
566 private KeymapManagerEx getKeymapManager() {
567 if (myKeymapManager == null) {
568 myKeymapManager = KeymapManagerEx.getInstanceEx();
570 return myKeymapManager;
574 * @param keymapElement element which corresponds to "keymap" tag.
576 public void readExternal(Element keymapElement, Keymap[] existingKeymaps) throws InvalidDataException {
577 // Check and convert parameters
578 if(!KEY_MAP.equals(keymapElement.getName())){
579 throw new InvalidDataException("unknown element: "+keymapElement);
581 if(keymapElement.getAttributeValue(VERSION_ATTRIBUTE)==null){
582 Converter01.convert(keymapElement);
585 String parentName = keymapElement.getAttributeValue(PARENT_ATTRIBUTE);
586 if(parentName != null) {
587 for (Keymap existingKeymap : existingKeymaps) {
588 if (parentName.equals(existingKeymap.getName())) {
589 myParent = (KeymapImpl)existingKeymap;
590 myCanModify = true;
591 break;
595 myName = keymapElement.getAttributeValue(NAME_ATTRIBUTE);
597 HashMap<String,ArrayList<Shortcut>> id2shortcuts=new HashMap<String, ArrayList<Shortcut>>();
598 for (final Object o : keymapElement.getChildren()) {
599 Element actionElement = (Element)o;
600 if (ACTION.equals(actionElement.getName())) {
601 String id = actionElement.getAttributeValue(ID_ATTRIBUTE);
602 if (id == null) {
603 throw new InvalidDataException("Attribute 'id' cannot be null; Keymap's name=" + myName);
605 id2shortcuts.put(id, new ArrayList<Shortcut>(1));
606 for (final Object o1 : actionElement.getChildren()) {
607 Element shortcutElement = (Element)o1;
608 if (KEYBOARD_SHORTCUT.equals(shortcutElement.getName())) {
610 // Parse first keystroke
612 KeyStroke firstKeyStroke;
613 String firstKeyStrokeStr = shortcutElement.getAttributeValue(FIRST_KEYSTROKE_ATTRIBUTE);
614 if (firstKeyStrokeStr != null) {
615 firstKeyStroke = ActionManagerEx.getKeyStroke(firstKeyStrokeStr);
616 if (firstKeyStroke == null) {
617 throw new InvalidDataException(
618 "Cannot parse first-keystroke: '" + firstKeyStrokeStr + "'; " + "Action's id=" + id + "; Keymap's name=" + myName);
621 else {
622 throw new InvalidDataException("Attribute 'first-keystroke' cannot be null; Action's id=" + id + "; Keymap's name=" + myName);
625 // Parse second keystroke
627 KeyStroke secondKeyStroke = null;
628 String secondKeyStrokeStr = shortcutElement.getAttributeValue(SECOND_KEYSTROKE_ATTRIBUTE);
629 if (secondKeyStrokeStr != null) {
630 secondKeyStroke = ActionManagerEx.getKeyStroke(secondKeyStrokeStr);
631 if (secondKeyStroke == null) {
632 throw new InvalidDataException(
633 "Wrong second-keystroke: '" + secondKeyStrokeStr + "'; Action's id=" + id + "; Keymap's name=" + myName);
636 Shortcut shortcut = new KeyboardShortcut(firstKeyStroke, secondKeyStroke);
637 ArrayList<Shortcut> shortcuts = id2shortcuts.get(id);
638 shortcuts.add(shortcut);
639 } else if (KEYBOARD_GESTURE_SHORTCUT.equals(shortcutElement.getName())) {
640 KeyStroke stroke = null;
641 KeyboardGestureAction.ModifierType modifier = null;
642 final String strokeText = shortcutElement.getAttributeValue(KEYBOARD_GESTURE_KEY);
643 if (strokeText != null) {
644 stroke = ActionManagerEx.getKeyStroke(strokeText);
647 final String modifierText = shortcutElement.getAttributeValue(KEYBOARD_GESTURE_MODIFIER);
648 if (KeyboardGestureAction.ModifierType.dblClick.toString().equalsIgnoreCase(modifierText)) {
649 modifier = KeyboardGestureAction.ModifierType.dblClick;
650 } else if (KeyboardGestureAction.ModifierType.hold.toString().equalsIgnoreCase(modifierText)) {
651 modifier = KeyboardGestureAction.ModifierType.hold;
654 if (stroke == null) {
655 throw new InvalidDataException("Wrong keystroke=" + strokeText + " action id=" + id + " keymap=" + myName);
657 if (modifier == null) {
658 throw new InvalidDataException("Wrong modifier=" + modifierText + " action id=" + id + " keymap=" + myName);
661 Shortcut shortcut = KeyboardModifierGestureShortuct.newInstance(modifier, stroke);
662 final ArrayList<Shortcut> shortcuts = id2shortcuts.get(id);
663 shortcuts.add(shortcut);
664 } else if (MOUSE_SHORTCUT.equals(shortcutElement.getName())) {
665 String keystrokeString = shortcutElement.getAttributeValue(KEYSTROKE_ATTRIBUTE);
666 if (keystrokeString == null) {
667 throw new InvalidDataException("Attribute 'keystroke' cannot be null; Action's id=" + id + "; Keymap's name=" + myName);
670 try {
671 MouseShortcut shortcut = KeymapUtil.parseMouseShortcut(keystrokeString);
672 ArrayList<Shortcut> shortcuts = id2shortcuts.get(id);
673 shortcuts.add(shortcut);
675 catch (InvalidDataException exc) {
676 throw new InvalidDataException(
677 "Wrong mouse-shortcut: '" + keystrokeString + "'; Action's id=" + id + "; Keymap's name=" + myName);
680 else {
681 throw new InvalidDataException("unknown element: " + shortcutElement + "; Keymap's name=" + myName);
685 else {
686 throw new InvalidDataException("unknown element: " + actionElement + "; Keymap's name=" + myName);
689 // Add read shortcuts
690 for (String id : id2shortcuts.keySet()) {
691 myActionId2ListOfShortcuts
692 .put(id, new ArrayList<Shortcut>(2)); // It's a trick! After that paren's shortcuts are not added to the keymap
693 ArrayList<Shortcut> shortcuts = id2shortcuts.get(id);
694 for (Shortcut shortcut : shortcuts) {
695 addShortcutSilently(id, shortcut, false);
700 public Element writeExternal() {
701 Element keymapElement = new Element(KEY_MAP);
702 keymapElement.setAttribute(VERSION_ATTRIBUTE,Integer.toString(1));
703 keymapElement.setAttribute(NAME_ATTRIBUTE, myName);
705 if(myParent != null) {
706 keymapElement.setAttribute(PARENT_ATTRIBUTE, myParent.getName());
708 writeOwnActionIds(keymapElement);
709 return keymapElement;
712 private void writeOwnActionIds(final Element keymapElement) {
713 String[] ownActionIds = getOwnActionIds();
714 Arrays.sort(ownActionIds);
715 for (String actionId : ownActionIds) {
716 Element actionElement = new Element(ACTION);
717 actionElement.setAttribute(ID_ATTRIBUTE, actionId);
718 // Save keyboad shortcuts
719 Shortcut[] shortcuts = getShortcuts(actionId);
720 for (Shortcut shortcut : shortcuts) {
721 if (shortcut instanceof KeyboardShortcut) {
722 KeyboardShortcut keyboardShortcut = (KeyboardShortcut)shortcut;
723 Element element = new Element(KEYBOARD_SHORTCUT);
724 element.setAttribute(FIRST_KEYSTROKE_ATTRIBUTE, getKeyShortcutString(keyboardShortcut.getFirstKeyStroke()));
725 if (keyboardShortcut.getSecondKeyStroke() != null) {
726 element.setAttribute(SECOND_KEYSTROKE_ATTRIBUTE, getKeyShortcutString(keyboardShortcut.getSecondKeyStroke()));
728 actionElement.addContent(element);
730 else if (shortcut instanceof MouseShortcut) {
731 MouseShortcut mouseShortcut = (MouseShortcut)shortcut;
732 Element element = new Element(MOUSE_SHORTCUT);
733 element.setAttribute(KEYSTROKE_ATTRIBUTE, getMouseShortcutString(mouseShortcut));
734 actionElement.addContent(element);
736 else if (shortcut instanceof KeyboardModifierGestureShortuct) {
737 final KeyboardModifierGestureShortuct gesture = (KeyboardModifierGestureShortuct)shortcut;
738 final Element element = new Element(KEYBOARD_GESTURE_SHORTCUT);
739 element.setAttribute(KEYBOARD_GESTURE_SHORTCUT, getKeyShortcutString(gesture.getStroke()));
740 element.setAttribute(KEYBOARD_GESTURE_MODIFIER, gesture.getType().name());
741 actionElement.addContent(element);
742 } else {
743 throw new IllegalStateException("unknown shortcut class: " + shortcut);
746 keymapElement.addContent(actionElement);
750 private static boolean areShortcutsEqual(Shortcut[] shortcuts1, Shortcut[] shortcuts2) {
751 if(shortcuts1.length != shortcuts2.length) {
752 return false;
754 for (Shortcut shortcut : shortcuts1) {
755 Shortcut parentShortcutEqual = null;
756 for (Shortcut parentShortcut : shortcuts2) {
757 if (shortcut.equals(parentShortcut)) {
758 parentShortcutEqual = parentShortcut;
759 break;
762 if (parentShortcutEqual == null) {
763 return false;
766 return true;
770 * @return string representation of passed keystroke.
772 private static String getKeyShortcutString(KeyStroke keyStroke) {
773 StringBuffer buf = new StringBuffer();
774 int modifiers = keyStroke.getModifiers();
775 if((modifiers & InputEvent.SHIFT_MASK) != 0) {
776 buf.append(SHIFT);
777 buf.append(' ');
779 if((modifiers & InputEvent.CTRL_MASK) != 0) {
780 buf.append(CONTROL);
781 buf.append(' ');
783 if((modifiers & InputEvent.META_MASK) != 0) {
784 buf.append(META);
785 buf.append(' ');
787 if((modifiers & InputEvent.ALT_MASK) != 0) {
788 buf.append(ALT);
789 buf.append(' ');
791 if((modifiers & InputEvent.ALT_GRAPH_MASK) != 0) {
792 buf.append(ALT_GRAPH);
793 buf.append(' ');
796 buf.append(ourNamesForKeycodes.get(new Integer(keyStroke.getKeyCode())));
798 return buf.toString();
802 * @return string representation of passed mouse shortcut. This method should
803 * be used only for serializing of the <code>MouseShortcut</code>
805 private static String getMouseShortcutString(MouseShortcut shortcut){
806 StringBuffer buffer=new StringBuffer();
808 // modifiers
810 int modifiers=shortcut.getModifiers();
811 if((MouseEvent.SHIFT_DOWN_MASK&modifiers)>0){
812 buffer.append(SHIFT);
813 buffer.append(' ');
815 if((MouseEvent.CTRL_DOWN_MASK&modifiers)>0){
816 buffer.append(CONTROL);
817 buffer.append(' ');
819 if((MouseEvent.META_DOWN_MASK&modifiers)>0){
820 buffer.append(META);
821 buffer.append(' ');
823 if((MouseEvent.ALT_DOWN_MASK&modifiers)>0){
824 buffer.append(ALT);
825 buffer.append(' ');
827 if((MouseEvent.ALT_GRAPH_DOWN_MASK&modifiers)>0){
828 buffer.append(ALT_GRAPH);
829 buffer.append(' ');
832 // button
834 int button=shortcut.getButton();
835 if(MouseEvent.BUTTON1==button){
836 buffer.append(BUTTON1);
837 buffer.append(' ');
838 }else if(MouseEvent.BUTTON2==button){
839 buffer.append(BUTTON2);
840 buffer.append(' ');
841 }else if(MouseEvent.BUTTON3==button){
842 buffer.append(BUTTON3);
843 buffer.append(' ');
844 }else{
845 throw new IllegalStateException("unknown button: "+button);
848 if(shortcut.getClickCount()>1){
849 buffer.append(DOUBLE_CLICK);
851 return buffer.toString().trim(); // trim trailing space (if any)
855 * @return IDs of the action which are specified in the keymap. It doesn't
856 * return IDs of action from parent keymap.
858 public String[] getOwnActionIds() {
859 return myActionId2ListOfShortcuts.keySet().toArray(new String[myActionId2ListOfShortcuts.size()]);
862 public void clearOwnActionsIds(){
863 myActionId2ListOfShortcuts.clear();
866 public String[] getActionIds() {
867 ArrayList<String> ids = new ArrayList<String>();
868 if (myParent != null) {
869 String[] parentIds = getParentActionIds();
870 for (String id : parentIds) {
871 ids.add(id);
874 String[] ownActionIds = getOwnActionIds();
875 for (String id : ownActionIds) {
876 if (!ids.contains(id)) {
877 ids.add(id);
880 return ArrayUtil.toStringArray(ids);
883 protected String[] getParentActionIds() {
884 return myParent.getActionIds();
888 public HashMap<String, ArrayList<KeyboardShortcut>> getConflicts(String actionId, KeyboardShortcut keyboardShortcut) {
889 HashMap<String, ArrayList<KeyboardShortcut>> result = new HashMap<String, ArrayList<KeyboardShortcut>>();
891 String[] actionIds = getActionIds(keyboardShortcut.getFirstKeyStroke());
892 for (String id : actionIds) {
893 if (id.equals(actionId)) {
894 continue;
897 if (actionId.startsWith(EDITOR_ACTION_PREFIX) && id.equals("$" + actionId.substring(6))) {
898 continue;
900 if (StringUtil.startsWithChar(actionId, '$') && id.equals(EDITOR_ACTION_PREFIX + actionId.substring(1))) {
901 continue;
904 Shortcut[] shortcuts = getShortcuts(id);
905 for (Shortcut shortcut1 : shortcuts) {
906 if (!(shortcut1 instanceof KeyboardShortcut)) {
907 continue;
910 KeyboardShortcut shortcut = (KeyboardShortcut)shortcut1;
912 if (!shortcut.getFirstKeyStroke().equals(keyboardShortcut.getFirstKeyStroke())) {
913 continue;
916 if (keyboardShortcut.getSecondKeyStroke() != null && shortcut.getSecondKeyStroke() != null &&
917 !keyboardShortcut.getSecondKeyStroke().equals(shortcut.getSecondKeyStroke())) {
918 continue;
921 ArrayList<KeyboardShortcut> list = result.get(id);
922 if (list == null) {
923 list = new ArrayList<KeyboardShortcut>();
924 result.put(id, list);
927 list.add(shortcut);
931 return result;
934 public void addShortcutChangeListener(Keymap.Listener listener) {
935 myListeners.add(listener);
938 public void removeShortcutChangeListener(Keymap.Listener listener) {
939 myListeners.remove(listener);
942 private void fireShortcutChanged(String actionId) {
943 Keymap.Listener[] listeners = myListeners.toArray(new Keymap.Listener[myListeners.size()]);
944 for (Listener listener : listeners) {
945 listener.onShortcutChanged(actionId);
949 @NotNull
950 public ExternalInfo getExternalInfo() {
951 return myExternalInfo;