/******************************************************************************* * Copyright (c) 2015, 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 & implementation * *******************************************************************************/ package org.eclipse.gef.zest.fx.operations; import java.util.Collections; import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.commands.operations.AbstractOperation; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.gef.fx.nodes.InfiniteCanvas; import org.eclipse.gef.geometry.convert.fx.FX2Geometry; import org.eclipse.gef.geometry.planar.AffineTransform; import org.eclipse.gef.graph.Graph; import org.eclipse.gef.mvc.fx.behaviors.HoverBehavior; import org.eclipse.gef.mvc.fx.models.HoverModel; import org.eclipse.gef.mvc.fx.operations.ChangeContentsOperation; import org.eclipse.gef.mvc.fx.operations.ChangeFocusOperation; import org.eclipse.gef.mvc.fx.operations.ChangeSelectionOperation; import org.eclipse.gef.mvc.fx.operations.ChangeViewportOperation; import org.eclipse.gef.mvc.fx.operations.ForwardUndoCompositeOperation; import org.eclipse.gef.mvc.fx.operations.ITransactionalOperation; import org.eclipse.gef.mvc.fx.operations.ReverseUndoCompositeOperation; import org.eclipse.gef.mvc.fx.viewer.IViewer; import org.eclipse.gef.mvc.fx.viewer.InfiniteCanvasViewer; import org.eclipse.gef.zest.fx.models.NavigationModel; import org.eclipse.gef.zest.fx.models.NavigationModel.ViewportState; /** * The {@link NavigateOperation} is a {@link ReverseUndoCompositeOperation} that * combines a {@link ChangeContentsOperation} and an * {@link ChangeViewportOperation} to navigate between nested and parent * {@link Graph}s. * * @author mwienand * */ public class NavigateOperation extends ForwardUndoCompositeOperation { private final class UpdateViewportStateOperation extends AbstractOperation implements ITransactionalOperation { private ViewportState initialViewportState; private ViewportState finalViewportState; public UpdateViewportStateOperation(ViewportState initialViewportState) { super("Update Viewport State"); this.initialViewportState = initialViewportState == null ? null : initialViewportState.getCopy(); this.finalViewportState = initialViewportState; } @Override public IStatus execute(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { if (finalViewportState != null) { navigationModel.setViewportState(sourceGraph, finalViewportState); } else { navigationModel.removeViewportState(sourceGraph); } return Status.OK_STATUS; } @Override public boolean isContentRelevant() { return false; } @Override public boolean isNoOp() { return initialViewportState == null ? finalViewportState == null : initialViewportState.equals(finalViewportState); } @Override public IStatus redo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { return execute(monitor, info); } public void setFinalViewportState(ViewportState finalViewportState) { this.finalViewportState = finalViewportState; } @Override public IStatus undo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { if (initialViewportState != null) { navigationModel.setViewportState(sourceGraph, initialViewportState); } else { navigationModel.removeViewportState(sourceGraph); } return Status.OK_STATUS; } } private ChangeContentsOperation changeContentsOperation; private ChangeViewportOperation changeViewportOperation; private NavigationModel navigationModel; private IViewer viewer; private Graph sourceGraph; private UpdateViewportStateOperation updateViewportStateOperation; /** * Creates a new {@link NavigateOperation} that saves the layout and * viewport for the currently displayed {@link Graph}. The final state for * the operation can later be set using * {@link #setFinalState(Graph, boolean)}. * * @param viewer * The {@link InfiniteCanvasViewer} of which the contents and * viewport are manipulated. */ public NavigateOperation(IViewer viewer) { super("Navigate Graph"); this.viewer = viewer; sourceGraph = (Graph) viewer.getContents().get(0); // retrieve initial viewport state of source graph navigationModel = viewer.getAdapter(NavigationModel.class); // create operations to clear the selection and focus models add(new ChangeSelectionOperation(viewer, Collections.emptyList())); add(new ChangeFocusOperation(viewer, null)); // create sub-operations changeContentsOperation = new ChangeContentsOperation(viewer, Collections.singletonList(sourceGraph)); changeViewportOperation = new ChangeViewportOperation(((InfiniteCanvasViewer) viewer).getCanvas()); updateViewportStateOperation = new UpdateViewportStateOperation(navigationModel.getViewportState(sourceGraph)); // arrange sub-operations add(changeViewportOperation); add(changeContentsOperation); add(updateViewportStateOperation); } /** * Creates a new {@link NavigateOperation} that saves the layout and * viewport for the currently displayed {@link Graph}, loads the layout and * viewport of the <i>finalGraph</i>, and changes the viewer contents. If * the <i>isNestedGraph</i> flag is set to <code>true</code>, then the * viewport that was saved for <i>finalGraph</i> will not be restored, but * instead it will be reset. * * @param viewer * The {@link InfiniteCanvasViewer} of which the contents and * viewport are manipulated. * @param targetGraph * The final {@link Graph} to be displayed within the given * {@link InfiniteCanvasViewer}. * @param isNestedGraph * Specifies whether or not the given <i>finalGraph</i> is a * nested {@link Graph}. */ public NavigateOperation(IViewer viewer, Graph targetGraph, boolean isNestedGraph) { this(viewer); setFinalState(targetGraph, isNestedGraph); } @Override public IStatus execute(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { // XXX: HoverModel is not cleared via operation, because it is a // lightweight model, i.e. its state is only relevant for user // interaction viewer.getAdapter(HoverModel.class).clearHover(); viewer.getRootPart().getAdapter(HoverBehavior.class).deactivate(); IStatus status = super.execute(monitor, info); viewer.getRootPart().getAdapter(HoverBehavior.class).activate(); return status; } /** * Returns the {@link ChangeViewportOperation} that is used by this * {@link NavigateOperation} to update the viewport. * * @return The {@link ChangeViewportOperation} that is used by this * {@link NavigateOperation} to update the viewport. */ public ChangeViewportOperation getChangeViewportOperation() { return changeViewportOperation; } /** * Changes this {@link NavigateOperation}'s final state so that the given * <i>finalGraph</i> is opened. If the <i>isNestedGraph</i> flag is set to * <code>true</code>, then the viewport that was saved for <i>finalGraph</i> * will not be restored, but instead it will be reset. * * @param targetGraph * The {@link Graph} that is to be displayed. * @param isNestedGraph * Specifies whether or not the given <i>finalGraph</i> is a * nested {@link Graph}. */ public void setFinalState(Graph targetGraph, boolean isNestedGraph) { // persist the state of the current graph (before zooming in) InfiniteCanvas canvas = ((InfiniteCanvasViewer) viewer).getCanvas(); if (isNestedGraph) { // is we are navigating to a nested graph, store our viewport ViewportState sourceGraphFinalViewportState = new ViewportState(canvas.getHorizontalScrollOffset(), canvas.getVerticalScrollOffset(), canvas.getWidth(), canvas.getHeight(), FX2Geometry.toAffineTransform(canvas.getContentTransform())); updateViewportStateOperation.setFinalViewportState(sourceGraphFinalViewportState); changeViewportOperation.setInitialWidth(sourceGraphFinalViewportState.getWidth()); changeViewportOperation.setInitialHeight(sourceGraphFinalViewportState.getHeight()); changeViewportOperation.setInitialHorizontalScrollOffset(sourceGraphFinalViewportState.getTranslateX()); changeViewportOperation.setInitialVerticalScrollOffset(sourceGraphFinalViewportState.getTranslateY()); changeViewportOperation.setInitialContentTransform(sourceGraphFinalViewportState.getContentsTransform()); } else { updateViewportStateOperation.setFinalViewportState(null); } // update the change viewport operation ViewportState newViewportState = navigationModel.getViewportState(targetGraph); if (newViewportState == null || isNestedGraph) { newViewportState = new ViewportState(0, 0, canvas.getWidth(), canvas.getHeight(), new AffineTransform()); } changeViewportOperation.setNewWidth(newViewportState.getWidth()); changeViewportOperation.setNewHeight(newViewportState.getHeight()); changeViewportOperation.setNewHorizontalScrollOffset(newViewportState.getTranslateX()); changeViewportOperation.setNewVerticalScrollOffset(newViewportState.getTranslateY()); changeViewportOperation.setNewContentTransform(newViewportState.getContentsTransform()); // update the change contents operation changeContentsOperation.setNewContents(Collections.singletonList(targetGraph)); } }