/*******************************************************************************
* 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.ArrayList;
import java.util.HashMap;
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.IOnScrollHandler;
import org.eclipse.gef.mvc.fx.parts.PartUtils;
import org.eclipse.gef.mvc.fx.viewer.IViewer;
import javafx.animation.PauseTransition;
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.ScrollEvent;
import javafx.util.Duration;
/**
* The {@link ScrollGesture} is an {@link AbstractGesture} that handles mouse scroll
* events.
*
* @author mwienand
*
*/
public class ScrollGesture extends AbstractGesture {
/**
* The type of the policy that has to be supported by target parts.
*/
public static final Class<IOnScrollHandler> ON_SCROLL_POLICY_KEY = IOnScrollHandler.class;
/**
* The default duration in milliseconds that has to pass without receiving a
* {@link ScrollEvent} so that the gesture is assumed to have finished.
* <p>
* Value: 180 (ms)
*/
public static final int DEFAULT_FINISH_DELAY_MILLIS = 180;
private final Map<IViewer, ChangeListener<Boolean>> viewerFocusChangeListeners = new IdentityHashMap<>();
private final Set<IViewer> inScroll = new HashSet<>();
private final Map<IViewer, PauseTransition> finishDelayTransitions = new HashMap<>();
private final Map<IViewer, EventHandler<ScrollEvent>> scrollFilters = new HashMap<>();
private final Map<EventHandler<ScrollEvent>, Scene> scrollFilterScenes = new HashMap<>();
/**
* Aborts the currently active policies for the given {@link IViewer}.
*
* @param viewer
* The {@link IViewer}
*/
protected void abortPolicies(final IViewer viewer) {
inScroll.remove(viewer);
// cancel target policies
for (IOnScrollHandler policy : getActiveHandlers(viewer)) {
policy.abortScroll();
}
// clear active policies and close execution
// transaction
clearActiveHandlers(viewer);
getDomain().closeExecutionTransaction(ScrollGesture.this);
}
/**
*
* @param viewer
* The {@link IViewer}
* @return A {@link PauseTransition}
*/
protected PauseTransition createFinishDelayTransition(
final IViewer viewer) {
PauseTransition pauseTransition = new PauseTransition(
Duration.millis(getFinishDelayMillis()));
pauseTransition.setOnFinished((ae) -> {
scrollFinished(viewer);
inScroll.remove(viewer);
});
return pauseTransition;
}
/**
*
* @param viewer
* The {@link IViewer}
* @return An {@link EventHandler} for {@link ScrollEvent}.
*/
protected EventHandler<ScrollEvent> createScrollFilter(
final IViewer viewer) {
return new EventHandler<ScrollEvent>() {
@Override
public void handle(ScrollEvent event) {
if (!(event.getTarget() instanceof Node)
|| PartUtils.retrieveViewer(getDomain(),
(Node) event.getTarget()) != viewer) {
return;
}
playFinishDelayTransition(viewer);
if (!inScroll.contains(viewer)) {
inScroll.add(viewer);
scrollStarted(viewer, event);
} else {
scroll(viewer, event);
}
}
};
}
@Override
protected void doActivate() {
super.doActivate();
for (final IViewer viewer : getDomain().getViewers().values()) {
// register a viewer focus change listener
ChangeListener<Boolean> viewerFocusChangeListener = new ChangeListener<Boolean>() {
@Override
public void changed(
ObservableValue<? extends Boolean> observable,
Boolean oldValue, Boolean newValue) {
if (newValue == null || !newValue) {
abortPolicies(viewer);
}
}
};
viewer.viewerFocusedProperty()
.addListener(viewerFocusChangeListener);
viewerFocusChangeListeners.put(viewer, viewerFocusChangeListener);
// register scroll filter
Scene scene = viewer.getCanvas().getScene();
EventHandler<ScrollEvent> scrollFilter = createScrollFilter(viewer);
scrollFilters.put(viewer, scrollFilter);
scrollFilterScenes.put(scrollFilter, scene);
scene.addEventFilter(ScrollEvent.SCROLL, scrollFilter);
}
}
@Override
protected void doDeactivate() {
for (final IViewer viewer : new ArrayList<>(
viewerFocusChangeListeners.keySet())) {
abortPolicies(viewer);
if (finishDelayTransitions.containsKey(viewer)) {
finishDelayTransitions.remove(viewer).stop();
}
EventHandler<ScrollEvent> filter = scrollFilters.remove(viewer);
scrollFilterScenes.remove(filter)
.removeEventFilter(ScrollEvent.SCROLL, filter);
viewer.viewerFocusedProperty()
.removeListener(viewerFocusChangeListeners.remove(viewer));
}
super.doDeactivate();
}
@SuppressWarnings("unchecked")
@Override
public List<? extends IOnScrollHandler> getActiveHandlers(IViewer viewer) {
return (List<IOnScrollHandler>) super.getActiveHandlers(viewer);
}
/**
* Returns the duration in milliseconds that has to pass without receiving a
* {@link ScrollEvent} so that the gesture is assumed to have finished.
*
* @return The duration in milliseconds that has to pass without receiving a
* {@link ScrollEvent} so that the gesture is assumed to have
* finished.
*/
protected long getFinishDelayMillis() {
return DEFAULT_FINISH_DELAY_MILLIS;
}
/**
* (Re-)Starts playing the finish-delay-transition.
*
* @param viewer
* The {@link IViewer}
*/
protected void playFinishDelayTransition(IViewer viewer) {
if (!finishDelayTransitions.containsKey(viewer)) {
finishDelayTransitions.put(viewer,
createFinishDelayTransition(viewer));
}
PauseTransition pauseTransition = finishDelayTransitions.get(viewer);
pauseTransition.stop();
pauseTransition.setDuration(Duration.millis(getFinishDelayMillis()));
pauseTransition.playFromStart();
}
/**
* Callback method that is invoked for all but the first {@link ScrollEvent}
* of a scroll gesture.
*
* @param viewer
* The {@link IViewer}.
* @param event
* The corresponding {@link ScrollEvent}.
*/
protected void scroll(IViewer viewer, ScrollEvent event) {
for (IOnScrollHandler policy : getActiveHandlers(viewer)) {
policy.scroll(event);
}
}
/**
* Callback method that is invoked when the scroll gesture ends, i.e. no
* {@link ScrollEvent} was fired for the number of milliseconds specified in
* {@link #DEFAULT_FINISH_DELAY_MILLIS}.
*
* @param viewer
* The {@link IViewer}.
*/
protected void scrollFinished(IViewer viewer) {
for (IOnScrollHandler policy : getActiveHandlers(viewer)) {
policy.endScroll();
}
clearActiveHandlers(viewer);
getDomain().closeExecutionTransaction(ScrollGesture.this);
}
/**
* Callback method that is invoked for the first {@link ScrollEvent} of a
* scroll gesture.
*
* @param viewer
* The {@link IViewer}.
* @param event
* The corresponding {@link ScrollEvent}.
*/
protected void scrollStarted(IViewer viewer, ScrollEvent event) {
EventTarget eventTarget = event.getTarget();
getDomain().openExecutionTransaction(ScrollGesture.this);
setActiveHandlers(viewer,
getHandlerResolver().resolve(ScrollGesture.this,
eventTarget instanceof Node ? (Node) eventTarget : null,
viewer, ON_SCROLL_POLICY_KEY));
for (IOnScrollHandler policy : getActiveHandlers(viewer)) {
policy.startScroll(event);
}
}
}