content: /*
   * DefaultInputHandler.java - Default implementation of an input handler
   * 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.Toolkit;
  import java.awt.event.ActionListener;
  import java.awt.event.InputEvent;
  import java.awt.event.KeyEvent;
  import java.util.Hashtable;
  import java.util.StringTokenizer;
  
  import javax.swing.KeyStroke;
  
  /**
   * The default input handler. It maps sequences of keystrokes into actions and
   * inserts key typed events into the text area.
   *
   * @author Slava Pestov
   * @version $Id: DefaultInputHandler.java,v 1.18 1999/12/13 03:40:30 sp Exp $
   */
  public class DefaultInputHandler extends InputHandler {
  
  	/**
  	 * Creates a new input handler with no key bindings defined.
  	 */
  	public DefaultInputHandler() {
  		bindings = currentBindings = new Hashtable();
  	}
  
  	/**
  	 * Sets up the default key bindings.
  	 */
  	@Override
  	public void addDefaultKeyBindings() {
  		addKeyBinding("BACK_SPACE", BACKSPACE);
  		addKeyBinding("C+BACK_SPACE", BACKSPACE_WORD);
  		addKeyBinding("DELETE", DELETE);
  		addKeyBinding("C+DELETE", DELETE_WORD);
  
  		addKeyBinding("ENTER", INSERT_BREAK);
  		addKeyBinding("TAB", INSERT_TAB);
  
  		addKeyBinding("INSERT", OVERWRITE);
  		addKeyBinding("C+\\", TOGGLE_RECT);
  
  		addKeyBinding("HOME", HOME);
  		addKeyBinding("END", END);
  		addKeyBinding("C+A", SELECT_ALL);
  		addKeyBinding("S+HOME", SELECT_HOME);
  		addKeyBinding("S+END", SELECT_END);
  		addKeyBinding("C+HOME", DOCUMENT_HOME);
  		addKeyBinding("C+END", DOCUMENT_END);
  		addKeyBinding("CS+HOME", SELECT_DOC_HOME);
  		addKeyBinding("CS+END", SELECT_DOC_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);
  
  		addKeyBinding("C+ENTER", REPEAT);
  
  		// Clipboard
  		addKeyBinding("C+C", CLIP_COPY);
  		addKeyBinding("C+V", CLIP_PASTE);
  		addKeyBinding("C+X", CLIP_CUT);
  	}
  
  	/**
  	 * 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
  	 */
  	@Override
  	public void addKeyBinding(final String keyBinding, final ActionListener action) {
  		Hashtable current = bindings;
  
  		final StringTokenizer st = new StringTokenizer(keyBinding);
  		while (st.hasMoreTokens()) {
  			final 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
  	 */
  	@Override
  	public void removeKeyBinding(final String keyBinding) {
  		throw new InternalError("Not yet implemented");
  	}
  
  	/**
  	 * Removes all key bindings from this input handler.
  	 */
  	@Override
  	public void removeAllKeyBindings() {
  		bindings.clear();
  	}
  
  	/**
  	 * 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.
  	 */
  	@Override
  	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.
  	 */
  	@Override
  	public void keyPressed(final KeyEvent evt) {
  		final int keyCode = evt.getKeyCode();
  		final int modifiers = evt.getModifiers();
  		if (keyCode == KeyEvent.VK_CONTROL || keyCode == KeyEvent.VK_SHIFT || keyCode == KeyEvent.VK_ALT
  				|| keyCode == KeyEvent.VK_META) {
  			return;
  		}
  
  		if ((modifiers & ~InputEvent.SHIFT_MASK) != 0 || evt.isActionKey() || keyCode == KeyEvent.VK_BACK_SPACE
  				|| keyCode == KeyEvent.VK_DELETE || keyCode == KeyEvent.VK_ENTER || keyCode == KeyEvent.VK_TAB
  				|| keyCode == KeyEvent.VK_ESCAPE) {
  			if (grabAction != null) {
  				handleGrabAction(evt);
  				return;
  			}
  
  			final KeyStroke keyStroke = KeyStroke.getKeyStroke(keyCode, modifiers);
  			final 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
  					repeatCount = 0;
  					repeat = false;
  					evt.consume();
  				}
  				currentBindings = bindings;
  				// No binding for this keyStroke, pass it to menu
  				// (mnemonic, accelerator).  DPS 4-may-2010
  				mars.Globals.getGui().dispatchEventToMenu(evt);
  				evt.consume();
  				return;
  			} else if (o instanceof ActionListener) {
  				currentBindings = bindings;
  				executeAction((ActionListener) o, evt.getSource(), null);
  
  				evt.consume();
  				return;
  			} else if (o instanceof Hashtable) {
  				currentBindings = (Hashtable) o;
  				evt.consume();
  				return;
  			}
  		}
  	}
  
  	/**
  	 * Handle a key typed event. This inserts the key into the text area.
  	 */
  	@Override
  	public void keyTyped(final KeyEvent evt) {
  		final int modifiers = evt.getModifiers();
  		final char c = evt.getKeyChar();
  		// This IF statement needed to prevent Macintosh shortcut keyChar from
  		// being echoed to the text area.  E.g. Command-s, for Save, will echo
  		// the 's' character unless filtered out here.  Command modifier
  		// matches KeyEvent.META_MASK.   DPS 30-Nov-2010
  		if ((modifiers & InputEvent.META_MASK) != 0) { return; }
  		// DPS 9-Jan-2013.  Umberto Villano from Italy describes Alt combinations
  		// not working on Italian Mac keyboards, where # requires Alt (Option).
  		// This is preventing him from writing comments.  Similar complaint from
  		// Joachim Parrow in Sweden, only for the $ character.  Villano pointed
  		// me to this method.  Plus a Google search on "jeditsyntax alt key"
  		// (without quotes) took me to
  		// http://compgroups.net/comp.lang.java.programmer/option-key-in-jedit-syntax-package/1068227
  		// which says to comment out the second condition in this IF statement:
  		// if(c != KeyEvent.CHAR_UNDEFINED && (modifiers & KeyEvent.ALT_MASK) == 0)
  		// So let's give it a try!
  		// (...later) Bummer, it results in keystroke echoed into editing area when I use Alt
  		// combination for shortcut menu access (e.g. Alt+f to open the File menu).
  		//
  		// Torsten Maehne: This is a shortcoming of the menu
  		// shortcuts handling in the jedit component: It assumes that
  		// modifier keys are the same across all platforms. However,
  		// the menu shortcut keymask varies between OS X and
  		// Windows/Linux, it is Cmd + <key> instead of Alt +
  		// <key>. The "Java Development Guide for Mac" explicitly
  		// discusses the issue in:
  		// <https://developer.apple.com/library/mac/#documentation/Java/Conceptual/Java14Development/07-NativePlatformIntegration/NativePlatformIntegration.html#//apple_ref/doc/uid/TP40001909-211884-TPXREF130>
  		//
  		// As jedit always considers Alt + <key> as a keyboard
  		// shortcut, they block their output in the editor, which
  		// prevents the entry of special characters on OS X that uses
  		// Alt + <key> for this purpose instead of AltGr + <key>, as
  		// on Windows or Linux.
  		//
  		// For the latest jedit version (5.0.0), the menu
  		// accelerators don't work on OS X, at least the special
  		// characters can be entered using Alt + <key>. The issue is
  		// still open, but there seems to be progress:
  		//
  		// http://sourceforge.net/tracker/index.php?func=detail&aid=3558572&group_id=588&atid=300588
  		// http://sourceforge.net/tracker/?func=detail&atid=300588&aid=3604532&group_id=588
  		//
  		// Until this is resolved upstream, don't ignore characters
  		// on OS X, which have been entered with the ALT modifier:
  		if (c != KeyEvent.CHAR_UNDEFINED && ((modifiers & InputEvent.ALT_MASK) == 0 || System.getProperty("os.name")
  				.contains("OS X"))) {
  			if (c >= 0x20 && c != 0x7f) {
  				final KeyStroke keyStroke = KeyStroke.getKeyStroke(Character.toUpperCase(c));
  				final Object o = currentBindings.get(keyStroke);
  
  				if (o instanceof Hashtable) {
  					currentBindings = (Hashtable) o;
  					return;
  				} else if (o instanceof ActionListener) {
  					currentBindings = bindings;
  					executeAction((ActionListener) o, evt.getSource(), String.valueOf(c));
  					return;
  				}
  
  				currentBindings = bindings;
  
  				if (grabAction != null) {
  					handleGrabAction(evt);
  					return;
  				}
  
  				// 0-9 adds another 'digit' to the repeat number
  				if (repeat && Character.isDigit(c)) {
  					repeatCount *= 10;
  					repeatCount += c - '0';
  					return;
  				}
  				executeAction(INSERT_CHAR, evt.getSource(), String.valueOf(evt.getKeyChar()));
  				repeatCount = 0;
  				repeat = false;
  			}
  		}
  	}
  
  	/**
  	 * 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(final String keyStroke) {
  		if (keyStroke == null) { return null; }
  		int modifiers = 0;
  		final 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;
  				}
  			}
  		}
  		final String key = keyStroke.substring(index + 1);
  		if (key.length() == 1) {
  			final char ch = Character.toUpperCase(key.charAt(0));
  			if (modifiers == 0) {
  				return KeyStroke.getKeyStroke(ch);
  			} else {
  				return KeyStroke.getKeyStroke(ch, modifiers);
  			}
  		} else if (key.length() == 0) {
  			System.err.println("Invalid key stroke: " + keyStroke);
  			return null;
  		} else {
  			int ch;
  
  			try {
  				ch = KeyEvent.class.getField("VK_".concat(key)).getInt(null);
  			} catch (final Exception e) {
  				System.err.println("Invalid key stroke: " + keyStroke);
  				return null;
  			}
  
  			return KeyStroke.getKeyStroke(ch, modifiers);
  		}
  	}
  
  	// private members
  	private final Hashtable bindings;
  	private Hashtable currentBindings;
  
  	private DefaultInputHandler(final DefaultInputHandler copy) {
  		bindings = currentBindings = copy.bindings;
  	}
  }
  