/* * Copyright 2000-2016 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.Arrays; 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. * <p> * The ShortcutAction is triggered when the user presses a given key in * combination with the (optional) given modifier keys. * </p> * <p> * 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}. * </p> * <p> * 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. * </p> * * @author Vaadin Ltd. * @since 4.0.1 */ @SuppressWarnings("serial") public class ShortcutAction extends Action { private final int keyCode; private int[] modifiers; /** * Creates a shortcut that reacts to the given {@link KeyCode} and * (optionally) {@link ModifierKey}s. <br/> * 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; setModifiers(m); } /** * Creates a shortcut that reacts to the given {@link KeyCode} and * (optionally) {@link ModifierKey}s. <br/> * 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; setModifiers(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. * <p> * Insert one or more modifier characters before the character to use as * keycode. E.g <code>"&Save"</code> will make a shortcut responding to * ALT-S, <code>"E^xit"</code> will respond to CTRL-X.<br/> * Multiple modifiers can be used, e.g <code>"&^Delete"</code> will respond * to CTRL-ALT-D (the order of the modifier characters is not important). * </p> * <p> * 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 * <code>"Save&&&close"</code> will respond to ALT-C, and the caption will * say "Save&close". * </p> * * @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. * <p> * 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.<br/> * E.g * <code>new ShortcutAction("Do &stuff", new int[]{ShortcutAction.ModifierKey.CTRL}));</code> * will respond to CTRL-S. * </p> * * @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) { setModifiers(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; } } setModifiers(mod); } } else { keyCode = -1; setModifiers(modifierKeys); } } /** * When setting modifiers, make sure that modifiers is a valid array AND * that it's sorted. * * @param modifiers * the modifier keys for this shortcut */ private void setModifiers(int... modifiers) { if (modifiers == null) { this.modifiers = new int[0]; } else { this.modifiers = modifiers; } Arrays.sort(this.modifiers); } /** * 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; } }