/jEdit/branches/4.4.x-merge-request-for-r19201/org/gjt/sp/jedit/gui/KeyEventTranslator.java

# · Java · 532 lines · 365 code · 41 blank · 126 comment · 85 complexity · d54c1a0ff7d75f097bfa8c186935966b MD5 · raw file

  1. /*
  2. * KeyEventTranslator.java - Hides some warts of AWT event API
  3. * :tabSize=8:indentSize=8:noTabs=false:
  4. * :folding=explicit:collapseFolds=1:
  5. *
  6. * Copyright (C) 2003, 2005 Slava Pestov
  7. *
  8. * This program is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU General Public License
  10. * as published by the Free Software Foundation; either version 2
  11. * of the License, or any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program; if not, write to the Free Software
  20. * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  21. */
  22. package org.gjt.sp.jedit.gui;
  23. //{{{ Imports
  24. import java.awt.event.*;
  25. import java.util.HashMap;
  26. import java.util.Map;
  27. import org.gjt.sp.jedit.Debug;
  28. import org.gjt.sp.jedit.OperatingSystem;
  29. import org.gjt.sp.util.Log;
  30. import org.gjt.sp.util.StandardUtilities;
  31. //}}}
  32. /**
  33. * In conjunction with the <code>KeyEventWorkaround</code>, hides some
  34. * warts in the AWT key event API.
  35. *
  36. * @author Slava Pestov
  37. * @version $Id: KeyEventTranslator.java 17816 2010-05-12 15:00:13Z k_satoda $
  38. */
  39. public class KeyEventTranslator
  40. {
  41. //{{{ addTranslation() method
  42. /**
  43. * Adds a keyboard translation.
  44. * @param key1 Translate this key
  45. * @param key2 Into this key
  46. * @since jEdit 4.2pre3
  47. */
  48. public static void addTranslation(Key key1, Key key2)
  49. {
  50. transMap.put(key1,key2);
  51. } //}}}
  52. //{{{ translateKeyEvent() method
  53. protected static KeyEvent lastKeyPressEvent;
  54. protected static boolean lastKeyPressAccepted;
  55. /**
  56. * Pass this an event from {@link
  57. * KeyEventWorkaround#processKeyEvent(java.awt.event.KeyEvent)}.
  58. * @param evt the KeyEvent to translate
  59. * @since jEdit 4.2pre3
  60. */
  61. public static Key translateKeyEvent(KeyEvent evt)
  62. {
  63. int modifiers = evt.getModifiers();
  64. Key returnValue;
  65. switch(evt.getID())
  66. {
  67. case KeyEvent.KEY_PRESSED:
  68. int keyCode = evt.getKeyCode();
  69. if((keyCode >= KeyEvent.VK_0
  70. && keyCode <= KeyEvent.VK_9)
  71. || (keyCode >= KeyEvent.VK_A
  72. && keyCode <= KeyEvent.VK_Z))
  73. {
  74. if(Debug.ALTERNATIVE_DISPATCHER)
  75. return null;
  76. else
  77. {
  78. returnValue = new Key(
  79. modifiersToString(modifiers),
  80. '\0',Character.toLowerCase(
  81. (char)keyCode));
  82. }
  83. }
  84. else
  85. {
  86. if(keyCode == KeyEvent.VK_TAB)
  87. {
  88. evt.consume();
  89. returnValue = new Key(
  90. modifiersToString(modifiers),
  91. keyCode,'\0');
  92. }
  93. else if(keyCode == KeyEvent.VK_SPACE)
  94. {
  95. // for SPACE or S+SPACE we pass the
  96. // key typed since international
  97. // keyboards sometimes produce a
  98. // KEY_PRESSED SPACE but not a
  99. // KEY_TYPED SPACE, eg if you have to
  100. // do a "<space> to insert ".
  101. if((modifiers & ~InputEvent.SHIFT_MASK) == 0)
  102. returnValue = null;
  103. else if (Debug.ALTERNATIVE_DISPATCHER && (modifiers & ~InputEvent.META_MASK) == 0)
  104. returnValue = null;
  105. else
  106. {
  107. returnValue = new Key(
  108. modifiersToString(modifiers),
  109. 0,' ');
  110. }
  111. }
  112. else
  113. {
  114. returnValue = new Key(
  115. modifiersToString(modifiers),
  116. keyCode,'\0');
  117. }
  118. }
  119. break;
  120. case KeyEvent.KEY_TYPED:
  121. char ch = evt.getKeyChar();
  122. if(KeyEventWorkaround.isMacControl(evt))
  123. ch |= 0x60;
  124. switch(ch)
  125. {
  126. case '\n':
  127. case '\t':
  128. case '\b':
  129. return null;
  130. case ' ':
  131. if (Debug.ALTERNATIVE_DISPATCHER && (modifiers & ~InputEvent.META_MASK) == 0)
  132. returnValue = new Key(
  133. modifiersToString(modifiers),
  134. 0,' ');
  135. else if((modifiers & ~InputEvent.SHIFT_MASK) != 0)
  136. return null;
  137. }
  138. int ignoreMods;
  139. if(Debug.ALT_KEY_PRESSED_DISABLED)
  140. {
  141. /* on MacOS, A+ can be user input */
  142. ignoreMods = InputEvent.SHIFT_MASK
  143. | InputEvent.ALT_GRAPH_MASK
  144. | InputEvent.ALT_MASK;
  145. }
  146. else
  147. {
  148. /* on MacOS, A+ can be user input */
  149. ignoreMods = InputEvent.SHIFT_MASK
  150. | InputEvent.ALT_GRAPH_MASK;
  151. }
  152. if((modifiers & InputEvent.ALT_GRAPH_MASK) == 0
  153. && (modifiers & ~ignoreMods) != 0)
  154. {
  155. if(Debug.ALTERNATIVE_DISPATCHER)
  156. {
  157. returnValue = new Key(
  158. modifiersToString(modifiers),
  159. 0,ch);
  160. }
  161. else
  162. return null;
  163. }
  164. else
  165. {
  166. if(ch == ' ')
  167. {
  168. returnValue = new Key(
  169. modifiersToString(modifiers),
  170. 0,ch);
  171. }
  172. else
  173. returnValue = new Key(null,0,ch);
  174. }
  175. break;
  176. default:
  177. return null;
  178. }
  179. /* I guess translated events do not have the 'evt' field set
  180. so consuming won't work. I don't think this is a problem as
  181. nothing uses translation anyway */
  182. Key trans = transMap.get(returnValue);
  183. if(trans == null)
  184. return returnValue;
  185. else
  186. return trans;
  187. }
  188. /**
  189. * Pass this an event from {@link
  190. * KeyEventWorkaround#processKeyEvent(java.awt.event.KeyEvent)}.
  191. * @param evt the KeyEvent to translate
  192. * @since jEdit 4.2pre3
  193. * @deprecated
  194. * This gives completely same result with translateKeyEvent()
  195. * since jEdit 4.4pre1.
  196. */
  197. @Deprecated
  198. public static Key translateKeyEvent2(KeyEvent evt)
  199. {
  200. return translateKeyEvent(evt);
  201. } //}}}
  202. //{{{ parseKey() method
  203. /**
  204. * Converts a string to a keystroke. The string should be of the
  205. * form <i>modifiers</i>+<i>shortcut</i> where <i>modifiers</i>
  206. * is any combination of A for Alt, C for Control, S for Shift
  207. * or M for Meta, and <i>shortcut</i> is either a single character,
  208. * or a keycode name from the <code>KeyEvent</code> class, without
  209. * the <code>VK_</code> prefix.
  210. * @param keyStroke A string description of the key stroke
  211. * @since jEdit 4.2pre3
  212. */
  213. public static Key parseKey(String keyStroke)
  214. {
  215. if(keyStroke == null)
  216. return null;
  217. int modifiers = 0;
  218. String key;
  219. int endOfModifiers = keyStroke.indexOf('+');
  220. if(endOfModifiers <= 0) // not found or found at first
  221. {
  222. key = keyStroke;
  223. }
  224. else
  225. {
  226. for(int i = 0; i < endOfModifiers; i++)
  227. {
  228. switch(Character.toUpperCase(keyStroke
  229. .charAt(i)))
  230. {
  231. case 'A':
  232. modifiers |= a;
  233. break;
  234. case 'C':
  235. modifiers |= c;
  236. break;
  237. case 'M':
  238. modifiers |= m;
  239. break;
  240. case 'S':
  241. modifiers |= s;
  242. break;
  243. }
  244. }
  245. key = keyStroke.substring(endOfModifiers + 1);
  246. }
  247. if(key.length() == 1)
  248. {
  249. return new Key(modifiersToString(modifiers),0,key.charAt(0));
  250. }
  251. else if(key.length() == 0)
  252. {
  253. Log.log(Log.ERROR,KeyEventTranslator.class,
  254. "Invalid key stroke: " + keyStroke);
  255. return null;
  256. }
  257. else if(key.equals("SPACE"))
  258. {
  259. return new Key(modifiersToString(modifiers),0,' ');
  260. }
  261. else
  262. {
  263. int ch;
  264. try
  265. {
  266. ch = KeyEvent.class.getField("VK_".concat(key))
  267. .getInt(null);
  268. }
  269. catch(Exception e)
  270. {
  271. Log.log(Log.ERROR,KeyEventTranslator.class,
  272. "Invalid key stroke: "
  273. + keyStroke);
  274. return null;
  275. }
  276. return new Key(modifiersToString(modifiers),ch,'\0');
  277. }
  278. } //}}}
  279. //{{{ setModifierMapping() method
  280. /**
  281. * Changes the mapping between symbolic modifier key names
  282. * (<code>C</code>, <code>A</code>, <code>M</code>, <code>S</code>) and
  283. * Java modifier flags.
  284. *
  285. * You can map more than one Java modifier to a symobolic modifier, for
  286. * example :
  287. * <p><code><pre>
  288. * setModifierMapping(
  289. * InputEvent.CTRL_MASK,
  290. * InputEvent.ALT_MASK | InputEvent.META_MASK,
  291. * 0,
  292. * InputEvent.SHIFT_MASK);
  293. *<pre></code></p>
  294. *
  295. * You cannot map a Java modifer to more than one symbolic modifier.
  296. *
  297. * @param c The modifier(s) to map the <code>C</code> modifier to
  298. * @param a The modifier(s) to map the <code>A</code> modifier to
  299. * @param m The modifier(s) to map the <code>M</code> modifier to
  300. * @param s The modifier(s) to map the <code>S</code> modifier to
  301. *
  302. * @since jEdit 4.2pre3
  303. */
  304. public static void setModifierMapping(int c, int a, int m, int s)
  305. {
  306. int duplicateMapping =
  307. (c & a) | (c & m) | (c & s) | (a & m) | (a & s) | (m & s);
  308. if((duplicateMapping & InputEvent.CTRL_MASK) != 0)
  309. {
  310. throw new IllegalArgumentException(
  311. "CTRL is mapped to more than one modifier");
  312. }
  313. if((duplicateMapping & InputEvent.ALT_MASK) != 0)
  314. {
  315. throw new IllegalArgumentException(
  316. "ALT is mapped to more than one modifier");
  317. }
  318. if((duplicateMapping & InputEvent.META_MASK) != 0)
  319. {
  320. throw new IllegalArgumentException(
  321. "META is mapped to more than one modifier");
  322. }
  323. if((duplicateMapping & InputEvent.SHIFT_MASK) != 0)
  324. {
  325. throw new IllegalArgumentException(
  326. "SHIFT is mapped to more than one modifier");
  327. }
  328. KeyEventTranslator.c = c;
  329. KeyEventTranslator.a = a;
  330. KeyEventTranslator.m = m;
  331. KeyEventTranslator.s = s;
  332. } //}}}
  333. //{{{ getSymbolicModifierName() method
  334. /**
  335. * Returns a the symbolic modifier name for the specified Java modifier
  336. * flag.
  337. *
  338. * @param mod A modifier constant from <code>InputEvent</code>
  339. *
  340. * @since jEdit 4.2pre3
  341. */
  342. public static char getSymbolicModifierName(int mod)
  343. {
  344. if((mod & c) != 0)
  345. return 'C';
  346. else if((mod & a) != 0)
  347. return 'A';
  348. else if((mod & m) != 0)
  349. return 'M';
  350. else if((mod & s) != 0)
  351. return 'S';
  352. else
  353. return '\0';
  354. } //}}}
  355. //{{{ modifiersToString() method
  356. private static final int[] MODS = {
  357. InputEvent.CTRL_MASK,
  358. InputEvent.ALT_MASK,
  359. InputEvent.META_MASK,
  360. InputEvent.SHIFT_MASK
  361. };
  362. public static String modifiersToString(int mods)
  363. {
  364. StringBuilder buf = null;
  365. for(int i = 0; i < MODS.length; i++)
  366. {
  367. if((mods & MODS[i]) != 0)
  368. buf = lazyAppend(buf,getSymbolicModifierName(MODS[i]));
  369. }
  370. if(buf == null)
  371. return null;
  372. else
  373. return buf.toString();
  374. } //}}}
  375. //{{{ getModifierString() method
  376. /**
  377. * Returns a string containing symbolic modifier names set in the
  378. * specified event.
  379. *
  380. * @param evt The event
  381. *
  382. * @since jEdit 4.2pre3
  383. */
  384. public static String getModifierString(InputEvent evt)
  385. {
  386. StringBuilder buf = new StringBuilder();
  387. if(evt.isControlDown())
  388. buf.append(getSymbolicModifierName(InputEvent.CTRL_MASK));
  389. if(evt.isAltDown())
  390. buf.append(getSymbolicModifierName(InputEvent.ALT_MASK));
  391. if(evt.isMetaDown())
  392. buf.append(getSymbolicModifierName(InputEvent.META_MASK));
  393. if(evt.isShiftDown())
  394. buf.append(getSymbolicModifierName(InputEvent.SHIFT_MASK));
  395. return buf.length() == 0 ? null : buf.toString();
  396. } //}}}
  397. static int c, a, m, s;
  398. //{{{ Private members
  399. /** This map is a pool of Key. */
  400. private static final Map<Key, Key> transMap = new HashMap<Key, Key>();
  401. private static StringBuilder lazyAppend(StringBuilder buf, char ch)
  402. {
  403. if(buf == null)
  404. buf = new StringBuilder();
  405. if(buf.indexOf(String.valueOf(ch)) == -1)
  406. buf.append(ch);
  407. return buf;
  408. } //}}}
  409. static
  410. {
  411. if(OperatingSystem.isMacOS())
  412. {
  413. setModifierMapping(
  414. InputEvent.META_MASK, /* == C+ */
  415. InputEvent.CTRL_MASK, /* == A+ */
  416. /* M+ discarded by key event workaround! */
  417. InputEvent.ALT_MASK, /* == M+ */
  418. InputEvent.SHIFT_MASK /* == S+ */);
  419. }
  420. else
  421. {
  422. setModifierMapping(
  423. InputEvent.CTRL_MASK,
  424. InputEvent.ALT_MASK,
  425. InputEvent.META_MASK,
  426. InputEvent.SHIFT_MASK);
  427. }
  428. } //}}}
  429. //{{{ Key class
  430. public static class Key
  431. {
  432. public final String modifiers;
  433. public final int key;
  434. public final char input;
  435. private final int hashCode;
  436. /**
  437. Wether this Key event applies to all jEdit windows (and not only a specific jEdit GUI component).
  438. */
  439. protected boolean isFromGlobalContext;
  440. public Key(String modifiers, int key, char input)
  441. {
  442. this.modifiers = modifiers;
  443. this.key = key;
  444. this.input = input;
  445. hashCode = key + input;
  446. }
  447. @Override
  448. public int hashCode()
  449. {
  450. return hashCode;
  451. }
  452. @Override
  453. public boolean equals(Object o)
  454. {
  455. if(o instanceof Key)
  456. {
  457. Key k = (Key)o;
  458. if(StandardUtilities.objectsEqual(modifiers,
  459. k.modifiers) && key == k.key
  460. && input == k.input)
  461. {
  462. return true;
  463. }
  464. }
  465. return false;
  466. }
  467. @Override
  468. public String toString()
  469. {
  470. return (modifiers == null ? "" : modifiers)
  471. + '<'
  472. + Integer.toString(key,16)
  473. + ','
  474. + Integer.toString(input,16)
  475. + '>';
  476. }
  477. public void setIsFromGlobalContext(boolean to)
  478. {
  479. isFromGlobalContext = to;
  480. }
  481. public boolean isFromGlobalContext()
  482. {
  483. return isFromGlobalContext;
  484. }
  485. @Deprecated
  486. public boolean isPhantom()
  487. {
  488. return false;
  489. }
  490. } //}}}
  491. }