/* * DefaultInputHandler.java - Default implementation of an input handler * Copyright (C) 1999 Slava Pestov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ package textarea; import java.awt.Component; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.util.EventObject; import java.util.Hashtable; import java.util.StringTokenizer; import javax.swing.JPopupMenu; import javax.swing.KeyStroke; import javax.swing.text.BadLocationException; /** * The default input handler. It maps sequences of keystrokes into actions * and inserts key typed events into the text area. It also defines the * standard set of text area actions which can be used in any input handler. * @author Slava Pestov * @version $Id$ */ public class DefaultInputHandler implements InputHandler { public static final ActionListener BACKSPACE = new backspace(); public static final ActionListener DELETE = new delete(); public static final ActionListener END = new end(false); public static final ActionListener SELECT_END = new 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 SELECT_HOME = new 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 TOGGLE_RECT = new toggle_rect(); public static final ActionListener[] ACTIONS = { BACKSPACE, DELETE, END, SELECT_END, INSERT_BREAK, INSERT_TAB, HOME, SELECT_HOME, NEXT_CHAR, NEXT_LINE, NEXT_PAGE, NEXT_WORD, SELECT_NEXT_CHAR, SELECT_NEXT_LINE, SELECT_NEXT_PAGE, SELECT_NEXT_WORD, OVERWRITE, PREV_CHAR, PREV_LINE, PREV_PAGE, PREV_WORD, SELECT_PREV_CHAR, SELECT_PREV_LINE, SELECT_PREV_PAGE, SELECT_PREV_WORD, TOGGLE_RECT }; public static final String[] ACTION_NAMES = { "backspace", "delete", "end", "select-end", "insert-break", "insert-tab", "home", "select-home", "next-char", "next-line", "next-page", "next-word", "select-next-char", "select-next-line", "select-next-page", "select-next-word", "overwrite", "prev-char", "prev-line", "prev-page", "prev-word", "select-prev-char", "select-prev-line", "select-prev-page", "select-prev-word", "toggle-rect" }; /** * Creates a new input handler with no key bindings defined. */ public DefaultInputHandler() { bindings = currentBindings = new Hashtable(); } /** * Sets up the default key bindings. */ public void addDefaultKeyBindings() { addKeyBinding("BACK_SPACE",BACKSPACE); addKeyBinding("DELETE",DELETE); addKeyBinding("ENTER",INSERT_BREAK); addKeyBinding("TAB",INSERT_TAB); addKeyBinding("INSERT",OVERWRITE); addKeyBinding("C+\\",TOGGLE_RECT); addKeyBinding("HOME",HOME); addKeyBinding("END",END); addKeyBinding("S+HOME",SELECT_HOME); addKeyBinding("S+END",SELECT_END); addKeyBinding("PAGE_UP",PREV_PAGE); addKeyBinding("PAGE_DOWN",NEXT_PAGE); addKeyBinding("S+PAGE_UP",SELECT_PREV_PAGE); addKeyBinding("S+PAGE_DOWN",SELECT_NEXT_PAGE); addKeyBinding("LEFT",PREV_CHAR); addKeyBinding("S+LEFT",SELECT_PREV_CHAR); addKeyBinding("C+LEFT",PREV_WORD); addKeyBinding("CS+LEFT",SELECT_PREV_WORD); addKeyBinding("RIGHT",NEXT_CHAR); addKeyBinding("S+RIGHT",SELECT_NEXT_CHAR); addKeyBinding("C+RIGHT",NEXT_WORD); addKeyBinding("CS+RIGHT",SELECT_NEXT_WORD); addKeyBinding("UP",PREV_LINE); addKeyBinding("S+UP",SELECT_PREV_LINE); addKeyBinding("DOWN",NEXT_LINE); addKeyBinding("S+DOWN",SELECT_NEXT_LINE); } /** * Adds a key binding to this input handler. The key binding is * a list of white space separated key strokes of the form * <i>[modifiers+]key</i> where modifier is C for Control, A for Alt, * or S for Shift, and key is either a character (a-z) or a field * name in the KeyEvent class prefixed with VK_ (e.g., BACK_SPACE) * @param keyBinding The key binding * @param action The action */ public void addKeyBinding(String keyBinding, ActionListener action) { Hashtable current = bindings; StringTokenizer st = new StringTokenizer(keyBinding); while(st.hasMoreTokens()) { KeyStroke keyStroke = parseKeyStroke(st.nextToken()); if(keyStroke == null) return; if(st.hasMoreTokens()) { Object o = current.get(keyStroke); if(o instanceof Hashtable) current = (Hashtable)o; else { o = new Hashtable(); current.put(keyStroke,o); current = (Hashtable)o; } } else current.put(keyStroke,action); } } /** * Removes a key binding from this input handler. This is not yet * implemented. * @param keyBinding The key binding */ public void removeKeyBinding(String keyBinding) { throw new InternalError("Not yet implemented"); } /** * Removes all key bindings from this input handler. */ public void removeAllKeyBindings() { bindings.clear(); } /** * 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(ActionListener listener) { grabAction = listener; } /** * 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 InputHandler copy() { return new DefaultInputHandler(this); } /** * Handle a key pressed event. This will look up the binding for * the key stroke and execute it. */ public void keyPressed(KeyEvent evt) { int keyCode = evt.getKeyCode(); int modifiers = evt.getModifiers(); if((modifiers & ~KeyEvent.SHIFT_MASK) != 0 || evt.isActionKey() || keyCode == KeyEvent.VK_BACK_SPACE || keyCode == KeyEvent.VK_DELETE || keyCode == KeyEvent.VK_ENTER || keyCode == KeyEvent.VK_TAB) { if(grabAction != null) { grabAction.actionPerformed(new ActionEvent( evt.getSource(),ActionEvent.ACTION_PERFORMED, "\0",modifiers)); grabAction = null; return; } KeyStroke keyStroke = KeyStroke.getKeyStroke(keyCode, modifiers); Object o = currentBindings.get(keyStroke); if(o == null) { // Don't beep if the user presses some // key we don't know about unless a // prefix is active. Otherwise it will // beep when caps lock is pressed, etc. if(currentBindings != bindings) { Toolkit.getDefaultToolkit().beep(); // F10 should be passed on, but C+e F10 // shouldn't evt.consume(); } currentBindings = bindings; return; } else if(o instanceof ActionListener) { ((ActionListener)o).actionPerformed( new ActionEvent(evt.getSource(), ActionEvent.ACTION_PERFORMED, null,modifiers)); currentBindings = bindings; evt.consume(); return; } else if(o instanceof Hashtable) { currentBindings = (Hashtable)o; evt.consume(); return; } else if(keyCode != KeyEvent.VK_ALT && keyCode != KeyEvent.VK_CONTROL && keyCode != KeyEvent.VK_SHIFT && keyCode != KeyEvent.VK_META) { return; } } } /** * Handle a key released event. These are ignored. */ public void keyReleased(KeyEvent evt) { } /** * Handle a key typed event. This inserts the key into the text area. */ public void keyTyped(KeyEvent evt) { int modifiers = evt.getModifiers(); char c = evt.getKeyChar(); if(c != KeyEvent.CHAR_UNDEFINED && (modifiers & KeyEvent.ALT_MASK) == 0) { if(c >= 0x20 && c != 0x7f) { currentBindings = bindings; JEditTextArea textArea = getTextArea(evt); if(grabAction != null) { grabAction.actionPerformed(new ActionEvent( textArea,ActionEvent.ACTION_PERFORMED, String.valueOf(c))); grabAction = null; return; } if(!textArea.isEditable()) { textArea.getToolkit().beep(); return; } textArea.overwriteSetSelectedText(String.valueOf(c)); } } } /** * Converts a string to a keystroke. The string should be of the * form <i>modifiers</i>+<i>shortcut</i> where <i>modifiers</i> * is any combination of A for Alt, C for Control, S for Shift * or M for Meta, and <i>shortcut</i> is either a single character, * or a keycode name from the <code>KeyEvent</code> class, without * the <code>VK_</code> prefix. * @param keyStroke A string description of the key stroke */ public static KeyStroke parseKeyStroke(String keyStroke) { if(keyStroke == null) return null; int modifiers = 0; int ch = '\0'; int index = keyStroke.indexOf('+'); if(index != -1) { for(int i = 0; i < index; i++) { switch(Character.toUpperCase(keyStroke .charAt(i))) { case 'A': modifiers |= InputEvent.ALT_MASK; break; case 'C': modifiers |= InputEvent.CTRL_MASK; break; case 'M': modifiers |= InputEvent.META_MASK; break; case 'S': modifiers |= InputEvent.SHIFT_MASK; break; } } } String key = keyStroke.substring(index + 1); if(key.length() == 1) ch = Character.toUpperCase(key.charAt(0)); else if(key.length() == 0) { System.err.println("Invalid key stroke: " + keyStroke); return null; } else { try { ch = KeyEvent.class.getField("VK_".concat(key)) .getInt(null); } catch(Exception e) { System.err.println("Invalid key stroke: " + keyStroke); return null; } } return KeyStroke.getKeyStroke(ch,modifiers); } public static JEditTextArea getTextArea(EventObject evt) { if(evt != null) { 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; } // private members private Hashtable bindings; private Hashtable currentBindings; private ActionListener grabAction; private DefaultInputHandler(DefaultInputHandler copy) { bindings = currentBindings = copy.bindings; } public static class backspace implements ActionListener { public void actionPerformed(ActionEvent evt) { JEditTextArea textArea = getTextArea(evt); if(!textArea.isEditable()) { textArea.getToolkit().beep(); return; } if(textArea.getSelectionStart() != textArea.getSelectionEnd()) { textArea.setSelectedText(""); } else { int caret = textArea.getCaretPosition(); if(caret == 0) { textArea.getToolkit().beep(); return; } try { textArea.getDocument().remove(caret - 1,1); } catch(BadLocationException bl) { bl.printStackTrace(); } } } } public static class delete implements ActionListener { public void actionPerformed(ActionEvent evt) { JEditTextArea textArea = getTextArea(evt); if(!textArea.isEditable()) { textArea.getToolkit().beep(); return; } if(textArea.getSelectionStart() != textArea.getSelectionEnd()) { textArea.setSelectedText(""); } else { int caret = textArea.getCaretPosition(); if(caret == textArea.getDocumentLength()) { textArea.getToolkit().beep(); return; } try { textArea.getDocument().remove(caret,1); } catch(BadLocationException bl) { bl.printStackTrace(); } } } } public static class end implements ActionListener { private boolean select; public end(boolean select) { this.select = select; } public void actionPerformed(ActionEvent evt) { JEditTextArea textArea = getTextArea(evt); int caret = textArea.getCaretPosition(); 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); int lastVisible = textArea.getLineEndOffset(lastVisibleLine) - 1; int lastDocument = textArea.getDocumentLength(); if(caret == lastDocument) { textArea.getToolkit().beep(); return; } 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 home implements ActionListener { private boolean select; public home(boolean select) { this.select = select; } public void actionPerformed(ActionEvent evt) { JEditTextArea textArea = getTextArea(evt); int caret = textArea.getCaretPosition(); int firstLine = textArea.getFirstLine(); int firstOfLine = textArea.getLineStartOffset( textArea.getCaretLine()); int firstVisibleLine = (firstLine == 0 ? 0 : firstLine + textArea.getElectricScroll()); int firstVisible = textArea.getLineStartOffset( firstVisibleLine); if(caret == 0) { textArea.getToolkit().beep(); return; } 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 insert_break implements ActionListener { public void actionPerformed(ActionEvent evt) { JEditTextArea textArea = getTextArea(evt); if(!textArea.isEditable()) { textArea.getToolkit().beep(); return; } textArea.setSelectedText("\n"); } } public static class insert_tab implements ActionListener { public void actionPerformed(ActionEvent evt) { JEditTextArea textArea = getTextArea(evt); if(!textArea.isEditable()) { textArea.getToolkit().beep(); return; } textArea.overwriteSetSelectedText("\t"); } } public static class next_char implements ActionListener { private boolean select; public next_char(boolean select) { this.select = select; } public void actionPerformed(ActionEvent evt) { JEditTextArea textArea = getTextArea(evt); 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 boolean select; public next_line(boolean select) { this.select = select; } public void actionPerformed(ActionEvent evt) { JEditTextArea textArea = getTextArea(evt); int caret = textArea.getCaretPosition(); 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 boolean select; public next_page(boolean select) { this.select = select; } public void actionPerformed(ActionEvent evt) { JEditTextArea textArea = getTextArea(evt); int lineCount = textArea.getLineCount(); int firstLine = textArea.getFirstLine(); int visibleLines = textArea.getVisibleLines(); int line = textArea.getCaretLine(); firstLine += visibleLines; if(firstLine + visibleLines >= lineCount - 1) firstLine = lineCount - visibleLines; textArea.setFirstLine(firstLine); 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 boolean select; public next_word(boolean select) { this.select = select; } public void actionPerformed(ActionEvent evt) { JEditTextArea textArea = getTextArea(evt); int caret = textArea.getCaretPosition(); int line = textArea.getCaretLine(); int lineStart = textArea.getLineStartOffset(line); caret -= lineStart; String lineText = textArea.getLineText(textArea .getCaretLine()); if(caret == lineText.length()) { if(lineStart + caret == textArea.getDocumentLength()) { textArea.getToolkit().beep(); return; } caret++; } else { char ch = lineText.charAt(caret); String noWordSep = (String)textArea.getDocument() .getProperty("noWordSep"); if(noWordSep == null) noWordSep = ""; boolean selectNoLetter = (!Character .isLetterOrDigit(ch) && noWordSep.indexOf(ch) == -1); int wordEnd = lineText.length(); for(int i = caret; i < lineText.length(); i++) { ch = lineText.charAt(i); if(selectNoLetter ^ (!Character .isLetterOrDigit(ch) && noWordSep.indexOf(ch) == -1)) { wordEnd = i; break; } } caret = wordEnd; } if(select) textArea.select(textArea.getMarkPosition(), lineStart + caret); else textArea.setCaretPosition(lineStart + caret); } } public static class overwrite implements ActionListener { public void actionPerformed(ActionEvent evt) { JEditTextArea textArea = getTextArea(evt); textArea.setOverwriteEnabled( !textArea.isOverwriteEnabled()); } } public static class prev_char implements ActionListener { private boolean select; public prev_char(boolean select) { this.select = select; } public void actionPerformed(ActionEvent evt) { JEditTextArea textArea = getTextArea(evt); 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 boolean select; public prev_line(boolean select) { this.select = select; } public void actionPerformed(ActionEvent evt) { JEditTextArea textArea = getTextArea(evt); int caret = textArea.getCaretPosition(); 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 boolean select; public prev_page(boolean select) { this.select = select; } public void actionPerformed(ActionEvent evt) { JEditTextArea textArea = getTextArea(evt); int firstLine = textArea.getFirstLine(); int visibleLines = textArea.getVisibleLines(); int line = textArea.getCaretLine(); if(firstLine < visibleLines) firstLine = visibleLines; textArea.setFirstLine(firstLine - visibleLines); 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 boolean select; public prev_word(boolean select) { this.select = select; } public void actionPerformed(ActionEvent evt) { JEditTextArea textArea = getTextArea(evt); int caret = textArea.getCaretPosition(); int line = textArea.getCaretLine(); int lineStart = textArea.getLineStartOffset(line); caret -= lineStart; String lineText = textArea.getLineText(textArea .getCaretLine()); if(caret == 0) { if(lineStart == 0) { textArea.getToolkit().beep(); return; } caret--; } else { char ch = lineText.charAt(caret - 1); String noWordSep = (String)textArea.getDocument() .getProperty("noWordSep"); if(noWordSep == null) noWordSep = ""; boolean selectNoLetter = (!Character .isLetterOrDigit(ch) && noWordSep.indexOf(ch) == -1); int wordStart = 0; for(int i = caret - 1; i >= 0; i--) { ch = lineText.charAt(i); if(selectNoLetter ^ (!Character .isLetterOrDigit(ch) && noWordSep.indexOf(ch) == -1)) { wordStart = i + 1; break; } } caret = wordStart; } if(select) textArea.select(textArea.getMarkPosition(), lineStart + caret); else textArea.setCaretPosition(lineStart + caret); } } public static class toggle_rect implements ActionListener { public void actionPerformed(ActionEvent evt) { JEditTextArea textArea = getTextArea(evt); textArea.setSelectionRectangular( !textArea.isSelectionRectangular()); } } } /* * ChangeLog: * $Log$ * Revision 1.2 2005/03/17 21:51:13 davidmartinez * Turning into an eclipse project, global import optimize and warning-busting * * Revision 1.1.1.1 2001/09/07 02:47:32 davidmartinez * Initial Checkin of the Alpha tree * * Revision 1.1.1.1 1999/10/20 00:09:21 david * Initial Import * * Revision 1.2 1999/10/20 00:09:21 david * Ran Dos2Unix on all the .java files within the textarea package. * * Revision 1.1.1.1 1999/10/18 15:00:23 david * Initial Check-in * * Revision 1.6 1999/09/30 12:21:05 sp * No net access for a month... so here's one big jEdit 2.1pre1 * */