/******************************************************************************* * Copyright (c) 2012-2017 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.ide.keybinding; import elemental.dom.Element; import elemental.events.Event; import elemental.events.EventListener; import elemental.events.KeyboardEvent; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.dom.client.AreaElement; import com.google.gwt.dom.client.InputElement; import com.google.inject.Inject; import com.google.inject.Provider; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.ide.api.action.Action; import org.eclipse.che.ide.api.action.ActionEvent; import org.eclipse.che.ide.api.action.ActionManager; import org.eclipse.che.ide.api.keybinding.KeyBindingAgent; import org.eclipse.che.ide.api.keybinding.Scheme; import org.eclipse.che.ide.api.parts.PerspectiveManager; import org.eclipse.che.ide.ui.toolbar.PresentationFactory; import org.eclipse.che.ide.util.browser.UserAgent; import org.eclipse.che.ide.util.dom.Elements; import org.eclipse.che.ide.util.input.CharCodeWithModifiers; import org.eclipse.che.ide.util.input.SignalEvent; import org.eclipse.che.ide.util.input.SignalEventUtils; import javax.validation.constraints.NotNull; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Implementation of the {@link KeyBindingAgent}. * * @author Evgen Vidolob * @author Dmitry Shnurenko * @author <a href="mailto:ak@nuxeo.com">Arnaud Kervern</a> */ public class KeyBindingManager implements KeyBindingAgent { public static final String SCHEME_ECLIPSE_ID = "ide.ui.keyBinding.eclipse"; public static final String SCHEME_GLOBAL_ID = "ide.ui.keyBinding.global"; private final PresentationFactory presentationFactory; private final Provider<PerspectiveManager> perspectiveManager; private final Map<String, Scheme> schemes = new HashMap<>(); private String activeScheme; private ActionManager actionManager; @Inject public KeyBindingManager(ActionManager actionManager, Provider<PerspectiveManager> perspectiveManager) { this.actionManager = actionManager; this.perspectiveManager = perspectiveManager; addScheme(new SchemeImpl(SCHEME_GLOBAL_ID, "Global")); addScheme(new SchemeImpl(SCHEME_ECLIPSE_ID, "Eclipse Scheme")); //TODO check user settings activeScheme = SCHEME_GLOBAL_ID; presentationFactory = new PresentationFactory(); // Attach the listeners. final Element documentElement = Elements.getDocument().getDocumentElement(); EventListener downListener = new EventListener() { @Override public void handleEvent(Event event) { SignalEvent signalEvent = SignalEventUtils.create(event, false); if (signalEvent == null) { return; } /* Temporary solution to prevent calling actions if focus is in input element. The problem in that, some actions, may be bound to Ctrl+C/X/V/Z or Delete so We should allow browser to process event natively instead of calling actions. Need to be reworked in nearest future. */ final JavaScriptObject jso = (JavaScriptObject)event.getTarget(); if (InputElement.is(jso) || AreaElement.is(jso)) { return; } //handle event in active scheme int digest = CharCodeWithModifiers.computeKeyDigest(signalEvent); preventDefaultBrowserAction((KeyboardEvent)event, digest); List<String> actionIds = getActive().getActionIds(digest); if (!actionIds.isEmpty()) { runActions(actionIds, event); } //else handle event in global scheme else if (!(actionIds = getGlobal().getActionIds(digest)).isEmpty()) { runActions(actionIds, event); } //default, lets this event handle other part of the IDE } }; if (UserAgent.isFirefox()) { // firefox fires keypress events documentElement.addEventListener(Event.KEYPRESS, downListener, true); } else { //webkit fires keydown events documentElement.addEventListener(Event.KEYDOWN, downListener, true); } } private void preventDefaultBrowserAction(KeyboardEvent keyboardEvent, int digest) { //prevent browser default action on Ctrl + S if (digest == 65651) { keyboardEvent.preventDefault(); } } /** * Finds and runs an action cancelling original key event * * @param actionIds * list containing action ids * @param keyEvent * original key event */ private void runActions(List<String> actionIds, Event keyEvent) { for (String actionId : actionIds) { Action action = actionManager.getAction(actionId); if (action == null) { continue; } ActionEvent e = new ActionEvent(presentationFactory.getPresentation(action), actionManager, perspectiveManager.get()); action.update(e); if (e.getPresentation().isEnabled() && e.getPresentation().isVisible()) { /** Stop handling the key event */ keyEvent.preventDefault(); keyEvent.stopPropagation(); /** Perform the action */ action.actionPerformed(e); } } } /** {@inheritDoc} */ @Override public Scheme getGlobal() { return getScheme(SCHEME_GLOBAL_ID); } /** {@inheritDoc} */ @Override public Scheme getEclipse() { return getScheme(SCHEME_ECLIPSE_ID); } /** {@inheritDoc} */ @Override public Scheme getActive() { return getScheme(activeScheme); } public void setActive(String scheme) { if (schemes.containsKey(scheme)) { activeScheme = scheme; } else { // Fallback on global scheme activeScheme = SCHEME_GLOBAL_ID; } } /** {@inheritDoc} */ @Override public void addScheme(Scheme scheme) { schemes.put(scheme.getSchemeId(), scheme); } /** {@inheritDoc} */ @Override public List<Scheme> getSchemes() { return new ArrayList<>(this.schemes.values()); } /** {@inheritDoc} */ @Override public Scheme getScheme(String id) { if (schemes.containsKey(id)) { return schemes.get(id); } return null; } /** {@inheritDoc} */ @Nullable @Override public CharCodeWithModifiers getKeyBinding(@NotNull String actionId) { CharCodeWithModifiers keyBinding = getActive().getKeyBinding(actionId); if (keyBinding != null) return keyBinding; else { return getGlobal().getKeyBinding(actionId); } } }