/jEdit/branches/notifications/org/gjt/sp/jedit/gui/InputHandler.java

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