KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > gjt > sp > jedit > gui > KeyEventTranslator


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
23 package org.gjt.sp.jedit.gui;
24
25 //{{{ Imports
26
import java.awt.event.*;
27 import java.util.*;
28 import org.gjt.sp.jedit.*;
29 import org.gjt.sp.util.Log;
30 import org.gjt.sp.util.StandardUtilities;
31 //}}}
32

33 /**
34  * In conjunction with the <code>KeyEventWorkaround</code>, hides some
35  * warts in the AWT key event API.
36  *
37  * @author Slava Pestov
38  * @version $Id: KeyEventTranslator.java 8289 2007-01-01 19:07:40Z kpouer $
39  */

40 public class KeyEventTranslator
41 {
42     //{{{ addTranslation() method
43
/**
44      * Adds a keyboard translation.
45      * @param key1 Translate this key
46      * @param key2 Into this key
47      * @since jEdit 4.2pre3
48      */

49     public static void addTranslation(Key key1, Key key2)
50     {
51         transMap.put(key1,key2);
52     } //}}}
53

54     //{{{ translateKeyEvent() method
55

56     protected static KeyEvent lastKeyPressEvent;
57     
58     protected static boolean lastKeyPressAccepted;
59
60     /**
61      * Pass this an event from {@link
62      * KeyEventWorkaround#processKeyEvent(java.awt.event.KeyEvent)}.
63      * @param evt the KeyEvent to translate
64      * @since jEdit 4.2pre3
65      */

66     public static Key translateKeyEvent(KeyEvent evt)
67     {
68         Key key = translateKeyEvent2(evt);
69         
70         if (key!=null) {
71             if (key.isPhantom()) {
72                 key = null;
73             }
74         }
75         
76         return key;
77     }
78     
79     /**
80      * Pass this an event from {@link
81      * KeyEventWorkaround#processKeyEvent(java.awt.event.KeyEvent)}.
82      * @param evt the KeyEvent to translate
83      * @since jEdit 4.2pre3
84      */

85     public static Key translateKeyEvent2(KeyEvent evt)
86     {
87         if (Options.SIMPLIFIED_KEY_HANDLING)
88         { // This is still experimental code.
89

90             /**
91                 A summary of Java key handling intricacies:
92                 (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 released" events are generated for them
93                 (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)
94                 (3) If a key is pressed longer, not only additional "key typed" events but also additional "key released", "key pressed" events are generated.
95                 (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.
96                     
97                 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)).
98                 For (1), we could simply ignore "key pressed" events. But then we would suffer from (2).
99                 
100                 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.
101                 This makes it necessary to store information about the last "key pressed" event (to be able to distinguish).
102                     
103             */

104             char keyChar = evt.getKeyChar();
105             int keyCode = evt.getKeyCode();
106             int modifiers = evt.getModifiers();
107             boolean usecooked = !evt.isActionKey();
108
109 // Log.log(Log.DEBUG,"KeyEventTranslator","translateKeyEvent(): 1: keyChar="+((int) keyChar)+",keyCode="+keyCode+",modifiers="+modifiers+", usecooked="+usecooked+", event="+evt+".");
110

111             /*
112                 Workaround against the bug of jdk1.5, that Ctrl+A has keyChar 0x1 instead of keyChar 0x41:
113                 
114                 This bug may be related to http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6320676
115             */

116             if ((modifiers&InputEvent.CTRL_MASK)!=0) {
117 // Log.log(Log.DEBUG,"KeyEventTranslator","translateKeyEvent(): keyChar="+((int) keyChar)+",keyCode="+keyCode+",modifiers="+modifiers+": 1.");
118
if (keyChar<0x20) {
119 // Log.log(Log.DEBUG,"KeyEventTranslator","translateKeyEvent(): keyChar="+((int) keyChar)+",keyCode="+keyCode+",modifiers="+modifiers+": 1.1.");
120
if (keyChar!=keyCode) { // specifically: if the real Escape, Backspace, Delete, Tab, Enter key was pressed, then this is false
121
// Log.log(Log.DEBUG,"KeyEventTranslator","translateKeyEvent(): keyChar="+((int) keyChar)+",keyCode="+keyCode+",modifiers="+modifiers+": 1.1.1");
122
keyChar+=0x40;
123                         
124                         if ((keyChar>='A')&&(keyChar<='Z')) { // if they are uppercase letters
125
keyChar+=0x20; // make them lowercase letters
126
}
127 // usecooked = false;
128

129                     }
130                 }
131                 
132                 if (keyChar=='\\') { // for compatibility with traditional jEdit installations (Shortcuts are called "C+BACK_SLASH" instead of "C+\")
133
// Log.log(Log.DEBUG,"KeyEventTranslator","translateKeyEvent(): keyChar="+((int) keyChar)+",keyCode="+keyCode+",modifiers="+modifiers+": 1.1.1: backslash.");
134
keyChar = 0;
135                     keyCode = KeyEvent.VK_BACK_SLASH;
136                 }
137                 
138 // Log.log(Log.DEBUG,"KeyEventTranslator","translateKeyEvent(): 2: keyChar="+((int) keyChar)+",keyCode="+keyCode+",modifiers="+modifiers+", event="+evt+".");
139
}
140             
141             
142             
143             /**
144                 These keys are hidden "control keys". That is, they are used as special function key (instead of representing a character to be input), but
145                 Java delivers them with a valid keyChar. We intentionally ignore this keyChar.
146                 (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.)
147             */

148             switch (keyChar) {
149                 case 0x1b: // case KeyEvent.VK_ESCAPE:
150
case 0x08: // case KeyEvent.VK_BACK_SPACE:
151
case 0x7f: // case KeyEvent.VK_DELETE:
152
case 0x09: // case KeyEvent.VK_TAB:
153
case 0x0a: // case KeyEvent.VK_ENTER:
154
case KeyEvent.CHAR_UNDEFINED:
155                     usecooked = false;
156                     keyChar = 0;
157             }
158
159             boolean accept = false;
160             boolean acceptAsPhantom = false;
161             if (true) {
162                 switch(evt.getID()) {
163                     case KeyEvent.KEY_PRESSED:
164                         accept = !usecooked;
165                         acceptAsPhantom = !accept;
166                         lastKeyPressAccepted = accept;
167                         lastKeyPressEvent = evt;
168                     break;
169                     case KeyEvent.KEY_TYPED:
170                         if (lastKeyPressAccepted&&(lastKeyPressEvent!=null)&&(lastKeyPressEvent.getKeyChar()==evt.getKeyChar())) {
171                             // Do not emit internal key event twice.
172
// 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.
173
} else {
174                             accept = usecooked;
175                             acceptAsPhantom = !accept;
176                         }
177                     break;
178                     default:
179                 }
180             } else {
181                 /*
182                     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).
183                     The reason is that java 1.5.0_06 (on Linux) does not deliver "key pressed" events for those keys, only "key typed" events.
184                 */

185                 /*
186                     We ignore all the "key typed" events, as key repeat is already synthetically generated by synthetic "key pressed" "key released" events.
187                     "key typed" events have less information.
188                     
189                     This is highly experimental, as this relies on the JVM to generate these synthetic "key released", "key pressed" events.
190                 */

191                 switch(evt.getID()) {
192                     case KeyEvent.KEY_PRESSED:
193                         accept = true;
194                         if (usecooked) { // This destroys information, but this is what the rest of jEdit is used to :-(
195
keyCode = 0;
196                         }
197                     break;
198                     default:
199                 }
200             }
201                 
202             Key returnValue = null;
203             
204             if (accept||acceptAsPhantom) {
205                 if (!accept && acceptAsPhantom) {
206                     if (keyChar!=0) {
207                         keyCode = 0;
208                     }
209                 }
210                 
211                 returnValue = new Key(modifiersToString(modifiers),keyCode,keyChar);
212                 
213                 if (!accept && acceptAsPhantom) {
214                     if (keyChar!=0) {
215                     }
216                     returnValue.setIsPhantom(true);
217                 }
218             }
219
220             return returnValue;
221         }
222         else
223         {
224             int modifiers = evt.getModifiers();
225             Key returnValue;
226     
227             switch(evt.getID())
228             {
229             case KeyEvent.KEY_PRESSED:
230                 int keyCode = evt.getKeyCode();
231                 if((keyCode >= KeyEvent.VK_0
232                     && keyCode <= KeyEvent.VK_9)
233                     || (keyCode >= KeyEvent.VK_A
234                     && keyCode <= KeyEvent.VK_Z))
235                 {
236                     if(Debug.ALTERNATIVE_DISPATCHER)
237                         return null;
238                     else
239                     {
240                         returnValue = new Key(
241                             modifiersToString(modifiers),
242                             '\0',Character.toLowerCase(
243                             (char)keyCode));
244                     }
245                 }
246                 else
247                 {
248                     if(keyCode == KeyEvent.VK_TAB)
249                     {
250                         evt.consume();
251                         returnValue = new Key(
252                             modifiersToString(modifiers),
253                             keyCode,'\0');
254                     }
255                     else if(keyCode == KeyEvent.VK_SPACE)
256                     {
257                         // for SPACE or S+SPACE we pass the
258
// key typed since international
259
// keyboards sometimes produce a
260
// KEY_PRESSED SPACE but not a
261
// KEY_TYPED SPACE, eg if you have to
262
// do a "<space> to insert ".
263
if((modifiers & ~InputEvent.SHIFT_MASK) == 0)
264                             returnValue = null;
265                         else
266                         {
267                             returnValue = new Key(
268                                 modifiersToString(modifiers),
269                                 0,' ');
270                         }
271                     }
272                     else
273                     {
274                         returnValue = new Key(
275                             modifiersToString(modifiers),
276                             keyCode,'\0');
277                     }
278                 }
279                 break;
280             case KeyEvent.KEY_TYPED:
281                 char ch = evt.getKeyChar();
282     
283                 if(KeyEventWorkaround.isMacControl(evt))
284                     ch |= 0x60;
285     
286                 switch(ch)
287                 {
288                 case '\n':
289                 case '\t':
290                 case '\b':
291                     return null;
292                 case ' ':
293                     if((modifiers & ~InputEvent.SHIFT_MASK) != 0)
294                         return null;
295                 }
296     
297                 int ignoreMods;
298                 if(Debug.ALT_KEY_PRESSED_DISABLED)
299                 {
300                     /* on MacOS, A+ can be user input */
301                     ignoreMods = InputEvent.SHIFT_MASK
302                         | InputEvent.ALT_GRAPH_MASK
303                         | InputEvent.ALT_MASK;
304                 }
305                 else
306                 {
307                     /* on MacOS, A+ can be user input */
308                     ignoreMods = InputEvent.SHIFT_MASK
309                         | InputEvent.ALT_GRAPH_MASK;
310                 }
311     
312                 if((modifiers & InputEvent.ALT_GRAPH_MASK) == 0
313                     && evt.getWhen()
314                     - KeyEventWorkaround.lastKeyTime < 750L
315                     && (KeyEventWorkaround.modifiers & ~ignoreMods)
316                     != 0)
317                 {
318                     if(Debug.ALTERNATIVE_DISPATCHER)
319                     {
320                         returnValue = new Key(
321                             modifiersToString(modifiers),
322                             0,ch);
323                     }
324                     else
325                         return null;
326                 }
327                 else
328                 {
329                     if(ch == ' ')
330                     {
331                         returnValue = new Key(
332                             modifiersToString(modifiers),
333                             0,ch);
334                     }
335                     else
336                         returnValue = new Key(null,0,ch);
337                 }
338                 break;
339             default:
340                 return null;
341             }
342     
343             /* I guess translated events do not have the 'evt' field set
344             so consuming won't work. I don't think this is a problem as
345             nothing uses translation anyway */

346             Key trans = transMap.get(returnValue);
347             if(trans == null)
348                 return returnValue;
349             else
350                 return trans;
351         }
352     } //}}}
353

354     //{{{ parseKey() method
355
/**
356      * Converts a string to a keystroke. The string should be of the
357      * form <i>modifiers</i>+<i>shortcut</i> where <i>modifiers</i>
358      * is any combination of A for Alt, C for Control, S for Shift
359      * or M for Meta, and <i>shortcut</i> is either a single character,
360      * or a keycode name from the <code>KeyEvent</code> class, without
361      * the <code>VK_</code> prefix.
362      * @param keyStroke A string description of the key stroke
363      * @since jEdit 4.2pre3
364      */

365     public static Key parseKey(String JavaDoc keyStroke)
366     {
367         if(keyStroke == null)
368             return null;
369         int index = keyStroke.indexOf('+');
370         int modifiers = 0;
371         if(index != -1)
372         {
373             for(int i = 0; i < index; i++)
374             {
375                 switch(Character.toUpperCase(keyStroke
376                     .charAt(i)))
377                 {
378                 case 'A':
379                     modifiers |= a;
380                     break;
381                 case 'C':
382                     modifiers |= c;
383                     break;
384                 case 'M':
385                     modifiers |= m;
386                     break;
387                 case 'S':
388                     modifiers |= s;
389                     break;
390                 }
391             }
392         }
393         String JavaDoc key = keyStroke.substring(index + 1);
394         if(key.length() == 1)
395         {
396             return new Key(modifiersToString(modifiers),0,key.charAt(0));
397         }
398         else if(key.length() == 0)
399         {
400             Log.log(Log.ERROR,KeyEventTranslator.class,
401                 "Invalid key stroke: " + keyStroke);
402             return null;
403         }
404         else if(key.equals("SPACE"))
405         {
406             return new Key(modifiersToString(modifiers),0,' ');
407         }
408         else
409         {
410             int ch;
411
412             try
413             {
414                 ch = KeyEvent.class.getField("VK_".concat(key))
415                     .getInt(null);
416             }
417             catch(Exception JavaDoc e)
418             {
419                 Log.log(Log.ERROR,KeyEventTranslator.class,
420                     "Invalid key stroke: "
421                     + keyStroke);
422                 return null;
423             }
424
425             return new Key(modifiersToString(modifiers),ch,'\0');
426         }
427     } //}}}
428

429     //{{{ setModifierMapping() method
430
/**
431      * Changes the mapping between symbolic modifier key names
432      * (<code>C</code>, <code>A</code>, <code>M</code>, <code>S</code>) and
433      * Java modifier flags.
434      *
435      * You can map more than one Java modifier to a symobolic modifier, for
436      * example :
437      * <p><code><pre>
438      * setModifierMapping(
439      * InputEvent.CTRL_MASK,
440      * InputEvent.ALT_MASK | InputEvent.META_MASK,
441      * 0,
442      * InputEvent.SHIFT_MASK);
443      *<pre></code></p>
444      *
445      * You cannot map a Java modifer to more than one symbolic modifier.
446      *
447      * @param c The modifier(s) to map the <code>C</code> modifier to
448      * @param a The modifier(s) to map the <code>A</code> modifier to
449      * @param m The modifier(s) to map the <code>M</code> modifier to
450      * @param s The modifier(s) to map the <code>S</code> modifier to
451      *
452      * @since jEdit 4.2pre3
453      */

454     public static void setModifierMapping(int c, int a, int m, int s)
455     {
456     
457         int duplicateMapping =
458             (c & a) | (c & m) | (c & s) | (a & m) | (a & s) | (m & s);
459         
460         if((duplicateMapping & InputEvent.CTRL_MASK) != 0) {
461             throw new IllegalArgumentException JavaDoc(
462                 "CTRL is mapped to more than one modifier");
463         }
464         if((duplicateMapping & InputEvent.ALT_MASK) != 0) {
465             throw new IllegalArgumentException JavaDoc(
466                 "ALT is mapped to more than one modifier");
467         }
468         if((duplicateMapping & InputEvent.META_MASK) != 0) {
469             throw new IllegalArgumentException JavaDoc(
470                 "META is mapped to more than one modifier");
471         }
472         if((duplicateMapping & InputEvent.SHIFT_MASK) != 0) {
473             throw new IllegalArgumentException JavaDoc(
474                 "SHIFT is mapped to more than one modifier");
475         }
476             
477         KeyEventTranslator.c = c;
478         KeyEventTranslator.a = a;
479         KeyEventTranslator.m = m;
480         KeyEventTranslator.s = s;
481     } //}}}
482

483     //{{{ getSymbolicModifierName() method
484
/**
485      * Returns a the symbolic modifier name for the specified Java modifier
486      * flag.
487      *
488      * @param mod A modifier constant from <code>InputEvent</code>
489      *
490      * @since jEdit 4.2pre3
491      */

492     public static char getSymbolicModifierName(int mod)
493     {
494         if((mod & c) != 0)
495             return 'C';
496         else if((mod & a) != 0)
497             return 'A';
498         else if((mod & m) != 0)
499             return 'M';
500         else if((mod & s) != 0)
501             return 'S';
502         else
503             return '\0';
504     } //}}}
505

506     //{{{ modifiersToString() method
507
private static final int[] MODS = {
508         InputEvent.CTRL_MASK,
509         InputEvent.ALT_MASK,
510         InputEvent.META_MASK,
511         InputEvent.SHIFT_MASK
512     };
513
514     public static String JavaDoc modifiersToString(int mods)
515     {
516         StringBuffer JavaDoc buf = null;
517
518         for(int i = 0; i < MODS.length; i++)
519         {
520             if((mods & MODS[i]) != 0)
521                 buf = lazyAppend(buf,getSymbolicModifierName(MODS[i]));
522         }
523
524         if(buf == null)
525             return null;
526         else
527             return buf.toString();
528     } //}}}
529

530     //{{{ getModifierString() method
531
/**
532      * Returns a string containing symbolic modifier names set in the
533      * specified event.
534      *
535      * @param evt The event
536      *
537      * @since jEdit 4.2pre3
538      */

539     public static String JavaDoc getModifierString(InputEvent evt)
540     {
541         StringBuilder JavaDoc buf = new StringBuilder JavaDoc();
542         if(evt.isControlDown())
543             buf.append(getSymbolicModifierName(InputEvent.CTRL_MASK));
544         if(evt.isAltDown())
545             buf.append(getSymbolicModifierName(InputEvent.ALT_MASK));
546         if(evt.isMetaDown())
547             buf.append(getSymbolicModifierName(InputEvent.META_MASK));
548         if(evt.isShiftDown())
549             buf.append(getSymbolicModifierName(InputEvent.SHIFT_MASK));
550         return buf.length() == 0 ? null : buf.toString();
551     } //}}}
552

553     static int c, a, m, s;
554
555     //{{{ Private members
556
/** This map is a pool of Key. */
557     private static final Map<Key, Key> transMap = new HashMap<Key, Key>();
558
559     private static StringBuffer JavaDoc lazyAppend(StringBuffer JavaDoc buf, char ch)
560     {
561         if(buf == null)
562             buf = new StringBuffer JavaDoc();
563         if(buf.indexOf(String.valueOf(ch)) == -1)
564             buf.append(ch);
565         return buf;
566     } //}}}
567

568     static
569     {
570         if(OperatingSystem.isMacOS())
571         {
572             setModifierMapping(
573                 InputEvent.META_MASK, /* == C+ */
574                 InputEvent.CTRL_MASK, /* == A+ */
575                 /* M+ discarded by key event workaround! */
576                 InputEvent.ALT_MASK, /* == M+ */
577                 InputEvent.SHIFT_MASK /* == S+ */);
578         }
579         else
580         {
581             setModifierMapping(
582                 InputEvent.CTRL_MASK,
583                 InputEvent.ALT_MASK,
584                 InputEvent.META_MASK,
585                 InputEvent.SHIFT_MASK);
586         }
587     } //}}}
588

589     //{{{ Key class
590
public static class Key
591     {
592         public final String JavaDoc modifiers;
593         public final int key;
594         public final char input;
595
596         private final int hashCode;
597         /**
598             Wether this Key event applies to all jEdit windows (and not only a specific jEdit GUI component).
599         */

600         protected boolean isFromGlobalContext;
601         
602         /**
603             Wether this Key event is a phantom key event. A phantom key event is a kind of duplicate key event which
604             should not - due to its nature of being a duplicate - generate any action on data.
605             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,
606             would generate any action and thus would be consumed.
607         */

608         protected boolean isPhantom;
609
610         public Key(String JavaDoc modifiers, int key, char input)
611         {
612             this.modifiers = modifiers;
613             this.key = key;
614             this.input = input;
615             hashCode = key + input;
616         }
617
618         public int hashCode()
619         {
620             return hashCode;
621         }
622
623         public boolean equals(Object JavaDoc o)
624         {
625             if(o instanceof Key)
626             {
627                 Key k = (Key)o;
628                 if(StandardUtilities.objectsEqual(modifiers,
629                     k.modifiers) && key == k.key
630                     && input == k.input)
631                 {
632                     return true;
633                 }
634             }
635
636             return false;
637         }
638
639         public String JavaDoc toString()
640         {
641             return (modifiers == null ? "" : modifiers)
642                 + '<'
643                 + Integer.toString(key,16)
644                 + ','
645                 + Integer.toString(input,16)
646                 + '>';
647         }
648         
649         public void setIsFromGlobalContext(boolean to) {
650             isFromGlobalContext = to;
651         }
652         
653         public boolean isFromGlobalContext() {
654             return isFromGlobalContext;
655         }
656         
657         public void setIsPhantom(boolean to) {
658             isPhantom = to;
659         }
660         
661         public boolean isPhantom() {
662             return isPhantom;
663         }
664     } //}}}
665
}
666
Popular Tags