content: /*
   * InputHandler.java - Manages key bindings and executes actions Copyright (C)
   * 1999 Slava Pestov
   *
   * You may use and modify this package for any purpose. Redistribution is
   * permitted, in both source and binary form, provided that this notice remains
   * intact in all source distributions of this package.
   */
  
  package mars.venus.editors.jeditsyntax;
  
  import java.awt.Component;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
  import java.awt.event.KeyAdapter;
  import java.awt.event.KeyEvent;
  import java.util.Enumeration;
  import java.util.EventObject;
  import java.util.Hashtable;
  
  import javax.swing.JPopupMenu;
  import javax.swing.text.BadLocationException;
  
  /**
   * An input handler converts the user's key strokes into concrete actions. It
   * also takes care of macro recording and action repetition.
   * <p>
   * This class provides all the necessary support code for an input handler, but
   * doesn't actually do any key binding logic. It is up to the implementations of
   * this class to do so.
   *
   * @author Slava Pestov
   * @version $Id: InputHandler.java,v 1.14 1999/12/13 03:40:30 sp Exp $
   * @see org.syntax.jedit.DefaultInputHandler 08/12/2002 Clipboard actions
   *      (Oliver Henning)
   */
  public abstract class InputHandler extends KeyAdapter {
  
  	/**
  	 * If this client property is set to Boolean.TRUE on the text area, the home/end
  	 * keys will support 'smart' BRIEF-like behaviour (one press = start/end of
  	 * line, two presses = start/end of viewscreen, three presses = start/end of
  	 * document). By default, this property is not set.
  	 */
  	public static final String SMART_HOME_END_PROPERTY = "InputHandler.homeEnd";
  
  	public static final ActionListener BACKSPACE = new backspace();
  	public static final ActionListener BACKSPACE_WORD = new backspace_word();
  	public static final ActionListener DELETE = new delete();
  	public static final ActionListener DELETE_WORD = new delete_word();
  	public static final ActionListener END = new end(false);
  	public static final ActionListener DOCUMENT_END = new document_end(false);
  	public static final ActionListener SELECT_ALL = new select_all();
  	public static final ActionListener SELECT_END = new end(true);
  	public static final ActionListener SELECT_DOC_END = new document_end(true);
  	public static final ActionListener INSERT_BREAK = new insert_break();
  	public static final ActionListener INSERT_TAB = new insert_tab();
  	public static final ActionListener HOME = new home(false);
  	public static final ActionListener DOCUMENT_HOME = new document_home(false);
  	public static final ActionListener SELECT_HOME = new home(true);
  	public static final ActionListener SELECT_DOC_HOME = new document_home(true);
  	public static final ActionListener NEXT_CHAR = new next_char(false);
  	public static final ActionListener NEXT_LINE = new next_line(false);
  	public static final ActionListener NEXT_PAGE = new next_page(false);
  	public static final ActionListener NEXT_WORD = new next_word(false);
  	public static final ActionListener SELECT_NEXT_CHAR = new next_char(true);
  	public static final ActionListener SELECT_NEXT_LINE = new next_line(true);
  	public static final ActionListener SELECT_NEXT_PAGE = new next_page(true);
  	public static final ActionListener SELECT_NEXT_WORD = new next_word(true);
  	public static final ActionListener OVERWRITE = new overwrite();
  	public static final ActionListener PREV_CHAR = new prev_char(false);
  	public static final ActionListener PREV_LINE = new prev_line(false);
  	public static final ActionListener PREV_PAGE = new prev_page(false);
  	public static final ActionListener PREV_WORD = new prev_word(false);
  	public static final ActionListener SELECT_PREV_CHAR = new prev_char(true);
  	public static final ActionListener SELECT_PREV_LINE = new prev_line(true);
  	public static final ActionListener SELECT_PREV_PAGE = new prev_page(true);
  	public static final ActionListener SELECT_PREV_WORD = new prev_word(true);
  	public static final ActionListener REPEAT = new repeat();
  	public static final ActionListener TOGGLE_RECT = new toggle_rect();
  	// Clipboard
  	public static final ActionListener CLIP_COPY = new clip_copy();
  	public static final ActionListener CLIP_PASTE = new clip_paste();
  	public static final ActionListener CLIP_CUT = new clip_cut();
  
  	// Default action
  	public static final ActionListener INSERT_CHAR = new insert_char();
  
  	private static Hashtable actions;
  
  	static {
  		actions = new Hashtable();
  		actions.put("backspace", BACKSPACE);
  		actions.put("backspace-word", BACKSPACE_WORD);
  		actions.put("delete", DELETE);
  		actions.put("delete-word", DELETE_WORD);
  		actions.put("end", END);
  		actions.put("select-all", SELECT_ALL);
  		actions.put("select-end", SELECT_END);
  		actions.put("document-end", DOCUMENT_END);
  		actions.put("select-doc-end", SELECT_DOC_END);
  		actions.put("insert-break", INSERT_BREAK);
  		actions.put("insert-tab", INSERT_TAB);
  		actions.put("home", HOME);
  		actions.put("select-home", SELECT_HOME);
  		actions.put("document-home", DOCUMENT_HOME);
  		actions.put("select-doc-home", SELECT_DOC_HOME);
  		actions.put("next-char", NEXT_CHAR);
  		actions.put("next-line", NEXT_LINE);
  		actions.put("next-page", NEXT_PAGE);
  		actions.put("next-word", NEXT_WORD);
  		actions.put("select-next-char", SELECT_NEXT_CHAR);
  		actions.put("select-next-line", SELECT_NEXT_LINE);
  		actions.put("select-next-page", SELECT_NEXT_PAGE);
  		actions.put("select-next-word", SELECT_NEXT_WORD);
  		actions.put("overwrite", OVERWRITE);
  		actions.put("prev-char", PREV_CHAR);
  		actions.put("prev-line", PREV_LINE);
  		actions.put("prev-page", PREV_PAGE);
  		actions.put("prev-word", PREV_WORD);
  		actions.put("select-prev-char", SELECT_PREV_CHAR);
  		actions.put("select-prev-line", SELECT_PREV_LINE);
  		actions.put("select-prev-page", SELECT_PREV_PAGE);
  		actions.put("select-prev-word", SELECT_PREV_WORD);
  		actions.put("repeat", REPEAT);
  		actions.put("toggle-rect", TOGGLE_RECT);
  		actions.put("insert-char", INSERT_CHAR);
  		actions.put("clipboard-copy", CLIP_COPY);
  		actions.put("clipboard-paste", CLIP_PASTE);
  		actions.put("clipboard-cut", CLIP_CUT);
  	}
  
  	/**
  	 * Returns a named text area action.
  	 *
  	 * @param name The action name
  	 */
  	public static ActionListener getAction(final String name) {
  		return (ActionListener) actions.get(name);
  	}
  
  	/**
  	 * Returns the name of the specified text area action.
  	 *
  	 * @param listener The action
  	 */
  	public static String getActionName(final ActionListener listener) {
  		final Enumeration enumeration = getActions();
  		while (enumeration.hasMoreElements()) {
  			final String name = (String) enumeration.nextElement();
  			final ActionListener _listener = getAction(name);
  			if (_listener == listener) { return name; }
  		}
  		return null;
  	}
  
  	/**
  	 * Returns an enumeration of all available actions.
  	 */
  	public static Enumeration getActions() { return actions.keys(); }
  
  	/**
  	 * Adds the default key bindings to this input handler. This should not be
  	 * called in the constructor of this input handler, because applications might
  	 * load the key bindings from a file, etc.
  	 */
  	public abstract void addDefaultKeyBindings();
  
  	/**
  	 * Adds a key binding to this input handler.
  	 *
  	 * @param keyBinding The key binding (the format of this is input-handler
  	 *                   specific)
  	 * @param action     The action
  	 */
  	public abstract void addKeyBinding(String keyBinding, ActionListener action);
  
  	/**
  	 * Removes a key binding from this input handler.
  	 *
  	 * @param keyBinding The key binding
  	 */
  	public abstract void removeKeyBinding(String keyBinding);
  
  	/**
  	 * Removes all key bindings from this input handler.
  	 */
  	public abstract void removeAllKeyBindings();
  
  	/**
  	 * Grabs the next key typed event and invokes the specified action with the key
  	 * as a the action command.
  	 *
  	 * @param action The action
  	 */
  	public void grabNextKeyStroke(final ActionListener listener) {
  		grabAction = listener;
  	}
  
  	/**
  	 * Returns if repeating is enabled. When repeating is enabled, actions will be
  	 * executed multiple times. This is usually invoked with a special key stroke in
  	 * the input handler.
  	 */
  	public boolean isRepeatEnabled() { return repeat; }
  
  	/**
  	 * Enables repeating. When repeating is enabled, actions will be executed
  	 * multiple times. Once repeating is enabled, the input handler should read a
  	 * number from the keyboard.
  	 */
  	public void setRepeatEnabled(final boolean repeat) { this.repeat = repeat; }
  
  	/**
  	 * Returns the number of times the next action will be repeated.
  	 */
  	public int getRepeatCount() { return repeat ? Math.max(1, repeatCount) : 1; }
  
  	/**
  	 * Sets the number of times the next action will be repeated.
  	 *
  	 * @param repeatCount The repeat count
  	 */
  	public void setRepeatCount(final int repeatCount) { this.repeatCount = repeatCount; }
  
  	/**
  	 * Returns the macro recorder. If this is non-null, all executed actions should
  	 * be forwarded to the recorder.
  	 */
  	public InputHandler.MacroRecorder getMacroRecorder() { return recorder; }
  
  	/**
  	 * Sets the macro recorder. If this is non-null, all executed actions should be
  	 * forwarded to the recorder.
  	 *
  	 * @param recorder The macro recorder
  	 */
  	public void setMacroRecorder(final InputHandler.MacroRecorder recorder) { this.recorder = recorder; }
  
  	/**
  	 * Returns a copy of this input handler that shares the same key bindings.
  	 * Setting key bindings in the copy will also set them in the original.
  	 */
  	public abstract InputHandler copy();
  
  	/**
  	 * Executes the specified action, repeating and recording it as necessary.
  	 *
  	 * @param listener      The action listener
  	 * @param source        The event source
  	 * @param actionCommand The action command
  	 */
  	public void executeAction(final ActionListener listener, final Object source, final String actionCommand) {
  		// create event
  		final ActionEvent evt = new ActionEvent(source, ActionEvent.ACTION_PERFORMED, actionCommand);
  
  		// don't do anything if the action is a wrapper
  		// (like EditAction.Wrapper)
  		if (listener instanceof Wrapper) {
  			listener.actionPerformed(evt);
  			return;
  		}
  
  		// remember old values, in case action changes them
  		final boolean _repeat = repeat;
  		final int _repeatCount = getRepeatCount();
  
  		// execute the action
  		if (listener instanceof InputHandler.NonRepeatable) {
  			listener.actionPerformed(evt);
  		} else {
  			for (int i = 0; i < Math.max(1, repeatCount); i++) {
  				listener.actionPerformed(evt);
  			}
  		}
  
  		// do recording. Notice that we do no recording whatsoever
  		// for actions that grab keys
  		if (grabAction == null) {
  			if (recorder != null) {
  				if (!(listener instanceof InputHandler.NonRecordable)) {
  					if (_repeatCount != 1) { recorder.actionPerformed(REPEAT, String.valueOf(_repeatCount)); }
  
  					recorder.actionPerformed(listener, actionCommand);
  				}
  			}
  
  			// If repeat was true originally, clear it
  			// Otherwise it might have been set by the action, etc
  			if (_repeat) {
  				repeat = false;
  				repeatCount = 0;
  			}
  		}
  	}
  
  	/**
  	 * Returns the text area that fired the specified event.
  	 *
  	 * @param evt The event
  	 */
  	public static JEditTextArea getTextArea(final EventObject evt) {
  		if (evt != null) {
  			final Object o = evt.getSource();
  			if (o instanceof Component) {
  				// find the parent text area
  				Component c = (Component) o;
  				for (;;) {
  					if (c instanceof JEditTextArea) {
  						return (JEditTextArea) c;
  					} else if (c == null) { break; }
  					if (c instanceof JPopupMenu) {
  						c = ((JPopupMenu) c).getInvoker();
  					} else {
  						c = c.getParent();
  					}
  				}
  			}
  		}
  
  		// this shouldn't happen
  		System.err.println("BUG: getTextArea() returning null");
  		System.err.println("Report this to Slava Pestov <sp@gjt.org>");
  		return null;
  	}
  
  	// protected members
  
  	/**
  	 * If a key is being grabbed, this method should be called with the appropriate
  	 * key event. It executes the grab action with the typed character as the
  	 * parameter.
  	 */
  	protected void handleGrabAction(final KeyEvent evt) {
  		// Clear it *before* it is executed so that executeAction()
  		// resets the repeat count
  		final ActionListener _grabAction = grabAction;
  		grabAction = null;
  		executeAction(_grabAction, evt.getSource(), String.valueOf(evt.getKeyChar()));
  	}
  
  	// protected members
  	protected ActionListener grabAction;
  	protected boolean repeat;
  	protected int repeatCount;
  	protected InputHandler.MacroRecorder recorder;
  
  	/**
  	 * If an action implements this interface, it should not be repeated. Instead,
  	 * it will handle the repetition itself.
  	 */
  	public interface NonRepeatable {}
  
  	/**
  	 * If an action implements this interface, it should not be recorded by the
  	 * macro recorder. Instead, it will do its own recording.
  	 */
  	public interface NonRecordable {}
  
  	/**
  	 * For use by EditAction.Wrapper only.
  	 *
  	 * @since jEdit 2.2final
  	 */
  	public interface Wrapper {}
  
  	/**
  	 * Macro recorder.
  	 */
  	public interface MacroRecorder {
  
  		void actionPerformed(ActionListener listener, String actionCommand);
  	}
  
  	public static class backspace implements ActionListener {
  
  		@Override
  		public void actionPerformed(final ActionEvent evt) {
  			final JEditTextArea textArea = getTextArea(evt);
  
  			if (!textArea.isEditable()) {
  				textArea.getToolkit().beep();
  				return;
  			}
  
  			if (textArea.getSelectionStart() != textArea.getSelectionEnd()) {
  				textArea.setSelectedText("");
  			} else {
  				final int caret = textArea.getCaretPosition();
  				if (caret == 0) {
  					textArea.getToolkit().beep();
  					return;
  				}
  				try {
  					textArea.getDocument().remove(caret - 1, 1);
  				} catch (final BadLocationException bl) {
  					bl.printStackTrace();
  				}
  			}
  		}
  	}
  
  	public static class backspace_word implements ActionListener {
  
  		@Override
  		public void actionPerformed(final ActionEvent evt) {
  			final JEditTextArea textArea = getTextArea(evt);
  			final int start = textArea.getSelectionStart();
  			if (start != textArea.getSelectionEnd()) { textArea.setSelectedText(""); }
  
  			final int line = textArea.getCaretLine();
  			final int lineStart = textArea.getLineStartOffset(line);
  			int caret = start - lineStart;
  
  			final String lineText = textArea.getLineText(textArea.getCaretLine());
  
  			if (caret == 0) {
  				if (lineStart == 0) {
  					textArea.getToolkit().beep();
  					return;
  				}
  				caret--;
  			} else {
  				final String noWordSep = (String) textArea.getDocument().getProperty("noWordSep");
  				caret = TextUtilities.findWordStart(lineText, caret, noWordSep);
  			}
  
  			try {
  				textArea.getDocument().remove(caret + lineStart, start - (caret + lineStart));
  			} catch (final BadLocationException bl) {
  				bl.printStackTrace();
  			}
  		}
  	}
  
  	public static class delete implements ActionListener {
  
  		@Override
  		public void actionPerformed(final ActionEvent evt) {
  			final JEditTextArea textArea = getTextArea(evt);
  
  			if (!textArea.isEditable()) {
  				textArea.getToolkit().beep();
  				return;
  			}
  
  			if (textArea.getSelectionStart() != textArea.getSelectionEnd()) {
  				textArea.setSelectedText("");
  			} else {
  				final int caret = textArea.getCaretPosition();
  				if (caret == textArea.getDocumentLength()) {
  					textArea.getToolkit().beep();
  					return;
  				}
  				try {
  					textArea.getDocument().remove(caret, 1);
  				} catch (final BadLocationException bl) {
  					bl.printStackTrace();
  				}
  			}
  		}
  	}
  
  	public static class delete_word implements ActionListener {
  
  		@Override
  		public void actionPerformed(final ActionEvent evt) {
  			final JEditTextArea textArea = getTextArea(evt);
  			final int start = textArea.getSelectionStart();
  			if (start != textArea.getSelectionEnd()) { textArea.setSelectedText(""); }
  
  			final int line = textArea.getCaretLine();
  			final int lineStart = textArea.getLineStartOffset(line);
  			int caret = start - lineStart;
  
  			final String lineText = textArea.getLineText(textArea.getCaretLine());
  
  			if (caret == lineText.length()) {
  				if (lineStart + caret == textArea.getDocumentLength()) {
  					textArea.getToolkit().beep();
  					return;
  				}
  				caret++;
  			} else {
  				final String noWordSep = (String) textArea.getDocument().getProperty("noWordSep");
  				caret = TextUtilities.findWordEnd(lineText, caret, noWordSep);
  			}
  
  			try {
  				textArea.getDocument().remove(start, caret + lineStart - start);
  			} catch (final BadLocationException bl) {
  				bl.printStackTrace();
  			}
  		}
  	}
  
  	public static class end implements ActionListener {
  
  		private final boolean select;
  
  		public end(final boolean select) {
  			this.select = select;
  		}
  
  		@Override
  		public void actionPerformed(final ActionEvent evt) {
  			final JEditTextArea textArea = getTextArea(evt);
  
  			int caret = textArea.getCaretPosition();
  
  			final int lastOfLine = textArea.getLineEndOffset(textArea.getCaretLine()) - 1;
  			int lastVisibleLine = textArea.getFirstLine() + textArea.getVisibleLines();
  			if (lastVisibleLine >= textArea.getLineCount()) {
  				lastVisibleLine = Math.min(textArea.getLineCount() - 1, lastVisibleLine);
  			} else {
  				lastVisibleLine -= textArea.getElectricScroll() + 1;
  			}
  
  			final int lastVisible = textArea.getLineEndOffset(lastVisibleLine) - 1;
  			final int lastDocument = textArea.getDocumentLength();
  
  			if (caret == lastDocument) {
  				textArea.getToolkit().beep();
  				return;
  			} else if (!Boolean.TRUE.equals(textArea.getClientProperty(SMART_HOME_END_PROPERTY))) {
  				caret = lastOfLine;
  			} else if (caret == lastVisible) {
  				caret = lastDocument;
  			} else if (caret == lastOfLine) {
  				caret = lastVisible;
  			} else {
  				caret = lastOfLine;
  			}
  
  			if (select) {
  				textArea.select(textArea.getMarkPosition(), caret);
  			} else {
  				textArea.setCaretPosition(caret);
  			}
  		}
  	}
  
  	public static class select_all implements ActionListener {
  
  		@Override
  		public void actionPerformed(final ActionEvent evt) {
  			final JEditTextArea textArea = getTextArea(evt);
  			textArea.selectAll();
  		}
  	}
  
  	public static class document_end implements ActionListener {
  
  		private final boolean select;
  
  		public document_end(final boolean select) {
  			this.select = select;
  		}
  
  		@Override
  		public void actionPerformed(final ActionEvent evt) {
  			final JEditTextArea textArea = getTextArea(evt);
  			if (select) {
  				textArea.select(textArea.getMarkPosition(), textArea.getDocumentLength());
  			} else {
  				textArea.setCaretPosition(textArea.getDocumentLength());
  			}
  		}
  	}
  
  	public static class home implements ActionListener {
  
  		private final boolean select;
  
  		public home(final boolean select) {
  			this.select = select;
  		}
  
  		@Override
  		public void actionPerformed(final ActionEvent evt) {
  			final JEditTextArea textArea = getTextArea(evt);
  
  			int caret = textArea.getCaretPosition();
  
  			final int firstLine = textArea.getFirstLine();
  
  			final int firstOfLine = textArea.getLineStartOffset(textArea.getCaretLine());
  			final int firstVisibleLine = firstLine == 0 ? 0 : firstLine + textArea.getElectricScroll();
  			final int firstVisible = textArea.getLineStartOffset(firstVisibleLine);
  
  			if (caret == 0) {
  				textArea.getToolkit().beep();
  				return;
  			} else if (!Boolean.TRUE.equals(textArea.getClientProperty(SMART_HOME_END_PROPERTY))) {
  				caret = firstOfLine;
  			} else if (caret == firstVisible) {
  				caret = 0;
  			} else if (caret == firstOfLine) {
  				caret = firstVisible;
  			} else {
  				caret = firstOfLine;
  			}
  
  			if (select) {
  				textArea.select(textArea.getMarkPosition(), caret);
  			} else {
  				textArea.setCaretPosition(caret);
  			}
  		}
  	}
  
  	public static class document_home implements ActionListener {
  
  		private final boolean select;
  
  		public document_home(final boolean select) {
  			this.select = select;
  		}
  
  		@Override
  		public void actionPerformed(final ActionEvent evt) {
  			final JEditTextArea textArea = getTextArea(evt);
  			if (select) {
  				textArea.select(textArea.getMarkPosition(), 0);
  			} else {
  				textArea.setCaretPosition(0);
  			}
  		}
  	}
  
  	public static class insert_break implements ActionListener {
  
  		@Override
  		public void actionPerformed(final ActionEvent evt) {
  			final JEditTextArea textArea = getTextArea(evt);
  
  			if (!textArea.isEditable()) {
  				textArea.getToolkit().beep();
  				return;
  			}
  			// AutoIndent feature added DPS 31-Dec-2010
  			textArea.setSelectedText("\n" + textArea.getAutoIndent());
  		}
  	}
  
  	public static class insert_tab implements ActionListener {
  
  		@Override
  		public void actionPerformed(final ActionEvent evt) {
  			final JEditTextArea textArea = getTextArea(evt);
  
  			if (!textArea.isEditable()) {
  				textArea.getToolkit().beep();
  				return;
  			}
  
  			textArea.overwriteSetSelectedText("\t");
  		}
  	}
  
  	public static class next_char implements ActionListener {
  
  		private final boolean select;
  
  		public next_char(final boolean select) {
  			this.select = select;
  		}
  
  		@Override
  		public void actionPerformed(final ActionEvent evt) {
  			final JEditTextArea textArea = getTextArea(evt);
  			final int caret = textArea.getCaretPosition();
  			if (caret == textArea.getDocumentLength()) {
  				textArea.getToolkit().beep();
  				return;
  			}
  
  			if (select) {
  				textArea.select(textArea.getMarkPosition(), caret + 1);
  			} else {
  				textArea.setCaretPosition(caret + 1);
  			}
  		}
  	}
  
  	public static class next_line implements ActionListener {
  
  		private final boolean select;
  
  		public next_line(final boolean select) {
  			this.select = select;
  		}
  
  		@Override
  		public void actionPerformed(final ActionEvent evt) {
  			final JEditTextArea textArea = getTextArea(evt);
  			int caret = textArea.getCaretPosition();
  			final int line = textArea.getCaretLine();
  
  			if (line == textArea.getLineCount() - 1) {
  				textArea.getToolkit().beep();
  				return;
  			}
  
  			int magic = textArea.getMagicCaretPosition();
  			if (magic == -1) { magic = textArea.offsetToX(line, caret - textArea.getLineStartOffset(line)); }
  
  			caret = textArea.getLineStartOffset(line + 1) + textArea.xToOffset(line + 1, magic);
  			if (select) {
  				textArea.select(textArea.getMarkPosition(), caret);
  			} else {
  				textArea.setCaretPosition(caret);
  			}
  			textArea.setMagicCaretPosition(magic);
  		}
  	}
  
  	public static class next_page implements ActionListener {
  
  		private final boolean select;
  
  		public next_page(final boolean select) {
  			this.select = select;
  		}
  
  		@Override
  		public void actionPerformed(final ActionEvent evt) {
  			final JEditTextArea textArea = getTextArea(evt);
  			final int lineCount = textArea.getLineCount();
  			int firstLine = textArea.getFirstLine();
  			final int visibleLines = textArea.getVisibleLines();
  			final int line = textArea.getCaretLine();
  
  			firstLine += visibleLines;
  
  			if (firstLine + visibleLines >= lineCount - 1) { firstLine = lineCount - visibleLines; }
  
  			textArea.setFirstLine(firstLine);
  
  			final int caret = textArea.getLineStartOffset(Math.min(textArea.getLineCount() - 1, line + visibleLines));
  			if (select) {
  				textArea.select(textArea.getMarkPosition(), caret);
  			} else {
  				textArea.setCaretPosition(caret);
  			}
  		}
  	}
  
  	public static class next_word implements ActionListener {
  
  		private final boolean select;
  
  		public next_word(final boolean select) {
  			this.select = select;
  		}
  
  		@Override
  		public void actionPerformed(final ActionEvent evt) {
  			final JEditTextArea textArea = getTextArea(evt);
  			int caret = textArea.getCaretPosition();
  			final int line = textArea.getCaretLine();
  			final int lineStart = textArea.getLineStartOffset(line);
  			caret -= lineStart;
  
  			final String lineText = textArea.getLineText(textArea.getCaretLine());
  
  			if (caret == lineText.length()) {
  				if (lineStart + caret == textArea.getDocumentLength()) {
  					textArea.getToolkit().beep();
  					return;
  				}
  				caret++;
  			} else {
  				final String noWordSep = (String) textArea.getDocument().getProperty("noWordSep");
  				caret = TextUtilities.findWordEnd(lineText, caret, noWordSep);
  			}
  
  			if (select) {
  				textArea.select(textArea.getMarkPosition(), lineStart + caret);
  			} else {
  				textArea.setCaretPosition(lineStart + caret);
  			}
  		}
  	}
  
  	public static class overwrite implements ActionListener {
  
  		@Override
  		public void actionPerformed(final ActionEvent evt) {
  			final JEditTextArea textArea = getTextArea(evt);
  			textArea.setOverwriteEnabled(!textArea.isOverwriteEnabled());
  		}
  	}
  
  	public static class prev_char implements ActionListener {
  
  		private final boolean select;
  
  		public prev_char(final boolean select) {
  			this.select = select;
  		}
  
  		@Override
  		public void actionPerformed(final ActionEvent evt) {
  			final JEditTextArea textArea = getTextArea(evt);
  			final int caret = textArea.getCaretPosition();
  			if (caret == 0) {
  				textArea.getToolkit().beep();
  				return;
  			}
  
  			if (select) {
  				textArea.select(textArea.getMarkPosition(), caret - 1);
  			} else {
  				textArea.setCaretPosition(caret - 1);
  			}
  		}
  	}
  
  	public static class prev_line implements ActionListener {
  
  		private final boolean select;
  
  		public prev_line(final boolean select) {
  			this.select = select;
  		}
  
  		@Override
  		public void actionPerformed(final ActionEvent evt) {
  			final JEditTextArea textArea = getTextArea(evt);
  			int caret = textArea.getCaretPosition();
  			final int line = textArea.getCaretLine();
  
  			if (line == 0) {
  				textArea.getToolkit().beep();
  				return;
  			}
  
  			int magic = textArea.getMagicCaretPosition();
  			if (magic == -1) { magic = textArea.offsetToX(line, caret - textArea.getLineStartOffset(line)); }
  
  			caret = textArea.getLineStartOffset(line - 1) + textArea.xToOffset(line - 1, magic);
  			if (select) {
  				textArea.select(textArea.getMarkPosition(), caret);
  			} else {
  				textArea.setCaretPosition(caret);
  			}
  			textArea.setMagicCaretPosition(magic);
  		}
  	}
  
  	public static class prev_page implements ActionListener {
  
  		private final boolean select;
  
  		public prev_page(final boolean select) {
  			this.select = select;
  		}
  
  		@Override
  		public void actionPerformed(final ActionEvent evt) {
  			final JEditTextArea textArea = getTextArea(evt);
  			int firstLine = textArea.getFirstLine();
  			final int visibleLines = textArea.getVisibleLines();
  			final int line = textArea.getCaretLine();
  
  			if (firstLine < visibleLines) { firstLine = visibleLines; }
  
  			textArea.setFirstLine(firstLine - visibleLines);
  
  			final int caret = textArea.getLineStartOffset(Math.max(0, line - visibleLines));
  			if (select) {
  				textArea.select(textArea.getMarkPosition(), caret);
  			} else {
  				textArea.setCaretPosition(caret);
  			}
  		}
  	}
  
  	public static class prev_word implements ActionListener {
  
  		private final boolean select;
  
  		public prev_word(final boolean select) {
  			this.select = select;
  		}
  
  		@Override
  		public void actionPerformed(final ActionEvent evt) {
  			final JEditTextArea textArea = getTextArea(evt);
  			int caret = textArea.getCaretPosition();
  			final int line = textArea.getCaretLine();
  			final int lineStart = textArea.getLineStartOffset(line);
  			caret -= lineStart;
  
  			final String lineText = textArea.getLineText(textArea.getCaretLine());
  
  			if (caret == 0) {
  				if (lineStart == 0) {
  					textArea.getToolkit().beep();
  					return;
  				}
  				caret--;
  			} else {
  				final String noWordSep = (String) textArea.getDocument().getProperty("noWordSep");
  				caret = TextUtilities.findWordStart(lineText, caret, noWordSep);
  			}
  
  			if (select) {
  				textArea.select(textArea.getMarkPosition(), lineStart + caret);
  			} else {
  				textArea.setCaretPosition(lineStart + caret);
  			}
  		}
  	}
  
  	public static class repeat implements ActionListener, InputHandler.NonRecordable {
  
  		@Override
  		public void actionPerformed(final ActionEvent evt) {
  			final JEditTextArea textArea = getTextArea(evt);
  			textArea.getInputHandler().setRepeatEnabled(true);
  			final String actionCommand = evt.getActionCommand();
  			if (actionCommand != null) { textArea.getInputHandler().setRepeatCount(Integer.parseInt(actionCommand)); }
  		}
  	}
  
  	public static class toggle_rect implements ActionListener {
  
  		@Override
  		public void actionPerformed(final ActionEvent evt) {
  			final JEditTextArea textArea = getTextArea(evt);
  			textArea.setSelectionRectangular(!textArea.isSelectionRectangular());
  		}
  	}
  
  	public static class insert_char implements ActionListener, InputHandler.NonRepeatable {
  
  		@Override
  		public void actionPerformed(final ActionEvent evt) {
  			final JEditTextArea textArea = getTextArea(evt);
  			final String str = evt.getActionCommand();
  			final int repeatCount = textArea.getInputHandler().getRepeatCount();
  
  			if (textArea.isEditable()) {
  				final StringBuffer buf = new StringBuffer();
  				for (int i = 0; i < repeatCount; i++) {
  					buf.append(str);
  				}
  				textArea.overwriteSetSelectedText(buf.toString());
  			} else {
  				textArea.getToolkit().beep();
  			}
  		}
  	}
  
  	public static class clip_copy implements ActionListener {
  
  		@Override
  		public void actionPerformed(final ActionEvent evt) {
  			final JEditTextArea textArea = getTextArea(evt);
  			textArea.copy();
  		}
  	}
  
  	public static class clip_paste implements ActionListener {
  
  		@Override
  		public void actionPerformed(final ActionEvent evt) {
  			final JEditTextArea textArea = getTextArea(evt);
  			textArea.paste();
  		}
  	}
  
  	public static class clip_cut implements ActionListener {
  
  		@Override
  		public void actionPerformed(final ActionEvent evt) {
  			final JEditTextArea textArea = getTextArea(evt);
  			textArea.cut();
  		}
  	}
  }
  