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
;
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
;
47 * @author Eugene Belyaev
48 * @author Anton Katilin
49 * @author Vladimir Kondratyev
51 public class KeymapImpl
implements Keymap
, ExternalizableScheme
{
53 private static final String KEY_MAP
= "keymap";
55 private static final String KEYBOARD_SHORTCUT
= "keyboard-shortcut";
57 private static final String KEYBOARD_GESTURE_SHORTCUT
= "keyboard-gesture-shortcut";
59 private static final String KEYBOARD_GESTURE_KEY
= "keystroke";
61 private static final String KEYBOARD_GESTURE_MODIFIER
= "modifier";
63 private static final String KEYSTROKE_ATTRIBUTE
= "keystroke";
65 private static final String FIRST_KEYSTROKE_ATTRIBUTE
= "first-keystroke";
67 private static final String SECOND_KEYSTROKE_ATTRIBUTE
= "second-keystroke";
69 private static final String ACTION
= "action";
71 private static final String VERSION_ATTRIBUTE
= "version";
73 private static final String PARENT_ATTRIBUTE
= "parent";
75 private static final String NAME_ATTRIBUTE
= "name";
77 private static final String ID_ATTRIBUTE
= "id";
79 private static final String TRUE_WORD
= "true";
81 private static final String FALSE_WORD
= "false";
83 private static final String DISABLE_MNEMONICS_ATTRIBUTE
= "disable-mnemonics";
85 private static final String MOUSE_SHORTCUT
= "mouse-shortcut";
87 private static final String SHIFT
= "shift";
89 private static final String CONTROL
= "control";
91 private static final String META
= "meta";
93 private static final String ALT
= "alt";
95 private static final String ALT_GRAPH
= "altGraph";
97 private static final String BUTTON1
= "button1";
99 private static final String BUTTON2
= "button2";
101 private static final String BUTTON3
= "button3";
103 private static final String DOUBLE_CLICK
= "doubleClick";
105 private static final String VIRTUAL_KEY_PREFIX
= "VK_";
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();
138 ourNamesForKeycodes
= new HashMap
<Integer
, String
>();
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
) {
154 public String
getName() {
158 public String
getPresentableName() {
162 public void setName(String name
) {
167 public KeymapImpl
deriveKeymap() {
169 KeymapImpl newKeymap
= new KeymapImpl();
171 newKeymap
.myParent
= this;
172 newKeymap
.myName
= null;
173 newKeymap
.myCanModify
= canModify();
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
);
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;
215 public int hashCode(){
218 hashCode
+=myName
.hashCode();
223 public Keymap
getParent() {
227 public boolean canModify() {
231 public void setCanModify(boolean 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
);
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
);
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
);
277 for(int i
=0; i
<list
.size(); i
++) {
278 if(shortcut
.equals(list
.get(i
))) {
280 if (myParent
!= null && areShortcutsEqual(getParentShortcuts(actionId
), getShortcuts(actionId
))) {
281 myActionId2ListOfShortcuts
.remove(actionId
);
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())) {
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
);
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
);
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
)) {
435 list
= new ArrayList
<String
>();
436 originalListInstance
= false;
438 else if (originalListInstance
) {
439 list
= (ArrayList
<String
>)list
.clone();
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
)) {
461 list
= new ArrayList
<String
>();
462 originalListInstance
= false;
464 else if (originalListInstance
) {
465 list
= (ArrayList
<String
>)list
.clone();
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
)) {
485 if (Comparing
.equal(firstKeyStroke
, ((KeyboardShortcut
)shortcut
).getFirstKeyStroke()) &&
486 Comparing
.equal(secondKeyStroke
, ((KeyboardShortcut
)shortcut
).getSecondKeyStroke())) {
487 actualBindings
.add(id
);
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
));
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
)) {
526 list
= new ArrayList
<String
>();
527 originalListInstance
= false;
529 else if (originalListInstance
) {
530 list
= (ArrayList
<String
>)list
.clone();
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());
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
);
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
;
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
);
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
);
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
);
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
);
681 throw new InvalidDataException("unknown element: " + shortcutElement
+ "; Keymap's name=" + myName
);
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
);
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
) {
754 for (Shortcut shortcut
: shortcuts1
) {
755 Shortcut parentShortcutEqual
= null;
756 for (Shortcut parentShortcut
: shortcuts2
) {
757 if (shortcut
.equals(parentShortcut
)) {
758 parentShortcutEqual
= parentShortcut
;
762 if (parentShortcutEqual
== null) {
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) {
779 if((modifiers
& InputEvent
.CTRL_MASK
) != 0) {
783 if((modifiers
& InputEvent
.META_MASK
) != 0) {
787 if((modifiers
& InputEvent
.ALT_MASK
) != 0) {
791 if((modifiers
& InputEvent
.ALT_GRAPH_MASK
) != 0) {
792 buf
.append(ALT_GRAPH
);
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();
810 int modifiers
=shortcut
.getModifiers();
811 if((MouseEvent
.SHIFT_DOWN_MASK
&modifiers
)>0){
812 buffer
.append(SHIFT
);
815 if((MouseEvent
.CTRL_DOWN_MASK
&modifiers
)>0){
816 buffer
.append(CONTROL
);
819 if((MouseEvent
.META_DOWN_MASK
&modifiers
)>0){
823 if((MouseEvent
.ALT_DOWN_MASK
&modifiers
)>0){
827 if((MouseEvent
.ALT_GRAPH_DOWN_MASK
&modifiers
)>0){
828 buffer
.append(ALT_GRAPH
);
834 int button
=shortcut
.getButton();
835 if(MouseEvent
.BUTTON1
==button
){
836 buffer
.append(BUTTON1
);
838 }else if(MouseEvent
.BUTTON2
==button
){
839 buffer
.append(BUTTON2
);
841 }else if(MouseEvent
.BUTTON3
==button
){
842 buffer
.append(BUTTON3
);
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
) {
874 String
[] ownActionIds
= getOwnActionIds();
875 for (String id
: ownActionIds
) {
876 if (!ids
.contains(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
)) {
897 if (actionId
.startsWith(EDITOR_ACTION_PREFIX
) && id
.equals("$" + actionId
.substring(6))) {
900 if (StringUtil
.startsWithChar(actionId
, '$') && id
.equals(EDITOR_ACTION_PREFIX
+ actionId
.substring(1))) {
904 Shortcut
[] shortcuts
= getShortcuts(id
);
905 for (Shortcut shortcut1
: shortcuts
) {
906 if (!(shortcut1
instanceof KeyboardShortcut
)) {
910 KeyboardShortcut shortcut
= (KeyboardShortcut
)shortcut1
;
912 if (!shortcut
.getFirstKeyStroke().equals(keyboardShortcut
.getFirstKeyStroke())) {
916 if (keyboardShortcut
.getSecondKeyStroke() != null && shortcut
.getSecondKeyStroke() != null &&
917 !keyboardShortcut
.getSecondKeyStroke().equals(shortcut
.getSecondKeyStroke())) {
921 ArrayList
<KeyboardShortcut
> list
= result
.get(id
);
923 list
= new ArrayList
<KeyboardShortcut
>();
924 result
.put(id
, list
);
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
);
950 public ExternalInfo
getExternalInfo() {
951 return myExternalInfo
;