/platform/platform-impl/src/com/intellij/openapi/keymap/impl/KeymapImpl.java

https://bitbucket.org/nbargnesi/idea · Java · 908 lines · 738 code · 109 blank · 61 comment · 208 complexity · c73cf617b8551951002d29531f0f08a6 MD5 · raw file

  1. /*
  2. * Copyright 2000-2009 JetBrains s.r.o.
  3. *
  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
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  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.
  15. */
  16. package com.intellij.openapi.keymap.impl;
  17. import com.intellij.openapi.actionSystem.*;
  18. import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
  19. import com.intellij.openapi.application.ApplicationManager;
  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.SystemInfo;
  29. import com.intellij.openapi.util.text.StringUtil;
  30. import com.intellij.util.ArrayUtil;
  31. import com.intellij.util.containers.ContainerUtil;
  32. import com.intellij.util.containers.HashMap;
  33. import gnu.trove.THashMap;
  34. import org.jdom.Element;
  35. import org.jetbrains.annotations.NonNls;
  36. import org.jetbrains.annotations.NotNull;
  37. import javax.swing.*;
  38. import java.awt.event.InputEvent;
  39. import java.awt.event.KeyEvent;
  40. import java.awt.event.MouseEvent;
  41. import java.lang.reflect.Field;
  42. import java.util.*;
  43. /**
  44. * @author Eugene Belyaev
  45. * @author Anton Katilin
  46. * @author Vladimir Kondratyev
  47. */
  48. public class KeymapImpl implements Keymap, ExternalizableScheme {
  49. private static final Logger LOG = Logger.getInstance("#com.intellij.keymap.KeymapImpl");
  50. @NonNls private static final String KEY_MAP = "keymap";
  51. @NonNls private static final String KEYBOARD_SHORTCUT = "keyboard-shortcut";
  52. @NonNls private static final String KEYBOARD_GESTURE_SHORTCUT = "keyboard-gesture-shortcut";
  53. @NonNls private static final String KEYBOARD_GESTURE_KEY = "keystroke";
  54. @NonNls private static final String KEYBOARD_GESTURE_MODIFIER = "modifier";
  55. @NonNls private static final String KEYSTROKE_ATTRIBUTE = "keystroke";
  56. @NonNls private static final String FIRST_KEYSTROKE_ATTRIBUTE = "first-keystroke";
  57. @NonNls private static final String SECOND_KEYSTROKE_ATTRIBUTE = "second-keystroke";
  58. @NonNls private static final String ACTION = "action";
  59. @NonNls private static final String VERSION_ATTRIBUTE = "version";
  60. @NonNls private static final String PARENT_ATTRIBUTE = "parent";
  61. @NonNls private static final String NAME_ATTRIBUTE = "name";
  62. @NonNls private static final String ID_ATTRIBUTE = "id";
  63. @NonNls private static final String MOUSE_SHORTCUT = "mouse-shortcut";
  64. @NonNls private static final String SHIFT = "shift";
  65. @NonNls private static final String CONTROL = "control";
  66. @NonNls private static final String META = "meta";
  67. @NonNls private static final String ALT = "alt";
  68. @NonNls private static final String ALT_GRAPH = "altGraph";
  69. @NonNls private static final String DOUBLE_CLICK = "doubleClick";
  70. @NonNls private static final String VIRTUAL_KEY_PREFIX = "VK_";
  71. @NonNls private static final String EDITOR_ACTION_PREFIX = "Editor";
  72. private String myName;
  73. private KeymapImpl myParent;
  74. private boolean myCanModify = true;
  75. private final Map<String, LinkedHashSet<Shortcut>> myActionId2ListOfShortcuts = new THashMap<String, LinkedHashSet<Shortcut>>();
  76. /**
  77. * Don't use this field directly! Use it only through <code>getKeystroke2ListOfIds</code>.
  78. */
  79. private Map<KeyStroke, List<String>> myKeystroke2ListOfIds = null;
  80. private Map<KeyboardModifierGestureShortcut, List<String>> myGesture2ListOfIds = null;
  81. // TODO[vova,anton] it should be final member
  82. /**
  83. * Don't use this field directly! Use it only through <code>getMouseShortcut2ListOfIds</code>.
  84. */
  85. private Map<MouseShortcut, List<String>> myMouseShortcut2ListOfIds = null;
  86. // TODO[vova,anton] it should be final member
  87. private static final Map<Integer, String> ourNamesForKeycodes;
  88. private static final Shortcut[] ourEmptyShortcutsArray = new Shortcut[0];
  89. private final List<Listener> myListeners = new ArrayList<Listener>();
  90. private KeymapManagerEx myKeymapManager;
  91. private final ExternalInfo myExternalInfo = new ExternalInfo();
  92. static {
  93. ourNamesForKeycodes = new HashMap<Integer, String>();
  94. try {
  95. Field[] fields = KeyEvent.class.getDeclaredFields();
  96. for (Field field : fields) {
  97. String fieldName = field.getName();
  98. if (fieldName.startsWith(VIRTUAL_KEY_PREFIX)) {
  99. int keyCode = field.getInt(KeyEvent.class);
  100. ourNamesForKeycodes.put(keyCode, fieldName.substring(3));
  101. }
  102. }
  103. }
  104. catch (Exception e) {
  105. LOG.error(e);
  106. }
  107. }
  108. public String getName() {
  109. return myName;
  110. }
  111. public String getPresentableName() {
  112. return getName();
  113. }
  114. public void setName(String name) {
  115. myName = name;
  116. }
  117. public KeymapImpl deriveKeymap() {
  118. if (canModify()) {
  119. return copy(false);
  120. }
  121. else {
  122. KeymapImpl newKeymap = new KeymapImpl();
  123. newKeymap.myParent = this;
  124. newKeymap.myName = null;
  125. newKeymap.myCanModify = canModify();
  126. return newKeymap;
  127. }
  128. }
  129. public KeymapImpl copy(boolean copyExternalInfo) {
  130. KeymapImpl newKeymap = new KeymapImpl();
  131. newKeymap.myParent = myParent;
  132. newKeymap.myName = myName;
  133. newKeymap.myCanModify = canModify();
  134. newKeymap.myKeystroke2ListOfIds = null;
  135. newKeymap.myMouseShortcut2ListOfIds = null;
  136. for (Map.Entry<String, LinkedHashSet<Shortcut>> entry : myActionId2ListOfShortcuts.entrySet()) {
  137. LinkedHashSet<Shortcut> list = entry.getValue();
  138. String key = entry.getKey();
  139. newKeymap.myActionId2ListOfShortcuts.put(key, new LinkedHashSet<Shortcut>(list));
  140. }
  141. if (copyExternalInfo) {
  142. newKeymap.myExternalInfo.copy(myExternalInfo);
  143. }
  144. return newKeymap;
  145. }
  146. public boolean equals(Object object) {
  147. if (!(object instanceof Keymap)) return false;
  148. KeymapImpl secondKeymap = (KeymapImpl)object;
  149. if (!Comparing.equal(myName, secondKeymap.myName)) return false;
  150. if (myCanModify != secondKeymap.myCanModify) return false;
  151. if (!Comparing.equal(myParent, secondKeymap.myParent)) return false;
  152. if (!Comparing.equal(myActionId2ListOfShortcuts, secondKeymap.myActionId2ListOfShortcuts)) return false;
  153. return true;
  154. }
  155. public int hashCode() {
  156. int hashCode = 0;
  157. if (myName != null) {
  158. hashCode += myName.hashCode();
  159. }
  160. return hashCode;
  161. }
  162. public Keymap getParent() {
  163. return myParent;
  164. }
  165. public boolean canModify() {
  166. return myCanModify;
  167. }
  168. public void setCanModify(boolean val) {
  169. myCanModify = val;
  170. }
  171. protected Shortcut[] getParentShortcuts(String actionId) {
  172. return myParent.getShortcuts(actionId);
  173. }
  174. public void addShortcut(String actionId, Shortcut shortcut) {
  175. addShortcutSilently(actionId, shortcut, true);
  176. fireShortcutChanged(actionId);
  177. }
  178. private void addShortcutSilently(String actionId, Shortcut shortcut, final boolean checkParentShortcut) {
  179. LinkedHashSet<Shortcut> list = myActionId2ListOfShortcuts.get(actionId);
  180. if (list == null) {
  181. list = new LinkedHashSet<Shortcut>();
  182. myActionId2ListOfShortcuts.put(actionId, list);
  183. if (myParent != null) {
  184. // copy parent shortcuts for this actionId
  185. Shortcut[] shortcuts = getParentShortcuts(actionId);
  186. // shortcuts are immutables
  187. ContainerUtil.addAll(list, shortcuts);
  188. }
  189. }
  190. list.add(shortcut);
  191. if (checkParentShortcut && myParent != null && areShortcutsEqual(getParentShortcuts(actionId), getShortcuts(actionId))) {
  192. myActionId2ListOfShortcuts.remove(actionId);
  193. }
  194. myKeystroke2ListOfIds = null;
  195. myMouseShortcut2ListOfIds = null;
  196. }
  197. public void removeAllActionShortcuts(String actionId) {
  198. Shortcut[] allShortcuts = getShortcuts(actionId);
  199. for (Shortcut shortcut : allShortcuts) {
  200. removeShortcut(actionId, shortcut);
  201. }
  202. }
  203. public void removeShortcut(String actionId, Shortcut shortcut) {
  204. LinkedHashSet<Shortcut> list = myActionId2ListOfShortcuts.get(actionId);
  205. if (list != null) {
  206. Iterator<Shortcut> it = list.iterator();
  207. while (it.hasNext()) {
  208. Shortcut each = it.next();
  209. if (shortcut.equals(each)) {
  210. it.remove();
  211. if (myParent != null && areShortcutsEqual(getParentShortcuts(actionId), getShortcuts(actionId))) {
  212. myActionId2ListOfShortcuts.remove(actionId);
  213. }
  214. break;
  215. }
  216. }
  217. }
  218. else if (myParent != null) {
  219. // put to the map the parent's bindings except for the removed binding
  220. Shortcut[] parentShortcuts = getParentShortcuts(actionId);
  221. LinkedHashSet<Shortcut> listOfShortcuts = new LinkedHashSet<Shortcut>();
  222. for (Shortcut parentShortcut : parentShortcuts) {
  223. if (!shortcut.equals(parentShortcut)) {
  224. listOfShortcuts.add(parentShortcut);
  225. }
  226. }
  227. myActionId2ListOfShortcuts.put(actionId, listOfShortcuts);
  228. }
  229. myKeystroke2ListOfIds = null;
  230. myMouseShortcut2ListOfIds = null;
  231. fireShortcutChanged(actionId);
  232. }
  233. private Map<KeyStroke, List<String>> getKeystroke2ListOfIds() {
  234. if (myKeystroke2ListOfIds != null) return myKeystroke2ListOfIds;
  235. myKeystroke2ListOfIds = new THashMap<KeyStroke, List<String>>();
  236. for (String id : ContainerUtil.concat(myActionId2ListOfShortcuts.keySet(), getKeymapManager().getBoundActions())) {
  237. addKeystrokesMap(id, myKeystroke2ListOfIds);
  238. }
  239. return myKeystroke2ListOfIds;
  240. }
  241. private Map<KeyboardModifierGestureShortcut, List<String>> getGesture2ListOfIds() {
  242. if (myGesture2ListOfIds == null) {
  243. myGesture2ListOfIds = new THashMap<KeyboardModifierGestureShortcut, List<String>>();
  244. fillShortcut2ListOfIds(myGesture2ListOfIds, KeyboardModifierGestureShortcut.class);
  245. }
  246. return myGesture2ListOfIds;
  247. }
  248. private <T extends Shortcut>void fillShortcut2ListOfIds(final Map<T,List<String>> map, final Class<T> shortcutClass) {
  249. for (String id : ContainerUtil.concat(myActionId2ListOfShortcuts.keySet(), getKeymapManager().getBoundActions())) {
  250. addAction2ShortcutsMap(id, map, shortcutClass);
  251. }
  252. }
  253. private Map<MouseShortcut, List<String>> getMouseShortcut2ListOfIds() {
  254. if (myMouseShortcut2ListOfIds == null) {
  255. myMouseShortcut2ListOfIds = new THashMap<MouseShortcut, List<String>>();
  256. fillShortcut2ListOfIds(myMouseShortcut2ListOfIds, MouseShortcut.class);
  257. }
  258. return myMouseShortcut2ListOfIds;
  259. }
  260. private <T extends Shortcut>void addAction2ShortcutsMap(final String actionId, final Map<T, List<String>> strokesMap, final Class<T> shortcutClass) {
  261. LinkedHashSet<Shortcut> listOfShortcuts = _getShortcuts(actionId);
  262. for (Shortcut shortcut : listOfShortcuts) {
  263. if (!shortcutClass.isAssignableFrom(shortcut.getClass())) {
  264. continue;
  265. }
  266. @SuppressWarnings({"unchecked"})
  267. T t = (T)shortcut;
  268. List<String> listOfIds = strokesMap.get(t);
  269. if (listOfIds == null) {
  270. listOfIds = new ArrayList<String>();
  271. strokesMap.put(t, listOfIds);
  272. }
  273. // action may have more that 1 shortcut with same first keystroke
  274. if (!listOfIds.contains(actionId)) {
  275. listOfIds.add(actionId);
  276. }
  277. }
  278. }
  279. private void addKeystrokesMap(final String actionId, final Map<KeyStroke, List<String>> strokesMap) {
  280. LinkedHashSet<Shortcut> listOfShortcuts = _getShortcuts(actionId);
  281. for (Shortcut shortcut : listOfShortcuts) {
  282. if (!(shortcut instanceof KeyboardShortcut)) {
  283. continue;
  284. }
  285. KeyStroke firstKeyStroke = ((KeyboardShortcut)shortcut).getFirstKeyStroke();
  286. List<String> listOfIds = strokesMap.get(firstKeyStroke);
  287. if (listOfIds == null) {
  288. listOfIds = new ArrayList<String>();
  289. strokesMap.put(firstKeyStroke, listOfIds);
  290. }
  291. // action may have more that 1 shortcut with same first keystroke
  292. if (!listOfIds.contains(actionId)) {
  293. listOfIds.add(actionId);
  294. }
  295. }
  296. }
  297. private LinkedHashSet<Shortcut> _getShortcuts(final String actionId) {
  298. KeymapManagerEx keymapManager = getKeymapManager();
  299. LinkedHashSet<Shortcut> listOfShortcuts = myActionId2ListOfShortcuts.get(actionId);
  300. if (listOfShortcuts != null) {
  301. return listOfShortcuts;
  302. }
  303. else {
  304. listOfShortcuts = new LinkedHashSet<Shortcut>();
  305. }
  306. final String actionBinding = keymapManager.getActionBinding(actionId);
  307. if (actionBinding != null) {
  308. listOfShortcuts.addAll(_getShortcuts(actionBinding));
  309. }
  310. return listOfShortcuts;
  311. }
  312. protected String[] getParentActionIds(KeyStroke firstKeyStroke) {
  313. return myParent.getActionIds(firstKeyStroke);
  314. }
  315. protected String[] getParentActionIds(KeyboardModifierGestureShortcut gesture) {
  316. return myParent.getActionIds(gesture);
  317. }
  318. private String[] getActionIds(KeyboardModifierGestureShortcut shortcut) {
  319. // first, get keystrokes from own map
  320. final Map<KeyboardModifierGestureShortcut, List<String>> map = getGesture2ListOfIds();
  321. List<String> list = new ArrayList<String>();
  322. for (Map.Entry<KeyboardModifierGestureShortcut, List<String>> entry : map.entrySet()) {
  323. if (shortcut.startsWith(entry.getKey())) {
  324. list.addAll(entry.getValue());
  325. }
  326. }
  327. if (myParent != null) {
  328. String[] ids = getParentActionIds(shortcut);
  329. if (ids.length > 0) {
  330. for (String id : ids) {
  331. // add actions from parent keymap only if they are absent in this keymap
  332. if (!myActionId2ListOfShortcuts.containsKey(id)) {
  333. list.add(id);
  334. }
  335. }
  336. }
  337. }
  338. return sortInOrderOfRegistration(ArrayUtil.toStringArray(list));
  339. }
  340. public String[] getActionIds(KeyStroke firstKeyStroke) {
  341. // first, get keystrokes from own map
  342. List<String> list = getKeystroke2ListOfIds().get(firstKeyStroke);
  343. if (myParent != null) {
  344. String[] ids = getParentActionIds(firstKeyStroke);
  345. if (ids.length > 0) {
  346. boolean originalListInstance = true;
  347. for (String id : ids) {
  348. // add actions from parent keymap only if they are absent in this keymap
  349. if (!myActionId2ListOfShortcuts.containsKey(id)) {
  350. if (list == null) {
  351. list = new ArrayList<String>();
  352. originalListInstance = false;
  353. }
  354. else if (originalListInstance) {
  355. list = new ArrayList<String>(list);
  356. originalListInstance = false;
  357. }
  358. if (!list.contains(id)) list.add(id);
  359. }
  360. }
  361. }
  362. }
  363. if (list == null) return ArrayUtil.EMPTY_STRING_ARRAY;
  364. return sortInOrderOfRegistration(ArrayUtil.toStringArray(list));
  365. }
  366. public String[] getActionIds(KeyStroke firstKeyStroke, KeyStroke secondKeyStroke) {
  367. String[] ids = getActionIds(firstKeyStroke);
  368. ArrayList<String> actualBindings = new ArrayList<String>();
  369. for (String id : ids) {
  370. Shortcut[] shortcuts = getShortcuts(id);
  371. for (Shortcut shortcut : shortcuts) {
  372. if (!(shortcut instanceof KeyboardShortcut)) {
  373. continue;
  374. }
  375. if (Comparing.equal(firstKeyStroke, ((KeyboardShortcut)shortcut).getFirstKeyStroke()) &&
  376. Comparing.equal(secondKeyStroke, ((KeyboardShortcut)shortcut).getSecondKeyStroke())) {
  377. actualBindings.add(id);
  378. break;
  379. }
  380. }
  381. }
  382. return ArrayUtil.toStringArray(actualBindings);
  383. }
  384. public String[] getActionIds(final Shortcut shortcut) {
  385. if (shortcut instanceof KeyboardShortcut) {
  386. final KeyboardShortcut kb = (KeyboardShortcut)shortcut;
  387. final KeyStroke first = kb.getFirstKeyStroke();
  388. final KeyStroke second = kb.getSecondKeyStroke();
  389. return second != null ? getActionIds(first, second) : getActionIds(first);
  390. }
  391. else if (shortcut instanceof MouseShortcut) {
  392. return getActionIds((MouseShortcut)shortcut);
  393. }
  394. else if (shortcut instanceof KeyboardModifierGestureShortcut) {
  395. return getActionIds((KeyboardModifierGestureShortcut)shortcut);
  396. }
  397. else {
  398. return ArrayUtil.EMPTY_STRING_ARRAY;
  399. }
  400. }
  401. protected String[] getParentActionIds(MouseShortcut shortcut) {
  402. return myParent.getActionIds(shortcut);
  403. }
  404. public String[] getActionIds(MouseShortcut shortcut) {
  405. // first, get shortcuts from own map
  406. List<String> list = getMouseShortcut2ListOfIds().get(shortcut);
  407. if (myParent != null) {
  408. String[] ids = getParentActionIds(shortcut);
  409. if (ids.length > 0) {
  410. boolean originalListInstance = true;
  411. for (String id : ids) {
  412. // add actions from parent keymap only if they are absent in this keymap
  413. if (!myActionId2ListOfShortcuts.containsKey(id)) {
  414. if (list == null) {
  415. list = new ArrayList<String>();
  416. originalListInstance = false;
  417. }
  418. else if (originalListInstance) {
  419. list = new ArrayList<String>(list);
  420. }
  421. list.add(id);
  422. }
  423. }
  424. }
  425. }
  426. if (list == null) {
  427. return ArrayUtil.EMPTY_STRING_ARRAY;
  428. }
  429. return sortInOrderOfRegistration(ArrayUtil.toStringArray(list));
  430. }
  431. private static String[] sortInOrderOfRegistration(String[] ids) {
  432. Arrays.sort(ids, ActionManagerEx.getInstanceEx().getRegistrationOrderComparator());
  433. return ids;
  434. }
  435. public boolean isActionBound(@NotNull final String actionId) {
  436. return getKeymapManager().getBoundActions().contains(actionId);
  437. }
  438. public Shortcut[] getShortcuts(String actionId) {
  439. KeymapManagerEx keymapManager = getKeymapManager();
  440. if (keymapManager.getBoundActions().contains(actionId)) {
  441. return getShortcuts(keymapManager.getActionBinding(actionId));
  442. }
  443. LinkedHashSet<Shortcut> shortcuts = myActionId2ListOfShortcuts.get(actionId);
  444. if (shortcuts == null) {
  445. if (myParent != null) {
  446. return getParentShortcuts(actionId);
  447. }
  448. else {
  449. return ourEmptyShortcutsArray;
  450. }
  451. }
  452. return shortcuts.isEmpty() ? ourEmptyShortcutsArray : shortcuts.toArray(new Shortcut[shortcuts.size()]);
  453. }
  454. private KeymapManagerEx getKeymapManager() {
  455. if (myKeymapManager == null) {
  456. myKeymapManager = KeymapManagerEx.getInstanceEx();
  457. }
  458. return myKeymapManager;
  459. }
  460. /**
  461. * @param keymapElement element which corresponds to "keymap" tag.
  462. */
  463. public void readExternal(Element keymapElement, Keymap[] existingKeymaps) throws InvalidDataException {
  464. // Check and convert parameters
  465. if (!KEY_MAP.equals(keymapElement.getName())) {
  466. throw new InvalidDataException("unknown element: " + keymapElement);
  467. }
  468. if (keymapElement.getAttributeValue(VERSION_ATTRIBUTE) == null) {
  469. Converter01.convert(keymapElement);
  470. }
  471. //
  472. String parentName = keymapElement.getAttributeValue(PARENT_ATTRIBUTE);
  473. if (parentName != null) {
  474. for (Keymap existingKeymap : existingKeymaps) {
  475. if (parentName.equals(existingKeymap.getName())) {
  476. myParent = (KeymapImpl)existingKeymap;
  477. myCanModify = true;
  478. break;
  479. }
  480. }
  481. }
  482. myName = keymapElement.getAttributeValue(NAME_ATTRIBUTE);
  483. Map<String, ArrayList<Shortcut>> id2shortcuts = new HashMap<String, ArrayList<Shortcut>>();
  484. final boolean skipInserts = SystemInfo.isMac && !ApplicationManager.getApplication().isUnitTestMode();
  485. for (final Object o : keymapElement.getChildren()) {
  486. Element actionElement = (Element)o;
  487. if (ACTION.equals(actionElement.getName())) {
  488. String id = actionElement.getAttributeValue(ID_ATTRIBUTE);
  489. if (id == null) {
  490. throw new InvalidDataException("Attribute 'id' cannot be null; Keymap's name=" + myName);
  491. }
  492. id2shortcuts.put(id, new ArrayList<Shortcut>(1));
  493. for (final Object o1 : actionElement.getChildren()) {
  494. Element shortcutElement = (Element)o1;
  495. if (KEYBOARD_SHORTCUT.equals(shortcutElement.getName())) {
  496. // Parse first keystroke
  497. KeyStroke firstKeyStroke;
  498. String firstKeyStrokeStr = shortcutElement.getAttributeValue(FIRST_KEYSTROKE_ATTRIBUTE);
  499. if (skipInserts && firstKeyStrokeStr.contains("INSERT")) continue;
  500. if (firstKeyStrokeStr != null) {
  501. firstKeyStroke = ActionManagerEx.getKeyStroke(firstKeyStrokeStr);
  502. if (firstKeyStroke == null) {
  503. throw new InvalidDataException(
  504. "Cannot parse first-keystroke: '" + firstKeyStrokeStr + "'; " + "Action's id=" + id + "; Keymap's name=" + myName);
  505. }
  506. }
  507. else {
  508. throw new InvalidDataException("Attribute 'first-keystroke' cannot be null; Action's id=" + id + "; Keymap's name=" + myName);
  509. }
  510. // Parse second keystroke
  511. KeyStroke secondKeyStroke = null;
  512. String secondKeyStrokeStr = shortcutElement.getAttributeValue(SECOND_KEYSTROKE_ATTRIBUTE);
  513. if (secondKeyStrokeStr != null) {
  514. secondKeyStroke = ActionManagerEx.getKeyStroke(secondKeyStrokeStr);
  515. if (secondKeyStroke == null) {
  516. throw new InvalidDataException(
  517. "Wrong second-keystroke: '" + secondKeyStrokeStr + "'; Action's id=" + id + "; Keymap's name=" + myName);
  518. }
  519. }
  520. Shortcut shortcut = new KeyboardShortcut(firstKeyStroke, secondKeyStroke);
  521. ArrayList<Shortcut> shortcuts = id2shortcuts.get(id);
  522. shortcuts.add(shortcut);
  523. }
  524. else if (KEYBOARD_GESTURE_SHORTCUT.equals(shortcutElement.getName())) {
  525. KeyStroke stroke = null;
  526. final String strokeText = shortcutElement.getAttributeValue(KEYBOARD_GESTURE_KEY);
  527. if (strokeText != null) {
  528. stroke = ActionManagerEx.getKeyStroke(strokeText);
  529. }
  530. final String modifierText = shortcutElement.getAttributeValue(KEYBOARD_GESTURE_MODIFIER);
  531. KeyboardGestureAction.ModifierType modifier = null;
  532. if (KeyboardGestureAction.ModifierType.dblClick.toString().equalsIgnoreCase(modifierText)) {
  533. modifier = KeyboardGestureAction.ModifierType.dblClick;
  534. }
  535. else if (KeyboardGestureAction.ModifierType.hold.toString().equalsIgnoreCase(modifierText)) {
  536. modifier = KeyboardGestureAction.ModifierType.hold;
  537. }
  538. if (stroke == null) {
  539. throw new InvalidDataException("Wrong keystroke=" + strokeText + " action id=" + id + " keymap=" + myName);
  540. }
  541. if (modifier == null) {
  542. throw new InvalidDataException("Wrong modifier=" + modifierText + " action id=" + id + " keymap=" + myName);
  543. }
  544. Shortcut shortcut = KeyboardModifierGestureShortcut.newInstance(modifier, stroke);
  545. final ArrayList<Shortcut> shortcuts = id2shortcuts.get(id);
  546. shortcuts.add(shortcut);
  547. }
  548. else if (MOUSE_SHORTCUT.equals(shortcutElement.getName())) {
  549. String keystrokeString = shortcutElement.getAttributeValue(KEYSTROKE_ATTRIBUTE);
  550. if (keystrokeString == null) {
  551. throw new InvalidDataException("Attribute 'keystroke' cannot be null; Action's id=" + id + "; Keymap's name=" + myName);
  552. }
  553. try {
  554. MouseShortcut shortcut = KeymapUtil.parseMouseShortcut(keystrokeString);
  555. ArrayList<Shortcut> shortcuts = id2shortcuts.get(id);
  556. shortcuts.add(shortcut);
  557. }
  558. catch (InvalidDataException exc) {
  559. throw new InvalidDataException(
  560. "Wrong mouse-shortcut: '" + keystrokeString + "'; Action's id=" + id + "; Keymap's name=" + myName);
  561. }
  562. }
  563. else {
  564. throw new InvalidDataException("unknown element: " + shortcutElement + "; Keymap's name=" + myName);
  565. }
  566. }
  567. }
  568. else {
  569. throw new InvalidDataException("unknown element: " + actionElement + "; Keymap's name=" + myName);
  570. }
  571. }
  572. // Add read shortcuts
  573. for (String id : id2shortcuts.keySet()) {
  574. myActionId2ListOfShortcuts.put(id, new LinkedHashSet<Shortcut>(2)); // It's a trick! After that parent's shortcuts are not added to the keymap
  575. ArrayList<Shortcut> shortcuts = id2shortcuts.get(id);
  576. for (Shortcut shortcut : shortcuts) {
  577. addShortcutSilently(id, shortcut, false);
  578. }
  579. }
  580. }
  581. public Element writeExternal() {
  582. Element keymapElement = new Element(KEY_MAP);
  583. keymapElement.setAttribute(VERSION_ATTRIBUTE, Integer.toString(1));
  584. keymapElement.setAttribute(NAME_ATTRIBUTE, myName);
  585. if (myParent != null) {
  586. keymapElement.setAttribute(PARENT_ATTRIBUTE, myParent.getName());
  587. }
  588. writeOwnActionIds(keymapElement);
  589. return keymapElement;
  590. }
  591. private void writeOwnActionIds(final Element keymapElement) {
  592. String[] ownActionIds = getOwnActionIds();
  593. Arrays.sort(ownActionIds);
  594. for (String actionId : ownActionIds) {
  595. Element actionElement = new Element(ACTION);
  596. actionElement.setAttribute(ID_ATTRIBUTE, actionId);
  597. // Save keyboad shortcuts
  598. Shortcut[] shortcuts = getShortcuts(actionId);
  599. for (Shortcut shortcut : shortcuts) {
  600. if (shortcut instanceof KeyboardShortcut) {
  601. KeyboardShortcut keyboardShortcut = (KeyboardShortcut)shortcut;
  602. Element element = new Element(KEYBOARD_SHORTCUT);
  603. element.setAttribute(FIRST_KEYSTROKE_ATTRIBUTE, getKeyShortcutString(keyboardShortcut.getFirstKeyStroke()));
  604. if (keyboardShortcut.getSecondKeyStroke() != null) {
  605. element.setAttribute(SECOND_KEYSTROKE_ATTRIBUTE, getKeyShortcutString(keyboardShortcut.getSecondKeyStroke()));
  606. }
  607. actionElement.addContent(element);
  608. }
  609. else if (shortcut instanceof MouseShortcut) {
  610. MouseShortcut mouseShortcut = (MouseShortcut)shortcut;
  611. Element element = new Element(MOUSE_SHORTCUT);
  612. element.setAttribute(KEYSTROKE_ATTRIBUTE, getMouseShortcutString(mouseShortcut));
  613. actionElement.addContent(element);
  614. }
  615. else if (shortcut instanceof KeyboardModifierGestureShortcut) {
  616. final KeyboardModifierGestureShortcut gesture = (KeyboardModifierGestureShortcut)shortcut;
  617. final Element element = new Element(KEYBOARD_GESTURE_SHORTCUT);
  618. element.setAttribute(KEYBOARD_GESTURE_SHORTCUT, getKeyShortcutString(gesture.getStroke()));
  619. element.setAttribute(KEYBOARD_GESTURE_MODIFIER, gesture.getType().name());
  620. actionElement.addContent(element);
  621. }
  622. else {
  623. throw new IllegalStateException("unknown shortcut class: " + shortcut);
  624. }
  625. }
  626. keymapElement.addContent(actionElement);
  627. }
  628. }
  629. private static boolean areShortcutsEqual(Shortcut[] shortcuts1, Shortcut[] shortcuts2) {
  630. if (shortcuts1.length != shortcuts2.length) {
  631. return false;
  632. }
  633. for (Shortcut shortcut : shortcuts1) {
  634. Shortcut parentShortcutEqual = null;
  635. for (Shortcut parentShortcut : shortcuts2) {
  636. if (shortcut.equals(parentShortcut)) {
  637. parentShortcutEqual = parentShortcut;
  638. break;
  639. }
  640. }
  641. if (parentShortcutEqual == null) {
  642. return false;
  643. }
  644. }
  645. return true;
  646. }
  647. /**
  648. * @return string representation of passed keystroke.
  649. */
  650. public static String getKeyShortcutString(KeyStroke keyStroke) {
  651. StringBuffer buf = new StringBuffer();
  652. int modifiers = keyStroke.getModifiers();
  653. if ((modifiers & InputEvent.SHIFT_MASK) != 0) {
  654. buf.append(SHIFT);
  655. buf.append(' ');
  656. }
  657. if ((modifiers & InputEvent.CTRL_MASK) != 0) {
  658. buf.append(CONTROL);
  659. buf.append(' ');
  660. }
  661. if ((modifiers & InputEvent.META_MASK) != 0) {
  662. buf.append(META);
  663. buf.append(' ');
  664. }
  665. if ((modifiers & InputEvent.ALT_MASK) != 0) {
  666. buf.append(ALT);
  667. buf.append(' ');
  668. }
  669. if ((modifiers & InputEvent.ALT_GRAPH_MASK) != 0) {
  670. buf.append(ALT_GRAPH);
  671. buf.append(' ');
  672. }
  673. buf.append(ourNamesForKeycodes.get(keyStroke.getKeyCode()));
  674. return buf.toString();
  675. }
  676. /**
  677. * @return string representation of passed mouse shortcut. This method should
  678. * be used only for serializing of the <code>MouseShortcut</code>
  679. */
  680. private static String getMouseShortcutString(MouseShortcut shortcut) {
  681. StringBuffer buffer = new StringBuffer();
  682. // modifiers
  683. int modifiers = shortcut.getModifiers();
  684. if ((MouseEvent.SHIFT_DOWN_MASK & modifiers) > 0) {
  685. buffer.append(SHIFT);
  686. buffer.append(' ');
  687. }
  688. if ((MouseEvent.CTRL_DOWN_MASK & modifiers) > 0) {
  689. buffer.append(CONTROL);
  690. buffer.append(' ');
  691. }
  692. if ((MouseEvent.META_DOWN_MASK & modifiers) > 0) {
  693. buffer.append(META);
  694. buffer.append(' ');
  695. }
  696. if ((MouseEvent.ALT_DOWN_MASK & modifiers) > 0) {
  697. buffer.append(ALT);
  698. buffer.append(' ');
  699. }
  700. if ((MouseEvent.ALT_GRAPH_DOWN_MASK & modifiers) > 0) {
  701. buffer.append(ALT_GRAPH);
  702. buffer.append(' ');
  703. }
  704. // button
  705. buffer.append("button").append(shortcut.getButton()).append(' ');
  706. if (shortcut.getClickCount() > 1) {
  707. buffer.append(DOUBLE_CLICK);
  708. }
  709. return buffer.toString().trim(); // trim trailing space (if any)
  710. }
  711. /**
  712. * @return IDs of the action which are specified in the keymap. It doesn't
  713. * return IDs of action from parent keymap.
  714. */
  715. public String[] getOwnActionIds() {
  716. return myActionId2ListOfShortcuts.keySet().toArray(new String[myActionId2ListOfShortcuts.size()]);
  717. }
  718. public void clearOwnActionsIds() {
  719. myActionId2ListOfShortcuts.clear();
  720. }
  721. public String[] getActionIds() {
  722. ArrayList<String> ids = new ArrayList<String>();
  723. if (myParent != null) {
  724. String[] parentIds = getParentActionIds();
  725. ContainerUtil.addAll(ids, parentIds);
  726. }
  727. String[] ownActionIds = getOwnActionIds();
  728. for (String id : ownActionIds) {
  729. if (!ids.contains(id)) {
  730. ids.add(id);
  731. }
  732. }
  733. return ArrayUtil.toStringArray(ids);
  734. }
  735. protected String[] getParentActionIds() {
  736. return myParent.getActionIds();
  737. }
  738. public HashMap<String, ArrayList<KeyboardShortcut>> getConflicts(String actionId, KeyboardShortcut keyboardShortcut) {
  739. HashMap<String, ArrayList<KeyboardShortcut>> result = new HashMap<String, ArrayList<KeyboardShortcut>>();
  740. String[] actionIds = getActionIds(keyboardShortcut.getFirstKeyStroke());
  741. for (String id : actionIds) {
  742. if (id.equals(actionId)) {
  743. continue;
  744. }
  745. if (actionId.startsWith(EDITOR_ACTION_PREFIX) && id.equals("$" + actionId.substring(6))) {
  746. continue;
  747. }
  748. if (StringUtil.startsWithChar(actionId, '$') && id.equals(EDITOR_ACTION_PREFIX + actionId.substring(1))) {
  749. continue;
  750. }
  751. final String useShortcutOf = myKeymapManager.getActionBinding(id);
  752. if (useShortcutOf != null && useShortcutOf.equals(actionId)) {
  753. continue;
  754. }
  755. Shortcut[] shortcuts = getShortcuts(id);
  756. for (Shortcut shortcut1 : shortcuts) {
  757. if (!(shortcut1 instanceof KeyboardShortcut)) {
  758. continue;
  759. }
  760. KeyboardShortcut shortcut = (KeyboardShortcut)shortcut1;
  761. if (!shortcut.getFirstKeyStroke().equals(keyboardShortcut.getFirstKeyStroke())) {
  762. continue;
  763. }
  764. if (keyboardShortcut.getSecondKeyStroke() != null &&
  765. shortcut.getSecondKeyStroke() != null &&
  766. !keyboardShortcut.getSecondKeyStroke().equals(shortcut.getSecondKeyStroke())) {
  767. continue;
  768. }
  769. ArrayList<KeyboardShortcut> list = result.get(id);
  770. if (list == null) {
  771. list = new ArrayList<KeyboardShortcut>();
  772. result.put(id, list);
  773. }
  774. list.add(shortcut);
  775. }
  776. }
  777. return result;
  778. }
  779. public void addShortcutChangeListener(Listener listener) {
  780. myListeners.add(listener);
  781. }
  782. public void removeShortcutChangeListener(Listener listener) {
  783. myListeners.remove(listener);
  784. }
  785. private void fireShortcutChanged(String actionId) {
  786. Listener[] listeners = myListeners.toArray(new Listener[myListeners.size()]);
  787. for (Listener listener : listeners) {
  788. listener.onShortcutChanged(actionId);
  789. }
  790. }
  791. @NotNull
  792. public ExternalInfo getExternalInfo() {
  793. return myExternalInfo;
  794. }
  795. @Override
  796. public String toString() {
  797. return getPresentableName();
  798. }
  799. }