View Javadoc

1   /*
2    * DefaultInputHandler.java - Default implementation of an input handler
3    * Copyright (C) 1999 Slava Pestov
4    *
5    * You may use and modify this package for any purpose. Redistribution is
6    * permitted, in both source and binary form, provided that this notice
7    * remains intact in all source distributions of this package.
8    */
9   
10  package org.syntax.jedit;
11  
12  import java.awt.Toolkit;
13  import java.awt.event.ActionListener;
14  import java.awt.event.InputEvent;
15  import java.awt.event.KeyEvent;
16  import java.util.Hashtable;
17  import java.util.StringTokenizer;
18  
19  import javax.swing.KeyStroke;
20  
21  /***
22   * The default input handler. It maps sequences of keystrokes into actions and
23   * inserts key typed events into the text area.
24   * 
25   * @author Slava Pestov
26   * @version $Id$
27   */
28  public class DefaultInputHandler extends InputHandler
29  {
30  	/***
31  	 * Creates a new input handler with no key bindings defined.
32  	 */
33  	public DefaultInputHandler()
34  	{
35  		bindings = currentBindings = new Hashtable();
36  	}
37  
38  	/***
39  	 * Sets up the default key bindings.
40  	 */
41  	public void addDefaultKeyBindings()
42  	{
43  		addKeyBinding( "BACK_SPACE", BACKSPACE );
44  		addKeyBinding( "C+BACK_SPACE", BACKSPACE_WORD );
45  		addKeyBinding( "DELETE", DELETE );
46  		addKeyBinding( "C+DELETE", DELETE_WORD );
47  
48  		addKeyBinding( "ENTER", INSERT_BREAK );
49  		addKeyBinding( "TAB", INSERT_TAB );
50  
51  		addKeyBinding( "INSERT", OVERWRITE );
52  		addKeyBinding( "C+//", TOGGLE_RECT );
53  
54  		addKeyBinding( "HOME", HOME );
55  		addKeyBinding( "END", END );
56  		addKeyBinding( "C+A", SELECT_ALL );
57  		addKeyBinding( "S+HOME", SELECT_HOME );
58  		addKeyBinding( "S+END", SELECT_END );
59  		addKeyBinding( "C+HOME", DOCUMENT_HOME );
60  		addKeyBinding( "C+END", DOCUMENT_END );
61  		addKeyBinding( "CS+HOME", SELECT_DOC_HOME );
62  		addKeyBinding( "CS+END", SELECT_DOC_END );
63  
64  		addKeyBinding( "PAGE_UP", PREV_PAGE );
65  		addKeyBinding( "PAGE_DOWN", NEXT_PAGE );
66  		addKeyBinding( "S+PAGE_UP", SELECT_PREV_PAGE );
67  		addKeyBinding( "S+PAGE_DOWN", SELECT_NEXT_PAGE );
68  
69  		addKeyBinding( "LEFT", PREV_CHAR );
70  		addKeyBinding( "S+LEFT", SELECT_PREV_CHAR );
71  		addKeyBinding( "C+LEFT", PREV_WORD );
72  		addKeyBinding( "CS+LEFT", SELECT_PREV_WORD );
73  		addKeyBinding( "RIGHT", NEXT_CHAR );
74  		addKeyBinding( "S+RIGHT", SELECT_NEXT_CHAR );
75  		addKeyBinding( "C+RIGHT", NEXT_WORD );
76  		addKeyBinding( "CS+RIGHT", SELECT_NEXT_WORD );
77  		addKeyBinding( "UP", PREV_LINE );
78  		addKeyBinding( "S+UP", SELECT_PREV_LINE );
79  		addKeyBinding( "DOWN", NEXT_LINE );
80  		addKeyBinding( "S+DOWN", SELECT_NEXT_LINE );
81  
82  		addKeyBinding( "C+ENTER", REPEAT );
83  
84  		// Clipboard
85  		addKeyBinding( "C+C", CLIP_COPY );
86  		addKeyBinding( "C+V", CLIP_PASTE );
87  		addKeyBinding( "C+X", CLIP_CUT );
88  	}
89  
90  	/***
91  	 * Adds a key binding to this input handler. The key binding is a list of
92  	 * white space separated key strokes of the form <i>[modifiers+]key</i> where
93  	 * modifier is C for Control, A for Alt, or S for Shift, and key is either a
94  	 * character (a-z) or a field name in the KeyEvent class prefixed with VK_
95  	 * (e.g., BACK_SPACE)
96  	 * 
97  	 * @param keyBinding
98  	 *           The key binding
99  	 * @param action
100 	 *           The action
101 	 */
102 	public void addKeyBinding( String keyBinding, ActionListener action )
103 	{
104 		Hashtable current = bindings;
105 
106 		StringTokenizer st = new StringTokenizer( keyBinding );
107 		while( st.hasMoreTokens() )
108 		{
109 			KeyStroke keyStroke = parseKeyStroke( st.nextToken() );
110 			if( keyStroke == null )
111 				return;
112 
113 			if( st.hasMoreTokens() )
114 			{
115 				Object o = current.get( keyStroke );
116 				if( o instanceof Hashtable )
117 					current = ( Hashtable )o;
118 				else
119 				{
120 					o = new Hashtable();
121 					current.put( keyStroke, o );
122 					current = ( Hashtable )o;
123 				}
124 			}
125 			else
126 				current.put( keyStroke, action );
127 		}
128 	}
129 
130 	/***
131 	 * Removes a key binding from this input handler. This is not yet
132 	 * implemented.
133 	 * 
134 	 * @param keyBinding
135 	 *           The key binding
136 	 */
137 	public void removeKeyBinding( String keyBinding )
138 	{
139 		throw new InternalError( "Not yet implemented" );
140 	}
141 
142 	/***
143 	 * Removes all key bindings from this input handler.
144 	 */
145 	public void removeAllKeyBindings()
146 	{
147 		bindings.clear();
148 	}
149 
150 	/***
151 	 * Returns a copy of this input handler that shares the same key bindings.
152 	 * Setting key bindings in the copy will also set them in the original.
153 	 */
154 	public InputHandler copy()
155 	{
156 		return new DefaultInputHandler( this );
157 	}
158 
159 	/***
160 	 * Handle a key pressed event. This will look up the binding for the key
161 	 * stroke and execute it.
162 	 */
163 	public void keyPressed( KeyEvent evt )
164 	{
165 		int keyCode = evt.getKeyCode();
166 		int modifiers = evt.getModifiers();
167 
168 		if( keyCode == KeyEvent.VK_CONTROL || keyCode == KeyEvent.VK_SHIFT || keyCode == KeyEvent.VK_ALT
169 				|| keyCode == KeyEvent.VK_META )
170 			return;
171 
172 		if( ( modifiers & ~KeyEvent.SHIFT_MASK ) != 0 || evt.isActionKey() || keyCode == KeyEvent.VK_BACK_SPACE
173 				|| keyCode == KeyEvent.VK_DELETE || keyCode == KeyEvent.VK_ENTER || keyCode == KeyEvent.VK_TAB
174 				|| keyCode == KeyEvent.VK_ESCAPE )
175 		{
176 			if( grabAction != null )
177 			{
178 				handleGrabAction( evt );
179 				return;
180 			}
181 
182 			KeyStroke keyStroke = KeyStroke.getKeyStroke( keyCode, modifiers );
183 			Object o = currentBindings.get( keyStroke );
184 			if( o == null )
185 			{
186 				// Don't beep if the user presses some
187 				// key we don't know about unless a
188 				// prefix is active. Otherwise it will
189 				// beep when caps lock is pressed, etc.
190 				if( currentBindings != bindings )
191 				{
192 					Toolkit.getDefaultToolkit().beep();
193 					// F10 should be passed on, but C+e F10
194 					// shouldn't
195 					repeatCount = 0;
196 					repeat = false;
197 					evt.consume();
198 				}
199 				currentBindings = bindings;
200 				return;
201 			}
202 			else if( o instanceof ActionListener )
203 			{
204 				currentBindings = bindings;
205 
206 				executeAction( ( ( ActionListener )o ), evt.getSource(), null );
207 
208 				evt.consume();
209 				return;
210 			}
211 			else if( o instanceof Hashtable )
212 			{
213 				currentBindings = ( Hashtable )o;
214 				evt.consume();
215 				return;
216 			}
217 		}
218 	}
219 
220 	/***
221 	 * Handle a key typed event. This inserts the key into the text area.
222 	 */
223 	public void keyTyped( KeyEvent evt )
224 	{
225 		int modifiers = evt.getModifiers();
226 		char c = evt.getKeyChar();
227 		if( c != KeyEvent.CHAR_UNDEFINED && ( modifiers & KeyEvent.ALT_MASK ) == 0
228 				&& ( modifiers & KeyEvent.CTRL_MASK ) == 0 && ( modifiers & KeyEvent.META_MASK ) == 0 )
229 		{
230 			if( c >= 0x20 && c != 0x7f )
231 			{
232 				KeyStroke keyStroke = KeyStroke.getKeyStroke( Character.toUpperCase( c ) );
233 				Object o = currentBindings.get( keyStroke );
234 
235 				if( o instanceof Hashtable )
236 				{
237 					currentBindings = ( Hashtable )o;
238 					return;
239 				}
240 				else if( o instanceof ActionListener )
241 				{
242 					currentBindings = bindings;
243 					executeAction( ( ActionListener )o, evt.getSource(), String.valueOf( c ) );
244 					return;
245 				}
246 
247 				currentBindings = bindings;
248 
249 				if( grabAction != null )
250 				{
251 					handleGrabAction( evt );
252 					return;
253 				}
254 
255 				// 0-9 adds another 'digit' to the repeat number
256 				if( repeat && Character.isDigit( c ) )
257 				{
258 					repeatCount *= 10;
259 					repeatCount += ( c - '0' );
260 					return;
261 				}
262 
263 				executeAction( INSERT_CHAR, evt.getSource(), String.valueOf( evt.getKeyChar() ) );
264 
265 				repeatCount = 0;
266 				repeat = false;
267 			}
268 		}
269 	}
270 
271 	/***
272 	 * Converts a string to a keystroke. The string should be of the form
273 	 * <i>modifiers</i>+<i>shortcut</i> where <i>modifiers</i> is any combination
274 	 * of A for Alt, C for Control, S for Shift or M for Meta, and
275 	 * <i>shortcut</i> is either a single character, or a keycode name from the
276 	 * <code>KeyEvent</code> class, without the <code>VK_</code> prefix.
277 	 * 
278 	 * @param keyStroke
279 	 *           A string description of the key stroke
280 	 */
281 	public static KeyStroke parseKeyStroke( String keyStroke )
282 	{
283 		if( keyStroke == null )
284 			return null;
285 		int modifiers = 0;
286 		int index = keyStroke.indexOf( '+' );
287 		if( index != -1 )
288 		{
289 			for( int i = 0; i < index; i++ )
290 			{
291 				switch( Character.toUpperCase( keyStroke.charAt( i ) ) )
292 				{
293 				case 'A' :
294 					modifiers |= InputEvent.ALT_MASK;
295 					break;
296 				case 'C' :
297 					modifiers |= Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
298 					break;
299 				case 'M' :
300 					modifiers |= InputEvent.META_MASK;
301 					break;
302 				case 'S' :
303 					modifiers |= InputEvent.SHIFT_MASK;
304 					break;
305 				}
306 			}
307 		}
308 		String key = keyStroke.substring( index + 1 );
309 		if( key.length() == 1 )
310 		{
311 			char ch = Character.toUpperCase( key.charAt( 0 ) );
312 			if( modifiers == 0 )
313 				return KeyStroke.getKeyStroke( ch );
314 			else
315 				return KeyStroke.getKeyStroke( ch, modifiers );
316 		}
317 		else if( key.length() == 0 )
318 		{
319 			System.err.println( "Invalid key stroke: " + keyStroke );
320 			return null;
321 		}
322 		else
323 		{
324 			int ch;
325 
326 			try
327 			{
328 				ch = KeyEvent.class.getField( "VK_".concat( key ) ).getInt( null );
329 			}
330 			catch( Exception e )
331 			{
332 				System.err.println( "Invalid key stroke: " + keyStroke );
333 				return null;
334 			}
335 
336 			return KeyStroke.getKeyStroke( ch, modifiers );
337 		}
338 	}
339 
340 	// private members
341 	private Hashtable bindings;
342 	private Hashtable currentBindings;
343 
344 	private DefaultInputHandler( DefaultInputHandler copy )
345 	{
346 		bindings = currentBindings = copy.bindings;
347 	}
348 }