/*******************************************************************************
* 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:
* Alexander Nyßen (itemis AG) - initial API and implementation
*
*******************************************************************************/
package org.eclipse.gef.mvc.fx.handlers;
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.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.Node;
import javafx.scene.input.RotateEvent;
/**
* The {@link RotateSelectedOnRotateHandler} is an {@link IOnRotateHandler} that
* rotates the whole {@link SelectionModel selection} when its {@link #getHost()
* host} experiences a touch rotate gesture.
*
* @author anyssen
*
*/
public class RotateSelectedOnRotateHandler extends AbstractHandler
implements IOnRotateHandler {
private Point pivotInScene;
private Map<IContentPart<? extends Node>, Integer> rotationIndices = new HashMap<>();
private List<IContentPart<? extends Node>> targetParts;
// gesture validity
private boolean invalidGesture = false;
@Override
public void abortRotate() {
if (invalidGesture) {
return;
}
// roll back transform operations
for (IVisualPart<? extends Node> part : getTargetParts()) {
TransformPolicy transformPolicy = getTransformPolicy(part);
if (transformPolicy != null) {
restoreRefreshVisuals(part);
rollback(transformPolicy);
}
}
}
/**
* Returns a {@link List} containing all {@link IContentPart}s that should
* be rotated by this policy. Per default, the whole {@link SelectionModel
* selection} is returned.
*
* @return A {@link List} containing all {@link IContentPart}s that should
* be rotated by this policy.
*/
protected List<IContentPart<? extends Node>> determineTargetParts() {
return getHost().getRoot().getViewer().getAdapter(SelectionModel.class)
.getSelectionUnmodifiable();
}
@Override
public void endRotate(RotateEvent e) {
if (invalidGesture) {
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 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);
}
/**
* Returns <code>true</code> if the given {@link RotateEvent} should trigger
* rotation. Otherwise returns <code>false</code>. Per default always
* returns <code>true</code>.
*
* @param event
* The {@link RotateEvent} in question.
* @return <code>true</code> to indicate that the given {@link RotateEvent}
* should trigger rotation, otherwise <code>false</code>.
*/
protected boolean isRotate(RotateEvent event) {
return true;
}
@Override
public void rotate(RotateEvent e) {
if (invalidGesture) {
return;
}
for (IVisualPart<? extends Node> part : getTargetParts()) {
updateOperation(e, part);
}
}
@Override
public void startRotate(RotateEvent e) {
targetParts = determineTargetParts();
invalidGesture = !isRotate(e);
if (invalidGesture) {
return;
}
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(RotateEvent e,
IVisualPart<? extends Node> part) {
// Point2D pivot = pivotInTargetPartVisuals.get(part);
Angle rotationAngle = Angle.fromDeg(e.getTotalAngle());
TransformPolicy transformPolicy = getTransformPolicy(part);
if (transformPolicy != null) {
transformPolicy.setPostRotate(rotationIndices.get(part),
rotationAngle);
}
}
}