1 /* 2 * Copyright (c) 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 package com.sun.javafx.scene.control.inputmap; 26 27 import com.sun.javafx.util.Utils; 28 import com.sun.javafx.tk.Toolkit; 29 import javafx.event.EventType; 30 import javafx.scene.input.KeyCode; 31 import javafx.scene.input.KeyEvent; 32 33 import java.util.Objects; 34 35 import static com.sun.javafx.scene.control.inputmap.KeyBinding.OptionalBoolean.*; 36 37 /** 38 * KeyBindings are used to describe which action should occur based on some 39 * KeyEvent state and Control state. These bindings are used to populate the 40 * keyBindings variable on BehaviorBase. The KeyBinding can be subclassed to 41 * add additional matching criteria. A match in a subclass should always have 42 * a specificity that is 1 greater than its superclass in the case of a match, 43 * or 0 in the case where there is no match. 44 * 45 * Note that this API is, at present, quite odd in that you use a constructor 46 * and then use shift(), ctrl(), alt(), or meta() separately. It gave me an 47 * object-literal like approach but isn't ideal. We will want some builder 48 * approach here (similar as in other places). 49 * 50 * @since 9 51 */ 52 public class KeyBinding { 53 private final KeyCode code; 54 private final EventType<KeyEvent> eventType; 55 private OptionalBoolean shift = FALSE; 56 private OptionalBoolean ctrl = FALSE; 57 private OptionalBoolean alt = FALSE; 58 private OptionalBoolean meta = FALSE; 59 60 public KeyBinding(KeyCode code) { 61 this(code, null); 62 } 63 64 /** 65 * Designed for 'catch-all' situations, e.g. all KeyTyped events. 66 * @param type 67 */ 68 public KeyBinding(EventType<KeyEvent> type) { 69 this(null, type); 70 } 71 72 public KeyBinding(KeyCode code, EventType<KeyEvent> type) { 73 this.code = code; 74 this.eventType = type != null ? type : KeyEvent.KEY_PRESSED; 75 } 76 77 public final KeyBinding shift() { 78 return shift(TRUE); 79 } 80 81 public final KeyBinding shift(OptionalBoolean value) { 82 shift = value; 83 return this; 84 } 85 86 public final KeyBinding ctrl() { 87 return ctrl(TRUE); 88 } 89 90 public final KeyBinding ctrl(OptionalBoolean value) { 91 ctrl = value; 92 return this; 93 } 94 95 public final KeyBinding alt() { 96 return alt(TRUE); 97 } 98 99 public final KeyBinding alt(OptionalBoolean value) { 100 alt = value; 101 return this; 102 } 103 104 public final KeyBinding meta() { 105 return meta(TRUE); 106 } 107 108 public final KeyBinding meta(OptionalBoolean value) { 109 meta = value; 110 return this; 111 } 112 113 public final KeyBinding shortcut() { 114 if (Toolkit.getToolkit().getClass().getName().endsWith("StubToolkit")) { 115 // FIXME: We've hit the terrible StubToolkit (which only appears 116 // during testing). We will dumb down what we do here 117 if (Utils.isMac()) { 118 return meta(); 119 } else { 120 return ctrl(); 121 } 122 } else { 123 switch (Toolkit.getToolkit().getPlatformShortcutKey()) { 124 case SHIFT: 125 return shift(); 126 127 case CONTROL: 128 return ctrl(); 129 130 case ALT: 131 return alt(); 132 133 case META: 134 return meta(); 135 136 default: 137 return this; 138 } 139 } 140 } 141 142 143 144 public final KeyCode getCode() { return code; } 145 public final EventType<KeyEvent> getType() { return eventType; } 146 public final OptionalBoolean getShift() { return shift; } 147 public final OptionalBoolean getCtrl() { return ctrl; } 148 public final OptionalBoolean getAlt() { return alt; } 149 public final OptionalBoolean getMeta() { return meta; } 150 151 public int getSpecificity(KeyEvent event) { 152 int s = 0; 153 if (code != null && code != event.getCode()) return 0; else s = 1; 154 if (!shift.equals(event.isShiftDown())) return 0; else if (shift != ANY) s++; 155 if (!ctrl.equals(event.isControlDown())) return 0; else if (ctrl != ANY) s++; 156 if (!alt.equals(event.isAltDown())) return 0; else if (alt != ANY) s++; 157 if (!meta.equals(event.isMetaDown())) return 0; else if (meta != ANY) s++; 158 if (eventType != null && eventType != event.getEventType()) return 0; else s++; 159 // We can now trivially accept it 160 return s; 161 } 162 163 /** {@inheritDoc} */ 164 @Override public String toString() { 165 return "KeyBinding [code=" + code + ", shift=" + shift + 166 ", ctrl=" + ctrl + ", alt=" + alt + 167 ", meta=" + meta + ", type=" + eventType + "]"; 168 } 169 170 /** {@inheritDoc} */ 171 @Override public boolean equals(Object o) { 172 if (this == o) return true; 173 if (!(o instanceof KeyBinding)) return false; 174 KeyBinding that = (KeyBinding) o; 175 return Objects.equals(getCode(), that.getCode()) && 176 Objects.equals(eventType, that.eventType) && 177 Objects.equals(getShift(), that.getShift()) && 178 Objects.equals(getCtrl(), that.getCtrl()) && 179 Objects.equals(getAlt(), that.getAlt()) && 180 Objects.equals(getMeta(), that.getMeta()); 181 } 182 183 /** {@inheritDoc} */ 184 @Override public int hashCode() { 185 return Objects.hash(getCode(), eventType, getShift(), getCtrl(), getAlt(), getMeta()); 186 } 187 188 public static KeyBinding toKeyBinding(KeyEvent keyEvent) { 189 KeyBinding newKeyBinding = new KeyBinding(keyEvent.getCode(), keyEvent.getEventType()); 190 if (keyEvent.isShiftDown()) newKeyBinding.shift(); 191 if (keyEvent.isControlDown()) newKeyBinding.ctrl(); 192 if (keyEvent.isAltDown()) newKeyBinding.alt(); 193 if (keyEvent.isShortcutDown()) newKeyBinding.shortcut(); 194 return newKeyBinding; 195 } 196 197 /** 198 * A tri-state boolean used with KeyBinding. 199 */ 200 public enum OptionalBoolean { 201 TRUE, 202 FALSE, 203 ANY; 204 205 public boolean equals(boolean b) { 206 if (this == ANY) return true; 207 if (b && this == TRUE) return true; 208 if (!b && this == FALSE) return true; 209 return false; 210 } 211 } 212 213 }