001: /*
002: * KeyEventWorkaround.java - Works around bugs in Java event handling
003: * :tabSize=8:indentSize=8:noTabs=false:
004: * :folding=explicit:collapseFolds=1:
005: *
006: * Copyright (C) 2000, 2005 Slava Pestov
007: *
008: * This program is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU General Public License
010: * as published by the Free Software Foundation; either version 2
011: * of the License, or any later version.
012: *
013: * This program is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016: * GNU General Public License for more details.
017: *
018: * You should have received a copy of the GNU General Public License
019: * along with this program; if not, write to the Free Software
020: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
021: */
022:
023: package org.gjt.sp.jedit.gui;
024:
025: //{{{ Imports
026: import java.awt.event.*;
027: import org.gjt.sp.jedit.Debug;
028: import org.gjt.sp.jedit.OperatingSystem;
029: import org.gjt.sp.jedit.Options;
030: import org.gjt.sp.util.Log;
031:
032: //}}}
033:
034: /**
035: * Various hacks to get keyboard event handling to behave in a consistent manner
036: * across Java implementations. This type of stuff should not be necessary, but
037: * Java's keyboard handling is crap, to put it mildly.
038: *
039: * @author Slava Pestov
040: * @version $Id: KeyEventWorkaround.java 11038 2007-11-12 20:43:13Z kpouer $
041: */
042: public class KeyEventWorkaround {
043: //{{{ isBindable() method
044: public static boolean isBindable(int keyCode) {
045: switch (keyCode) {
046: case KeyEvent.VK_ALT:
047: case KeyEvent.VK_ALT_GRAPH:
048: case KeyEvent.VK_CONTROL:
049: case KeyEvent.VK_SHIFT:
050: case KeyEvent.VK_META:
051: case KeyEvent.VK_DEAD_GRAVE:
052: case KeyEvent.VK_DEAD_ACUTE:
053: case KeyEvent.VK_DEAD_CIRCUMFLEX:
054: case KeyEvent.VK_DEAD_TILDE:
055: case KeyEvent.VK_DEAD_MACRON:
056: case KeyEvent.VK_DEAD_BREVE:
057: case KeyEvent.VK_DEAD_ABOVEDOT:
058: case KeyEvent.VK_DEAD_DIAERESIS:
059: case KeyEvent.VK_DEAD_ABOVERING:
060: case KeyEvent.VK_DEAD_DOUBLEACUTE:
061: case KeyEvent.VK_DEAD_CARON:
062: case KeyEvent.VK_DEAD_CEDILLA:
063: case KeyEvent.VK_DEAD_OGONEK:
064: case KeyEvent.VK_DEAD_IOTA:
065: case KeyEvent.VK_DEAD_VOICED_SOUND:
066: case KeyEvent.VK_DEAD_SEMIVOICED_SOUND:
067: return false;
068: default:
069: return true;
070: }
071: } //}}}
072:
073: //{{{ isPrintable() method
074: /**
075: * We need to know if a keycode can potentially result in a
076: * keytyped.
077: * @since jEdit 4.3pre2
078: */
079: public static boolean isPrintable(int keyCode) {
080: switch (keyCode) {
081: /* case KeyEvent.VK_ENTER:
082: case KeyEvent.VK_TAB: */
083: case KeyEvent.VK_SPACE:
084: case KeyEvent.VK_COMMA:
085: case KeyEvent.VK_MINUS:
086: case KeyEvent.VK_PERIOD:
087: case KeyEvent.VK_SLASH:
088: case KeyEvent.VK_0:
089: case KeyEvent.VK_1:
090: case KeyEvent.VK_2:
091: case KeyEvent.VK_3:
092: case KeyEvent.VK_4:
093: case KeyEvent.VK_5:
094: case KeyEvent.VK_6:
095: case KeyEvent.VK_7:
096: case KeyEvent.VK_8:
097: case KeyEvent.VK_9:
098: case KeyEvent.VK_SEMICOLON:
099: case KeyEvent.VK_EQUALS:
100: case KeyEvent.VK_A:
101: case KeyEvent.VK_B:
102: case KeyEvent.VK_C:
103: case KeyEvent.VK_D:
104: case KeyEvent.VK_E:
105: case KeyEvent.VK_F:
106: case KeyEvent.VK_G:
107: case KeyEvent.VK_H:
108: case KeyEvent.VK_I:
109: case KeyEvent.VK_J:
110: case KeyEvent.VK_K:
111: case KeyEvent.VK_L:
112: case KeyEvent.VK_M:
113: case KeyEvent.VK_N:
114: case KeyEvent.VK_O:
115: case KeyEvent.VK_P:
116: case KeyEvent.VK_Q:
117: case KeyEvent.VK_R:
118: case KeyEvent.VK_S:
119: case KeyEvent.VK_T:
120: case KeyEvent.VK_U:
121: case KeyEvent.VK_V:
122: case KeyEvent.VK_W:
123: case KeyEvent.VK_X:
124: case KeyEvent.VK_Y:
125: case KeyEvent.VK_Z:
126: case KeyEvent.VK_OPEN_BRACKET:
127: case KeyEvent.VK_BACK_SLASH:
128: case KeyEvent.VK_CLOSE_BRACKET:
129: /* case KeyEvent.VK_NUMPAD0 :
130: case KeyEvent.VK_NUMPAD1 :
131: case KeyEvent.VK_NUMPAD2 :
132: case KeyEvent.VK_NUMPAD3 :
133: case KeyEvent.VK_NUMPAD4 :
134: case KeyEvent.VK_NUMPAD5 :
135: case KeyEvent.VK_NUMPAD6 :
136: case KeyEvent.VK_NUMPAD7 :
137: case KeyEvent.VK_NUMPAD8 :
138: case KeyEvent.VK_NUMPAD9 :
139: case KeyEvent.VK_MULTIPLY:
140: case KeyEvent.VK_ADD :
141: case KeyEvent.VK_SEPARATOR:
142: case KeyEvent.VK_SUBTRACT :
143: case KeyEvent.VK_DECIMAL :
144: case KeyEvent.VK_DIVIDE : */
145: case KeyEvent.VK_BACK_QUOTE:
146: case KeyEvent.VK_QUOTE:
147: case KeyEvent.VK_DEAD_GRAVE:
148: case KeyEvent.VK_DEAD_ACUTE:
149: case KeyEvent.VK_DEAD_CIRCUMFLEX:
150: case KeyEvent.VK_DEAD_TILDE:
151: case KeyEvent.VK_DEAD_MACRON:
152: case KeyEvent.VK_DEAD_BREVE:
153: case KeyEvent.VK_DEAD_ABOVEDOT:
154: case KeyEvent.VK_DEAD_DIAERESIS:
155: case KeyEvent.VK_DEAD_ABOVERING:
156: case KeyEvent.VK_DEAD_DOUBLEACUTE:
157: case KeyEvent.VK_DEAD_CARON:
158: case KeyEvent.VK_DEAD_CEDILLA:
159: case KeyEvent.VK_DEAD_OGONEK:
160: case KeyEvent.VK_DEAD_IOTA:
161: case KeyEvent.VK_DEAD_VOICED_SOUND:
162: case KeyEvent.VK_DEAD_SEMIVOICED_SOUND:
163: case KeyEvent.VK_AMPERSAND:
164: case KeyEvent.VK_ASTERISK:
165: case KeyEvent.VK_QUOTEDBL:
166: case KeyEvent.VK_LESS:
167: case KeyEvent.VK_GREATER:
168: case KeyEvent.VK_BRACELEFT:
169: case KeyEvent.VK_BRACERIGHT:
170: case KeyEvent.VK_AT:
171: case KeyEvent.VK_COLON:
172: case KeyEvent.VK_CIRCUMFLEX:
173: case KeyEvent.VK_DOLLAR:
174: case KeyEvent.VK_EURO_SIGN:
175: case KeyEvent.VK_EXCLAMATION_MARK:
176: case KeyEvent.VK_INVERTED_EXCLAMATION_MARK:
177: case KeyEvent.VK_LEFT_PARENTHESIS:
178: case KeyEvent.VK_NUMBER_SIGN:
179: case KeyEvent.VK_PLUS:
180: case KeyEvent.VK_RIGHT_PARENTHESIS:
181: case KeyEvent.VK_UNDERSCORE:
182: return true;
183: default:
184: return false;
185: }
186: } //}}}
187:
188: //{{{ isMacControl() method
189: /**
190: * Apple sucks.
191: */
192: public static boolean isMacControl(KeyEvent evt) {
193: return (OperatingSystem.isMacOS()
194: && (evt.getModifiers() & InputEvent.CTRL_MASK) != 0 && evt
195: .getKeyChar() <= 0x2B);
196: } //}}}
197:
198: //{{{ isNumericKeypad() method
199: public static boolean isNumericKeypad(int keyCode) {
200: switch (keyCode) {
201: case KeyEvent.VK_NUMPAD0:
202: case KeyEvent.VK_NUMPAD1:
203: case KeyEvent.VK_NUMPAD2:
204: case KeyEvent.VK_NUMPAD3:
205: case KeyEvent.VK_NUMPAD4:
206: case KeyEvent.VK_NUMPAD5:
207: case KeyEvent.VK_NUMPAD6:
208: case KeyEvent.VK_NUMPAD7:
209: case KeyEvent.VK_NUMPAD8:
210: case KeyEvent.VK_NUMPAD9:
211: case KeyEvent.VK_MULTIPLY:
212: case KeyEvent.VK_ADD:
213: /* case KeyEvent.VK_SEPARATOR: */
214: case KeyEvent.VK_SUBTRACT:
215: case KeyEvent.VK_DECIMAL:
216: case KeyEvent.VK_DIVIDE:
217: return true;
218: default:
219: return false;
220: }
221: } //}}}
222:
223: //{{{ processKeyEvent() method
224: public static KeyEvent processKeyEvent(KeyEvent evt) {
225: if (Options.SIMPLIFIED_KEY_HANDLING) {
226: } else {
227: int keyCode = evt.getKeyCode();
228: char ch = evt.getKeyChar();
229:
230: switch (evt.getID()) {
231: //{{{ KEY_PRESSED...
232: case KeyEvent.KEY_PRESSED:
233: lastKeyTime = evt.getWhen();
234: // get rid of keys we never need to handle
235: switch (keyCode) {
236: case '\0':
237: return null;
238: case KeyEvent.VK_ALT:
239: modifiers |= InputEvent.ALT_MASK;
240: break;
241: case KeyEvent.VK_ALT_GRAPH:
242: modifiers |= InputEvent.ALT_GRAPH_MASK;
243: break;
244: case KeyEvent.VK_CONTROL:
245: modifiers |= InputEvent.CTRL_MASK;
246: break;
247: case KeyEvent.VK_SHIFT:
248: modifiers |= InputEvent.SHIFT_MASK;
249: break;
250: case KeyEvent.VK_META:
251: modifiers |= InputEvent.META_MASK;
252: break;
253: default:
254: if (!evt.isMetaDown()) {
255: if (evt.isControlDown() && evt.isAltDown()) {
256: lastKeyTime = 0L;
257: } else if (!evt.isControlDown()
258: && !evt.isAltDown()) {
259: if (isPrintable(keyCode)) {
260: lastKeyTime = 0L;
261: return null;
262: }
263: }
264: }
265:
266: if (Debug.ALT_KEY_PRESSED_DISABLED) {
267: /* we don't handle key pressed A+ */
268: /* they're too troublesome */
269: if ((modifiers & InputEvent.ALT_MASK) != 0)
270: return null;
271: }
272:
273: if (isNumericKeypad(keyCode))
274: last = LAST_NUMKEYPAD;
275: else
276: last = LAST_NOTHING;
277:
278: break;
279: }
280: break;
281: //}}}
282: //{{{ KEY_TYPED...
283: case KeyEvent.KEY_TYPED:
284: // need to let \b through so that backspace will work
285: // in HistoryTextFields
286: if (!isMacControl(evt)
287: && (ch < 0x20 || ch == 0x7f || ch == 0xff)
288: && ch != '\b' && ch != '\t' && ch != '\n') {
289: return null;
290: }
291:
292: if (Debug.DUMP_KEY_EVENTS) {
293: Log.log(Log.DEBUG, "KEWa",
294: "Key event (working around): "
295: + GrabKeyDialog.toString(evt)
296: + ": evt.getWhen()-lastKeyTime="
297: + (evt.getWhen() - lastKeyTime)
298: + ",modifiers=" + modifiers
299: + ",last=" + last + ".");
300: }
301:
302: if (evt.getWhen() - lastKeyTime < 750) {
303: if (!Debug.ALTERNATIVE_DISPATCHER) {
304: if (((modifiers & InputEvent.CTRL_MASK) != 0 ^ (modifiers & InputEvent.ALT_MASK) != 0)
305: || (modifiers & InputEvent.META_MASK) != 0) {
306: return null;
307: }
308: }
309:
310: // if the last key was a numeric keypad key
311: // and NumLock is off, filter it out
312: if (last == LAST_NUMKEYPAD) {
313: last = LAST_NOTHING;
314: if ((ch >= '0' && ch <= '9') || ch == '.'
315: || ch == '/' || ch == '*' || ch == '-'
316: || ch == '+') {
317: return null;
318: }
319: }
320: // Windows JDK workaround
321: else if (last == LAST_ALT) {
322: last = LAST_NOTHING;
323: switch (ch) {
324: case 'B':
325: case 'M':
326: case 'X':
327: case 'c':
328: case '!':
329: case ',':
330: case '?':
331: return null;
332: }
333: }
334: } else {
335: if ((modifiers & InputEvent.SHIFT_MASK) != 0) {
336: switch (ch) {
337: case '\n':
338: case '\t':
339: return null;
340: }
341: }
342: modifiers = 0;
343: }
344: break;
345: //}}}
346: //{{{ KEY_RELEASED...
347: case KeyEvent.KEY_RELEASED:
348: switch (keyCode) {
349: case KeyEvent.VK_ALT:
350: modifiers &= ~InputEvent.ALT_MASK;
351: lastKeyTime = evt.getWhen();
352: // we consume this to work around the bug
353: // where A+TAB window switching activates
354: // the menu bar on Windows.
355: evt.consume();
356: break;
357: case KeyEvent.VK_ALT_GRAPH:
358: modifiers &= ~InputEvent.ALT_GRAPH_MASK;
359: break;
360: case KeyEvent.VK_CONTROL:
361: modifiers &= ~InputEvent.CTRL_MASK;
362: break;
363: case KeyEvent.VK_SHIFT:
364: modifiers &= ~InputEvent.SHIFT_MASK;
365: break;
366: case KeyEvent.VK_META:
367: modifiers &= ~InputEvent.META_MASK;
368: break;
369: case KeyEvent.VK_LEFT:
370: case KeyEvent.VK_RIGHT:
371: case KeyEvent.VK_UP:
372: case KeyEvent.VK_DOWN:
373: case KeyEvent.VK_PAGE_UP:
374: case KeyEvent.VK_PAGE_DOWN:
375: case KeyEvent.VK_END:
376: case KeyEvent.VK_HOME:
377: /* workaround for A+keys producing
378: * garbage on Windows */
379: if (modifiers == InputEvent.ALT_MASK)
380: last = LAST_ALT;
381: break;
382: }
383: break;
384: //}}}
385: }
386: }
387: return evt;
388: } //}}}
389:
390: //{{{ numericKeypadKey() method
391: /**
392: * A workaround for non-working NumLock status in some Java versions.
393: * @since jEdit 4.0pre8
394: */
395: public static void numericKeypadKey() {
396: last = LAST_NOTHING;
397: } //}}}
398:
399: //{{{ Package-private members
400: static long lastKeyTime;
401: static int modifiers;
402: //}}}
403:
404: //{{{ Private members
405: private static int last;
406: private static final int LAST_NOTHING = 0;
407: private static final int LAST_NUMKEYPAD = 1;
408: private static final int LAST_ALT = 2;
409: //}}}
410: }
|