/jEdit/branches/4.3.x-fix-view-leak/org/gjt/sp/jedit/gui/KeyEventTranslator.java

# · Java · 544 lines · 370 code · 45 blank · 129 comment · 79 complexity · 47da8f6f8b57221930ba10746ebad9de 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 16341 2009-10-14 10:05:51Z kpouer $
  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. Key key = translateKeyEvent2(evt);
  64. if (key!=null)
  65. {
  66. if (key.isPhantom())
  67. {
  68. key = null;
  69. }
  70. }
  71. return key;
  72. }
  73. /**
  74. * Pass this an event from {@link
  75. * KeyEventWorkaround#processKeyEvent(java.awt.event.KeyEvent)}.
  76. * @param evt the KeyEvent to translate
  77. * @since jEdit 4.2pre3
  78. */
  79. public static Key translateKeyEvent2(KeyEvent evt)
  80. {
  81. int modifiers = evt.getModifiers();
  82. Key returnValue;
  83. switch(evt.getID())
  84. {
  85. case KeyEvent.KEY_PRESSED:
  86. int keyCode = evt.getKeyCode();
  87. if((keyCode >= KeyEvent.VK_0
  88. && keyCode <= KeyEvent.VK_9)
  89. || (keyCode >= KeyEvent.VK_A
  90. && keyCode <= KeyEvent.VK_Z))
  91. {
  92. if(Debug.ALTERNATIVE_DISPATCHER)
  93. return null;
  94. else
  95. {
  96. returnValue = new Key(
  97. modifiersToString(modifiers),
  98. '\0',Character.toLowerCase(
  99. (char)keyCode));
  100. }
  101. }
  102. else
  103. {
  104. if(keyCode == KeyEvent.VK_TAB)
  105. {
  106. evt.consume();
  107. returnValue = new Key(
  108. modifiersToString(modifiers),
  109. keyCode,'\0');
  110. }
  111. else if(keyCode == KeyEvent.VK_SPACE)
  112. {
  113. // for SPACE or S+SPACE we pass the
  114. // key typed since international
  115. // keyboards sometimes produce a
  116. // KEY_PRESSED SPACE but not a
  117. // KEY_TYPED SPACE, eg if you have to
  118. // do a "<space> to insert ".
  119. if((modifiers & ~InputEvent.SHIFT_MASK) == 0)
  120. returnValue = null;
  121. else
  122. {
  123. returnValue = new Key(
  124. modifiersToString(modifiers),
  125. 0,' ');
  126. }
  127. }
  128. else
  129. {
  130. returnValue = new Key(
  131. modifiersToString(modifiers),
  132. keyCode,'\0');
  133. }
  134. }
  135. break;
  136. case KeyEvent.KEY_TYPED:
  137. char ch = evt.getKeyChar();
  138. if(KeyEventWorkaround.isMacControl(evt))
  139. ch |= 0x60;
  140. switch(ch)
  141. {
  142. case '\n':
  143. case '\t':
  144. case '\b':
  145. return null;
  146. case ' ':
  147. if((modifiers & ~InputEvent.SHIFT_MASK) != 0)
  148. return null;
  149. }
  150. int ignoreMods;
  151. if(Debug.ALT_KEY_PRESSED_DISABLED)
  152. {
  153. /* on MacOS, A+ can be user input */
  154. ignoreMods = InputEvent.SHIFT_MASK
  155. | InputEvent.ALT_GRAPH_MASK
  156. | InputEvent.ALT_MASK;
  157. }
  158. else
  159. {
  160. /* on MacOS, A+ can be user input */
  161. ignoreMods = InputEvent.SHIFT_MASK
  162. | InputEvent.ALT_GRAPH_MASK;
  163. }
  164. if((modifiers & InputEvent.ALT_GRAPH_MASK) == 0
  165. && (modifiers & ~ignoreMods) != 0)
  166. {
  167. if(Debug.ALTERNATIVE_DISPATCHER)
  168. {
  169. returnValue = new Key(
  170. modifiersToString(modifiers),
  171. 0,ch);
  172. }
  173. else
  174. return null;
  175. }
  176. else
  177. {
  178. if(ch == ' ')
  179. {
  180. returnValue = new Key(
  181. modifiersToString(modifiers),
  182. 0,ch);
  183. }
  184. else
  185. returnValue = new Key(null,0,ch);
  186. }
  187. break;
  188. default:
  189. return null;
  190. }
  191. /* I guess translated events do not have the 'evt' field set
  192. so consuming won't work. I don't think this is a problem as
  193. nothing uses translation anyway */
  194. Key trans = transMap.get(returnValue);
  195. if(trans == null)
  196. return returnValue;
  197. else
  198. return trans;
  199. } //}}}
  200. //{{{ parseKey() method
  201. /**
  202. * Converts a string to a keystroke. The string should be of the
  203. * form <i>modifiers</i>+<i>shortcut</i> where <i>modifiers</i>
  204. * is any combination of A for Alt, C for Control, S for Shift
  205. * or M for Meta, and <i>shortcut</i> is either a single character,
  206. * or a keycode name from the <code>KeyEvent</code> class, without
  207. * the <code>VK_</code> prefix.
  208. * @param keyStroke A string description of the key stroke
  209. * @since jEdit 4.2pre3
  210. */
  211. public static Key parseKey(String keyStroke)
  212. {
  213. if(keyStroke == null)
  214. return null;
  215. int modifiers = 0;
  216. String key;
  217. int endOfModifiers = keyStroke.indexOf('+');
  218. if(endOfModifiers <= 0) // not found or found at first
  219. {
  220. key = keyStroke;
  221. }
  222. else
  223. {
  224. for(int i = 0; i < endOfModifiers; i++)
  225. {
  226. switch(Character.toUpperCase(keyStroke
  227. .charAt(i)))
  228. {
  229. case 'A':
  230. modifiers |= a;
  231. break;
  232. case 'C':
  233. modifiers |= c;
  234. break;
  235. case 'M':
  236. modifiers |= m;
  237. break;
  238. case 'S':
  239. modifiers |= s;
  240. break;
  241. }
  242. }
  243. key = keyStroke.substring(endOfModifiers + 1);
  244. }
  245. if(key.length() == 1)
  246. {
  247. return new Key(modifiersToString(modifiers),0,key.charAt(0));
  248. }
  249. else if(key.length() == 0)
  250. {
  251. Log.log(Log.ERROR,KeyEventTranslator.class,
  252. "Invalid key stroke: " + keyStroke);
  253. return null;
  254. }
  255. else if(key.equals("SPACE"))
  256. {
  257. return new Key(modifiersToString(modifiers),0,' ');
  258. }
  259. else
  260. {
  261. int ch;
  262. try
  263. {
  264. ch = KeyEvent.class.getField("VK_".concat(key))
  265. .getInt(null);
  266. }
  267. catch(Exception e)
  268. {
  269. Log.log(Log.ERROR,KeyEventTranslator.class,
  270. "Invalid key stroke: "
  271. + keyStroke);
  272. return null;
  273. }
  274. return new Key(modifiersToString(modifiers),ch,'\0');
  275. }
  276. } //}}}
  277. //{{{ setModifierMapping() method
  278. /**
  279. * Changes the mapping between symbolic modifier key names
  280. * (<code>C</code>, <code>A</code>, <code>M</code>, <code>S</code>) and
  281. * Java modifier flags.
  282. *
  283. * You can map more than one Java modifier to a symobolic modifier, for
  284. * example :
  285. * <p><code><pre>
  286. * setModifierMapping(
  287. * InputEvent.CTRL_MASK,
  288. * InputEvent.ALT_MASK | InputEvent.META_MASK,
  289. * 0,
  290. * InputEvent.SHIFT_MASK);
  291. *<pre></code></p>
  292. *
  293. * You cannot map a Java modifer to more than one symbolic modifier.
  294. *
  295. * @param c The modifier(s) to map the <code>C</code> modifier to
  296. * @param a The modifier(s) to map the <code>A</code> modifier to
  297. * @param m The modifier(s) to map the <code>M</code> modifier to
  298. * @param s The modifier(s) to map the <code>S</code> modifier to
  299. *
  300. * @since jEdit 4.2pre3
  301. */
  302. public static void setModifierMapping(int c, int a, int m, int s)
  303. {
  304. int duplicateMapping =
  305. (c & a) | (c & m) | (c & s) | (a & m) | (a & s) | (m & s);
  306. if((duplicateMapping & InputEvent.CTRL_MASK) != 0)
  307. {
  308. throw new IllegalArgumentException(
  309. "CTRL is mapped to more than one modifier");
  310. }
  311. if((duplicateMapping & InputEvent.ALT_MASK) != 0)
  312. {
  313. throw new IllegalArgumentException(
  314. "ALT is mapped to more than one modifier");
  315. }
  316. if((duplicateMapping & InputEvent.META_MASK) != 0)
  317. {
  318. throw new IllegalArgumentException(
  319. "META is mapped to more than one modifier");
  320. }
  321. if((duplicateMapping & InputEvent.SHIFT_MASK) != 0)
  322. {
  323. throw new IllegalArgumentException(
  324. "SHIFT is mapped to more than one modifier");
  325. }
  326. KeyEventTranslator.c = c;
  327. KeyEventTranslator.a = a;
  328. KeyEventTranslator.m = m;
  329. KeyEventTranslator.s = s;
  330. } //}}}
  331. //{{{ getSymbolicModifierName() method
  332. /**
  333. * Returns a the symbolic modifier name for the specified Java modifier
  334. * flag.
  335. *
  336. * @param mod A modifier constant from <code>InputEvent</code>
  337. *
  338. * @since jEdit 4.2pre3
  339. */
  340. public static char getSymbolicModifierName(int mod)
  341. {
  342. if((mod & c) != 0)
  343. return 'C';
  344. else if((mod & a) != 0)
  345. return 'A';
  346. else if((mod & m) != 0)
  347. return 'M';
  348. else if((mod & s) != 0)
  349. return 'S';
  350. else
  351. return '\0';
  352. } //}}}
  353. //{{{ modifiersToString() method
  354. private static final int[] MODS = {
  355. InputEvent.CTRL_MASK,
  356. InputEvent.ALT_MASK,
  357. InputEvent.META_MASK,
  358. InputEvent.SHIFT_MASK
  359. };
  360. public static String modifiersToString(int mods)
  361. {
  362. StringBuilder buf = null;
  363. for(int i = 0; i < MODS.length; i++)
  364. {
  365. if((mods & MODS[i]) != 0)
  366. buf = lazyAppend(buf,getSymbolicModifierName(MODS[i]));
  367. }
  368. if(buf == null)
  369. return null;
  370. else
  371. return buf.toString();
  372. } //}}}
  373. //{{{ getModifierString() method
  374. /**
  375. * Returns a string containing symbolic modifier names set in the
  376. * specified event.
  377. *
  378. * @param evt The event
  379. *
  380. * @since jEdit 4.2pre3
  381. */
  382. public static String getModifierString(InputEvent evt)
  383. {
  384. StringBuilder buf = new StringBuilder();
  385. if(evt.isControlDown())
  386. buf.append(getSymbolicModifierName(InputEvent.CTRL_MASK));
  387. if(evt.isAltDown())
  388. buf.append(getSymbolicModifierName(InputEvent.ALT_MASK));
  389. if(evt.isMetaDown())
  390. buf.append(getSymbolicModifierName(InputEvent.META_MASK));
  391. if(evt.isShiftDown())
  392. buf.append(getSymbolicModifierName(InputEvent.SHIFT_MASK));
  393. return buf.length() == 0 ? null : buf.toString();
  394. } //}}}
  395. static int c, a, m, s;
  396. //{{{ Private members
  397. /** This map is a pool of Key. */
  398. private static final Map<Key, Key> transMap = new HashMap<Key, Key>();
  399. private static StringBuilder lazyAppend(StringBuilder buf, char ch)
  400. {
  401. if(buf == null)
  402. buf = new StringBuilder();
  403. if(buf.indexOf(String.valueOf(ch)) == -1)
  404. buf.append(ch);
  405. return buf;
  406. } //}}}
  407. static
  408. {
  409. if(OperatingSystem.isMacOS())
  410. {
  411. setModifierMapping(
  412. InputEvent.META_MASK, /* == C+ */
  413. InputEvent.CTRL_MASK, /* == A+ */
  414. /* M+ discarded by key event workaround! */
  415. InputEvent.ALT_MASK, /* == M+ */
  416. InputEvent.SHIFT_MASK /* == S+ */);
  417. }
  418. else
  419. {
  420. setModifierMapping(
  421. InputEvent.CTRL_MASK,
  422. InputEvent.ALT_MASK,
  423. InputEvent.META_MASK,
  424. InputEvent.SHIFT_MASK);
  425. }
  426. } //}}}
  427. //{{{ Key class
  428. public static class Key
  429. {
  430. public final String modifiers;
  431. public final int key;
  432. public final char input;
  433. private final int hashCode;
  434. /**
  435. Wether this Key event applies to all jEdit windows (and not only a specific jEdit GUI component).
  436. */
  437. protected boolean isFromGlobalContext;
  438. /**
  439. Wether this Key event is a phantom key event. A phantom key event is a kind of duplicate key event which
  440. should not - due to its nature of being a duplicate - generate any action on data.
  441. However, phantom key events may be necessary to notify the rest of the GUI that the key event, if it was not a phantom key event but a real key event,
  442. would generate any action and thus would be consumed.
  443. */
  444. protected boolean isPhantom;
  445. public Key(String modifiers, int key, char input)
  446. {
  447. this.modifiers = modifiers;
  448. this.key = key;
  449. this.input = input;
  450. hashCode = key + input;
  451. }
  452. @Override
  453. public int hashCode()
  454. {
  455. return hashCode;
  456. }
  457. @Override
  458. public boolean equals(Object o)
  459. {
  460. if(o instanceof Key)
  461. {
  462. Key k = (Key)o;
  463. if(StandardUtilities.objectsEqual(modifiers,
  464. k.modifiers) && key == k.key
  465. && input == k.input)
  466. {
  467. return true;
  468. }
  469. }
  470. return false;
  471. }
  472. @Override
  473. public String toString()
  474. {
  475. return (modifiers == null ? "" : modifiers)
  476. + '<'
  477. + Integer.toString(key,16)
  478. + ','
  479. + Integer.toString(input,16)
  480. + '>';
  481. }
  482. public void setIsFromGlobalContext(boolean to)
  483. {
  484. isFromGlobalContext = to;
  485. }
  486. public boolean isFromGlobalContext()
  487. {
  488. return isFromGlobalContext;
  489. }
  490. public void setIsPhantom(boolean to)
  491. {
  492. isPhantom = to;
  493. }
  494. public boolean isPhantom()
  495. {
  496. return isPhantom;
  497. }
  498. } //}}}
  499. }