1. /*
  2. * @(#)KeyStroke.java 1.32 01/11/29
  3. *
  4. * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing;
  8. import java.awt.event.KeyEvent;
  9. import java.awt.event.InputEvent;
  10. import java.util.Hashtable;
  11. import java.util.StringTokenizer;
  12. import java.io.Serializable;
  13. /**
  14. * A KeyStroke instance represents a key being typed on the keyboard -- it
  15. * contains both a char code for the key and a modifier (alt, shift, ctrl,
  16. * meta, or a combination).
  17. * <p>
  18. * KeyStroke objects are used to define high-level (semantic) action events.
  19. * Instead of trapping every keystroke and throwing away the ones you are
  20. * not interested in, those keystrokes you care about automatically initiate
  21. * actions on the components they are registered with.
  22. * <p>
  23. * KeyStroke objects handle both character-code generating keystrokes you
  24. * would trap with a KeyTyped event handler and key-code generating keystrokes
  25. * (like Enter or F1) that you would trap with a KeyPressed event handler.
  26. * <p>
  27. * KeyStroke objects are immutable and unique.
  28. * <p>
  29. * All KeyStroke objects are cached. To get one, use <code>getKeyStroke</code>.
  30. * <p>
  31. * <strong>Warning:</strong>
  32. * Serialized objects of this class will not be compatible with
  33. * future Swing releases. The current serialization support is appropriate
  34. * for short term storage or RMI between applications running the same
  35. * version of Swing. A future release of Swing will provide support for
  36. * long term persistence.
  37. *
  38. * @see JComponent#registerKeyboardAction
  39. * @see #getKeyStroke
  40. *
  41. * @version 1.32 11/29/01
  42. * @author Arnaud Weber
  43. */
  44. public class KeyStroke implements Serializable {
  45. private static final Object pressedCharCacheKey =
  46. new StringBuffer("KeyStroke.pressedCharCacheKey");
  47. private static final Object releasedCharCacheKey =
  48. new StringBuffer("KeyStroke.releasedCharCacheKey");
  49. private static final Object pressedCodeCacheKey =
  50. new StringBuffer("KeyStroke.pressedCodeCacheKey");
  51. private static final Object releasedCodeCacheKey =
  52. new StringBuffer("KeyStroke.releasedCodeCacheKey");
  53. char keyChar;
  54. int keyCode;
  55. int modifiers;
  56. boolean onKeyRelease;
  57. /* We cache 114 key stroke with keyChar (1368 bytes) */
  58. /* We cache 114 * 8 key stroke with keyCode and popular modifiers (10944 bytes) */
  59. /* Total cache is around 11K */
  60. static final int MIN_ASCII_CACHE_INDEX = '\n';
  61. static final int MAX_ASCII_CACHE_INDEX = 0x7F;
  62. /* Lock object used in place of class object for synchronization.
  63. * (4187686)
  64. */
  65. private static final Object classLock = new Object();
  66. /* It is impossible to instantiate a KeyStroke. Use getKeyStroke()
  67. * instead */
  68. private KeyStroke() {
  69. }
  70. static KeyStroke getCachedKeyCharKeyStroke(char keyChar,boolean onKeyRelease) {
  71. KeyStroke result = null;
  72. if(keyChar >= MIN_ASCII_CACHE_INDEX && keyChar < MAX_ASCII_CACHE_INDEX) {
  73. synchronized(classLock) {
  74. KeyStroke cache[];
  75. if(onKeyRelease)
  76. cache = (KeyStroke[])SwingUtilities.appContextGet(
  77. releasedCharCacheKey);
  78. else
  79. cache = (KeyStroke[])SwingUtilities.appContextGet(
  80. pressedCharCacheKey);
  81. if(cache != null)
  82. result = cache[((int)keyChar) - MIN_ASCII_CACHE_INDEX];
  83. }
  84. }
  85. return result;
  86. }
  87. static void cacheKeyCharKeyStroke(KeyStroke ks,boolean onKeyRelease) {
  88. if(ks.keyChar >= MIN_ASCII_CACHE_INDEX && ks.keyChar < MAX_ASCII_CACHE_INDEX) {
  89. synchronized(classLock) {
  90. if(onKeyRelease) {
  91. KeyStroke releasedKeyCharKeyStrokeCache[] = (KeyStroke[])
  92. SwingUtilities.appContextGet(releasedCharCacheKey);
  93. if(releasedKeyCharKeyStrokeCache == null) {
  94. releasedKeyCharKeyStrokeCache = new KeyStroke[MAX_ASCII_CACHE_INDEX - MIN_ASCII_CACHE_INDEX];
  95. SwingUtilities.appContextPut(
  96. releasedCharCacheKey, releasedKeyCharKeyStrokeCache);
  97. }
  98. releasedKeyCharKeyStrokeCache[((int)ks.keyChar) - MIN_ASCII_CACHE_INDEX] = ks;
  99. } else {
  100. KeyStroke pressedKeyCharKeyStrokeCache[] = (KeyStroke[])
  101. SwingUtilities.appContextGet(pressedCharCacheKey);
  102. if(pressedKeyCharKeyStrokeCache == null) {
  103. pressedKeyCharKeyStrokeCache = new KeyStroke[MAX_ASCII_CACHE_INDEX - MIN_ASCII_CACHE_INDEX];
  104. SwingUtilities.appContextPut(
  105. pressedCharCacheKey, pressedKeyCharKeyStrokeCache);
  106. }
  107. pressedKeyCharKeyStrokeCache[((int)ks.keyChar) - MIN_ASCII_CACHE_INDEX] = ks;
  108. }
  109. }
  110. }
  111. }
  112. static int subIndexForModifier(int modifiers) {
  113. if(modifiers == 0)
  114. return 0;
  115. else if(modifiers == InputEvent.SHIFT_MASK)
  116. return 1;
  117. else if(modifiers == InputEvent.CTRL_MASK)
  118. return 2;
  119. else if(modifiers == InputEvent.ALT_MASK)
  120. return 3;
  121. return -1;
  122. }
  123. static KeyStroke getCachedKeyStroke(int keyCode,int modifiers,boolean onKeyRelease) {
  124. int subIndex;
  125. KeyStroke result = null;
  126. if(keyCode >= MIN_ASCII_CACHE_INDEX && keyCode < MAX_ASCII_CACHE_INDEX &&
  127. (subIndex = subIndexForModifier(modifiers)) != -1) {
  128. synchronized(classLock) {
  129. KeyStroke cache[][];
  130. if(onKeyRelease)
  131. cache = (KeyStroke[][])SwingUtilities.appContextGet(
  132. pressedCodeCacheKey);
  133. else
  134. cache = (KeyStroke[][])SwingUtilities.appContextGet(
  135. releasedCodeCacheKey);
  136. if(cache != null)
  137. result = cache[subIndex][keyCode - MIN_ASCII_CACHE_INDEX];
  138. }
  139. }
  140. return result;
  141. }
  142. static void cacheKeyStroke(KeyStroke ks) {
  143. int subIndex = -1;
  144. if(ks.keyCode >= MIN_ASCII_CACHE_INDEX && ks.keyCode < MAX_ASCII_CACHE_INDEX &&
  145. (subIndex = subIndexForModifier(ks.modifiers)) != -1) {
  146. synchronized(classLock) {
  147. KeyStroke cache[][] = null;
  148. if(ks.onKeyRelease) {
  149. KeyStroke[][] pressedKeyCodeKeyStrokeCache = (KeyStroke[][])
  150. SwingUtilities.appContextGet(pressedCodeCacheKey);
  151. if(pressedKeyCodeKeyStrokeCache == null) {
  152. pressedKeyCodeKeyStrokeCache = new KeyStroke[4][MAX_ASCII_CACHE_INDEX -
  153. MIN_ASCII_CACHE_INDEX];
  154. SwingUtilities.appContextPut(
  155. pressedCodeCacheKey, pressedKeyCodeKeyStrokeCache);
  156. }
  157. cache = pressedKeyCodeKeyStrokeCache;
  158. } else {
  159. KeyStroke[][] releasedKeyCodeKeyStrokeCache = (KeyStroke[][])
  160. SwingUtilities.appContextGet(releasedCodeCacheKey);
  161. if(releasedKeyCodeKeyStrokeCache == null) {
  162. releasedKeyCodeKeyStrokeCache = new KeyStroke[4][MAX_ASCII_CACHE_INDEX -
  163. MIN_ASCII_CACHE_INDEX];
  164. SwingUtilities.appContextPut(
  165. releasedCodeCacheKey, releasedKeyCodeKeyStrokeCache);
  166. }
  167. cache = releasedKeyCodeKeyStrokeCache;
  168. }
  169. cache[subIndex][ks.keyCode - MIN_ASCII_CACHE_INDEX] = ks;
  170. }
  171. }
  172. }
  173. /**
  174. * Return a shared instance of a key stroke that is
  175. * activated when the key is pressed (i.e. a KeyStroke
  176. * for the KeyEvent.KEY_TYPED event).
  177. *
  178. * @param keyChar the character value for a keyboard key
  179. * @return a KeyStroke object for that key
  180. */
  181. public static KeyStroke getKeyStroke(char keyChar) {
  182. return getKeyStroke(keyChar,false);
  183. }
  184. /**
  185. * Return a shared instance of a key stroke, specifying
  186. * whether the key is considered to be activated when it is
  187. * pressed or when it is released.
  188. *
  189. * @param keyChar the character value for a keyboard key
  190. * @param onKeyRelease a boolean value. When true, specifies that
  191. * the key is active when it is released.
  192. * @return a KeyStroke object for that key
  193. * @deprecated use getKeyStroke(char)
  194. */
  195. public static KeyStroke getKeyStroke(char keyChar,boolean onKeyRelease) {
  196. KeyStroke result = getCachedKeyCharKeyStroke(keyChar,onKeyRelease);
  197. if(result == null) {
  198. result = new KeyStroke();
  199. result.keyChar = keyChar;
  200. result.modifiers = 0;
  201. result.onKeyRelease = onKeyRelease;
  202. cacheKeyCharKeyStroke(result,onKeyRelease);
  203. }
  204. return result;
  205. }
  206. /**
  207. * Return a shared instance of a key stroke given a numeric keycode and a set
  208. * of modifiers, specifying whether the key is activated when it is pressed
  209. * or released.
  210. * <p>
  211. * The "virtual key" constants defined in java.awt.event.KeyEvent can be
  212. * used to specify the key code. For example:<ul>
  213. * <li>java.awt.event.KeyEvent.VK_ENTER
  214. * <li>java.awt.event.KeyEvent.VK_TAB
  215. * <li>java.awt.event.KeyEvent.VK_SPACE
  216. * </ul>
  217. * The modifiers consist of any combination of:<ul>
  218. * <li>java.awt.Event.SHIFT_MASK (1)
  219. * <li>java.awt.Event.CTRL_MASK (2)
  220. * <li>java.awt.Event.META_MASK (4)
  221. * <li>java.awt.Event.ALT_MASK (8)
  222. * </ul>
  223. * Since these numbers are all different powers of two, any combination of
  224. * them is an integer in which each bit represents a different
  225. * modifier key.
  226. *
  227. * @param keyCode an int specifying the numeric code for a keyboard key
  228. * @param modifiers an int specifying any combination of the key modifiers.
  229. * @param onKeyRelease a boolean value. When true, specifies that
  230. * the key is active when it is released.
  231. * @return a KeyStroke object for that key
  232. *
  233. * @see java.awt.event.KeyEvent
  234. * @see java.awt.Event
  235. */
  236. public static KeyStroke getKeyStroke(int keyCode,int modifiers,boolean onKeyRelease) {
  237. KeyStroke result = getCachedKeyStroke(keyCode,modifiers,onKeyRelease);
  238. if(result == null) {
  239. result = new KeyStroke();
  240. result.keyCode = keyCode;
  241. result.modifiers = modifiers;
  242. result.onKeyRelease = onKeyRelease;
  243. cacheKeyStroke(result);
  244. }
  245. return result;
  246. }
  247. /**
  248. * Return a shared instance of a key stroke given a char code and a set
  249. * of modifiers -- the key is activated when it is pressed.
  250. * <p>
  251. * <p>
  252. * The "virtual key" constants defined in java.awt.event.KeyEvent can be
  253. * used to specify the key code. For example:<ul>
  254. * <li>java.awt.event.KeyEvent.VK_ENTER
  255. * <li>java.awt.event.KeyEvent.VK_TAB
  256. * <li>java.awt.event.KeyEvent.VK_SPACE
  257. * </ul>
  258. * The modifiers consist of any combination of:<ul>
  259. * <li>java.awt.Event.SHIFT_MASK (1)
  260. * <li>java.awt.Event.CTRL_MASK (2)
  261. * <li>java.awt.Event.META_MASK (4)
  262. * <li>java.awt.Event.ALT_MASK (8)
  263. * </ul>
  264. * Since these numbers are all different powers of two, any combination of
  265. * them is an integer in which each bit represents a different
  266. * modifier key.
  267. *
  268. * @param keyCode an int specifying the numeric code for a keyboard key
  269. * @param modifiers an int specifying any combination of the key modifiers.
  270. * @return a KeyStroke object for that key
  271. * @see java.awt.event.KeyEvent
  272. */
  273. public static KeyStroke getKeyStroke(int keyCode,int modifiers) {
  274. return getKeyStroke(keyCode,modifiers,false);
  275. }
  276. /**
  277. * Return a keystroke from an event.
  278. * <p>
  279. * This method obtains the keyChar from a KeyTyped event,
  280. * and the keyCode from a KeyPressed or KeyReleased event,
  281. * so you the type of event doesn't matter.
  282. *
  283. * @param anEvent the KeyEvent to obtain the KeyStroke from
  284. * @return the KeyStroke that precipitated the event
  285. */
  286. public static KeyStroke getKeyStrokeForEvent(KeyEvent anEvent) {
  287. KeyStroke ks = null;
  288. switch(anEvent.getID()) {
  289. case KeyEvent.KEY_PRESSED:
  290. ks = getKeyStroke(anEvent.getKeyCode(),anEvent.getModifiers(),false);
  291. break;
  292. case KeyEvent.KEY_RELEASED:
  293. ks = getKeyStroke(anEvent.getKeyCode(),anEvent.getModifiers(),true);
  294. break;
  295. case KeyEvent.KEY_TYPED:
  296. ks = getKeyStroke(anEvent.getKeyChar());
  297. break;
  298. }
  299. return ks;
  300. }
  301. /**
  302. * Parse a string with the following syntax and return an a KeyStroke:
  303. * <pre>
  304. * "<modifiers>* <key>"
  305. * modifiers := shift | control | meta | alt | button1 | button2 | button3
  306. * key := KeyEvent keycode name, i.e. the name following "VK_".
  307. * </pre>
  308. * Here are some examples:
  309. * <pre>
  310. * "INSERT" => new KeyStroke(0, KeyEvent.VK_INSERT);
  311. * "control DELETE" => new KeyStroke(InputEvent.CTRL_MASK, KeyEvent.VK_DELETE);
  312. * "alt shift X" => new KeyStroke(InputEvent.ALT_MASK | InputEvent.SHIFT_MASK, KeyEvent.VK_X);
  313. * </pre>
  314. */
  315. public static KeyStroke getKeyStroke(String s) {
  316. StringTokenizer st = new StringTokenizer(s);
  317. String token;
  318. int mask = 0;
  319. while((token = st.nextToken()) != null) {
  320. int tokenMask = 0;
  321. /* if token matches a modifier keyword update mask and continue */
  322. for(int i = 0; (tokenMask == 0) && (i < modifierKeywords.length); i++) {
  323. tokenMask = modifierKeywords[i].getModifierMask(token);
  324. }
  325. if (tokenMask != 0) {
  326. mask |= tokenMask;
  327. continue;
  328. }
  329. /* otherwise token is the keycode name less the "VK_" prefix */
  330. String keycodeName = versionMap("VK_" + token);
  331. int keycode;
  332. try {
  333. keycode = KeyEvent.class.getField(keycodeName).getInt(KeyEvent.class);
  334. }
  335. catch (Exception e) {
  336. e.printStackTrace();
  337. throw new Error("Unrecognized keycode name: " + keycodeName);
  338. }
  339. return KeyStroke.getKeyStroke(keycode, mask);
  340. }
  341. throw new Error("Can't parse KeyStroke: \"" + s + "\"");
  342. }
  343. private static String versionMap(String s) {
  344. if (!SwingUtilities.is1dot2) {
  345. if (s.equals("VK_KP_UP")) {
  346. s = "VK_UP";
  347. }
  348. if (s.equals("VK_KP_DOWN")) {
  349. s = "VK_DOWN";
  350. }
  351. if (s.equals("VK_KP_LEFT")) {
  352. s = "VK_LEFT";
  353. }
  354. if (s.equals("VK_KP_RIGHT")) {
  355. s = "VK_RIGHT";
  356. }
  357. }
  358. return s;
  359. }
  360. /*
  361. * // see getKeyStroke (String)
  362. */
  363. private static class ModifierKeyword {
  364. final String keyword;
  365. final int mask;
  366. ModifierKeyword(String keyword, int mask) {
  367. this.keyword = keyword;
  368. this.mask = mask;
  369. }
  370. int getModifierMask(String s) {
  371. return (s.equals(keyword)) ? mask : 0;
  372. }
  373. };
  374. /*
  375. * // see getKeyStroke (String)
  376. */
  377. private static ModifierKeyword[] modifierKeywords = {
  378. new ModifierKeyword("shift", InputEvent.SHIFT_MASK),
  379. new ModifierKeyword("control", InputEvent.CTRL_MASK),
  380. new ModifierKeyword("ctrl", InputEvent.CTRL_MASK),
  381. new ModifierKeyword("meta", InputEvent.META_MASK),
  382. new ModifierKeyword("alt", InputEvent.ALT_MASK),
  383. new ModifierKeyword("button1", InputEvent.BUTTON1_MASK),
  384. new ModifierKeyword("button2", InputEvent.BUTTON2_MASK),
  385. new ModifierKeyword("button3", InputEvent.BUTTON3_MASK)
  386. };
  387. /**
  388. * Returns the character defined by this KeyStroke object.
  389. * @return a char value
  390. * @see #getKeyStroke(char)
  391. */
  392. public char getKeyChar() { return keyChar; }
  393. /**
  394. * Returns the numeric keycode defined by this KeyStroke object.
  395. * @return an int containing the keycode value
  396. * @see #getKeyStroke(int,int)
  397. */
  398. public int getKeyCode() { return keyCode; }
  399. /**
  400. * Returns the modifier keys defined by this KeyStroke object.
  401. * @return an int containing the modifiers
  402. * @see #getKeyStroke(int,int)
  403. */
  404. public int getModifiers() { return modifiers; }
  405. /**
  406. * Returns true if this keystroke is active on key release.
  407. * @return true if active on key release, false if active on key press
  408. * @see #getKeyStroke(int,int,boolean)
  409. */
  410. public boolean isOnKeyRelease() { return onKeyRelease; }
  411. private static String getStringRepresentation(char keyChar,int modifiers,boolean kr) {
  412. return "keyChar " + KeyEvent.getKeyModifiersText(modifiers) + keyChar +
  413. (kr?"-R":"-P");
  414. }
  415. private static String getStringRepresentation(int keyCode,int modifiers,boolean kr) {
  416. return "keyCode " + KeyEvent.getKeyModifiersText(modifiers) + KeyEvent.getKeyText(keyCode) +
  417. (kr?"-R":"-P");
  418. }
  419. /**
  420. * Returns a numeric value for this object that is likely to be
  421. * reasonably unique, so it can be used as the index value in a
  422. * Hashtable.
  423. *
  424. * @return an int that "represents" this object
  425. * @see java.util.Hashtable
  426. */
  427. public int hashCode() {
  428. return (((int) keyChar) + 1) * (2 * (keyCode + 1)) * (modifiers+1) +
  429. (onKeyRelease ? 1 : 2);
  430. }
  431. /**
  432. * Returns true if this object is identical to the specified object.
  433. *
  434. * @param anObject the Object to compare this object to
  435. * @return true if the objects are identical
  436. */
  437. public boolean equals(Object anObject) {
  438. if(anObject instanceof KeyStroke) {
  439. KeyStroke ks = (KeyStroke) anObject;
  440. if(ks.keyChar == keyChar && ks.keyCode == keyCode &&
  441. ks.onKeyRelease == onKeyRelease && ks.modifiers == modifiers)
  442. return true;
  443. }
  444. return false;
  445. }
  446. /**
  447. * Returns a string that displays and identifies this
  448. * object's properties.
  449. *
  450. * @return a String representation of this object
  451. */
  452. public String toString() {
  453. if(keyChar == 0)
  454. return getStringRepresentation(keyCode,modifiers,onKeyRelease);
  455. else
  456. return getStringRepresentation(keyChar,0,onKeyRelease);
  457. }
  458. }