Source code: org/gjt/sp/jedit/gui/InputHandler.java
1 /*
2 * InputHandler.java - Manages key bindings and executes actions
3 * :tabSize=8:indentSize=8:noTabs=false:
4 * :folding=explicit:collapseFolds=1:
5 *
6 * Copyright (C) 1999, 2003 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 javax.swing.JOptionPane;
27 import org.gjt.sp.jedit.textarea.JEditTextArea;
28 import org.gjt.sp.jedit.*;
29 //}}}
30
31 /**
32 * An input handler converts the user's key strokes into concrete actions.
33 * It also takes care of macro recording and action repetition.<p>
34 *
35 * This class provides all the necessary support code for an input
36 * handler, but doesn't actually do any key binding logic. It is up
37 * to the implementations of this class to do so.
38 *
39 * @author Slava Pestov
40 * @version $Id: InputHandler.java,v 1.20 2003/08/28 22:05:24 spestov Exp $
41 * @see org.gjt.sp.jedit.gui.DefaultInputHandler
42 */
43 public abstract class InputHandler
44 {
45 //{{{ InputHandler constructor
46 /**
47 * Creates a new input handler.
48 * @param view The view
49 */
50 public InputHandler(View view)
51 {
52 this.view = view;
53 repeatCount = 1;
54 } //}}}
55
56 //{{{ addKeyBinding() method
57 /**
58 * Adds a key binding to this input handler.
59 * @param keyBinding The key binding (the format of this is
60 * input-handler specific)
61 * @param action The action
62 */
63 public abstract void addKeyBinding(String keyBinding, String action);
64 //}}}
65
66 //{{{ addKeyBinding() method
67 /**
68 * Adds a key binding to this input handler.
69 * @param keyBinding The key binding (the format of this is
70 * input-handler specific)
71 * @param action The action
72 */
73 public abstract void addKeyBinding(String keyBinding, EditAction action);
74 //}}}
75
76 //{{{ removeKeyBinding() method
77 /**
78 * Removes a key binding from this input handler.
79 * @param keyBinding The key binding
80 */
81 public abstract void removeKeyBinding(String keyBinding);
82 //}}}
83
84 //{{{ removeAllKeyBindings() method
85 /**
86 * Removes all key bindings from this input handler.
87 */
88 public abstract void removeAllKeyBindings();
89 //}}}
90
91 //{{{ isPrefixActive() method
92 /**
93 * Returns if a prefix key has been pressed.
94 */
95 public boolean isPrefixActive()
96 {
97 return false;
98 } //}}}
99
100 //{{{ handleKey() method
101 /**
102 * Handles a keystroke.
103 * @param keyStroke The key stroke.
104 * @since jEdit 4.2pre5
105 */
106 public abstract boolean handleKey(KeyEventTranslator.Key keyStroke);
107 //}}}
108
109 //{{{ getRepeatCount() method
110 /**
111 * Returns the number of times the next action will be repeated.
112 */
113 public int getRepeatCount()
114 {
115 return repeatCount;
116 } //}}}
117
118 //{{{ setRepeatCount() method
119 /**
120 * Sets the number of times the next action will be repeated.
121 * @param repeatCount The repeat count
122 */
123 public void setRepeatCount(int repeatCount)
124 {
125 int oldRepeatCount = this.repeatCount;
126 this.repeatCount = repeatCount;
127 if(oldRepeatCount != repeatCount)
128 view.getStatus().setMessage(null);
129 } //}}}
130
131 //{{{ getLastAction() method
132 /**
133 * Returns the last executed action.
134 * @since jEdit 2.5pre5
135 */
136 public EditAction getLastAction()
137 {
138 return lastAction;
139 } //}}}
140
141 //{{{ getLastActionCount() method
142 /**
143 * Returns the number of times the last action was executed.
144 * @since jEdit 2.5pre5
145 */
146 public int getLastActionCount()
147 {
148 return lastActionCount;
149 } //}}}
150
151 //{{{ readNextChar() method
152 /**
153 * Invokes the specified BeanShell code, replacing __char__ in the
154 * code with the next input character.
155 * @param msg The prompt to display in the status bar
156 * @param code The code
157 * @since jEdit 3.2pre2
158 */
159 public void readNextChar(String msg, String code)
160 {
161 view.getStatus().setMessage(msg);
162 readNextChar = code;
163 } //}}}
164
165 //{{{ readNextChar() method
166 /**
167 * @deprecated Use the other form of this method instead
168 */
169 public void readNextChar(String code)
170 {
171 readNextChar = code;
172 } //}}}
173
174 //{{{ resetLastActionCount() method
175 /**
176 * Resets the last action count. This should be called when an
177 * editing operation that is not an action is invoked, for example
178 * a mouse click.
179 * @since jEdit 4.0pre1
180 */
181 public void resetLastActionCount()
182 {
183 lastActionCount = 0;
184 } //}}}
185
186 //{{{ invokeAction() method
187 /**
188 * Invokes the specified action, repeating and recording it as
189 * necessary.
190 * @param action The action
191 * @since jEdit 4.2pre1
192 */
193 public void invokeAction(String action)
194 {
195 invokeAction(jEdit.getAction(action));
196 } //}}}
197
198 //{{{ invokeAction() method
199 /**
200 * Invokes the specified action, repeating and recording it as
201 * necessary.
202 * @param action The action
203 */
204 public void invokeAction(EditAction action)
205 {
206 Buffer buffer = view.getBuffer();
207
208 /* if(buffer.insideCompoundEdit())
209 buffer.endCompoundEdit(); */
210
211 // remember the last executed action
212 if(!action.noRememberLast())
213 {
214 HistoryModel.getModel("action").addItem(action.getName());
215 if(lastAction == action)
216 lastActionCount++;
217 else
218 {
219 lastAction = action;
220 lastActionCount = 1;
221 }
222 }
223
224 // remember old values, in case action changes them
225 int _repeatCount = repeatCount;
226
227 // execute the action
228 if(action.noRepeat() || _repeatCount == 1)
229 action.invoke(view);
230 else
231 {
232 // stop people doing dumb stuff like C+ENTER 100 C+n
233 if(_repeatCount > REPEAT_COUNT_THRESHOLD)
234 {
235 String label = action.getLabel();
236 if(label == null)
237 label = action.getName();
238 else
239 label = GUIUtilities.prettifyMenuLabel(label);
240
241 Object[] pp = { label, new Integer(_repeatCount) };
242
243 if(GUIUtilities.confirm(view,"large-repeat-count",pp,
244 JOptionPane.WARNING_MESSAGE,
245 JOptionPane.YES_NO_OPTION)
246 != JOptionPane.YES_OPTION)
247 {
248 repeatCount = 1;
249 view.getStatus().setMessage(null);
250 return;
251 }
252 }
253
254 try
255 {
256 buffer.beginCompoundEdit();
257
258 for(int i = 0; i < _repeatCount; i++)
259 action.invoke(view);
260 }
261 finally
262 {
263 buffer.endCompoundEdit();
264 }
265 }
266
267 Macros.Recorder recorder = view.getMacroRecorder();
268
269 if(recorder != null && !action.noRecord())
270 recorder.record(_repeatCount,action.getCode());
271
272 // If repeat was true originally, clear it
273 // Otherwise it might have been set by the action, etc
274 if(_repeatCount != 1)
275 {
276 // first of all, if this action set a
277 // readNextChar, do not clear the repeat
278 if(readNextChar != null)
279 return;
280
281 repeatCount = 1;
282 view.getStatus().setMessage(null);
283 }
284 } //}}}
285
286 //{{{ invokeLastAction() method
287 public void invokeLastAction()
288 {
289 if(lastAction == null)
290 view.getToolkit().beep();
291 else
292 invokeAction(lastAction);
293 } //}}}
294
295 //{{{ Protected members
296 private static final int REPEAT_COUNT_THRESHOLD = 20;
297
298 //{{{ Instance variables
299 protected View view;
300 protected int repeatCount;
301
302 protected EditAction lastAction;
303 protected int lastActionCount;
304
305 protected String readNextChar;
306 //}}}
307
308 //{{{ userInput() method
309 protected void userInput(char ch)
310 {
311 lastActionCount = 0;
312
313 JEditTextArea textArea = view.getTextArea();
314
315 /* Buffer buffer = view.getBuffer();
316 if(!buffer.insideCompoundEdit())
317 buffer.beginCompoundEdit(); */
318
319 if(repeatCount == 1)
320 textArea.userInput(ch);
321 else
322 {
323 // stop people doing dumb stuff like C+ENTER 100 C+n
324 if(repeatCount > REPEAT_COUNT_THRESHOLD)
325 {
326 Object[] pp = { String.valueOf(ch),
327 new Integer(repeatCount) };
328
329 if(GUIUtilities.confirm(view,
330 "large-repeat-count.user-input",pp,
331 JOptionPane.WARNING_MESSAGE,
332 JOptionPane.YES_NO_OPTION)
333 != JOptionPane.YES_OPTION)
334 {
335 repeatCount = 1;
336 view.getStatus().setMessage(null);
337 return;
338 }
339 }
340
341 Buffer buffer = view.getBuffer();
342 try
343 {
344 if(repeatCount != 1)
345 buffer.beginCompoundEdit();
346 for(int i = 0; i < repeatCount; i++)
347 textArea.userInput(ch);
348 }
349 finally
350 {
351 if(repeatCount != 1)
352 buffer.endCompoundEdit();
353 }
354 }
355
356 Macros.Recorder recorder = view.getMacroRecorder();
357
358 if(recorder != null)
359 {
360 recorder.recordInput(repeatCount,ch,
361 textArea.isOverwriteEnabled());
362 }
363
364 repeatCount = 1;
365 } //}}}
366
367 //{{{ invokeReadNextChar() method
368 protected void invokeReadNextChar(char ch)
369 {
370 Buffer buffer = view.getBuffer();
371
372 /* if(buffer.insideCompoundEdit())
373 buffer.endCompoundEdit(); */
374
375 String charStr = MiscUtilities.charsToEscapes(String.valueOf(ch));
376
377 // this might be a bit slow if __char__ occurs a lot
378 int index;
379 while((index = readNextChar.indexOf("__char__")) != -1)
380 {
381 readNextChar = readNextChar.substring(0,index)
382 + '\'' + charStr + '\''
383 + readNextChar.substring(index + 8);
384 }
385
386 Macros.Recorder recorder = view.getMacroRecorder();
387 if(recorder != null)
388 recorder.record(getRepeatCount(),readNextChar);
389
390 if(getRepeatCount() != 1)
391 {
392 try
393 {
394 buffer.beginCompoundEdit();
395
396 BeanShell.eval(view,BeanShell.getNameSpace(),
397 "for(int i = 1; i < "
398 + getRepeatCount() + "; i++)\n{\n"
399 + readNextChar + "\n}");
400 }
401 finally
402 {
403 buffer.endCompoundEdit();
404 }
405 }
406 else
407 BeanShell.eval(view,BeanShell.getNameSpace(),readNextChar);
408
409 readNextChar = null;
410
411 view.getStatus().setMessage(null);
412 } //}}}
413
414 //}}}
415 }