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