1 /*
   2  * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javafx.scene.input;
  27 
  28 import javafx.beans.NamedArg;
  29 
  30 
  31 // PENDING_DOC_REVIEW
  32 /**
  33  * This class represents a key combination in which the main key is specified
  34  * by its {@code KeyCode}. Such key combination is independent on the keyboard
  35  * functional layout configured by the user at the time of key combination
  36  * matching.
  37  * @since JavaFX 2.0
  38  */
  39 public final class KeyCodeCombination extends KeyCombination {
  40     /** The key code associated with this key combination. */
  41     private KeyCode code;
  42 
  43     /** 
  44      * Gets the key code associated with this key combination.
  45      * @return The key code associated with this key combination
  46      */
  47     public final KeyCode getCode() {
  48         return code;
  49     }
  50 
  51     /**
  52      * Constructs a {@code KeyCodeCombination} for the specified main key and
  53      * with an explicit specification of all modifier keys. Each modifier key
  54      * can be set to {@code PRESSED}, {@code RELEASED} or {@code IGNORED}.
  55      *
  56      * @param code the key code of the main key
  57      * @param shift the value of the {@code shift} modifier key
  58      * @param control the value of the {@code control} modifier key
  59      * @param alt the value of the {@code alt} modifier key
  60      * @param meta the value of the {@code meta} modifier key
  61      * @param shortcut the value of the {@code shortcut} modifier key
  62      */
  63     public KeyCodeCombination(final @NamedArg("code") KeyCode code,
  64                               final @NamedArg("shift") ModifierValue shift,
  65                               final @NamedArg("control") ModifierValue control,
  66                               final @NamedArg("alt") ModifierValue alt,
  67                               final @NamedArg("meta") ModifierValue meta,
  68                               final @NamedArg("shortcut") ModifierValue shortcut) {
  69         super(shift, control, alt, meta, shortcut);
  70 
  71         validateKeyCode(code);
  72         this.code = code;
  73     }
  74 
  75     /**
  76      * Constructs a {@code KeyCodeCombination} for the specified main key and
  77      * with the specified list of modifiers. All modifier keys which are not
  78      * explicitly listed are set to the default {@code RELEASED} value.
  79      * <p>
  80      * All possible modifiers which change the default modifier value are
  81      * defined as constants in the {@code KeyCombination} class.
  82      *
  83      * @param code the key code of the main key
  84      * @param modifiers the list of modifier keys and their corresponding values
  85      */
  86     public KeyCodeCombination(final @NamedArg("code") KeyCode code,
  87                               final @NamedArg("modifiers") Modifier... modifiers) {
  88         super(modifiers);
  89 
  90         validateKeyCode(code);
  91         this.code = code;
  92     }
  93 
  94     /**
  95      * Tests whether this key combination matches the key combination in the
  96      * given {@code KeyEvent}. It uses only the key code and the state of the
  97      * modifier keys from the {@code KeyEvent} in the test. This means that the
  98      * method can return {@code true} only for {@code KEY_PRESSED} and
  99      * {@code KEY_RELEASED} events, but not for {@code KEY_TYPED} events, which
 100      * don't have valid key codes.
 101      *
 102      * @param event the key event
 103      * @return {@code true} if the key combinations match, {@code false}
 104      *      otherwise
 105      */
 106     @Override
 107     public boolean match(final KeyEvent event) {
 108         return (event.getCode() == getCode()) && super.match(event);
 109     }
 110 
 111     /**
 112      * Returns a string representation of this {@code KeyCodeCombination}.
 113      * <p>
 114      * The string representation consists of sections separated by plus
 115      * characters. Each section specifies either a modifier key or the main key.
 116      * <p>
 117      * A modifier key section contains the {@code KeyCode} name of a modifier
 118      * key. It can be prefixed with the {@code Ignored} keyword. A non-prefixed
 119      * modifier key implies its {@code PRESSED} value while the prefixed version
 120      * implies the {@code IGNORED} value. If some modifier key is not specified
 121      * in the string at all, it means it has the default {@code RELEASED} value.
 122      * <p>
 123      * The main key section contains the key code name of the main key and is
 124      * the last section in the returned string.
 125      *
 126      * @return the string representation of this {@code KeyCodeCombination}
 127      */
 128     @Override
 129     public String getName() {
 130         StringBuilder sb = new StringBuilder();
 131 
 132         sb.append(super.getName());
 133 
 134         if (sb.length() > 0) {
 135             sb.append("+");
 136         }
 137 
 138         return sb.append(code.getName()).toString();
 139     }
 140 
 141     /** {@inheritDoc} */
 142     @Override
 143     public String getDisplayText() {
 144         StringBuilder sb = new StringBuilder();
 145         sb.append(super.getDisplayText());
 146         final int initialLength = sb.length();
 147 
 148         char c = getSingleChar(code);
 149         if (c != 0) {
 150             sb.append(c);
 151             return sb.toString();
 152         }
 153 
 154         // Compute a name based on the enum name, e.g. F13 becomes F13 and
 155         // NUM_LOCK becomes Num Lock
 156         String name = code.toString();
 157 
 158         // We only want the first letter to be upper-case, so we convert 'ENTER'
 159         // to 'Enter' -- and we also convert multiple words separated by _
 160         // such that each individual word is capitalized and the underline replaced
 161         // by spaces.
 162 
 163         String[] words = com.sun.javafx.util.Utils.split(name, "_");
 164         for (String word : words) {
 165             if (sb.length() > initialLength) {
 166                 sb.append(' ');
 167             }
 168             sb.append(word.charAt(0));
 169             sb.append(word.substring(1).toLowerCase());
 170         }
 171         return sb.toString();
 172     }
 173 
 174     /**
 175      * Tests whether this {@code KeyCodeCombination} equals to the specified
 176      * object.
 177      *
 178      * @param obj the object to compare to
 179      * @return {@code true} if the objects are equal, {@code false} otherwise
 180      */
 181     @Override
 182     public boolean equals(final Object obj) {
 183         if (this == obj) {
 184             return true;
 185         }
 186 
 187         if (!(obj instanceof KeyCodeCombination)) {
 188             return false;
 189         }
 190 
 191         return (this.getCode() == ((KeyCodeCombination) obj).getCode())
 192                    && super.equals(obj);
 193     }
 194 
 195     /**
 196      * Returns a hash code value for this {@code KeyCodeCombination}.
 197      *
 198      * @return the hash code value
 199      */
 200     @Override
 201     public int hashCode() {
 202         return 23 * super.hashCode() + code.hashCode();
 203     }
 204 
 205     private static void validateKeyCode(final KeyCode keyCode) {
 206         if (keyCode == null) {
 207             throw new NullPointerException("Key code must not be null!");
 208         }
 209 
 210         if (getModifier(keyCode.getName()) != null) {
 211             throw new IllegalArgumentException(
 212                     "Key code must not match modifier key!");
 213         }
 214 
 215         if (keyCode == KeyCode.UNDEFINED) {
 216             throw new IllegalArgumentException(
 217                     "Key code must differ from undefined value!");
 218         }
 219     }
 220 
 221     /** Compute a single suitable char summarizing the code, if any, and 0 otherwise. */
 222     private static char getSingleChar(KeyCode code) {
 223         switch (code) {
 224             case ENTER: return '\u21B5';
 225             case LEFT: return '\u2190';
 226             case UP: return '\u2191';
 227             case RIGHT: return '\u2192';
 228             case DOWN: return '\u2193';
 229             case COMMA: return ',';
 230             case MINUS: return '-';
 231             case PERIOD: return '.';
 232             case SLASH: return '/';
 233             case SEMICOLON: return ';';
 234             case EQUALS: return '=';
 235             case OPEN_BRACKET: return '[';
 236             case BACK_SLASH: return '\\';
 237             case CLOSE_BRACKET: return ']';
 238             case MULTIPLY: return '*';
 239             case ADD: return '+';
 240             case SUBTRACT: return '-';
 241             case DECIMAL: return '.';
 242             case DIVIDE: return '/';
 243             case BACK_QUOTE: return '`';
 244             case QUOTE: return '"';
 245             case AMPERSAND: return '&';
 246             case ASTERISK: return '*';
 247             case LESS: return '<';
 248             case GREATER: return '>';
 249             case BRACELEFT: return '{';
 250             case BRACERIGHT: return '}';
 251             case AT: return '@';
 252             case COLON: return ':';
 253             case CIRCUMFLEX: return '^';
 254             case DOLLAR: return '$';
 255             case EURO_SIGN: return '\u20AC';
 256             case EXCLAMATION_MARK: return '!';
 257             case LEFT_PARENTHESIS: return '(';
 258             case NUMBER_SIGN: return '#';
 259             case PLUS: return '+';
 260             case RIGHT_PARENTHESIS: return ')';
 261             case UNDERSCORE: return '_';
 262             case DIGIT0: return '0';
 263             case DIGIT1: return '1';
 264             case DIGIT2: return '2';
 265             case DIGIT3: return '3';
 266             case DIGIT4: return '4';
 267             case DIGIT5: return '5';
 268             case DIGIT6: return '6';
 269             case DIGIT7: return '7';
 270             case DIGIT8: return '8';
 271             case DIGIT9: return '9';
 272             default:
 273                 break;
 274         }
 275 
 276         /*
 277         ** On Mac we display these unicode symbols,
 278         ** otherwise we default to the Text version of the char.
 279         */
 280         if (com.sun.javafx.PlatformUtil.isMac()) {
 281             switch (code) {
 282                 case BACK_SPACE: return '\u232B';
 283                 case ESCAPE: return '\u238B';
 284                 case DELETE: return '\u2326';
 285                 default:
 286                     break;
 287             }
 288         }
 289         return 0;
 290     }
 291 }