/******************************************************************************* * 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 and implementation * *******************************************************************************/ package org.eclipse.gef.mvc.fx.handlers; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.gef.geometry.convert.fx.FX2Geometry; import org.eclipse.gef.geometry.euclidean.Angle; import org.eclipse.gef.geometry.euclidean.Vector; import org.eclipse.gef.geometry.planar.Dimension; import org.eclipse.gef.geometry.planar.Point; import org.eclipse.gef.geometry.planar.Rectangle; import org.eclipse.gef.mvc.fx.models.SelectionModel; import org.eclipse.gef.mvc.fx.parts.IContentPart; import org.eclipse.gef.mvc.fx.parts.IVisualPart; import org.eclipse.gef.mvc.fx.parts.PartUtils; import org.eclipse.gef.mvc.fx.policies.TransformPolicy; import javafx.scene.Cursor; import javafx.scene.ImageCursor; import javafx.scene.Node; import javafx.scene.image.Image; import javafx.scene.input.KeyEvent; import javafx.scene.input.MouseEvent; /** * The {@link RotateSelectedOnHandleDragHandler} is an {@link IOnDragHandler} * that rotates the whole {@link SelectionModel selection} when a selection * handle is dragged. * * @author mwienand * */ public class RotateSelectedOnHandleDragHandler extends AbstractHandler implements IOnDragHandler { // indication cursor private CursorSupport cursorSupport = new CursorSupport(this); private ImageCursor rotateCursor; // gesture validity private boolean invalidGesture = false; // initial state private Point initialPointerLocationInScene; private Point pivotInScene; private Map<IContentPart<? extends Node>, Integer> rotationIndices = new HashMap<>(); private List<IContentPart<? extends Node>> targetParts; @Override public void abortDrag() { if (invalidGesture) { return; } // rollback transform operations for (IVisualPart<? extends Node> part : getTargetParts()) { TransformPolicy transformPolicy = getTransformPolicy(part); if (transformPolicy != null) { restoreRefreshVisuals(part); rollback(transformPolicy); } } } /** * Computes the clock-wise rotation angle based on the initial mouse * position and the actual mouse position. * * @param e * The latest {@link MouseEvent}. * @param part * The {@link IVisualPart} that is rotated. * @return The clock-wise rotation angle. */ protected Angle computeRotationAngleCW(MouseEvent e, IVisualPart<? extends Node> part) { Vector vStart = new Vector(pivotInScene, initialPointerLocationInScene); Vector vEnd = new Vector(pivotInScene, new Point(e.getSceneX(), e.getSceneY())); Angle angle = vStart.getAngleCW(vEnd); return angle; } /** * Returns the {@link Cursor} that is shown to indicate that this policy * will perform a rotation. * * @return The {@link Cursor} that is shown to indicate that this policy * will perform a rotation. */ protected ImageCursor createRotateCursor() { return new ImageCursor( new Image(RotateSelectedOnHandleDragHandler.class .getResource("/rotate_obj.gif").toExternalForm())); } /** * Returns a {@link List} containing the whole {@link SelectionModel * selection}. * * @return A {@link List} containing the whole {@link SelectionModel * selection}. */ protected List<IContentPart<? extends Node>> determineTargetParts() { return getHost().getRoot().getViewer().getAdapter(SelectionModel.class) .getSelectionUnmodifiable(); } @Override public void drag(MouseEvent e, Dimension delta) { // do nothing when the user does not press control if (invalidGesture) { return; } for (IVisualPart<? extends Node> part : getTargetParts()) { updateOperation(e, part); } } @Override public void endDrag(MouseEvent e, Dimension delta) { // do nothing when the user does not press control if (invalidGesture) { invalidGesture = false; return; } // commit transform operations for (IVisualPart<? extends Node> part : getTargetParts()) { updateOperation(e, part); TransformPolicy transformPolicy = getTransformPolicy(part); if (transformPolicy != null) { restoreRefreshVisuals(part); commit(transformPolicy); } } } /** * Returns the {@link CursorSupport} of this policy. * * @return The {@link CursorSupport} of this policy. */ protected CursorSupport getCursorSupport() { return cursorSupport; } /** * Returns the {@link Cursor} that indicates rotation. Delegates to * {@link #createRotateCursor()} to create that cursor if it was not created * yet. * * @return The {@link Cursor} that indicates rotation. */ protected Cursor getRotateCursor() { if (rotateCursor == null) { rotateCursor = createRotateCursor(); } return rotateCursor; } /** * Returns the target parts of this policy. * * @return The target parts of this policy. */ protected List<IContentPart<? extends Node>> getTargetParts() { return targetParts; } /** * Returns the {@link TransformPolicy} that is installed on the given * {@link IVisualPart}. * * @param part * The {@link IVisualPart} of which the {@link TransformPolicy} * is returned. * @return The {@link TransformPolicy} that is installed on the given * {@link IVisualPart}. */ protected TransformPolicy getTransformPolicy( IVisualPart<? extends Node> part) { return part.getAdapter(TransformPolicy.class); } @Override public void hideIndicationCursor() { getCursorSupport().restoreCursor(); } /** * Returns <code>true</code> if the given {@link MouseEvent} should trigger * rotation. Otherwise returns <code>false</code>. Per default returns * <code>true</code> if <code><Control></code> is pressed. * * @param event * The {@link MouseEvent} in question. * @return <code>true</code> if the given {@link MouseEvent} should trigger * rotation, otherwise <code>false</code>. */ protected boolean isRotate(MouseEvent event) { return event.isControlDown(); } /** * If the given flag <i>isControlDown</i> is <code>true</code>, then the * mouse cursor is changed to a rotate cursor. Otherwise, the mouse cursor * is not changed. Returns <code>true</code> if the mouse cursor was * changed. Otherwise returns <code>false</code>. * * @param isControlDown * Flag to indicate if the control modifier key is pressed. * @return <code>true</code> if the mouse cursor was changed, otherwise * <code>false</code>. */ protected boolean showIndicationCursor(boolean isControlDown) { if (isControlDown) { getCursorSupport().storeAndReplaceCursor(getRotateCursor()); return true; } return false; } @Override public boolean showIndicationCursor(KeyEvent event) { return showIndicationCursor(event.isControlDown()); } @Override public boolean showIndicationCursor(MouseEvent event) { return showIndicationCursor(event.isControlDown()); } @Override public void startDrag(MouseEvent e) { // do nothing when the user does not press control invalidGesture = !isRotate(e); if (invalidGesture) { return; } // save pointer location for later angle calculation initialPointerLocationInScene = new Point(e.getSceneX(), e.getSceneY()); targetParts = determineTargetParts(); if (targetParts == null) { targetParts = Collections.emptyList(); } Rectangle bounds = PartUtils.getUnionedVisualBoundsInScene(targetParts); if (bounds == null) { throw new IllegalStateException( "Cannot determine visual bounds (null)."); } pivotInScene = bounds.getCenter(); // initialize for all target parts rotationIndices.clear(); for (IContentPart<? extends Node> part : getTargetParts()) { // transform pivot point to local coordinates TransformPolicy transformPolicy = getTransformPolicy(part); if (transformPolicy != null) { storeAndDisableRefreshVisuals(part); init(transformPolicy); // transform pivot to parent coordinates Point pivotInLocal = FX2Geometry .toPoint(getHost().getVisual().getParent() .sceneToLocal(pivotInScene.x, pivotInScene.y)); // create transformations int translateIndex = transformPolicy.createPostTransform(); int rotateIndex = transformPolicy.createPostTransform(); int translateBackIndex = transformPolicy.createPostTransform(); // set translation transforms transformPolicy.setPostTranslate(translateIndex, -pivotInLocal.x, -pivotInLocal.y); transformPolicy.setPostTranslate(translateBackIndex, pivotInLocal.x, pivotInLocal.y); // save rotation index for later adjustment rotationIndices.put(part, rotateIndex); } } } private void updateOperation(MouseEvent e, IVisualPart<? extends Node> part) { // determine scaling TransformPolicy transformPolicy = getTransformPolicy(part); if (transformPolicy != null) { transformPolicy.setPostRotate(rotationIndices.get(part), computeRotationAngleCW(e, part)); } } }