/*******************************************************************************
* 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.handlers;
import org.eclipse.gef.geometry.planar.Dimension;
import org.eclipse.gef.geometry.planar.Point;
import org.eclipse.gef.mvc.fx.models.SelectionModel;
import org.eclipse.gef.mvc.fx.parts.AbstractSegmentHandlePart;
import org.eclipse.gef.mvc.fx.parts.ITransformableContentPart;
import org.eclipse.gef.mvc.fx.parts.IVisualPart;
import org.eclipse.gef.mvc.fx.policies.ResizePolicy;
import org.eclipse.gef.mvc.fx.policies.TransformPolicy;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
/**
* The {@link ResizeTranslateFirstAnchorageOnHandleDragHandler} is an
* {@link IOnDragHandler} that handles the resize and relocation of its
* (selected) first anchorage when an {@link AbstractSegmentHandlePart} of the
* box selection of the first anchorage is dragged with the mouse.
*
* @author mwienand
*
*/
// Only applicable for AbstractSegmentHandlePart, see #getHost().
public class ResizeTranslateFirstAnchorageOnHandleDragHandler
extends AbstractHandler implements IOnDragHandler {
private CursorSupport cursorSupport = new CursorSupport(this);
private SnapToGridSupport snapSupport = new SnapToGridSupport(this);
private boolean invalidGesture = false;
private Point initialPointerLocation;
private int translationIndex;
private IVisualPart<? extends Node> targetPart;
private Point initialVertex;
@Override
public void abortDrag() {
if (invalidGesture) {
return;
}
restoreRefreshVisuals(getTargetPart());
commit(getResizePolicy());
commit(getTransformPolicy());
}
/**
* Returns the target {@link IVisualPart} for this policy. Per default the
* first anchorage is returned.
*
* @return The target {@link IVisualPart} for this policy.
*/
protected IVisualPart<? extends Node> determineTargetPart() {
return getHost().getAnchoragesUnmodifiable().keySet().iterator().next();
}
@Override
public void drag(MouseEvent e, Dimension delta) {
// guard against uninitialized access
if (invalidGesture) {
return;
}
// compute end point
Node visual = getTargetPart().getVisual();
Point newEndScene = new Point(e.getSceneX(), e.getSceneY());
// compute delta in scene
Point deltaInScene = new Point(newEndScene.x - initialPointerLocation.x,
newEndScene.y - initialPointerLocation.y);
// apply delta to the moved vertex
Point newVertex = initialVertex.getTranslated(deltaInScene);
// snap the moved vertex (unless isPrecise(e))
Point snappedVertex = newVertex;
if (!isPrecise(e)) {
snappedVertex = snapSupport.snapToGrid(newVertex.x, newVertex.y);
}
// compute delta between initial and snapped vertex
Point2D startLocal = visual.sceneToLocal(initialVertex.x,
initialVertex.y);
Point2D endLocal = visual.sceneToLocal(snappedVertex.x,
snappedVertex.y);
// compute delta in local coordinates
double deltaX = endLocal.getX() - startLocal.getX();
double deltaY = endLocal.getY() - startLocal.getY();
// segment index determines logical position (0 = top left, 1 = top
// right, 2 = bottom right, 3 = bottom left)
int segment = getHost().getSegmentIndex();
// determine resize in local coordinates
double ldw, ldh;
if (segment == 1 || segment == 2) {
// right side
ldw = deltaX;
} else {
// left side
ldw = -deltaX;
}
if (segment == 2 || segment == 3) {
// bottom side
ldh = deltaY;
} else {
// top side
ldh = -deltaY;
}
// XXX: Resize before querying the applicable delta, so that the minimum
// size is respected.
getResizePolicy().resize(ldw, ldh);
Dimension applicableDelta = new Dimension(
getResizePolicy().getDeltaWidth(),
getResizePolicy().getDeltaHeight());
// Only apply translation if possible, i.e. if the resize cannot be
// applied in total, the translation can probably not be applied in
// total as well.
if (applicableDelta.width != ldw) {
deltaX = applicableDelta.width;
if (segment == 0 || segment == 3) {
deltaX = -deltaX;
}
}
if (applicableDelta.height != ldh) {
deltaY = applicableDelta.height;
if (segment == 0 || segment == 1) {
deltaY = -deltaY;
}
}
// compute (local) translation
double ldx = segment == 0 || segment == 3 ? deltaX : 0;
double ldy = segment == 0 || segment == 1 ? deltaY : 0;
// apply translation
getTransformPolicy().setPreTranslate(translationIndex, ldx, ldy);
}
@Override
public void endDrag(MouseEvent e, Dimension delta) {
if (invalidGesture) {
invalidGesture = false;
return;
}
restoreRefreshVisuals(getTargetPart());
commit(getResizePolicy());
commit(getTransformPolicy());
}
/**
* Returns the {@link CursorSupport} of this policy.
*
* @return The {@link CursorSupport} of this policy.
*/
protected CursorSupport getCursorSupport() {
return cursorSupport;
}
@Override
public AbstractSegmentHandlePart<? extends Node> getHost() {
return (AbstractSegmentHandlePart<? extends Node>) super.getHost();
}
/**
* Returns the {@link ResizePolicy} that is installed on the
* {@link #getTargetPart()}.
*
* @return The {@link ResizePolicy} that is installed on the
* {@link #getTargetPart()}.
*/
protected ResizePolicy getResizePolicy() {
return getTargetPart().getAdapter(ResizePolicy.class);
}
/**
* Returns the target part of this policy.
*
* @return The target part of this policy.
*/
protected ITransformableContentPart<? extends Node> getTargetPart() {
return (ITransformableContentPart<? extends Node>) targetPart;
}
/**
* Returns the {@link TransformPolicy} that is installed on the
* {@link #getTargetPart()}.
*
* @return The {@link TransformPolicy} that is installed on the
* {@link #getTargetPart()}.
*/
protected TransformPolicy getTransformPolicy() {
return getTargetPart().getAdapter(TransformPolicy.class);
}
@Override
public void hideIndicationCursor() {
getCursorSupport().restoreCursor();
}
private boolean isMultiSelection() {
return getTargetPart().getRoot().getViewer()
.getAdapter(SelectionModel.class).getSelectionUnmodifiable()
.size() > 1;
}
/**
* Returns <code>true</code> if precise manipulations should be performed
* for the given {@link MouseEvent}. Otherwise returns <code>false</code>.
*
* @param e
* The {@link MouseEvent} that is used to determine if precise
* manipulations should be performed (i.e. if the corresponding
* modifier key is pressed).
* @return <code>true</code> if precise manipulations should be performed,
* <code>false</code> otherwise.
*/
protected boolean isPrecise(MouseEvent e) {
return e.isShortcutDown();
}
/**
* Returns <code>true</code> if the given {@link MouseEvent} should trigger
* resize and translate. Otherwise returns <code>false</code>. Per default
* returns <code>true</code> if <code><Control></code> is not pressed
* and there is not more than single anchorage.
*
* @param event
* The {@link MouseEvent} in question.
* @return <code>true</code> if the given {@link MouseEvent} should trigger
* resize and translate, otherwise <code>false</code>.
*/
protected boolean isResizeTranslate(MouseEvent event) {
return !event.isControlDown() && !isMultiSelection();
}
/**
* Sets the target part (i.e. the part that is resized and translated) of
* this policy to the given value.
*
* @param determinedTargetPart
* The target part of this policy.
*/
protected void setTargetPart(
IVisualPart<? extends Node> determinedTargetPart) {
this.targetPart = determinedTargetPart;
}
@Override
public boolean showIndicationCursor(KeyEvent event) {
return false;
}
@Override
public boolean showIndicationCursor(MouseEvent event) {
return false;
}
@Override
public void startDrag(MouseEvent e) {
setTargetPart(determineTargetPart());
invalidGesture = !isResizeTranslate(e);
if (invalidGesture) {
return;
}
storeAndDisableRefreshVisuals(getTargetPart());
initialPointerLocation = new Point(e.getSceneX(), e.getSceneY());
init(getResizePolicy());
init(getTransformPolicy());
translationIndex = getTransformPolicy().createPreTransform();
// determine initial bounds in scene
Bounds layoutBounds = getTargetPart().getVisual().getLayoutBounds();
Bounds initialBoundsInScene = getTargetPart().getVisual()
.localToScene(layoutBounds);
// save moved vertex
int segment = getHost().getSegmentIndex();
if (segment == 0) {
initialVertex = new Point(initialBoundsInScene.getMinX(),
initialBoundsInScene.getMinY());
} else if (segment == 1) {
initialVertex = new Point(initialBoundsInScene.getMaxX(),
initialBoundsInScene.getMinY());
} else if (segment == 2) {
initialVertex = new Point(initialBoundsInScene.getMaxX(),
initialBoundsInScene.getMaxY());
} else if (segment == 3) {
initialVertex = new Point(initialBoundsInScene.getMinX(),
initialBoundsInScene.getMaxY());
}
}
}