Package org.jnode.command.system

Source Code of org.jnode.command.system.BindKeysCommand

/*
* $Id$
*
* Copyright (C) 2003-2014 JNode.org
*
* 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.1 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jnode.command.system;

import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jnode.driver.console.Console;
import org.jnode.driver.console.KeyEventBindings;
import org.jnode.driver.console.TextConsole;
import org.jnode.driver.console.VirtualKey;
import org.jnode.driver.console.textscreen.ConsoleKeyEventBindings;
import org.jnode.driver.console.textscreen.KeyboardReaderAction;
import org.jnode.shell.AbstractCommand;
import org.jnode.shell.CommandLine.Token;
import org.jnode.shell.ShellUtils;
import org.jnode.shell.syntax.Argument;
import org.jnode.shell.syntax.CommandSyntaxException;
import org.jnode.shell.syntax.EnumArgument;
import org.jnode.shell.syntax.FlagArgument;

/**
* This command allows the user to examine and change JNode's key bindings.
*
* @author crawley@jnode.org
*/
public class BindKeysCommand extends AbstractCommand {

    private static final String help_reset = "reset the binding to the default values";
    private static final String help_add = "add bindings";
    private static final String help_remove = "remove bindings";
    private static final String help_action = "a keyboard reader action";
    private static final String help_vkspec = "a virtual key specification";
    private static final String help_char = "a character";
    private static final String help_super = "Display or change the keyboard bindings";
    private static final String err_not_text = "The current console is not a TextConsole";
    private static final String err_no_bind = "There are no binding for action '%s'%n";
    private static final String fmt_update = "Updated the current console's key bindings for action '%s'%n";
    private static final String str_reset = "Reset the current console's key bindings";
    private static final String str_not_bound = "not bound";
    private static final String ex_inv_char = "invalid character";
    private static final String ex_unknown_vkey = "'%s' is an unknown virtual key name";
    private static final String ex_unknown_mod = "'%s' is an unknown modifier";
   
    private static final Map<String, Integer> VK_NAME_MAP =
        new HashMap<String, Integer>();
    private static final Map<Integer, String> VK_MAP =
        new HashMap<Integer, String>();
    private static final Map<String, Integer> MODIFIER_NAME_MAP =
        new HashMap<String, Integer>();
    private static final Map<Integer, String> MODIFIER_MAP =
        new HashMap<Integer, String>();
   
    static {
        // This is the best way I can think of to enumerate all of the VK_ codes
        // defined by the KeyEvent class.
        for (Field field : KeyEvent.class.getFields()) {
            if (Modifier.isStatic(field.getModifiers()) &&
                    field.getName().startsWith("VK_")) {
                try {
                    Integer vk = (Integer) field.get(null);
                    String name = constCase(KeyEvent.getKeyText(vk));
                    VK_NAME_MAP.put(name, vk);
                    VK_MAP.put(vk, name);
                } catch (IllegalAccessException ex) {
                    // This cannot happen.  But if it does we'll just ignore
                    // the virtual key constant that caused it.
                }
            }
        }
        // Now do the same for the modifiers.  Note that we map the names to the 'new'
        // modifier mask values; see KeyEvent javadoc ...
        initModifier("AWT.shift", "Shift", KeyEvent.SHIFT_DOWN_MASK);
        initModifier("AWT.control", "Ctrl", KeyEvent.CTRL_DOWN_MASK);
        initModifier("AWT.alt", "Alt", KeyEvent.ALT_DOWN_MASK);
        initModifier("AWT.meta", "Meta", KeyEvent.META_DOWN_MASK);
        initModifier("AWT.button1", "Button 1", KeyEvent.BUTTON1_DOWN_MASK);
        initModifier("AWT.button2", "Button 2", KeyEvent.BUTTON2_DOWN_MASK);
        initModifier("AWT.button3", "Button 3", KeyEvent.BUTTON3_DOWN_MASK);
        initModifier("AWT.altGraph", "Alt Graph", KeyEvent.ALT_GRAPH_DOWN_MASK);
    }
   
    private static void initModifier(String propName, String dflt, int modifier) {
        String name = constCase(Toolkit.getProperty(propName, dflt));
        MODIFIER_NAME_MAP.put(name,  modifier);
        MODIFIER_MAP.put(modifier, name);
    }
   
