123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295 |
- /*
- @VaadinApache2LicenseForJavaFiles@
- */
-
- package com.vaadin.terminal.gwt.client.ui;
-
- import java.util.ArrayList;
- import java.util.Iterator;
-
- import com.google.gwt.core.client.Scheduler;
- import com.google.gwt.user.client.Command;
- import com.google.gwt.user.client.DOM;
- import com.google.gwt.user.client.Element;
- import com.google.gwt.user.client.Event;
- import com.google.gwt.user.client.ui.HasWidgets;
- import com.google.gwt.user.client.ui.KeyboardListener;
- import com.google.gwt.user.client.ui.KeyboardListenerCollection;
- import com.vaadin.terminal.gwt.client.ApplicationConnection;
- import com.vaadin.terminal.gwt.client.BrowserInfo;
- import com.vaadin.terminal.gwt.client.ComponentConnector;
- import com.vaadin.terminal.gwt.client.UIDL;
- import com.vaadin.terminal.gwt.client.Util;
- import com.vaadin.terminal.gwt.client.ui.richtextarea.VRichTextArea;
-
- /**
- * A helper class to implement keyboard shorcut handling. Keeps a list of owners
- * actions and fires actions to server. User class needs to delegate keyboard
- * events to handleKeyboardEvents function.
- *
- * @author Vaadin Ltd
- */
- public class ShortcutActionHandler {
-
- /**
- * An interface implemented by those users of this helper class that want to
- * support special components like {@link VRichTextArea} that don't properly
- * propagate key down events. Those components can build support for
- * shortcut actions by traversing the closest
- * {@link ShortcutActionHandlerOwner} from the component hierarchy an
- * passing keydown events to {@link ShortcutActionHandler}.
- */
- public interface ShortcutActionHandlerOwner extends HasWidgets {
-
- /**
- * Returns the ShortCutActionHandler currently used or null if there is
- * currently no shortcutactionhandler
- */
- ShortcutActionHandler getShortcutActionHandler();
- }
-
- /**
- * A focusable {@link ComponentConnector} implementing this interface will
- * be notified before shortcut actions are handled if it will be the target
- * of the action (most commonly means it is the focused component during the
- * keyboard combination is triggered by the user).
- */
- public interface BeforeShortcutActionListener extends ComponentConnector {
- /**
- * This method is called by ShortcutActionHandler before firing the
- * shortcut if the Paintable is currently focused (aka the target of the
- * shortcut action). Eg. a field can update its possibly changed value
- * to the server before shortcut action is fired.
- *
- * @param e
- * the event that triggered the shortcut action
- */
- public void onBeforeShortcutAction(Event e);
- }
-
- private final ArrayList<ShortcutAction> actions = new ArrayList<ShortcutAction>();
- private ApplicationConnection client;
- private String paintableId;
-
- /**
- *
- * @param pid
- * Paintable id
- * @param c
- * reference to application connections
- */
- public ShortcutActionHandler(String pid, ApplicationConnection c) {
- paintableId = pid;
- client = c;
- }
-
- /**
- * Updates list of actions this handler listens to.
- *
- * @param c
- * UIDL snippet containing actions
- */
- public void updateActionMap(UIDL c) {
- actions.clear();
- final Iterator<?> it = c.getChildIterator();
- while (it.hasNext()) {
- final UIDL action = (UIDL) it.next();
-
- int[] modifiers = null;
- if (action.hasAttribute("mk")) {
- modifiers = action.getIntArrayAttribute("mk");
- }
-
- final ShortcutKeyCombination kc = new ShortcutKeyCombination(
- action.getIntAttribute("kc"), modifiers);
- final String key = action.getStringAttribute("key");
- final String caption = action.getStringAttribute("caption");
- actions.add(new ShortcutAction(key, kc, caption));
- }
- }
-
- public void handleKeyboardEvent(final Event event, ComponentConnector target) {
- final int modifiers = KeyboardListenerCollection
- .getKeyboardModifiers(event);
- final char keyCode = (char) DOM.eventGetKeyCode(event);
- final ShortcutKeyCombination kc = new ShortcutKeyCombination(keyCode,
- modifiers);
- final Iterator<ShortcutAction> it = actions.iterator();
- while (it.hasNext()) {
- final ShortcutAction a = it.next();
- if (a.getShortcutCombination().equals(kc)) {
- fireAction(event, a, target);
- break;
- }
- }
-
- }
-
- public void handleKeyboardEvent(final Event event) {
- handleKeyboardEvent(event, null);
- }
-
- private void fireAction(final Event event, final ShortcutAction a,
- ComponentConnector target) {
- final Element et = DOM.eventGetTarget(event);
- if (target == null) {
- target = Util.findPaintable(client, et);
- }
- final ComponentConnector finalTarget = target;
-
- event.preventDefault();
-
- /*
- * The target component might have unpublished changes, try to
- * synchronize them before firing shortcut action.
- */
- if (finalTarget instanceof BeforeShortcutActionListener) {
- ((BeforeShortcutActionListener) finalTarget)
- .onBeforeShortcutAction(event);
- } else {
- shakeTarget(et);
- Scheduler.get().scheduleDeferred(new Command() {
- public void execute() {
- shakeTarget(et);
- }
- });
- }
-
- Scheduler.get().scheduleDeferred(new Command() {
- public void execute() {
- if (finalTarget != null) {
- client.updateVariable(paintableId, "actiontarget",
- finalTarget, false);
- }
- client.updateVariable(paintableId, "action", a.getKey(), true);
- }
- });
- }
-
- /**
- * We try to fire value change in the component the key combination was
- * typed. Eg. textfield may contain newly typed text that is expected to be
- * sent to server. This is done by removing focus and then returning it
- * immediately back to target element.
- * <p>
- * This is practically a hack and should be replaced with an interface
- * {@link BeforeShortcutActionListener} via widgets could be notified when
- * they should fire value change. Big task for TextFields, DateFields and
- * various selects.
- *
- * <p>
- * TODO separate opera impl with generator
- */
- private static void shakeTarget(final Element e) {
- blur(e);
- if (BrowserInfo.get().isOpera()) {
- // will mess up with focus and blur event if the focus is not
- // deferred. Will cause a small flickering, so not doing it for all
- // browsers.
- Scheduler.get().scheduleDeferred(new Command() {
- public void execute() {
- focus(e);
- }
- });
- } else {
- focus(e);
- }
- }
-
- private static native void blur(Element e)
- /*-{
- if(e.blur) {
- e.blur();
- }
- }-*/;
-
- private static native void focus(Element e)
- /*-{
- if(e.blur) {
- e.focus();
- }
- }-*/;
-
- }
-
- class ShortcutKeyCombination {
-
- public static final int SHIFT = 16;
- public static final int CTRL = 17;
- public static final int ALT = 18;
- public static final int META = 91;
-
- char keyCode = 0;
- private int modifiersMask;
-
- public ShortcutKeyCombination() {
- }
-
- ShortcutKeyCombination(char kc, int modifierMask) {
- keyCode = kc;
- modifiersMask = modifierMask;
- }
-
- ShortcutKeyCombination(int kc, int[] modifiers) {
- keyCode = (char) kc;
-
- modifiersMask = 0;
- if (modifiers != null) {
- for (int i = 0; i < modifiers.length; i++) {
- switch (modifiers[i]) {
- case ALT:
- modifiersMask = modifiersMask
- | KeyboardListener.MODIFIER_ALT;
- break;
- case CTRL:
- modifiersMask = modifiersMask
- | KeyboardListener.MODIFIER_CTRL;
- break;
- case SHIFT:
- modifiersMask = modifiersMask
- | KeyboardListener.MODIFIER_SHIFT;
- break;
- case META:
- modifiersMask = modifiersMask
- | KeyboardListener.MODIFIER_META;
- break;
- default:
- break;
- }
- }
- }
- }
-
- public boolean equals(ShortcutKeyCombination other) {
- if (keyCode == other.keyCode && modifiersMask == other.modifiersMask) {
- return true;
- }
- return false;
- }
- }
-
- class ShortcutAction {
-
- private final ShortcutKeyCombination sc;
- private final String caption;
- private final String key;
-
- public ShortcutAction(String key, ShortcutKeyCombination sc, String caption) {
- this.sc = sc;
- this.key = key;
- this.caption = caption;
- }
-
- public ShortcutKeyCombination getShortcutCombination() {
- return sc;
- }
-
- public String getCaption() {
- return caption;
- }
-
- public String getKey() {
- return key;
- }
-
- }
|