PageRenderTime 182ms CodeModel.GetById 142ms app.highlight 33ms RepoModel.GetById 1ms app.codeStats 1ms

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