/******************************************************************************* * Copyright (c) 2014, 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: * Matthias Wienand (itemis AG) - initial API and implementation * Alexander Nyßen (itemis AG) - refactorings * *******************************************************************************/ 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.IOnPinchSpreadHandler; 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.ZoomEvent; /** * The {@link PinchSpreadGesture} is an {@link AbstractGesture} to handle * pinch/spread (zoom) interaction gestures. * <p> * The {@link PinchSpreadGesture} 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 mwienand * @author anyssen * */ public class PinchSpreadGesture extends AbstractGesture { /** * The type of the policy that has to be supported by target parts. */ public static final Class<IOnPinchSpreadHandler> ON_PINCH_SPREAD_POLICY_KEY = IOnPinchSpreadHandler.class; private final Map<IViewer, ChangeListener<Boolean>> viewerFocusChangeListeners = new IdentityHashMap<>(); private Map<Scene, EventHandler<ZoomEvent>> zoomFilters = 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 (IOnPinchSpreadHandler policy : getActiveHandlers( viewer)) { policy.abortZoom(); } // clear active policies and close execution // transaction clearActiveHandlers(viewer); getDomain() .closeExecutionTransaction(PinchSpreadGesture.this); } } }; return viewerFocusChangeListener; } /** * Creates an {@link EventHandler} for {@link ZoomEvent}s that forwards the * events to the {@link IOnPinchSpreadHandler} 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<ZoomEvent> createZoomFilter(Scene scene) { return new EventHandler<ZoomEvent>() { private IViewer activeViewer; @Override public void handle(ZoomEvent event) { if (ZoomEvent.ZOOM.equals(event.getEventType())) { if (activeViewer == null) { return; } for (IOnPinchSpreadHandler policy : getActiveHandlers( activeViewer)) { policy.zoom(event); } } else if (ZoomEvent.ZOOM_STARTED .equals(event.getEventType())) { if (!(event.getTarget() instanceof Node)) { return; } IViewer viewer = PartUtils.retrieveViewer(getDomain(), (Node) event.getTarget()); if (viewer == null) { return; } activeViewer = viewer; // zoom finish may not occur, so close any preceding // transaction just in case if (!getDomain().isExecutionTransactionOpen( PinchSpreadGesture.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( PinchSpreadGesture.this); } // determine target policies EventTarget eventTarget = event.getTarget(); setActiveHandlers(activeViewer, getHandlerResolver().resolve( PinchSpreadGesture.this, eventTarget instanceof Node ? (Node) eventTarget : null, activeViewer, ON_PINCH_SPREAD_POLICY_KEY)); // send event to the policies for (IOnPinchSpreadHandler policy : getActiveHandlers( viewer)) { policy.startZoom(event); } } else if (ZoomEvent.ZOOM_FINISHED .equals(event.getEventType())) { if (activeViewer == null) { return; } for (IOnPinchSpreadHandler policy : getActiveHandlers( activeViewer)) { policy.endZoom(event); } clearActiveHandlers(activeViewer); getDomain() .closeExecutionTransaction(PinchSpreadGesture.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 (zoomFilters.containsKey(scene)) { // we are already listening for events of this scene continue; } // register zoom filter EventHandler<ZoomEvent> zoomFilter = createZoomFilter(scene); scene.addEventFilter(ZoomEvent.ANY, zoomFilter); zoomFilters.put(scene, zoomFilter); } } @Override protected void doDeactivate() { for (Scene scene : new ArrayList<>(zoomFilters.keySet())) { scene.removeEventFilter(ZoomEvent.ANY, zoomFilters.remove(scene)); } for (final IViewer viewer : new ArrayList<>( viewerFocusChangeListeners.keySet())) { viewer.viewerFocusedProperty() .removeListener(viewerFocusChangeListeners.remove(viewer)); } super.doDeactivate(); } @SuppressWarnings("unchecked") @Override public List<? extends IOnPinchSpreadHandler> getActiveHandlers( IViewer viewer) { return (List<IOnPinchSpreadHandler>) super.getActiveHandlers(viewer); } }