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