/jEdit/tags/jedit-4-3-pre9/org/gjt/sp/jedit/gui/InputHandler.java

# · Java · 597 lines · 343 code · 73 blank · 181 comment · 89 complexity · f54527adb4c11480a934a17daf115fe4 MD5 · raw file

  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. package org.gjt.sp.jedit.gui;
  23. //{{{ Imports
  24. import javax.swing.*;
  25. import javax.swing.text.JTextComponent;
  26. import org.gjt.sp.jedit.textarea.JEditTextArea;
  27. import org.gjt.sp.jedit.*;
  28. import org.gjt.sp.jedit.buffer.JEditBuffer;
  29. import org.gjt.sp.jedit.input.AbstractInputHandler;
  30. import org.gjt.sp.util.Log;
  31. import java.awt.event.KeyEvent;
  32. import java.awt.*;
  33. //}}}
  34. /**
  35. * An input handler converts the user's key strokes into concrete actions.
  36. * It also takes care of macro recording and action repetition.<p>
  37. *
  38. * This class provides all the necessary support code for an input
  39. * handler, but doesn't actually do any key binding logic. It is up
  40. * to the implementations of this class to do so.
  41. *
  42. * @author Slava Pestov
  43. * @version $Id: InputHandler.java 8244 2006-12-19 08:06:03Z kpouer $
  44. * @see org.gjt.sp.jedit.gui.DefaultInputHandler
  45. */
  46. public abstract class InputHandler extends AbstractInputHandler
  47. {
  48. //{{{ InputHandler constructor
  49. /**
  50. * Creates a new input handler.
  51. * @param view The view
  52. */
  53. public InputHandler(View view)
  54. {
  55. super();
  56. this.view = view;
  57. } //}}}
  58. //{{{ addKeyBinding() method
  59. /**
  60. * Adds a key binding to this input handler.
  61. * @param keyBinding The key binding (the format of this is
  62. * input-handler specific)
  63. * @param action The action
  64. */
  65. public abstract void addKeyBinding(String keyBinding, String action);
  66. //}}}
  67. //{{{ addKeyBinding() method
  68. /**
  69. * Adds a key binding to this input handler.
  70. * @param keyBinding The key binding (the format of this is
  71. * input-handler specific)
  72. * @param action The action
  73. */
  74. public abstract void addKeyBinding(String keyBinding, EditAction action);
  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. //{{{ removeAllKeyBindings() method
  84. /**
  85. * Removes all key bindings from this input handler.
  86. */
  87. public abstract void removeAllKeyBindings();
  88. //}}}
  89. /**
  90. * Handles a keystroke.
  91. * @param keyStroke The key stroke.
  92. * @return true if the input could be handled.
  93. * @since jEdit 4.2pre5
  94. */
  95. public final boolean handleKey(KeyEventTranslator.Key keyStroke)
  96. {
  97. return handleKey(keyStroke, false);
  98. }
  99. /**
  100. * Forwards key events directly to the input handler.
  101. * This is slightly faster than using a KeyListener
  102. * because some Swing overhead is avoided.
  103. * @since 4.3pre7
  104. */
  105. public void processKeyEvent(KeyEvent evt, int from, boolean global)
  106. {
  107. if(Debug.DUMP_KEY_EVENTS)
  108. {
  109. Log.log(Log.DEBUG,this,"Key event : "
  110. + GrabKeyDialog.toString(evt) + " from " + from);
  111. Log.log(Log.DEBUG,this,view+".isFocused()="+view.isFocused()+'.',new Exception());
  112. }
  113. if(view.getTextArea().hasFocus() && from == View.VIEW)
  114. return;
  115. evt = _preprocessKeyEvent(evt);
  116. if(evt == null)
  117. return;
  118. if(Debug.DUMP_KEY_EVENTS)
  119. {
  120. Log.log(Log.DEBUG,this,"Key event after workaround: "
  121. + GrabKeyDialog.toString(evt) + " from " + from);
  122. }
  123. Component prefixFocusOwner = view.getPrefixFocusOwner();
  124. boolean focusOnTextArea = false;
  125. switch(evt.getID())
  126. {
  127. case KeyEvent.KEY_TYPED:
  128. // if the user pressed eg C+e n n in the
  129. // search bar we want focus to go back there
  130. // after the prefix is done
  131. if(prefixFocusOwner != null)
  132. {
  133. if(prefixFocusOwner.isShowing())
  134. {
  135. prefixFocusOwner.requestFocus();
  136. focusOnTextArea = true;
  137. }
  138. }
  139. if(keyEventInterceptor != null)
  140. keyEventInterceptor.keyTyped(evt);
  141. else if(from == View.ACTION_BAR
  142. || (Debug.GLOBAL_SHORTCUTS_FOR_DOCKED_DOCKABLES &&
  143. Options.SIMPLIFIED_KEY_HANDLING)
  144. || isPrefixActive()
  145. || view.getTextArea().hasFocus())
  146. {
  147. processKeyEventKeyStrokeHandling(evt,from,"type ",global);
  148. }
  149. processKeyEventSub(focusOnTextArea);
  150. break;
  151. case KeyEvent.KEY_PRESSED:
  152. if(keyEventInterceptor != null)
  153. keyEventInterceptor.keyPressed(evt);
  154. else if(KeyEventWorkaround.isBindable(evt.getKeyCode()))
  155. {
  156. if(prefixFocusOwner != null)
  157. {
  158. if(prefixFocusOwner.isShowing())
  159. {
  160. prefixFocusOwner.requestFocus();
  161. focusOnTextArea = true;
  162. }
  163. view.setPrefixFocusOwner(null);
  164. }
  165. processKeyEventKeyStrokeHandling(evt,from,"press",global);
  166. processKeyEventSub(focusOnTextArea);
  167. }
  168. break;
  169. case KeyEvent.KEY_RELEASED:
  170. if(keyEventInterceptor != null)
  171. keyEventInterceptor.keyReleased(evt);
  172. break;
  173. }
  174. } //}}}
  175. //{{{ _preprocessKeyEvent() method
  176. private KeyEvent _preprocessKeyEvent(KeyEvent evt)
  177. {
  178. if(view.isClosed())
  179. return null;
  180. Component focusOwner = view.getFocusOwner();
  181. if (Options.SIMPLIFIED_KEY_HANDLING)
  182. {
  183. /*
  184. It seems that the "else" path below does
  185. not work. Apparently, is is there to prevent
  186. some keyboard events to be "swallowed" by
  187. jEdit when the keyboard event in fact should
  188. be scheduled to swing for further handling.
  189. On some "key typed" events, the "return null;"
  190. is triggered. However, these key events
  191. actually do not seem to be handled elseewhere,
  192. so they are not handled at all.
  193. This behaviour exists with old keyboard handling
  194. as well as with new keyboard handling. However,
  195. the new keyboard handling is more sensitive
  196. about what kinds of key events it receives. It
  197. expects to see all "key typed" events,
  198. which is incompatible with the "return null;"
  199. below.
  200. This bug triggers jEdit bug 1493185 ( https://sourceforge.net/tracker/?func=detail&aid=1493185&group_id=588&atid=100588 ).
  201. Thus, we disable the possibility of
  202. key event swallowing for the new key event
  203. handling.
  204. */
  205. }
  206. else
  207. {
  208. if(focusOwner instanceof JComponent)
  209. {
  210. JComponent comp = (JComponent)focusOwner;
  211. InputMap map = comp.getInputMap();
  212. ActionMap am = comp.getActionMap();
  213. if(map != null && am != null && comp.isEnabled())
  214. {
  215. KeyStroke keyStroke = KeyStroke.getKeyStrokeForEvent(evt);
  216. Object binding = map.get(keyStroke);
  217. if(binding != null && am.get(binding) != null)
  218. {
  219. return null;
  220. }
  221. }
  222. }
  223. }
  224. if(focusOwner instanceof JTextComponent)
  225. {
  226. // fix for the bug where key events in JTextComponents
  227. // inside views are also handled by the input handler
  228. if(evt.getID() == KeyEvent.KEY_PRESSED)
  229. {
  230. switch(evt.getKeyCode())
  231. {
  232. case KeyEvent.VK_ENTER:
  233. case KeyEvent.VK_TAB:
  234. case KeyEvent.VK_BACK_SPACE:
  235. case KeyEvent.VK_SPACE:
  236. return null;
  237. }
  238. }
  239. }
  240. if(evt.isConsumed())
  241. return null;
  242. if(Debug.DUMP_KEY_EVENTS)
  243. {
  244. Log.log(Log.DEBUG,this,"Key event (preprocessing) : "
  245. + GrabKeyDialog.toString(evt));
  246. }
  247. return KeyEventWorkaround.processKeyEvent(evt);
  248. } //}}}
  249. //{{{ processKeyEventSub() method
  250. private void processKeyEventSub(boolean focusOnTextArea)
  251. {
  252. // we might have been closed as a result of
  253. // the above
  254. if(view.isClosed())
  255. return;
  256. // this is a weird hack.
  257. // we don't want C+e a to insert 'a' in the
  258. // search bar if the search bar has focus...
  259. if(isPrefixActive())
  260. {
  261. Component focusOwner = view.getFocusOwner();
  262. if(focusOwner instanceof JTextComponent)
  263. {
  264. view.setPrefixFocusOwner(focusOwner);
  265. view.getTextArea().requestFocus();
  266. }
  267. else if(focusOnTextArea)
  268. {
  269. view.getTextArea().requestFocus();
  270. }
  271. else
  272. {
  273. view.setPrefixFocusOwner(null);
  274. }
  275. }
  276. else
  277. {
  278. view.setPrefixFocusOwner(null);
  279. }
  280. }
  281. //}}}
  282. //{{{ getRepeatCount() method
  283. /**
  284. * Returns the number of times the next action will be repeated.
  285. */
  286. public int getRepeatCount()
  287. {
  288. return repeatCount;
  289. } //}}}
  290. //{{{ setRepeatCount() method
  291. /**
  292. * Sets the number of times the next action will be repeated.
  293. * @param repeatCount The repeat count
  294. */
  295. public void setRepeatCount(int repeatCount)
  296. {
  297. int oldRepeatCount = this.repeatCount;
  298. this.repeatCount = repeatCount;
  299. if(oldRepeatCount != repeatCount)
  300. view.getStatus().setMessage(null);
  301. } //}}}
  302. //{{{ getLastAction() method
  303. /**
  304. * Returns the last executed action.
  305. * @since jEdit 2.5pre5
  306. */
  307. public EditAction getLastAction()
  308. {
  309. return lastAction;
  310. } //}}}
  311. //{{{ readNextChar() method
  312. /**
  313. * Invokes the specified BeanShell code, replacing __char__ in the
  314. * code with the next input character.
  315. * @param msg The prompt to display in the status bar
  316. * @param code The code
  317. * @since jEdit 3.2pre2
  318. */
  319. public void readNextChar(String msg, String code)
  320. {
  321. view.getStatus().setMessage(msg);
  322. readNextChar = code;
  323. } //}}}
  324. //{{{ readNextChar() method
  325. /**
  326. * @deprecated Use the other form of this method instead
  327. */
  328. public void readNextChar(String code)
  329. {
  330. readNextChar = code;
  331. } //}}}
  332. //{{{ invokeAction() method
  333. /**
  334. * Invokes the specified action, repeating and recording it as
  335. * necessary.
  336. * @param action The action
  337. * @since jEdit 4.2pre1
  338. */
  339. public void invokeAction(String action)
  340. {
  341. invokeAction(jEdit.getAction(action));
  342. } //}}}
  343. //{{{ invokeAction() method
  344. /**
  345. * Invokes the specified action, repeating and recording it as
  346. * necessary.
  347. * @param action The action
  348. */
  349. public void invokeAction(EditAction action)
  350. {
  351. JEditBuffer buffer = view.getBuffer();
  352. /* if(buffer.insideCompoundEdit())
  353. buffer.endCompoundEdit(); */
  354. // remember the last executed action
  355. if(!action.noRememberLast())
  356. {
  357. HistoryModel.getModel("action").addItem(action.getName());
  358. if(lastAction == action)
  359. lastActionCount++;
  360. else
  361. {
  362. lastAction = action;
  363. lastActionCount = 1;
  364. }
  365. }
  366. // remember old values, in case action changes them
  367. int _repeatCount = repeatCount;
  368. // execute the action
  369. if(action.noRepeat() || _repeatCount == 1)
  370. action.invoke(view);
  371. else
  372. {
  373. // stop people doing dumb stuff like C+ENTER 100 C+n
  374. if(_repeatCount > REPEAT_COUNT_THRESHOLD)
  375. {
  376. String label = action.getLabel();
  377. if(label == null)
  378. label = action.getName();
  379. else
  380. label = GUIUtilities.prettifyMenuLabel(label);
  381. Object[] pp = { label, _repeatCount };
  382. if(GUIUtilities.confirm(view,"large-repeat-count",pp,
  383. JOptionPane.WARNING_MESSAGE,
  384. JOptionPane.YES_NO_OPTION)
  385. != JOptionPane.YES_OPTION)
  386. {
  387. repeatCount = 1;
  388. view.getStatus().setMessage(null);
  389. return;
  390. }
  391. }
  392. try
  393. {
  394. buffer.beginCompoundEdit();
  395. for(int i = 0; i < _repeatCount; i++)
  396. action.invoke(view);
  397. }
  398. finally
  399. {
  400. buffer.endCompoundEdit();
  401. }
  402. }
  403. Macros.Recorder recorder = view.getMacroRecorder();
  404. if(recorder != null && !action.noRecord())
  405. recorder.record(_repeatCount,action.getCode());
  406. // If repeat was true originally, clear it
  407. // Otherwise it might have been set by the action, etc
  408. if(_repeatCount != 1)
  409. {
  410. // first of all, if this action set a
  411. // readNextChar, do not clear the repeat
  412. if(readNextChar != null)
  413. return;
  414. repeatCount = 1;
  415. view.getStatus().setMessage(null);
  416. }
  417. } //}}}
  418. //{{{ invokeLastAction() method
  419. public void invokeLastAction()
  420. {
  421. if(lastAction == null)
  422. view.getToolkit().beep();
  423. else
  424. invokeAction(lastAction);
  425. } //}}}
  426. //{{{ Instance variables
  427. protected View view;
  428. protected EditAction lastAction;
  429. //}}}
  430. //{{{ userInput() method
  431. protected void userInput(char ch)
  432. {
  433. lastActionCount = 0;
  434. JEditTextArea textArea = view.getTextArea();
  435. /* Buffer buffer = view.getBuffer();
  436. if(!buffer.insideCompoundEdit())
  437. buffer.beginCompoundEdit(); */
  438. if(repeatCount == 1)
  439. textArea.userInput(ch);
  440. else
  441. {
  442. // stop people doing dumb stuff like C+ENTER 100 C+n
  443. if(repeatCount > REPEAT_COUNT_THRESHOLD)
  444. {
  445. Object[] pp = { String.valueOf(ch),
  446. repeatCount };
  447. if(GUIUtilities.confirm(view,
  448. "large-repeat-count.user-input",pp,
  449. JOptionPane.WARNING_MESSAGE,
  450. JOptionPane.YES_NO_OPTION)
  451. != JOptionPane.YES_OPTION)
  452. {
  453. repeatCount = 1;
  454. view.getStatus().setMessage(null);
  455. return;
  456. }
  457. }
  458. JEditBuffer buffer = view.getBuffer();
  459. try
  460. {
  461. if(repeatCount != 1)
  462. buffer.beginCompoundEdit();
  463. for(int i = 0; i < repeatCount; i++)
  464. textArea.userInput(ch);
  465. }
  466. finally
  467. {
  468. if(repeatCount != 1)
  469. buffer.endCompoundEdit();
  470. }
  471. }
  472. Macros.Recorder recorder = view.getMacroRecorder();
  473. if(recorder != null)
  474. {
  475. recorder.recordInput(repeatCount,ch,
  476. textArea.isOverwriteEnabled());
  477. }
  478. repeatCount = 1;
  479. } //}}}
  480. //{{{ invokeReadNextChar() method
  481. protected void invokeReadNextChar(char ch)
  482. {
  483. JEditBuffer buffer = view.getBuffer();
  484. /* if(buffer.insideCompoundEdit())
  485. buffer.endCompoundEdit(); */
  486. String charStr = MiscUtilities.charsToEscapes(String.valueOf(ch));
  487. // this might be a bit slow if __char__ occurs a lot
  488. int index;
  489. while((index = readNextChar.indexOf("__char__")) != -1)
  490. {
  491. readNextChar = readNextChar.substring(0,index)
  492. + '\'' + charStr + '\''
  493. + readNextChar.substring(index + 8);
  494. }
  495. Macros.Recorder recorder = view.getMacroRecorder();
  496. if(recorder != null)
  497. recorder.record(getRepeatCount(),readNextChar);
  498. view.getStatus().setMessage(null);
  499. if(getRepeatCount() != 1)
  500. {
  501. try
  502. {
  503. buffer.beginCompoundEdit();
  504. BeanShell.eval(view,BeanShell.getNameSpace(),
  505. "for(int i = 1; i < "
  506. + getRepeatCount() + "; i++)\n{\n"
  507. + readNextChar + "\n}");
  508. }
  509. finally
  510. {
  511. buffer.endCompoundEdit();
  512. }
  513. }
  514. else
  515. BeanShell.eval(view,BeanShell.getNameSpace(),readNextChar);
  516. readNextChar = null;
  517. } //}}}
  518. //}}}
  519. }