1 /*
   2  * Copyright (c) 2010, 2013, 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 com.sun.javafx.scene.control.behavior;
  27 
  28 import com.sun.javafx.util.Utils;
  29 import static com.sun.javafx.scene.control.behavior.OptionalBoolean.ANY;
  30 import static com.sun.javafx.scene.control.behavior.OptionalBoolean.FALSE;
  31 import static com.sun.javafx.scene.control.behavior.OptionalBoolean.TRUE;
  32 import com.sun.javafx.tk.Toolkit;
  33 import javafx.event.EventType;
  34 import javafx.scene.control.Control;
  35 import javafx.scene.input.KeyCode;
  36 import javafx.scene.input.KeyEvent;
  37 
  38 /**
  39  * KeyBindings are used to describe which action should occur based on some
  40  * KeyEvent state and Control state. These bindings are used to populate the
  41  * keyBindings variable on BehaviorBase. The KeyBinding can be subclassed to
  42  * add additional matching criteria. A match in a subclass should always have
  43  * a specificity that is 1 greater than its superclass in the case of a match,
  44  * or 0 in the case where there is no match.
  45  *
  46  * Note that this API is, at present, quite odd in that you use a constructor
  47  * and then use shift(), ctrl(), alt(), or meta() separately. It gave me an
  48  * object-literal like approach but isn't ideal. We will want some builder
  49  * approach here (similar as in other places).
  50  */
  51 public class KeyBinding {
  52     private KeyCode code;
  53     private EventType<KeyEvent> eventType = KeyEvent.KEY_PRESSED;
  54     private String action;
  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, String action) {
  61         this.code = code;
  62         this.action = action;
  63     }
  64 
  65     public KeyBinding(KeyCode code, EventType<KeyEvent> type, String action) {
  66         this.code = code;
  67         this.eventType = type;
  68         this.action = action;
  69     }
  70 
  71     public KeyBinding shift() {
  72         return shift(TRUE);
  73     }
  74 
  75     public KeyBinding shift(OptionalBoolean value) {
  76         shift = value;
  77         return this;
  78     }
  79 
  80     public KeyBinding ctrl() {
  81         return ctrl(TRUE);
  82     }
  83 
  84     public KeyBinding ctrl(OptionalBoolean value) {
  85         ctrl = value;
  86         return this;
  87     }
  88 
  89     public KeyBinding alt() {
  90         return alt(TRUE);
  91     }
  92 
  93     public KeyBinding alt(OptionalBoolean value) {
  94         alt = value;
  95         return this;
  96     }
  97     
  98     public KeyBinding meta() {
  99         return meta(TRUE);
 100     }
 101 
 102     public KeyBinding meta(OptionalBoolean value) {
 103         meta = value;
 104         return this;
 105     }
 106     
 107     public KeyBinding shortcut() {
 108         if (Toolkit.getToolkit().getClass().getName().endsWith("StubToolkit")) {
 109             // FIXME: We've hit the terrible StubToolkit (which only appears 
 110             // during testing). We will dumb down what we do here
 111             if (Utils.isMac()) {
 112                 return meta();
 113             } else {
 114                 return ctrl();
 115             }
 116         } else {
 117             switch (Toolkit.getToolkit().getPlatformShortcutKey()) {
 118                 case SHIFT:
 119                     return shift();
 120 
 121                 case CONTROL:
 122                     return ctrl();
 123 
 124                 case ALT:
 125                     return alt();
 126 
 127                 case META:
 128                     return meta();
 129 
 130                 default:
 131                     return this;
 132             }
 133         }
 134     }
 135 
 136     public final KeyCode getCode() { return code; }
 137     public final EventType<KeyEvent> getType() { return eventType; }
 138     public final String getAction() { return action; }
 139     public final OptionalBoolean getShift() { return shift; }
 140     public final OptionalBoolean getCtrl() { return ctrl; }
 141     public final OptionalBoolean getAlt() { return alt; }
 142     public final OptionalBoolean getMeta() { return meta; }
 143 
 144     public int getSpecificity(Control control, KeyEvent event) {
 145         int s = 0;
 146         if (code != null && code != event.getCode()) return 0; else s = 1;
 147         if (!shift.equals(event.isShiftDown())) return 0; else if (shift != ANY) s++;
 148         if (!ctrl.equals(event.isControlDown())) return 0; else if (ctrl != ANY) s++;
 149         if (!alt.equals(event.isAltDown())) return 0; else if (alt != ANY) s++;
 150         if (!meta.equals(event.isMetaDown())) return 0; else if (meta != ANY) s++;
 151         if (eventType != null && eventType != event.getEventType()) return 0; else s++;
 152         // We can now trivially accept it
 153         return s;
 154     }
 155 
 156     @Override public String toString() {
 157         return "KeyBinding [code=" + code + ", shift=" + shift +
 158                 ", ctrl=" + ctrl + ", alt=" + alt + 
 159                 ", meta=" + meta + ", type=" + eventType + 
 160                 ", action=" + action + "]";
 161     }
 162 }