All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vaadin.client.ui.ShortcutActionHandler Maven / Gradle / Ivy

The newest version!
/*
 * 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.client.ui;

import java.util.ArrayList;
import java.util.Iterator;

import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
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.client.ApplicationConnection;
import com.vaadin.client.ComponentConnector;
import com.vaadin.client.UIDL;
import com.vaadin.client.Util;
import com.vaadin.client.ui.combobox.ComboBoxConnector;
import com.vaadin.client.ui.datefield.DateTimeFieldConnector;
import com.vaadin.client.ui.textfield.AbstractTextFieldConnector;

/**
 * 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
 */
@SuppressWarnings("deprecation")
public class ShortcutActionHandler {

    /**
     * An interface implemented by those users of this helper class that want to
     * support special components like {@code 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
         * 
         * @return the shortcut action handler
         */
        ShortcutActionHandler getShortcutActionHandler();
    }

    private final ArrayList actions = new ArrayList<>();
    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);
        if (keyCode == 0) {
            return;
        }
        final ShortcutKeyCombination kc = new ShortcutKeyCombination(keyCode,
                modifiers);
        final Iterator 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 focused component might have unpublished changes, try to
         * synchronize them before firing shortcut action.
         */
        client.flushActiveConnector();

        // Performance tuning. Enter and Shift-Enter are handled on client, and server gets only focus lost.
        // This will only work, in case of jumping from one simple editor to the next simple editor.
        if (et.hasTagName("input") && target.getWidget().getElement().getClassName().contains(" editor ") && event.getKeyCode() == 13)
        {
            Element next = getNextFocusElement(Document.get(), et, event.getShiftKey());
            
            ComponentConnector nextTarget = Util.findPaintable(client, next);

            if (nextTarget instanceof AbstractTextFieldConnector)
            {
                AbstractTextFieldWidget widget = (AbstractTextFieldWidget)((AbstractTextFieldConnector)nextTarget).getWidget();
                
                widget.selectAll();
                next.focus();
                
                return;
            }
            else if (nextTarget instanceof ComboBoxConnector)
            {
                VComboBox widget = ((ComboBoxConnector)nextTarget).getWidget();

                widget.tb.selectAll();
                next.focus();
                
                return;
            }
            else if (nextTarget instanceof DateTimeFieldConnector)
            {
                VPopupTimeCalendar widget = ((DateTimeFieldConnector)nextTarget).getWidget();
                
                widget.text.selectAll();
                next.focus();
                
                return;
            }
        }

        Scheduler.get().scheduleDeferred(new Command() {
            @Override
            public void execute() {
                if (finalTarget != null) {
                    client.updateVariable(paintableId, "actiontarget",
                            finalTarget, false);
                }
                client.updateVariable(paintableId, "action", a.getKey(), true);
            }
        });
    }

    private static native void blur(Element e)
    /*-{
        if(e.blur) {
            e.blur();
       }
    }-*/;

    private static native void focus(Element e)
    /*-{
        if(e.blur) {
            e.focus();
       }
    }-*/;

    /**
     * Find the next or previous focusable component. (For simulation of tab / shift tab).
     * @param doc the document
     * @param elem the current element
     * @param previous search previous or next otherwise
     * @return next or previous focusable component
     */
    private static native Element getNextFocusElement(Document doc, Element elem, boolean previous)
    /*-{
        var focusable = Array.prototype.filter.call(
            doc.querySelectorAll('a:not([disabled]), button:not([disabled]), input:not([disabled]), textarea:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"]:not([tabindex="-2"])'),
            function (element) {
                return (element.offsetWidth > 0 || element.offsetHeight > 0) && element.tabIndex >= 0;
            }
        );
        
        var index = focusable.indexOf(elem);
        if (index < 0)
        {
            return null;
        }
        else if (previous)
        {
            if (index == 0)
            {
                return focusable[focusable.length - 1];
            }
            else
            {
                return focusable[index - 1];
            }
        }
        else
        {
            if (index < focusable.length - 1)
            {
                return focusable[index + 1];
            }
            else
            {
                return focusable[0];
            }
        }
    }-*/;

}

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;
    }

    @SuppressWarnings("deprecation")
    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;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy