/*
* Copyright 2000-2013 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.event;
import java.io.Serializable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.vaadin.server.Resource;
import com.vaadin.ui.ComponentContainer;
import com.vaadin.ui.Panel;
import com.vaadin.ui.Window;
/**
* Shortcuts are a special type of {@link Action}s used to create keyboard
* shortcuts.
*
* The ShortcutAction is triggered when the user presses a given key in
* combination with the (optional) given modifier keys.
*
*
* ShortcutActions can be global (by attaching to the {@link Window}), or
* attached to different parts of the UI so that a specific shortcut is only
* valid in part of the UI. For instance, one can attach shortcuts to a specific
* {@link Panel} - look for {@link ComponentContainer}s implementing
* {@link Handler Action.Handler} or {@link Notifier Action.Notifier}.
*
*
* ShortcutActions have a caption that may be used to display the shortcut
* visually. This allows the ShortcutAction to be used as a plain Action while
* still reacting to a keyboard shortcut. Note that this functionality is not
* very well supported yet, but it might still be a good idea to give a caption
* to the shortcut.
*
*
* @author Vaadin Ltd.
* @since 4.0.1
*/
@SuppressWarnings("serial")
public class ShortcutAction extends Action {
private final int keyCode;
private final int[] modifiers;
/**
* Creates a shortcut that reacts to the given {@link KeyCode} and
* (optionally) {@link ModifierKey}s.
* The shortcut might be shown in the UI (e.g context menu), in which case
* the caption will be used.
*
* @param caption
* used when displaying the shortcut visually
* @param kc
* KeyCode that the shortcut reacts to
* @param m
* optional modifier keys
*/
public ShortcutAction(String caption, int kc, int... m) {
super(caption);
keyCode = kc;
modifiers = m;
}
/**
* Creates a shortcut that reacts to the given {@link KeyCode} and
* (optionally) {@link ModifierKey}s.
* The shortcut might be shown in the UI (e.g context menu), in which case
* the caption and icon will be used.
*
* @param caption
* used when displaying the shortcut visually
* @param icon
* used when displaying the shortcut visually
* @param kc
* KeyCode that the shortcut reacts to
* @param m
* optional modifier keys
*/
public ShortcutAction(String caption, Resource icon, int kc, int... m) {
super(caption, icon);
keyCode = kc;
modifiers = m;
}
/**
* Used in the caption shorthand notation to indicate the ALT modifier.
*/
public static final char SHORTHAND_CHAR_ALT = '&';
/**
* Used in the caption shorthand notation to indicate the SHIFT modifier.
*/
public static final char SHORTHAND_CHAR_SHIFT = '_';
/**
* Used in the caption shorthand notation to indicate the CTRL modifier.
*/
public static final char SHORTHAND_CHAR_CTRL = '^';
// regex-quote (escape) the characters
private static final String SHORTHAND_ALT = Pattern.quote(Character
.toString(SHORTHAND_CHAR_ALT));
private static final String SHORTHAND_SHIFT = Pattern.quote(Character
.toString(SHORTHAND_CHAR_SHIFT));
private static final String SHORTHAND_CTRL = Pattern.quote(Character
.toString(SHORTHAND_CHAR_CTRL));
// Used for replacing escaped chars, e.g && with &
private static final Pattern SHORTHAND_ESCAPE = Pattern.compile("("
+ SHORTHAND_ALT + "?)" + SHORTHAND_ALT + "|(" + SHORTHAND_SHIFT
+ "?)" + SHORTHAND_SHIFT + "|(" + SHORTHAND_CTRL + "?)"
+ SHORTHAND_CTRL);
// Used for removing escaped chars, only leaving real shorthands
private static final Pattern SHORTHAND_REMOVE = Pattern.compile("(["
+ SHORTHAND_ALT + "|" + SHORTHAND_SHIFT + "|" + SHORTHAND_CTRL
+ "])\\1");
// Mnemonic char, optionally followed by another, and optionally a third
private static final Pattern SHORTHANDS = Pattern.compile("("
+ SHORTHAND_ALT + "|" + SHORTHAND_SHIFT + "|" + SHORTHAND_CTRL
+ ")(?!\\1)(?:(" + SHORTHAND_ALT + "|" + SHORTHAND_SHIFT + "|"
+ SHORTHAND_CTRL + ")(?!\\1|\\2))?(?:(" + SHORTHAND_ALT + "|"
+ SHORTHAND_SHIFT + "|" + SHORTHAND_CTRL + ")(?!\\1|\\2|\\3))?.");
/**
* Constructs a ShortcutAction using a shorthand notation to encode the
* keycode and modifiers in the caption.
*
* Insert one or more modifier characters before the character to use as
* keycode. E.g "&Save"
will make a shortcut responding to
* ALT-S, "E^xit"
will respond to CTRL-X.
* Multiple modifiers can be used, e.g "&^Delete"
will respond
* to CTRL-ALT-D (the order of the modifier characters is not important).
*
*
* The modifier characters will be removed from the caption. The modifier
* character is be escaped by itself: two consecutive characters are turned
* into the original character w/o the special meaning. E.g
* "Save&&&close"
will respond to ALT-C, and the caption will
* say "Save&close".
*
*
* @param shorthandCaption
* the caption in modifier shorthand
*/
public ShortcutAction(String shorthandCaption) {
this(shorthandCaption, null);
}
/**
* Constructs a ShortcutAction using a shorthand notation to encode the
* keycode a in the caption.
*
* This works the same way as {@link #ShortcutAction(String)}, with the
* exception that the modifiers given override those indicated in the
* caption. I.e use any of the modifier characters in the caption to
* indicate the keycode, but the modifier will be the given set.
* E.g
* new ShortcutAction("Do &stuff", new int[]{ShortcutAction.ModifierKey.CTRL}));
* will respond to CTRL-S.
*
*
* @param shorthandCaption
* @param modifierKeys
*/
public ShortcutAction(String shorthandCaption, int... modifierKeys) {
// && -> & etc
super(SHORTHAND_ESCAPE.matcher(shorthandCaption).replaceAll("$1$2$3"));
// replace escaped chars with something that won't accidentally match
shorthandCaption = SHORTHAND_REMOVE.matcher(shorthandCaption)
.replaceAll("\u001A");
Matcher matcher = SHORTHANDS.matcher(shorthandCaption);
if (matcher.find()) {
String match = matcher.group();
// KeyCode from last char in match, uppercase
keyCode = Character.toUpperCase(matcher.group().charAt(
match.length() - 1));
// Given modifiers override this indicated in the caption
if (modifierKeys != null) {
modifiers = modifierKeys;
} else {
// Read modifiers from caption
int[] mod = new int[match.length() - 1];
for (int i = 0; i < mod.length; i++) {
int kc = match.charAt(i);
switch (kc) {
case SHORTHAND_CHAR_ALT:
mod[i] = ModifierKey.ALT;
break;
case SHORTHAND_CHAR_CTRL:
mod[i] = ModifierKey.CTRL;
break;
case SHORTHAND_CHAR_SHIFT:
mod[i] = ModifierKey.SHIFT;
break;
}
}
modifiers = mod;
}
} else {
keyCode = -1;
modifiers = modifierKeys;
}
}
/**
* Get the {@link KeyCode} that this shortcut reacts to (in combination with
* the {@link ModifierKey}s).
*
* @return keycode for this shortcut
*/
public int getKeyCode() {
return keyCode;
}
/**
* Get the {@link ModifierKey}s required for the shortcut to react.
*
* @return modifier keys for this shortcut
*/
public int[] getModifiers() {
return modifiers;
}
/**
* Key codes that can be used for shortcuts
*
*/
public interface KeyCode extends Serializable {
public static final int ENTER = 13;
public static final int ESCAPE = 27;
public static final int PAGE_UP = 33;
public static final int PAGE_DOWN = 34;
public static final int TAB = 9;
public static final int ARROW_LEFT = 37;
public static final int ARROW_UP = 38;
public static final int ARROW_RIGHT = 39;
public static final int ARROW_DOWN = 40;
public static final int BACKSPACE = 8;
public static final int DELETE = 46;
public static final int INSERT = 45;
public static final int END = 35;
public static final int HOME = 36;
public static final int F1 = 112;
public static final int F2 = 113;
public static final int F3 = 114;
public static final int F4 = 115;
public static final int F5 = 116;
public static final int F6 = 117;
public static final int F7 = 118;
public static final int F8 = 119;
public static final int F9 = 120;
public static final int F10 = 121;
public static final int F11 = 122;
public static final int F12 = 123;
public static final int A = 65;
public static final int B = 66;
public static final int C = 67;
public static final int D = 68;
public static final int E = 69;
public static final int F = 70;
public static final int G = 71;
public static final int H = 72;
public static final int I = 73;
public static final int J = 74;
public static final int K = 75;
public static final int L = 76;
public static final int M = 77;
public static final int N = 78;
public static final int O = 79;
public static final int P = 80;
public static final int Q = 81;
public static final int R = 82;
public static final int S = 83;
public static final int T = 84;
public static final int U = 85;
public static final int V = 86;
public static final int W = 87;
public static final int X = 88;
public static final int Y = 89;
public static final int Z = 90;
public static final int NUM0 = 48;
public static final int NUM1 = 49;
public static final int NUM2 = 50;
public static final int NUM3 = 51;
public static final int NUM4 = 52;
public static final int NUM5 = 53;
public static final int NUM6 = 54;
public static final int NUM7 = 55;
public static final int NUM8 = 56;
public static final int NUM9 = 57;
public static final int SPACEBAR = 32;
}
/**
* Modifier key constants
*
*/
public interface ModifierKey extends Serializable {
public static final int SHIFT = 16;
public static final int CTRL = 17;
public static final int ALT = 18;
public static final int META = 91;
}
}