    private static final String[] ASCII_NAMES = new String[] {
        "NUL", "SOH", "STC", "ETX", "EOT", "ENQ", "ACK", "BEL",
        "BS", "HT", "NL", "VT", "FF", "CR", "SO", "SI",
        "DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB",
        "CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US", "SP"
    };
   
    private static class ActionArgument extends EnumArgument<KeyboardReaderAction> {
        public ActionArgument(String label, int flags, String description) {
            super(label, flags, KeyboardReaderAction.class, description);
        }

        @Override
        protected String argumentKind() {
            return "keyboard reader action";
        }
    }
   
    private static class VirtualKeyArgument extends Argument<VirtualKey> {

        protected VirtualKeyArgument(String label, int flags, String description) {
            super(label, flags, new VirtualKey[0], description);
        }

        @Override
        protected String argumentKind() {
            return "VK spec";
        }

        @Override
        protected VirtualKey doAccept(Token value, int flags) throws CommandSyntaxException {
            String str = value.text;
            String[] parts = str.split("\\+");
            int modifiers = 0;
            for (int i = 0; i < parts.length - 1; i++) {
                Integer m = MODIFIER_NAME_MAP.get(constCase(parts[i]));
                if (m == null) {
                    throw new CommandSyntaxException(String.format(ex_unknown_mod, parts[i]));
                }
                modifiers |= m;
            }
            Integer vk = VK_NAME_MAP.get(constCase(parts[parts.length - 1]));
            if (vk == null) {
                throw new CommandSyntaxException(String.format(ex_unknown_vkey, parts[parts.length - 1]));
            }
            return new VirtualKey(vk, modifiers);
        }
    }
   
    private static class CharacterArgument extends Argument<Character> {

        protected CharacterArgument(String label, int flags, String description) {
            super(label, flags, new Character[0], description);
        }

        @Override
        protected String argumentKind() {
            return "character";
        }

        @Override
        protected Character doAccept(Token value, int flags) throws CommandSyntaxException {
            String str = value.text;
            String upper = str.toUpperCase();
            for (int i = 0; i < ASCII_NAMES.length; i++) {
                if (ASCII_NAMES[i].equals(upper)) {
                    return (char) i;
                }
            }
            if (upper.equals("DEL")) {
                return '\177';
            }
            if (str.length() == 3 && str.charAt(0) == '\'' && str.charAt(2) == '\'') {
                return str.charAt(1);
            }
            if (str.startsWith("0x") || str.startsWith("0X")) {
                int ch = Integer.parseInt(str.substring(2), 16);
                return (char) ch;
            }
            throw new CommandSyntaxException(ex_inv_char);
        }
    }
   
    private final FlagArgument argReset;
    private final FlagArgument argAdd;
    private final FlagArgument argRemove;
    private final ActionArgument argAction;
    private final VirtualKeyArgument argVkSpec;
    private final CharacterArgument argCharacter;
   
    private PrintWriter out;
    private PrintWriter err;
   
    public BindKeysCommand() {
        super(help_super);
        argReset     = new FlagArgument("reset", Argument.OPTIONAL, help_reset);
        argAdd       = new FlagArgument("add", Argument.OPTIONAL, help_add);
        argRemove    = new FlagArgument("remove", Argument.OPTIONAL, help_remove);
        argAction    = new ActionArgument("action", Argument.OPTIONAL, help_action);
        argVkSpec    = new VirtualKeyArgument("vkSpec", Argument.OPTIONAL | Argument.MULTIPLE, help_vkspec);
        argCharacter = new CharacterArgument("character", Argument.OPTIONAL | Argument.MULTIPLE, help_char);
        registerArguments(argReset, argAdd, argRemove, argAction, argVkSpec, argCharacter);
    }

    private static String constCase(String keyText) {
        StringBuilder sb = new StringBuilder(keyText);
        int len = keyText.length();
        for (int i = 0; i < len; i++) {
            char ch = sb.charAt(i);
            if (ch == ' ') {
                sb.setCharAt(i, '_');
            } else {
                sb.setCharAt(i, Character.toUpperCase(ch));
            }
        }
        return sb.toString();
    }
   
