/*******************************************************************************
* Copyright (c) 2014, 2016 itemis AG and others.
* 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:
* Matthias Wienand (itemis AG) - initial API and implementation
*
*******************************************************************************/
package org.eclipse.gef.mvc.fx.gestures;
import java.util.Collection;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.gef.mvc.fx.handlers.IOnStrokeHandler;
import org.eclipse.gef.mvc.fx.handlers.IOnTypeHandler;
import org.eclipse.gef.mvc.fx.models.FocusModel;
import org.eclipse.gef.mvc.fx.parts.PartUtils;
import org.eclipse.gef.mvc.fx.viewer.IViewer;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.event.EventTarget;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
/**
* The {@link TypeGesture} is an {@link AbstractGesture} that handles keyboard input.
*
* @author mwienand
*
*/
public class TypeGesture extends AbstractGesture {
/**
* The type of the policy that has to be supported by target parts.
*/
public static final Class<IOnTypeHandler> ON_TYPE_POLICY_KEY = IOnTypeHandler.class;
/**
* The type of the policy that has to be supported by target parts.
*/
public static final Class<IOnStrokeHandler> ON_STROKE_POLICY_KEY = IOnStrokeHandler.class;
private Map<Scene, EventHandler<? super KeyEvent>> pressedFilterMap = new IdentityHashMap<>();
private Map<Scene, EventHandler<? super KeyEvent>> releasedFilterMap = new IdentityHashMap<>();
private Map<Scene, EventHandler<? super KeyEvent>> typedFilterMap = new IdentityHashMap<>();
private Map<IViewer, ChangeListener<Boolean>> viewerFocusChangeListeners = new IdentityHashMap<>();
private IViewer activeViewer;
@Override
protected void doActivate() {
for (final IViewer viewer : getDomain().getViewers().values()) {
// check if we have access to a FocusModel
FocusModel focusModel = viewer.getAdapter(FocusModel.class);
if (focusModel == null) {
throw new IllegalStateException("Cannot find FocusModel.");
}
// store the key that is initially pressed so that we can wait for
// it to be released
final Set<KeyCode> pressedKeys = new HashSet<>();
// register a viewer focus change listener to release the initially
// pressed key when the window loses focus
ChangeListener<Boolean> viewerFocusChangeListener = new ChangeListener<Boolean>() {
@Override
public void changed(
ObservableValue<? extends Boolean> observable,
Boolean oldValue, Boolean newValue) {
// cannot abort if no activeViewer
if (activeViewer == null) {
return;
}
// check if any viewer is focused
for (IViewer v : getDomain().getViewers().values()) {
if (v.isViewerFocused()) {
return;
}
}
// cancel target policies
for (IOnStrokeHandler policy : getActiveHandlers(
activeViewer)) {
policy.abortPress();
}
// clear active policies
clearActiveHandlers(activeViewer);
activeViewer = null;
// close execution transaction
getDomain().closeExecutionTransaction(TypeGesture.this);
// unset pressed keys
pressedKeys.clear();
}
};
viewer.viewerFocusedProperty()
.addListener(viewerFocusChangeListener);
viewerFocusChangeListeners.put(viewer, viewerFocusChangeListener);
// XXX: Input filters are only registered once per Scene. The
// IViewer is determined from the individual events.
Scene scene = viewer.getRootPart().getVisual().getScene();
if (pressedFilterMap.containsKey(scene)) {
continue;
}
// generate event handlers
EventHandler<KeyEvent> pressedFilter = new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent event) {
boolean isInitialPress = false;
if (pressedKeys.isEmpty()) {
// determine viewer that contains the given target part
Node targetNode = null;
EventTarget target = event.getTarget();
if (target instanceof Node) {
targetNode = (Node) target;
activeViewer = PartUtils.retrieveViewer(getDomain(),
targetNode);
} else if (target instanceof Scene) {
// first focused viewer in that scene
for (IViewer v : getDomain().getViewers()
.values()) {
if (v.getRootPart().getVisual()
.getScene() == target) {
if (v.isViewerFocused()) {
activeViewer = v;
break;
}
}
}
if (activeViewer != null) {
targetNode = activeViewer.getRootPart()
.getVisual();
}
} else {
throw new IllegalStateException(
"Unsupported event target: " + target);
}
if (activeViewer == null) {
// no focused viewer could be found for the target
// scene
return;
}
// open execution transaction
getDomain().openExecutionTransaction(TypeGesture.this);
isInitialPress = true;
// determine target policies on first key press
setActiveHandlers(activeViewer,
getHandlerResolver().resolve(
TypeGesture.this, targetNode, activeViewer,
ON_STROKE_POLICY_KEY));
}
// store initially pressed key
pressedKeys.add(event.getCode());
// notify target policies
for (IOnStrokeHandler policy : getActiveHandlers(
activeViewer)) {
if (isInitialPress) {
policy.initialPress(event);
} else {
policy.press(event);
}
}
}
};
pressedFilterMap.put(scene, pressedFilter);
EventHandler<KeyEvent> releasedFilter = new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent event) {
boolean isFinalRelease = pressedKeys.size() == 1
&& pressedKeys.contains(event.getCode());
// notify target policies
for (IOnStrokeHandler policy : getActiveHandlers(
activeViewer)) {
if (isFinalRelease) {
policy.finalRelease(event);
} else {
policy.release(event);
}
}
// check if the last pressed key is released now
if (isFinalRelease) {
// clear active policies and close execution transaction
// only when the initially pressed key is released
clearActiveHandlers(activeViewer);
activeViewer = null;
getDomain().closeExecutionTransaction(TypeGesture.this);
}
pressedKeys.remove(event.getCode());
}
};
releasedFilterMap.put(scene, releasedFilter);
EventHandler<KeyEvent> typedFilter = new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent event) {
// System.out.println("typed " + event);
if (pressedKeys.isEmpty()) {
getDomain().openExecutionTransaction(TypeGesture.this);
}
// determine viewer that contains the given target part
EventTarget target = event.getTarget();
Node targetNode = null;
if (target instanceof Node) {
targetNode = (Node) target;
} else if (target instanceof Scene) {
// first focused viewer in that scene
for (IViewer v : getDomain().getViewers().values()) {
if (v.getRootPart().getVisual()
.getScene() == target) {
if (v.isViewerFocused()) {
targetNode = v.getRootPart().getVisual();
break;
}
}
}
} else {
throw new IllegalStateException(
"Unsupported event target: " + target);
}
IViewer targetViewer = PartUtils.retrieveViewer(getDomain(),
targetNode);
Collection<? extends IOnTypeHandler> policies = getHandlerResolver()
.resolve(TypeGesture.this, targetNode,
targetViewer, ON_TYPE_POLICY_KEY);
// active policies are unnecessary because TYPED is not a
// gesture, just one event at one point in time
for (IOnTypeHandler policy : policies) {
policy.type(event, pressedKeys);
}
if (pressedKeys.isEmpty()) {
getDomain().closeExecutionTransaction(TypeGesture.this);
}
}
};
typedFilterMap.put(scene, typedFilter);
scene.addEventFilter(KeyEvent.KEY_PRESSED, pressedFilter);
scene.addEventFilter(KeyEvent.KEY_RELEASED, releasedFilter);
scene.addEventFilter(KeyEvent.KEY_TYPED, typedFilter);
}
}
@Override
protected void doDeactivate() {
for (IViewer viewer : getDomain().getViewers().values()) {
viewer.viewerFocusedProperty()
.removeListener(viewerFocusChangeListeners.remove(viewer));
Scene scene = viewer.getRootPart().getVisual().getScene();
if (pressedFilterMap.containsKey(scene)) {
scene.removeEventFilter(KeyEvent.KEY_PRESSED,
pressedFilterMap.remove(scene));
}
if (releasedFilterMap.containsKey(scene)) {
scene.removeEventFilter(KeyEvent.KEY_RELEASED,
releasedFilterMap.remove(scene));
}
if (typedFilterMap.containsKey(scene)) {
scene.removeEventFilter(KeyEvent.KEY_TYPED,
typedFilterMap.remove(scene));
}
}
}
@SuppressWarnings("unchecked")
@Override
public List<? extends IOnStrokeHandler> getActiveHandlers(IViewer viewer) {
return (List<IOnStrokeHandler>) super.getActiveHandlers(viewer);
}
}