PageRenderTime 47ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-3-pre5/org/gjt/sp/jedit/gui/KeyEventTranslator.java

#
Java | 598 lines | 393 code | 50 blank | 155 comment | 92 complexity | 636847b1bceef49e8b83b22c6467032c MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, Apache-2.0, LGPL-2.0, LGPL-3.0, GPL-2.0, CC-BY-SA-3.0, LGPL-2.1, GPL-3.0, MPL-2.0-no-copyleft-exception, IPL-1.0
  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.awt.Toolkit;
  26. import java.util.*;
  27. import org.gjt.sp.jedit.*;
  28. import org.gjt.sp.util.Log;
  29. //}}}
  30. /**
  31. * In conjunction with the <code>KeyEventWorkaround</code>, hides some
  32. * warts in the AWT key event API.
  33. *
  34. * @author Slava Pestov
  35. * @version $Id: KeyEventTranslator.java 5478 2006-06-22 21:12:01Z mediumnet $
  36. */
  37. public class KeyEventTranslator
  38. {
  39. //{{{ addTranslation() method
  40. /**
  41. * Adds a keyboard translation.
  42. * @param key1 Translate this key
  43. * @param key2 Into this key
  44. * @since jEdit 4.2pre3
  45. */
  46. public static void addTranslation(Key key1, Key key2)
  47. {
  48. transMap.put(key1,key2);
  49. } //}}}
  50. //{{{ translateKeyEvent() method
  51. protected static KeyEvent lastKeyPressEvent = null;
  52. protected static boolean lastKeyPressAccepted = false;
  53. /**
  54. * Pass this an event from {@link
  55. * KeyEventWorkaround#processKeyEvent(java.awt.event.KeyEvent)}.
  56. * @since jEdit 4.2pre3
  57. */
  58. public static Key translateKeyEvent(KeyEvent evt)
  59. {
  60. if (Debug.SIMPLIFIED_KEY_HANDLING)
  61. { // This is still experimental code.
  62. /**
  63. A summary of Java key handling intricacies:
  64. (1) No "key pressed" events are generated for umlaut keys and for "combined characters" (key for diacritic mark + key for base character), only "key typed" and "key relesed" events are generated for them
  65. (2) The "key typed" event for Ctrl+J is indistinguishable from the "key typed" event for Ctrl+Return (in both cases: keycode=0, keychar=0xa) (in Java 1.5 under linux, but not in Java 1.6)
  66. (3) If a key is pressed longer, not only additional "key typed" events but also additional "key released", "key pressed" events are generated.
  67. (4) There are no proper key events generated for dead key + space (like '^' + ' ' resulting in '^') in Java 1.5 under linux. In Java 1.6, this bug is fixed.
  68. For (2), we could simply ignore "key typed" events (as (3) allows us to do so). But then we would loose umlaut keys and combined characters (due to (1)).
  69. For (1), we could simply ignore "key pressed" events. But then we would suffer from (2).
  70. Thus, we need to distinguish for (2) at the "key pressed" event state, however fire the internal key events only at the "key typed" stage.
  71. This makes it necessary to store information about the last "key pressed" event (to be able to distinguish).
  72. */
  73. char keyChar = evt.getKeyChar();
  74. int keyCode = evt.getKeyCode();
  75. int modifiers = evt.getModifiers();
  76. boolean usecooked = !evt.isActionKey();
  77. boolean accept = false;
  78. // Log.log(Log.DEBUG,"KeyEventTranslator","translateKeyEvent(): 1: keyChar="+((int) keyChar)+",keyCode="+keyCode+",modifiers="+modifiers+", usecooked="+usecooked+", event="+evt+".");
  79. /*
  80. Workaround against the bug of jdk1.5, that Ctrl+A has keyChar 0x1 instead of keyChar 0x41:
  81. This bug may be related to http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6320676
  82. */
  83. if ((modifiers&KeyEvent.CTRL_MASK)!=0) {
  84. // Log.log(Log.DEBUG,"KeyEventTranslator","translateKeyEvent(): keyChar="+((int) keyChar)+",keyCode="+keyCode+",modifiers="+modifiers+": 1.");
  85. if (keyChar<0x20) {
  86. // Log.log(Log.DEBUG,"KeyEventTranslator","translateKeyEvent(): keyChar="+((int) keyChar)+",keyCode="+keyCode+",modifiers="+modifiers+": 1.1.");
  87. if (keyChar!=keyCode) { // specifically: if the real Escape, Backspace, Delete, Tab, Enter key was pressed, then this is false
  88. // Log.log(Log.DEBUG,"KeyEventTranslator","translateKeyEvent(): keyChar="+((int) keyChar)+",keyCode="+keyCode+",modifiers="+modifiers+": 1.1.1");
  89. keyChar+=0x40;
  90. if ((keyChar>='A')&&(keyChar<='Z')) { // if they are uppercase letters
  91. keyChar+=0x20; // make them lowercase letters
  92. }
  93. // usecooked = false;
  94. }
  95. }
  96. if (keyChar=='\\') { // for compatibility with traditional jEdit installations (Shortcuts are called "C+BACK_SLASH" instead of "C+\")
  97. // Log.log(Log.DEBUG,"KeyEventTranslator","translateKeyEvent(): keyChar="+((int) keyChar)+",keyCode="+keyCode+",modifiers="+modifiers+": 1.1.1: backslash.");
  98. keyChar = 0;
  99. keyCode = KeyEvent.VK_BACK_SLASH;
  100. }
  101. // Log.log(Log.DEBUG,"KeyEventTranslator","translateKeyEvent(): 2: keyChar="+((int) keyChar)+",keyCode="+keyCode+",modifiers="+modifiers+", event="+evt+".");
  102. }
  103. /**
  104. These keys are hidden "control keys". That is, they are used as special function key (instead of representing a character to be input), but
  105. Java delivers them with a valid keyChar. We intentionally ignore this keyChar.
  106. (However, not ignoring the keyChar would be an easy way to enter "escape" or "delete" characters into the edited text document, but this is not what we want.)
  107. */
  108. switch (keyChar) {
  109. case 0x1b: // case KeyEvent.VK_ESCAPE:
  110. case 0x08: // case KeyEvent.VK_BACK_SPACE:
  111. case 0x7f: // case KeyEvent.VK_DELETE:
  112. case 0x09: // case KeyEvent.VK_TAB:
  113. case 0x0a: // case KeyEvent.VK_ENTER:
  114. case KeyEvent.CHAR_UNDEFINED:
  115. usecooked = false;
  116. keyChar = 0;
  117. }
  118. if (true) {
  119. switch(evt.getID()) {
  120. case KeyEvent.KEY_PRESSED:
  121. accept = !usecooked;
  122. lastKeyPressAccepted = accept;
  123. lastKeyPressEvent = evt;
  124. break;
  125. case KeyEvent.KEY_TYPED:
  126. if (lastKeyPressAccepted&&(lastKeyPressEvent!=null)&&(lastKeyPressEvent.getKeyChar()==evt.getKeyChar())) {
  127. // Do not emit internal key event twice.
  128. // This works around the case where "Ctrl+J" and "Ctrl+Return" are indistinguishable in that "Ctrl+Return" is handled at the "key pressed" stage where "Ctrl+J" is handled at the "key typed" stage.
  129. } else {
  130. accept = usecooked;
  131. }
  132. break;
  133. default:
  134. }
  135. } else {
  136. /*
  137. This attempt does work for the "Ctrl+Enter"-Problem, but this does work neither for umlauts nor for combined synthetic keys (like characters with diacritic marks).
  138. The reason is that java 1.5.0_06 (on Linux) does not deliver "key pressed" events for those keys, only "key typed" events.
  139. */
  140. /*
  141. We ignore all the "key typed" events, as key repeat is already synthetically generated by synthetic "key pressed" "key released" events.
  142. "key typed" events have less information.
  143. This is highly experimental, as this relies on the JVM to generate these synthetic "key released", "key pressed" events.
  144. */
  145. switch(evt.getID()) {
  146. case KeyEvent.KEY_PRESSED:
  147. accept = true;
  148. if (usecooked) { // This destroys information, but this is what the rest of jEdit is used to :-(
  149. keyCode = 0;
  150. }
  151. break;
  152. default:
  153. }
  154. }
  155. Key returnValue = null;
  156. if (accept) {
  157. returnValue = new Key(modifiersToString(modifiers),keyCode,keyChar);
  158. }
  159. return returnValue;
  160. }
  161. else
  162. {
  163. int modifiers = evt.getModifiers();
  164. Key returnValue = null;
  165. switch(evt.getID())
  166. {
  167. case KeyEvent.KEY_PRESSED:
  168. int keyCode = evt.getKeyCode();
  169. if((keyCode >= KeyEvent.VK_0
  170. && keyCode <= KeyEvent.VK_9)
  171. || (keyCode >= KeyEvent.VK_A
  172. && keyCode <= KeyEvent.VK_Z))
  173. {
  174. if(Debug.ALTERNATIVE_DISPATCHER)
  175. return null;
  176. else
  177. {
  178. returnValue = new Key(
  179. modifiersToString(modifiers),
  180. '\0',Character.toLowerCase(
  181. (char)keyCode));
  182. }
  183. }
  184. else
  185. {
  186. if(keyCode == KeyEvent.VK_TAB)
  187. {
  188. evt.consume();
  189. returnValue = new Key(
  190. modifiersToString(modifiers),
  191. keyCode,'\0');
  192. }
  193. else if(keyCode == KeyEvent.VK_SPACE)
  194. {
  195. // for SPACE or S+SPACE we pass the
  196. // key typed since international
  197. // keyboards sometimes produce a
  198. // KEY_PRESSED SPACE but not a
  199. // KEY_TYPED SPACE, eg if you have to
  200. // do a "<space> to insert ".
  201. if((modifiers & ~InputEvent.SHIFT_MASK) == 0)
  202. returnValue = null;
  203. else
  204. {
  205. returnValue = new Key(
  206. modifiersToString(modifiers),
  207. 0,' ');
  208. }
  209. }
  210. else
  211. {
  212. returnValue = new Key(
  213. modifiersToString(modifiers),
  214. keyCode,'\0');
  215. }
  216. }
  217. break;
  218. case KeyEvent.KEY_TYPED:
  219. char ch = evt.getKeyChar();
  220. if(KeyEventWorkaround.isMacControl(evt))
  221. ch |= 0x60;
  222. switch(ch)
  223. {
  224. case '\n':
  225. case '\t':
  226. case '\b':
  227. return null;
  228. case ' ':
  229. if((modifiers & ~InputEvent.SHIFT_MASK) != 0)
  230. return null;
  231. }
  232. int ignoreMods;
  233. if(Debug.ALT_KEY_PRESSED_DISABLED)
  234. {
  235. /* on MacOS, A+ can be user input */
  236. ignoreMods = (InputEvent.SHIFT_MASK
  237. | InputEvent.ALT_GRAPH_MASK
  238. | InputEvent.ALT_MASK);
  239. }
  240. else
  241. {
  242. /* on MacOS, A+ can be user input */
  243. ignoreMods = (InputEvent.SHIFT_MASK
  244. | InputEvent.ALT_GRAPH_MASK);
  245. }
  246. if((modifiers & InputEvent.ALT_GRAPH_MASK) == 0
  247. && evt.getWhen()
  248. - KeyEventWorkaround.lastKeyTime < 750
  249. && (KeyEventWorkaround.modifiers & ~ignoreMods)
  250. != 0)
  251. {
  252. if(Debug.ALTERNATIVE_DISPATCHER)
  253. {
  254. returnValue = new Key(
  255. modifiersToString(modifiers),
  256. 0,ch);
  257. }
  258. else
  259. return null;
  260. }
  261. else
  262. {
  263. if(ch == ' ')
  264. {
  265. returnValue = new Key(
  266. modifiersToString(modifiers),
  267. 0,ch);
  268. }
  269. else
  270. returnValue = new Key(null,0,ch);
  271. }
  272. break;
  273. default:
  274. return null;
  275. }
  276. /* I guess translated events do not have the 'evt' field set
  277. so consuming won't work. I don't think this is a problem as
  278. nothing uses translation anyway */
  279. Key trans = (Key)transMap.get(returnValue);
  280. if(trans == null)
  281. return returnValue;
  282. else
  283. return trans;
  284. }
  285. } //}}}
  286. //{{{ parseKey() method
  287. /**
  288. * Converts a string to a keystroke. The string should be of the
  289. * form <i>modifiers</i>+<i>shortcut</i> where <i>modifiers</i>
  290. * is any combination of A for Alt, C for Control, S for Shift
  291. * or M for Meta, and <i>shortcut</i> is either a single character,
  292. * or a keycode name from the <code>KeyEvent</code> class, without
  293. * the <code>VK_</code> prefix.
  294. * @param keyStroke A string description of the key stroke
  295. * @since jEdit 4.2pre3
  296. */
  297. public static Key parseKey(String keyStroke)
  298. {
  299. if(keyStroke == null)
  300. return null;
  301. int index = keyStroke.indexOf('+');
  302. int modifiers = 0;
  303. if(index != -1)
  304. {
  305. for(int i = 0; i < index; i++)
  306. {
  307. switch(Character.toUpperCase(keyStroke
  308. .charAt(i)))
  309. {
  310. case 'A':
  311. modifiers |= a;
  312. break;
  313. case 'C':
  314. modifiers |= c;
  315. break;
  316. case 'M':
  317. modifiers |= m;
  318. break;
  319. case 'S':
  320. modifiers |= s;
  321. break;
  322. }
  323. }
  324. }
  325. String key = keyStroke.substring(index + 1);
  326. if(key.length() == 1)
  327. {
  328. return new Key(modifiersToString(modifiers),0,key.charAt(0));
  329. }
  330. else if(key.length() == 0)
  331. {
  332. Log.log(Log.ERROR,DefaultInputHandler.class,
  333. "Invalid key stroke: " + keyStroke);
  334. return null;
  335. }
  336. else if(key.equals("SPACE"))
  337. {
  338. return new Key(modifiersToString(modifiers),0,' ');
  339. }
  340. else
  341. {
  342. int ch;
  343. try
  344. {
  345. ch = KeyEvent.class.getField("VK_".concat(key))
  346. .getInt(null);
  347. }
  348. catch(Exception e)
  349. {
  350. Log.log(Log.ERROR,DefaultInputHandler.class,
  351. "Invalid key stroke: "
  352. + keyStroke);
  353. return null;
  354. }
  355. return new Key(modifiersToString(modifiers),ch,'\0');
  356. }
  357. } //}}}
  358. //{{{ setModifierMapping() method
  359. /**
  360. * Changes the mapping between symbolic modifier key names
  361. * (<code>C</code>, <code>A</code>, <code>M</code>, <code>S</code>) and
  362. * Java modifier flags.
  363. *
  364. * You can map more than one Java modifier to a symobolic modifier, for
  365. * example :
  366. * <p><code><pre>
  367. * setModifierMapping(
  368. * InputEvent.CTRL_MASK,
  369. * InputEvent.ALT_MASK | InputEvent.META_MASK,
  370. * 0,
  371. * InputEvent.SHIFT_MASK);
  372. *<pre></code></p>
  373. *
  374. * You cannot map a Java modifer to more than one symbolic modifier.
  375. *
  376. * @param c The modifier(s) to map the <code>C</code> modifier to
  377. * @param a The modifier(s) to map the <code>A</code> modifier to
  378. * @param m The modifier(s) to map the <code>M</code> modifier to
  379. * @param s The modifier(s) to map the <code>S</code> modifier to
  380. *
  381. * @since jEdit 4.2pre3
  382. */
  383. public static void setModifierMapping(int c, int a, int m, int s)
  384. {
  385. int duplicateMapping =
  386. ((c & a) | (c & m) | (c & s) | (a & m) | (a & s) | (m & s));
  387. if((duplicateMapping & InputEvent.CTRL_MASK) != 0) {
  388. throw new IllegalArgumentException(
  389. "CTRL is mapped to more than one modifier");
  390. }
  391. if((duplicateMapping & InputEvent.ALT_MASK) != 0) {
  392. throw new IllegalArgumentException(
  393. "ALT is mapped to more than one modifier");
  394. }
  395. if((duplicateMapping & InputEvent.META_MASK) != 0) {
  396. throw new IllegalArgumentException(
  397. "META is mapped to more than one modifier");
  398. }
  399. if((duplicateMapping & InputEvent.SHIFT_MASK) != 0) {
  400. throw new IllegalArgumentException(
  401. "SHIFT is mapped to more than one modifier");
  402. }
  403. KeyEventTranslator.c = c;
  404. KeyEventTranslator.a = a;
  405. KeyEventTranslator.m = m;
  406. KeyEventTranslator.s = s;
  407. } //}}}
  408. //{{{ getSymbolicModifierName() method
  409. /**
  410. * Returns a the symbolic modifier name for the specified Java modifier
  411. * flag.
  412. *
  413. * @param mod A modifier constant from <code>InputEvent</code>
  414. *
  415. * @since jEdit 4.2pre3
  416. */
  417. public static char getSymbolicModifierName(int mod)
  418. {
  419. if((mod & c) != 0)
  420. return 'C';
  421. else if((mod & a) != 0)
  422. return 'A';
  423. else if((mod & m) != 0)
  424. return 'M';
  425. else if((mod & s) != 0)
  426. return 'S';
  427. else
  428. return '\0';
  429. } //}}}
  430. //{{{ modifiersToString() method
  431. private static int[] MODS = {
  432. InputEvent.CTRL_MASK,
  433. InputEvent.ALT_MASK,
  434. InputEvent.META_MASK,
  435. InputEvent.SHIFT_MASK
  436. };
  437. public static String modifiersToString(int mods)
  438. {
  439. StringBuffer buf = null;
  440. for(int i = 0; i < MODS.length; i++)
  441. {
  442. if((mods & MODS[i]) != 0)
  443. buf = lazyAppend(buf,getSymbolicModifierName(MODS[i]));
  444. }
  445. if(buf == null)
  446. return null;
  447. else
  448. return buf.toString();
  449. } //}}}
  450. //{{{ getModifierString() method
  451. /**
  452. * Returns a string containing symbolic modifier names set in the
  453. * specified event.
  454. *
  455. * @param evt The event
  456. *
  457. * @since jEdit 4.2pre3
  458. */
  459. public static String getModifierString(InputEvent evt)
  460. {
  461. StringBuffer buf = new StringBuffer();
  462. if(evt.isControlDown())
  463. buf.append(getSymbolicModifierName(InputEvent.CTRL_MASK));
  464. if(evt.isAltDown())
  465. buf.append(getSymbolicModifierName(InputEvent.ALT_MASK));
  466. if(evt.isMetaDown())
  467. buf.append(getSymbolicModifierName(InputEvent.META_MASK));
  468. if(evt.isShiftDown())
  469. buf.append(getSymbolicModifierName(InputEvent.SHIFT_MASK));
  470. return (buf.length() == 0 ? null : buf.toString());
  471. } //}}}
  472. static int c, a, m, s;
  473. //{{{ Private members
  474. private static Map transMap = new HashMap();
  475. private static StringBuffer lazyAppend(StringBuffer buf, char ch)
  476. {
  477. if(buf == null)
  478. buf = new StringBuffer();
  479. if(buf.indexOf(String.valueOf(ch)) == -1)
  480. buf.append(ch);
  481. return buf;
  482. } //}}}
  483. static
  484. {
  485. if(OperatingSystem.isMacOS())
  486. {
  487. setModifierMapping(
  488. InputEvent.META_MASK, /* == C+ */
  489. InputEvent.CTRL_MASK, /* == A+ */
  490. /* M+ discarded by key event workaround! */
  491. InputEvent.ALT_MASK, /* == M+ */
  492. InputEvent.SHIFT_MASK /* == S+ */);
  493. }
  494. else
  495. {
  496. setModifierMapping(
  497. InputEvent.CTRL_MASK,
  498. InputEvent.ALT_MASK,
  499. InputEvent.META_MASK,
  500. InputEvent.SHIFT_MASK);
  501. }
  502. } //}}}
  503. //{{{ Key class
  504. public static class Key
  505. {
  506. public String modifiers;
  507. public int key;
  508. public char input;
  509. public Key(String modifiers, int key, char input)
  510. {
  511. this.modifiers = modifiers;
  512. this.key = key;
  513. this.input = input;
  514. }
  515. public int hashCode()
  516. {
  517. return key + input;
  518. }
  519. public boolean equals(Object o)
  520. {
  521. if(o instanceof Key)
  522. {
  523. Key k = (Key)o;
  524. if(MiscUtilities.objectsEqual(modifiers,
  525. k.modifiers) && key == k.key
  526. && input == k.input)
  527. {
  528. return true;
  529. }
  530. }
  531. return false;
  532. }
  533. public String toString()
  534. {
  535. return (modifiers == null ? "" : modifiers)
  536. + "<"
  537. + Integer.toString(key,16)
  538. + ","
  539. + Integer.toString(input,16)
  540. + ">";
  541. }
  542. } //}}}
  543. }