    @Override
    public void execute() throws Exception {
        out = getOutput().getPrintWriter();
        err = getError().getPrintWriter();
        Console console = ShellUtils.getCurrentShell().getConsole();
        if (!(console instanceof TextConsole)) {
            err.println(err_not_text);
        }
        TextConsole textConsole = (TextConsole) console;
        if (argReset.isSet()) {
            resetBindings(textConsole);
        } else if (argAdd.isSet()) {
            addBindings(textConsole);
        } else if (argRemove.isSet()) {
            removeBindings(textConsole);
        } else {
            displayBindings(textConsole);
        }
    }

    /**
     * Remove bindings for an action.
     *
     * @param console the console whose bindings are to be changed
     */
    private void removeBindings(TextConsole console) {
        ConsoleKeyEventBindings bindings = console.getKeyEventBindings();
        // This throws an unchecked exception if the action is not supplied.  It signals
        // a bug in the command syntax and should be allowed to propagate to the shell.
        KeyboardReaderAction action = argAction.getValue();
       
        if (argVkSpec.isSet() || argCharacter.isSet()) {
            // If virtual key names were supplied, remove only those bindings.
            if (argVkSpec.isSet()) {
                for (VirtualKey vk : argVkSpec.getValues()) {
                    bindings.unsetVKAction(vk);
                }
            }
            if (argCharacter.isSet()) {
                for (char ch : argCharacter.getValues()) {
                    bindings.unsetCharAction(ch);
                }
            }
        } else {
            // Otherwise remove all bindings for the action.
            int count = 0;
            List<Character> chars = buildCharMap(bindings).get(action);
            if (chars != null) {
                for (char ch : chars) {
                    bindings.unsetCharAction(ch);
                    count++;
                }
            }
            List<VirtualKey> vks = buildVKMap(bindings).get(action);
            if (vks != null) {
                for (VirtualKey vk : vks) {
                    bindings.unsetVKAction(vk);
                    count++;
                }
            }
            if (count == 0) {
                err.format(err_no_bind, action);
                exit(1);
            }
        }
        console.setKeyEventBindings(bindings);
        out.format(fmt_update, action);
    }
   
    private void addBindings(TextConsole console) {
        ConsoleKeyEventBindings bindings = console.getKeyEventBindings();
        // This throws an unchecked exception if the action is not supplied.  It signals
        // a bug in the command syntax and should be allowed to propagate to the shell.
        KeyboardReaderAction action = argAction.getValue();

        int count = 0;
        if (argVkSpec.isSet()) {
            for (VirtualKey vk : argVkSpec.getValues()) {
                bindings.setVKAction(vk, action);
                count++;
            }
        }
        if (argCharacter.isSet()) {
            for (char ch : argCharacter.getValues()) {
                bindings.setCharAction(ch, action);
                count++;
            }
        }
        if (count > 0) {
            console.setKeyEventBindings(bindings);
            out.format(fmt_update, action);
        }
    }

    private void resetBindings(TextConsole console) {
        console.setKeyEventBindings(ConsoleKeyEventBindings.createDefault());
        out.println(str_reset);
    }
   
    private void displayBindings(TextConsole console) {
        ConsoleKeyEventBindings bindings = console.getKeyEventBindings();
       
        Map<KeyboardReaderAction, List<Character>> charMap = buildCharMap(bindings);
        Map<KeyboardReaderAction, List<VirtualKey>> vkMap = buildVKMap(bindings);
       
        for (KeyboardReaderAction action : KeyboardReaderAction.values()) {
            List<Character> chars = charMap.get(action);
            List<VirtualKey> vks = vkMap.get(action);
            if (chars == null && vks == null &&
                    (action == KeyboardReaderAction.getDefaultCharAction() ||
                     action == KeyboardReaderAction.getDefaultVKAction())) {
                continue;
            }
            StringBuilder sb = new StringBuilder(40);
            sb.append(describe(action)).append(" : ");
            if (chars == null && vks == null) {
                sb.append(str_not_bound);
            } else {
                boolean first = true;
                if (chars != null) {
                    for (char ch : chars) {
                        if (first) {
                            first = false;
                        } else {
                            sb.append(", ");
                        }
                        sb.append(describe(ch));
                    }
                }
                if (vks != null) {
                    for (VirtualKey vk : vks) {
                        if (first) {
                            first = false;
                        } else {
                            sb.append(", ");
                        }
                        sb.append(describe(vk));
                    }
                }
            }
            out.println(sb);
        }
    }
   
