/*
 * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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 General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package javafx.scene.input;

import com.sun.javafx.tk.Toolkit;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

// PENDING_DOC_REVIEW
Represents a combination of keys which are used in keyboard shortcuts. A key combination consists of a main key and a set of modifier keys. The main key can be specified by its key code - KeyCodeCombination or key character - KeyCharacterCombination. A modifier key is shift, control, alt, meta or shortcut and can be defined as DOWN, UP or ANY.

The shortcut modifier is used to represent the modifier key which is used commonly in keyboard shortcuts on the host platform. This is for example control on Windows and meta (command key) on Mac. By using shortcut key modifier developers can create platform independent shortcuts. So the "Shortcut+C" key combination is handled internally as "Ctrl+C" on Windows and "Meta+C" on Mac.

Since:JavaFX 2.0
/** * Represents a combination of keys which are used in keyboard shortcuts. * A key combination consists of a main key and a set of modifier keys. The main * key can be specified by its key code - {@code KeyCodeCombination} or key * character - {@code KeyCharacterCombination}. A modifier key is {@code shift}, * {@code control}, {@code alt}, {@code meta} or {@code shortcut} and can be * defined as {@code DOWN}, {@code UP} or {@code ANY}. * <p> * The {@code shortcut} modifier is used to represent the modifier key which is * used commonly in keyboard shortcuts on the host platform. This is for * example {@code control} on Windows and {@code meta} (command key) on Mac. * By using {@code shortcut} key modifier developers can create platform * independent shortcuts. So the "Shortcut+C" key combination is handled * internally as "Ctrl+C" on Windows and "Meta+C" on Mac. * @since JavaFX 2.0 */
public abstract class KeyCombination {
Modifier which specifies that the shift key must be down.
/** Modifier which specifies that the {@code shift} key must be down. */
public static final Modifier SHIFT_DOWN = new Modifier(KeyCode.SHIFT, ModifierValue.DOWN);
Modifier which specifies that the shift key can be either up or down.
/** * Modifier which specifies that the {@code shift} key can be either up or * down. */
public static final Modifier SHIFT_ANY = new Modifier(KeyCode.SHIFT, ModifierValue.ANY);
Modifier which specifies that the control key must be down.
/** Modifier which specifies that the {@code control} key must be down. */
public static final Modifier CONTROL_DOWN = new Modifier(KeyCode.CONTROL, ModifierValue.DOWN);
Modifier which specifies that the control key can be either up or down.
/** * Modifier which specifies that the {@code control} key can be either up or * down. */
public static final Modifier CONTROL_ANY = new Modifier(KeyCode.CONTROL, ModifierValue.ANY);
Modifier which specifies that the alt key must be down.
/** Modifier which specifies that the {@code alt} key must be down. */
public static final Modifier ALT_DOWN = new Modifier(KeyCode.ALT, ModifierValue.DOWN);
Modifier which specifies that the alt key can be either up or down.
/** * Modifier which specifies that the {@code alt} key can be either up or * down. */
public static final Modifier ALT_ANY = new Modifier(KeyCode.ALT, ModifierValue.ANY);
Modifier which specifies that the meta key must be down.
/** Modifier which specifies that the {@code meta} key must be down. */
public static final Modifier META_DOWN = new Modifier(KeyCode.META, ModifierValue.DOWN);
Modifier which specifies that the meta key can be either up or down.
/** * Modifier which specifies that the {@code meta} key can be either up or * down. */
public static final Modifier META_ANY = new Modifier(KeyCode.META, ModifierValue.ANY);
Modifier which specifies that the shortcut key must be down.
/** Modifier which specifies that the {@code shortcut} key must be down. */
public static final Modifier SHORTCUT_DOWN = new Modifier(KeyCode.SHORTCUT, ModifierValue.DOWN);
Modifier which specifies that the shortcut key can be either up or down.
/** * Modifier which specifies that the {@code shortcut} key can be either up * or down. */
public static final Modifier SHORTCUT_ANY = new Modifier(KeyCode.SHORTCUT, ModifierValue.ANY); private static final Modifier[] POSSIBLE_MODIFIERS = { SHIFT_DOWN, SHIFT_ANY, CONTROL_DOWN, CONTROL_ANY, ALT_DOWN, ALT_ANY, META_DOWN, META_ANY, SHORTCUT_DOWN, SHORTCUT_ANY };
A KeyCombination that will match with no events.
/** * A KeyCombination that will match with no events. */
public static final KeyCombination NO_MATCH = new KeyCombination() { @Override public boolean match(KeyEvent e) { return false; } };
The state of the shift key in this key combination.
/** The state of the {@code shift} key in this key combination. */
private final ModifierValue shift;
The state of the shift key in this key combination.
Returns:The state of the shift key in this key combination
/** * The state of the {@code shift} key in this key combination. * @return The state of the {@code shift} key in this key combination */
public final ModifierValue getShift() { return shift; }
The state of the control key in this key combination.
/** The state of the {@code control} key in this key combination. */
private final ModifierValue control;
The state of the control key in this key combination.
Returns:The state of the control key in this key combination
/** * The state of the {@code control} key in this key combination. * @return The state of the {@code control} key in this key combination */
public final ModifierValue getControl() { return control; }
The state of the alt key in this key combination.
/** The state of the {@code alt} key in this key combination. */
private final ModifierValue alt;
The state of the alt key in this key combination.
Returns:The state of the alt key in this key combination.
/** * The state of the {@code alt} key in this key combination. * @return The state of the {@code alt} key in this key combination. */
public final ModifierValue getAlt() { return alt; }
The state of the meta key in this key combination.
/** The state of the {@code meta} key in this key combination. */
private final ModifierValue meta;
The state of the meta key in this key combination.
Returns:The state of the meta key in this key combination
/** * The state of the {@code meta} key in this key combination. * @return The state of the {@code meta} key in this key combination */
public final ModifierValue getMeta() { return meta; }
The state of the shortcut key in this key combination.
/** The state of the {@code shortcut} key in this key combination. */
private final ModifierValue shortcut;
The state of the shortcut key in this key combination.
Returns:The state of the shortcut key in this key combination
/** * The state of the {@code shortcut} key in this key combination. * @return The state of the {@code shortcut} key in this key combination */
public final ModifierValue getShortcut() { return shortcut; }
Constructs a KeyCombination with an explicit specification of all modifier keys. Each modifier key can be set to DOWN, UP or ANY.
Params:
  • shift – the value of the shift modifier key
  • control – the value of the control modifier key
  • alt – the value of the alt modifier key
  • meta – the value of the meta modifier key
  • shortcut – the value of the shortcut modifier key
/** * Constructs a {@code KeyCombination} with an explicit specification * of all modifier keys. Each modifier key can be set to {@code DOWN}, * {@code UP} or {@code ANY}. * * @param shift the value of the {@code shift} modifier key * @param control the value of the {@code control} modifier key * @param alt the value of the {@code alt} modifier key * @param meta the value of the {@code meta} modifier key * @param shortcut the value of the {@code shortcut} modifier key */
protected KeyCombination(final ModifierValue shift, final ModifierValue control, final ModifierValue alt, final ModifierValue meta, final ModifierValue shortcut) { if ((shift == null) || (control == null) || (alt == null) || (meta == null) || (shortcut == null)) { throw new NullPointerException("Modifier value must not be null!"); } this.shift = shift; this.control = control; this.alt = alt; this.meta = meta; this.shortcut = shortcut; }
Constructs a KeyCombination with the specified list of modifiers. All modifier keys which are not explicitly listed are set to the default UP value.

All possible modifiers which change the default modifier value are defined as constants in the KeyCombination class.

Params:
  • modifiers – the list of modifier keys and their corresponding values
/** * Constructs a {@code KeyCombination} with the specified list of modifiers. * All modifier keys which are not explicitly listed are set to the * default {@code UP} value. * <p> * All possible modifiers which change the default modifier value are * defined as constants in the {@code KeyCombination} class. * * @param modifiers the list of modifier keys and their corresponding values */
protected KeyCombination(final Modifier... modifiers) { this(getModifierValue(modifiers, KeyCode.SHIFT), getModifierValue(modifiers, KeyCode.CONTROL), getModifierValue(modifiers, KeyCode.ALT), getModifierValue(modifiers, KeyCode.META), getModifierValue(modifiers, KeyCode.SHORTCUT)); }
Tests whether this key combination matches the combination in the given KeyEvent.

The implementation of this method in the KeyCombination class does only a partial test with the modifier keys. This method is overridden in subclasses to include the main key in the test.

Params:
  • event – the key event
Returns:true if the key combinations match, false otherwise
/** * Tests whether this key combination matches the combination in the given * {@code KeyEvent}. * <p> * The implementation of this method in the {@code KeyCombination} class * does only a partial test with the modifier keys. This method is * overridden in subclasses to include the main key in the test. * * @param event the key event * @return {@code true} if the key combinations match, {@code false} * otherwise */
public boolean match(final KeyEvent event) { final KeyCode shortcutKey = Toolkit.getToolkit().getPlatformShortcutKey(); return test(KeyCode.SHIFT, shift, shortcutKey, shortcut, event.isShiftDown()) && test(KeyCode.CONTROL, control, shortcutKey, shortcut, event.isControlDown()) && test(KeyCode.ALT, alt, shortcutKey, shortcut, event.isAltDown()) && test(KeyCode.META, meta, shortcutKey, shortcut, event.isMetaDown()); }
Returns a string representation of this KeyCombination.

The string representation consists of sections separated by plus characters. Each section specifies either a modifier key or the main key.

A modifier key section contains the KeyCode name of a modifier key. It can be prefixed with the Ignored keyword. A non-prefixed modifier key implies its DOWN value while the prefixed version implies the ANY (ignored) value. If some modifier key is not specified in the string at all, it means it has the default UP value.

The format of the main key section of the key combination string depends on the KeyCombination subclass. It is either the key code name for KeyCodeCombination or the single quoted key character for KeyCharacterCombination.

Examples of KeyCombination string representations:

"Ctrl+Alt+Q"
"Ignore Shift+Ctrl+A"
"Alt+'w'"
Returns:the string representation of this KeyCombination
/** * Returns a string representation of this {@code KeyCombination}. * <p> * The string representation consists of sections separated by plus * characters. Each section specifies either a modifier key or the main key. * <p> * A modifier key section contains the {@code KeyCode} name of a modifier * key. It can be prefixed with the {@code Ignored} keyword. A non-prefixed * modifier key implies its {@code DOWN} value while the prefixed version * implies the {@code ANY} (ignored) value. If some modifier key is not * specified in the string at all, it means it has the default {@code UP} * value. * <p> * The format of the main key section of the key combination string depends * on the {@code KeyCombination} subclass. It is either the key code name * for {@code KeyCodeCombination} or the single quoted key character for * {@code KeyCharacterCombination}. * <p> * Examples of {@code KeyCombination} string representations: <PRE> "Ctrl+Alt+Q" "Ignore Shift+Ctrl+A" "Alt+'w'" </PRE> * @return the string representation of this {@code KeyCombination} */
public String getName() { StringBuilder sb = new StringBuilder(); addModifiersIntoString(sb); return sb.toString(); }
Returns a string representation of this KeyCombination that is suitable for display in a user interface (for example, beside a menu item).
Returns:A string representation of this KeyCombination, suitable for display in a user interface.
Since:JavaFX 8u20
/** * Returns a string representation of this {@code KeyCombination} that is * suitable for display in a user interface (for example, beside a menu item). * * @return A string representation of this {@code KeyCombination}, suitable * for display in a user interface. * @since JavaFX 8u20 */
public String getDisplayText() { StringBuilder stringBuilder = new StringBuilder(); if (com.sun.javafx.PlatformUtil.isMac()) { // Macs have a different convention for keyboard accelerators - // no pluses to separate modifiers, and special symbols for // each modifier (in a particular order), etc if (getControl() == KeyCombination.ModifierValue.DOWN) { stringBuilder.append("\u2303"); } if (getAlt() == KeyCombination.ModifierValue.DOWN) { stringBuilder.append("\u2325"); } if (getShift() == KeyCombination.ModifierValue.DOWN) { stringBuilder.append("\u21e7"); } if (getMeta() == KeyCombination.ModifierValue.DOWN || getShortcut() == KeyCombination.ModifierValue.DOWN) { stringBuilder.append("\u2318"); } // TODO refer to RT-14486 for remaining glyphs } else { if (getControl() == KeyCombination.ModifierValue.DOWN || getShortcut() == KeyCombination.ModifierValue.DOWN ) { stringBuilder.append("Ctrl+"); } if (getAlt() == KeyCombination.ModifierValue.DOWN) { stringBuilder.append("Alt+"); } if (getShift() == KeyCombination.ModifierValue.DOWN) { stringBuilder.append("Shift+"); } if (getMeta() == KeyCombination.ModifierValue.DOWN) { stringBuilder.append("Meta+"); } } return stringBuilder.toString(); }
Tests whether this KeyCombination equals to the specified object.
Params:
  • obj – the object to compare to
Returns:true if the objects are equal, false otherwise
/** * Tests whether this {@code KeyCombination} equals to the specified object. * * @param obj the object to compare to * @return {@code true} if the objects are equal, {@code false} otherwise */
@Override public boolean equals(final Object obj) { if (!(obj instanceof KeyCombination)) { return false; } final KeyCombination other = (KeyCombination) obj; return (shift == other.shift) && (control == other.control) && (alt == other.alt) && (meta == other.meta) && (shortcut == other.shortcut); }
Returns a hash code value for this KeyCombination.
Returns:the hash code value
/** * Returns a hash code value for this {@code KeyCombination}. * * @return the hash code value */
@Override public int hashCode() { int hash = 7; hash = 23 * hash + shift.hashCode(); hash = 23 * hash + control.hashCode(); hash = 23 * hash + alt.hashCode(); hash = 23 * hash + meta.hashCode(); hash = 23 * hash + shortcut.hashCode(); return hash; }
Returns a string representation of this object. Implementation returns the result of the getName() call.
Returns:the string representation of this KeyCombination
/** * Returns a string representation of this object. Implementation returns * the result of the {@code getName()} call. * * @return the string representation of this {@code KeyCombination} */
@Override public String toString() { return getName(); }
Constructs a new KeyCombination from the specified string. The string should be in the same format as produced by the getName method.

If the main key section string is quoted in single quotes the method creates a new KeyCharacterCombination for the unquoted substring. Otherwise it finds the key code which name corresponds to the main key section string and creates a KeyCodeCombination for it. If this can't be done, it falls back to the KeyCharacterCombination.

Params:
  • value – the string which represents the requested key combination
Returns:the constructed KeyCombination
Since:JavaFX 2.1
/** * Constructs a new {@code KeyCombination} from the specified string. The * string should be in the same format as produced by the {@code getName} * method. * <p> * If the main key section string is quoted in single quotes the method * creates a new {@code KeyCharacterCombination} for the unquoted substring. * Otherwise it finds the key code which name corresponds to the main key * section string and creates a {@code KeyCodeCombination} for it. If this * can't be done, it falls back to the {@code KeyCharacterCombination}. * * @param value the string which represents the requested key combination * @return the constructed {@code KeyCombination} * @since JavaFX 2.1 */
public static KeyCombination valueOf(String value) { final List<Modifier> modifiers = new ArrayList<Modifier>(4); final String[] tokens = splitName(value); KeyCode keyCode = null; String keyCharacter = null; for (String token : tokens) { if ((token.length() > 2) && (token.charAt(0) == '\'') && (token.charAt(token.length() - 1) == '\'')) { if ((keyCode != null) || (keyCharacter != null)) { throw new IllegalArgumentException( "Cannot parse key binding " + value); } keyCharacter = token.substring(1, token.length() - 1) .replace("\\'", "'"); continue; } final String normalizedToken = normalizeToken(token); final Modifier modifier = getModifier(normalizedToken); if (modifier != null) { modifiers.add(modifier); continue; } if ((keyCode != null) || (keyCharacter != null)) { throw new IllegalArgumentException( "Cannot parse key binding " + value); } keyCode = KeyCode.getKeyCode(normalizedToken); if (keyCode == null) { keyCharacter = token; } } if ((keyCode == null) && (keyCharacter == null)) { throw new IllegalArgumentException( "Cannot parse key binding " + value); } final Modifier[] modifierArray = modifiers.toArray(new Modifier[modifiers.size()]); return (keyCode != null) ? new KeyCodeCombination(keyCode, modifierArray) : new KeyCharacterCombination(keyCharacter, modifierArray); }
Constructs a new KeyCombination from the specified string. This method simply delegates to valueOf(String).
Params:
  • name – the string which represents the requested key combination
See Also:
Returns:the constructed KeyCombination
/** * Constructs a new {@code KeyCombination} from the specified string. This * method simply delegates to {@link #valueOf(String)}. * * @param name the string which represents the requested key combination * @return the constructed {@code KeyCombination} * * @see #valueOf(String) */
public static KeyCombination keyCombination(String name) { return valueOf(name); }
This class represents a pair of modifier key and its value.
Since:JavaFX 2.0
/** * This class represents a pair of modifier key and its value. * @since JavaFX 2.0 */
public static final class Modifier { private final KeyCode key; private final ModifierValue value; private Modifier(final KeyCode key, final ModifierValue value) { this.key = key; this.value = value; }
Gets the modifier key of this Modifier.
Returns:the modifier key
/** * Gets the modifier key of this {@code Modifier}. * * @return the modifier key */
public KeyCode getKey() { return key; }
Gets the modifier value of this Modifier.
Returns:the modifier value
/** * Gets the modifier value of this {@code Modifier}. * * @return the modifier value */
public ModifierValue getValue() { return value; }
Returns a string representation of the modifier.
Returns:a string representation of the modifier
/** * Returns a string representation of the modifier. * @return a string representation of the modifier */
@Override public String toString() { return ((value == ModifierValue.ANY) ? "Ignore " : "") + key.getName(); } }
ModifierValue specifies state of modifier keys.
Since:JavaFX 2.0
/** * {@code ModifierValue} specifies state of modifier keys. * @since JavaFX 2.0 */
public static enum ModifierValue {
Constant which indicates that the modifier key must be down.
/** Constant which indicates that the modifier key must be down. */
DOWN,
Constant which indicates that the modifier key must be up.
/** Constant which indicates that the modifier key must be up. */
UP,
Constant which indicates that the modifier key can be either up or down.
/** * Constant which indicates that the modifier key can be either up or * down. */
ANY } private void addModifiersIntoString(final StringBuilder sb) { addModifierIntoString(sb, KeyCode.SHIFT, shift); addModifierIntoString(sb, KeyCode.CONTROL, control); addModifierIntoString(sb, KeyCode.ALT, alt); addModifierIntoString(sb, KeyCode.META, meta); addModifierIntoString(sb, KeyCode.SHORTCUT, shortcut); } private static void addModifierIntoString( final StringBuilder sb, final KeyCode modifierKey, final ModifierValue modifierValue) { if (modifierValue == ModifierValue.UP) { return; } if (sb.length() > 0) { sb.append("+"); } if (modifierValue == ModifierValue.ANY) { sb.append("Ignore "); } sb.append(modifierKey.getName()); } private static boolean test(final KeyCode testedModifierKey, final ModifierValue testedModifierValue, final KeyCode shortcutModifierKey, final ModifierValue shortcutModifierValue, final boolean isKeyDown) { final ModifierValue finalModifierValue = (testedModifierKey == shortcutModifierKey) ? resolveModifierValue(testedModifierValue, shortcutModifierValue) : testedModifierValue; return test(finalModifierValue, isKeyDown); } private static boolean test(final ModifierValue modifierValue, final boolean isDown) { switch (modifierValue) { case DOWN: return isDown; case UP: return !isDown; case ANY: default: return true; } } private static ModifierValue resolveModifierValue( final ModifierValue firstValue, final ModifierValue secondValue) { if ((firstValue == ModifierValue.DOWN) || (secondValue == ModifierValue.DOWN)) { return ModifierValue.DOWN; } if ((firstValue == ModifierValue.ANY) || (secondValue == ModifierValue.ANY)) { return ModifierValue.ANY; } return ModifierValue.UP; } static Modifier getModifier(final String name) { for (final Modifier modifier: POSSIBLE_MODIFIERS) { if (modifier.toString().equals(name)) { return modifier; } } return null; } private static ModifierValue getModifierValue( final Modifier[] modifiers, final KeyCode modifierKey) { ModifierValue modifierValue = ModifierValue.UP; for (final Modifier modifier: modifiers) { if (modifier == null) { throw new NullPointerException("Modifier must not be null!"); } if (modifier.getKey() == modifierKey) { if (modifierValue != ModifierValue.UP) { throw new IllegalArgumentException( (modifier.getValue() != modifierValue) ? "Conflicting modifiers specified!" : "Duplicate modifiers specified!"); } modifierValue = modifier.getValue(); } } return modifierValue; } private static String normalizeToken(final String token) { final String[] words = token.split("\\s+"); final StringBuilder sb = new StringBuilder(); for (final String word: words) { if (sb.length() > 0) { sb.append(' '); } sb.append(word.substring(0, 1).toUpperCase(Locale.ROOT)); sb.append(word.substring(1).toLowerCase(Locale.ROOT)); } return sb.toString(); } private static String[] splitName(String name) { List<String> tokens = new ArrayList<String>(); char[] chars = name.trim().toCharArray(); final int STATE_BASIC = 0; // general text final int STATE_WHITESPACE = 1; // spaces found final int STATE_SEPARATOR = 2; // plus found final int STATE_QUOTED = 3; // quoted text int state = STATE_BASIC; int tokenStart = 0; int tokenEnd = -1; for (int i = 0; i < chars.length; i++) { char c = chars[i]; switch(state) { case STATE_BASIC: switch(c) { case ' ': case '\t': case '\n': case '\f': case '\r': case '\u000B': tokenEnd = i; state = STATE_WHITESPACE; break; case '+': tokenEnd = i; state = STATE_SEPARATOR; break; case '\'': if (i == 0 || chars[i - 1] != '\\') { state = STATE_QUOTED; } break; default: break; } break; case STATE_WHITESPACE: switch(c) { case ' ': case '\t': case '\n': case '\f': case '\r': case '\u000B': break; case '+': state = STATE_SEPARATOR; break; case '\'': state = STATE_QUOTED; tokenEnd = -1; break; default: state = STATE_BASIC; tokenEnd = -1; break; } break; case STATE_SEPARATOR: switch(c) { case ' ': case '\t': case '\n': case '\f': case '\r': case '\u000B': break; case '+': throw new IllegalArgumentException( "Cannot parse key binding " + name); default: if (tokenEnd <= tokenStart) { throw new IllegalArgumentException( "Cannot parse key binding " + name); } tokens.add(new String(chars, tokenStart, tokenEnd - tokenStart)); tokenStart = i; tokenEnd = -1; state = (c == '\'' ? STATE_QUOTED : STATE_BASIC); break; } break; case STATE_QUOTED: if (c == '\'' && chars[i - 1] != '\\') { state = STATE_BASIC; } break; } } switch(state) { case STATE_BASIC: case STATE_WHITESPACE: tokens.add(new String(chars, tokenStart, chars.length - tokenStart)); break; case STATE_SEPARATOR: case STATE_QUOTED: throw new IllegalArgumentException( "Cannot parse key binding " + name); } return tokens.toArray(new String[tokens.size()]); } }