/*******************************************************************************
* Copyright (c) 2015, 2017 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:
* Alexander Nyßen (itemis AG) - initial API and implementation
* Matthias Wienand (itemis AG) - inline rotate gesture
*
*******************************************************************************/
package org.eclipse.gef.mvc.fx.gestures;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.gef.mvc.fx.domain.IDomain;
import org.eclipse.gef.mvc.fx.handlers.IOnRotateHandler;
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.RotateEvent;
/**
* The {@link RotateGesture} is an {@link AbstractGesture} to handle rotate
* interaction gestures.
* <p>
* The {@link RotateGesture} handles the opening and closing of an transaction
* operation via the {@link IDomain}, to which it is adapted. It controls that a
* single transaction operation is used for the complete interaction , so all
* interaction results can be undone in a single undo step.
*
* @author anyssen
*
*/
public class RotateGesture extends AbstractGesture {
/**
* The type of the policy that has to be supported by target parts.
*/
public static final Class<IOnRotateHandler> ON_ROTATE_POLICY_KEY = IOnRotateHandler.class;
private final Map<IViewer, ChangeListener<Boolean>> viewerFocusChangeListeners = new IdentityHashMap<>();
private Map<Scene, EventHandler<RotateEvent>> rotateFilters = new IdentityHashMap<>();
/**
* Creates a {@link ChangeListener} for the
* {@link IViewer#viewerFocusedProperty() focused} property of the given
* {@link IViewer}.
*
* @param viewer
* The {@link IViewer} for which to create a
* {@link ChangeListener} for its
* {@link IViewer#viewerFocusedProperty() focused} property.
* @return The newly created {@link ChangeListener} for the
* {@link IViewer#viewerFocusedProperty() focused} property of the
* given {@link IViewer}.
*/
protected ChangeListener<Boolean> createFocusChangeListener(
final IViewer viewer) {
ChangeListener<Boolean> viewerFocusChangeListener = new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable,
Boolean oldValue, Boolean newValue) {
if (newValue == null || !newValue) {
// cancel target policies
for (IOnRotateHandler policy : getActiveHandlers(viewer)) {
policy.abortRotate();
}
// clear active policies and close execution
// transaction
clearActiveHandlers(viewer);
getDomain().closeExecutionTransaction(RotateGesture.this);
}
}
};
return viewerFocusChangeListener;
}
/**
* Creates an {@link EventHandler} for {@link RotateEvent}s that forwards
* the events to the {@link IOnRotateHandler} implementations that can be
* determined for the individual events and the given {@link IViewer}.
*
* @param scene
* The {@link Scene} where the {@link EventHandler} will be
* registered.
* @return The {@link EventHandler} that can be registered at the given
* {@link Scene}.
*/
protected EventHandler<RotateEvent> createRotateFilter(Scene scene) {
return new EventHandler<RotateEvent>() {
@Override
public void handle(RotateEvent event) {
if (!(event.getTarget() instanceof Node)) {
return;
}
IViewer viewer = PartUtils.retrieveViewer(getDomain(),
(Node) event.getTarget());
if (viewer == null) {
return;
}
if (RotateEvent.ROTATE.equals(event.getEventType())) {
for (IOnRotateHandler policy : getActiveHandlers(viewer)) {
policy.rotate(event);
}
} else if (RotateEvent.ROTATION_STARTED
.equals(event.getEventType())) {
// zoom finish may not occur, so close any preceding
// transaction just in case
if (!getDomain()
.isExecutionTransactionOpen(RotateGesture.this)) {
// TODO: this case should already be handled by the
// underlying gesture
// finish event may not properly occur in all cases; we
// may continue to use the still open transaction
getDomain().openExecutionTransaction(RotateGesture.this);
}
// determine target policies
EventTarget eventTarget = event.getTarget();
setActiveHandlers(viewer,
getHandlerResolver().resolve(
RotateGesture.this,
eventTarget instanceof Node
? (Node) eventTarget : null,
viewer, ON_ROTATE_POLICY_KEY));
// send event to the policies
for (IOnRotateHandler policy : getActiveHandlers(viewer)) {
policy.startRotate(event);
}
} else if (RotateEvent.ROTATION_FINISHED
.equals(event.getEventType())) {
for (IOnRotateHandler policy : getActiveHandlers(viewer)) {
policy.endRotate(event);
}
clearActiveHandlers(viewer);
getDomain().closeExecutionTransaction(RotateGesture.this);
}
}
};
}
@Override
protected void doActivate() {
super.doActivate();
for (final IViewer viewer : getDomain().getViewers().values()) {
// register a viewer focus change listener
ChangeListener<Boolean> viewerFocusChangeListener = createFocusChangeListener(
viewer);
viewer.viewerFocusedProperty()
.addListener(viewerFocusChangeListener);
viewerFocusChangeListeners.put(viewer, viewerFocusChangeListener);
// check if we already registered zoom listeners at the viewer's
// scene (in case two viewer's share a single scene)
Scene scene = viewer.getCanvas().getScene();
if (rotateFilters.containsKey(scene)) {
// we are already listening for events of this scene
continue;
}
// register zoom filter
EventHandler<RotateEvent> rotateFilter = createRotateFilter(scene);
scene.addEventFilter(RotateEvent.ANY, rotateFilter);
rotateFilters.put(scene, rotateFilter);
}
}
@Override
protected void doDeactivate() {
for (Scene scene : new ArrayList<>(rotateFilters.keySet())) {
scene.removeEventFilter(RotateEvent.ANY,
rotateFilters.remove(scene));
}
for (final IViewer viewer : new ArrayList<>(
viewerFocusChangeListeners.keySet())) {
viewer.viewerFocusedProperty()
.removeListener(viewerFocusChangeListeners.remove(viewer));
}
super.doDeactivate();
}
@SuppressWarnings("unchecked")
@Override
public List<? extends IOnRotateHandler> getActiveHandlers(IViewer viewer) {
return (List<IOnRotateHandler>) super.getActiveHandlers(viewer);
}
}