    /**
     * Build a map from actions to the virtual keys that map to them.
     * @param bindings
     * @return the map
     */
    private Map<KeyboardReaderAction, List<VirtualKey>> buildVKMap(
            KeyEventBindings<KeyboardReaderAction> bindings) {
        VirtualKey[] boundKeys = bindings.getBoundVKs();
        Map<KeyboardReaderAction, List<VirtualKey>> vkMap =
            new HashMap<KeyboardReaderAction, List<VirtualKey>>();
        for (VirtualKey vk : boundKeys) {
            KeyboardReaderAction action = bindings.getVKAction(vk);
            List<VirtualKey> list = vkMap.get(action);
            if (list == null) {
                list = new ArrayList<VirtualKey>();
                vkMap.put(action, list);
            }
            list.add(vk);
        }
        return vkMap;
    }

    /**
     * Build a map from actions to the characters that map to them.
     * @param bindings
     * @return the map.
     */
    private Map<KeyboardReaderAction, List<Character>> buildCharMap(
            KeyEventBindings<KeyboardReaderAction> bindings) {
        char[] boundChars = bindings.getBoundChars();
        Map<KeyboardReaderAction, List<Character>> charMap =
            new HashMap<KeyboardReaderAction, List<Character>>();
        for (char ch : boundChars) {
            KeyboardReaderAction action = bindings.getCharAction(ch);
            List<Character> list = charMap.get(action);
            if (list == null) {
                list = new ArrayList<Character>();
                charMap.put(action, list);
            }
            list.add(ch);
        }
        return charMap;
    }

    private String describe(KeyboardReaderAction action) {
        return action.toString();
    }

    private String describe(char ch) {
        StringBuilder sb = new StringBuilder();
        if (ch < 0x1f) {
            sb.append(ASCII_NAMES[ch]);
        } else if (ch == ' ') {
            sb.append("SP");
        } else if (ch == '\177') {
            sb.append("DEL");
        } else if (ch < '\177') {
            sb.append('\'').append(ch).append('\'');
        } else {
            sb.append('\'');
            sb.append(ch);
            sb.append("' (0x");
            sb.append(Integer.toHexString(ch));
            sb.append(')');
        }
        return sb.toString();
    }

    private String describe(VirtualKey vk) {
        int modifiers = vk.getModifiers();
        StringBuilder sb = new StringBuilder();
        // We don't use the KeyEvent.getKeyModifierText method because it
        // expects the 'old' mask values and conflates some of the masks.
        if ((modifiers & KeyEvent.SHIFT_DOWN_MASK) != 0) {
            sb.append(MODIFIER_MAP.get(KeyEvent.SHIFT_DOWN_MASK)).append('+');
        }
        if ((modifiers & KeyEvent.CTRL_DOWN_MASK) != 0) {
            sb.append(MODIFIER_MAP.get(KeyEvent.CTRL_DOWN_MASK)).append('+');
        }
        if ((modifiers & KeyEvent.ALT_DOWN_MASK) != 0) {
            sb.append(MODIFIER_MAP.get(KeyEvent.ALT_DOWN_MASK)).append('+');
        }
        if ((modifiers & KeyEvent.META_DOWN_MASK) != 0) {
            sb.append(MODIFIER_MAP.get(KeyEvent.META_DOWN_MASK)).append('+');
        }
        if ((modifiers & KeyEvent.BUTTON1_DOWN_MASK) != 0) {
            sb.append(MODIFIER_MAP.get(KeyEvent.BUTTON1_DOWN_MASK)).append('+');
        }
        if ((modifiers & KeyEvent.BUTTON2_DOWN_MASK) != 0) {
            sb.append(MODIFIER_MAP.get(KeyEvent.BUTTON2_DOWN_MASK)).append('+');
        }
        if ((modifiers & KeyEvent.BUTTON3_DOWN_MASK) != 0) {
            sb.append(MODIFIER_MAP.get(KeyEvent.BUTTON3_DOWN_MASK)).append('+');
        }
        if ((modifiers & KeyEvent.ALT_GRAPH_DOWN_MASK) != 0) {
            sb.append(MODIFIER_MAP.get(KeyEvent.ALT_GRAPH_DOWN_MASK)).append('+');
        }
        sb.append("VK_");
        sb.append(VK_MAP.get(vk.getVKCode()));
        return sb.toString();
    }
}
TOP

Related Classes of org.jnode.command.system.BindKeysCommand

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.