blob: 63066d9d6e2e767d36f0dd3af559e371ebac96c2 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2010 IBM Corporation 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.gef.tools;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.PositionConstants;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PrecisionPoint;
import org.eclipse.draw2d.geometry.PrecisionRectangle;
import org.eclipse.gef.AutoexposeHelper;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPartViewer;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.LayerConstants;
import org.eclipse.gef.Request;
import org.eclipse.gef.RequestConstants;
import org.eclipse.gef.SharedCursors;
import org.eclipse.gef.SnapToHelper;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.commands.CompoundCommand;
import org.eclipse.gef.commands.UnexecutableCommand;
import org.eclipse.gef.editparts.LayerManager;
import org.eclipse.gef.handles.HandleBounds;
import org.eclipse.gef.requests.ChangeBoundsRequest;
/**
* A DragTracker that moves {@link org.eclipse.gef.EditPart EditParts}.
*/
public class DragEditPartsTracker extends SelectEditPartTracker {
/**
* Key modifier for cloning. It's ALT on Mac, and CTRL on all other
* platforms.
*/
static final int MODIFIER_CLONE;
static {
if (Platform.OS_MACOSX.equals(Platform.getOS()))
MODIFIER_CLONE = SWT.ALT;
else
MODIFIER_CLONE = SWT.CTRL;
}
/**
* Key modifier for constrained move. It's SHIFT on all platforms.
*/
static final int MODIFIER_CONSTRAINED_MOVE = SWT.SHIFT;
private static final int FLAG_SOURCE_FEEDBACK = SelectEditPartTracker.MAX_FLAG << 1;
/** Max flag */
protected static final int MAX_FLAG = FLAG_SOURCE_FEEDBACK;
private List exclusionSet;
private PrecisionPoint sourceRelativeStartPoint;
private SnapToHelper snapToHelper;
private PrecisionRectangle sourceRectangle, compoundSrcRect;
private boolean cloneActive;
/**
* Constructs a new DragEditPartsTracker with the given source edit part.
*
* @param sourceEditPart
* the source edit part
*/
public DragEditPartsTracker(EditPart sourceEditPart) {
super(sourceEditPart);
cloneActive = false;
setDisabledCursor(SharedCursors.NO);
}
/**
* Returns true if the control key was the key in the key event and the tool
* is in an acceptable state for this event.
*
* @param e
* the key event
* @return true if the key was control and can be accepted.
*/
private boolean acceptClone(KeyEvent e) {
int key = e.keyCode;
if (!(isInState(STATE_DRAG_IN_PROGRESS | STATE_ACCESSIBLE_DRAG
| STATE_ACCESSIBLE_DRAG_IN_PROGRESS)))
return false;
return (key == MODIFIER_CLONE);
}
private boolean acceptSHIFT(KeyEvent e) {
return isInState(STATE_DRAG_IN_PROGRESS | STATE_ACCESSIBLE_DRAG
| STATE_ACCESSIBLE_DRAG_IN_PROGRESS)
&& e.keyCode == SWT.SHIFT;
}
/**
* Returns the cursor used under normal conditions.
*
* @see #setDefaultCursor(Cursor)
* @return the default cursor
*/
protected Cursor getDefaultCursor() {
if (isCloneActive())
return SharedCursors.CURSOR_TREE_ADD;
return super.getDefaultCursor();
}
/**
* Erases feedback and calls {@link #performDrag()}. Sets the state to
* terminal.
*
* @see org.eclipse.gef.tools.AbstractTool#commitDrag()
*/
public void commitDrag() {
eraseSourceFeedback();
eraseTargetFeedback();
performDrag();
setState(STATE_TERMINAL);
}
/**
* Captures the bounds of the source being dragged, and the unioned bounds
* of all figures being dragged. These bounds are used for snapping by the
* snap strategies in <code>updateTargetRequest()</code>.
*/
private void captureSourceDimensions() {
List editparts = getOperationSet();
for (int i = 0; i < editparts.size(); i++) {
GraphicalEditPart child = (GraphicalEditPart) editparts.get(i);
IFigure figure = child.getFigure();
PrecisionRectangle bounds = null;
if (figure instanceof HandleBounds)
bounds = new PrecisionRectangle(
((HandleBounds) figure).getHandleBounds());
else
bounds = new PrecisionRectangle(figure.getBounds());
figure.translateToAbsolute(bounds);
if (compoundSrcRect == null)
compoundSrcRect = new PrecisionRectangle(bounds);
else
compoundSrcRect = compoundSrcRect.union(bounds);
if (child == getSourceEditPart())
sourceRectangle = bounds;
}
if (sourceRectangle == null) {
IFigure figure = ((GraphicalEditPart) getSourceEditPart())
.getFigure();
if (figure instanceof HandleBounds)
sourceRectangle = new PrecisionRectangle(
((HandleBounds) figure).getHandleBounds());
else
sourceRectangle = new PrecisionRectangle(figure.getBounds());
figure.translateToAbsolute(sourceRectangle);
}
}
/**
* Returns a List of top-level edit parts excluding dependants (by calling
* {@link ToolUtilities#getSelectionWithoutDependants(EditPartViewer)} that
* understand the current target request (by calling
* {@link ToolUtilities#filterEditPartsUnderstanding(List, Request)}.
*
* @see org.eclipse.gef.tools.AbstractTool#createOperationSet()
*/
protected List createOperationSet() {
if (getCurrentViewer() != null) {
List list = ToolUtilities
.getSelectionWithoutDependants(getCurrentViewer());
ToolUtilities
.filterEditPartsUnderstanding(list, getTargetRequest());
return list;
}
return new ArrayList();
}
/**
* Creates a {@link ChangeBoundsRequest}. By default, the type is
* {@link RequestConstants#REQ_MOVE}. Later on when the edit parts are asked
* to contribute to the overall command, the request type will be either
* {@link RequestConstants#REQ_MOVE} or {@link RequestConstants#REQ_ORPHAN},
* depending on the result of {@link #isMove()}.
*
* @see org.eclipse.gef.tools.TargetingTool#createTargetRequest()
*/
protected Request createTargetRequest() {
if (isCloneActive())
return new ChangeBoundsRequest(REQ_CLONE);
else
return new ChangeBoundsRequest(REQ_MOVE);
}
/**
* Erases source feedback and sets the autoexpose helper to
* <code>null</code>.
*
* @see org.eclipse.gef.Tool#deactivate()
*/
public void deactivate() {
eraseSourceFeedback();
super.deactivate();
exclusionSet = null;
sourceRelativeStartPoint = null;
sourceRectangle = null;
compoundSrcRect = null;
snapToHelper = null;
}
/**
* Asks the edit parts in the {@link AbstractTool#getOperationSet()
* operation set} to erase their source feedback.
*/
protected void eraseSourceFeedback() {
if (!getFlag(FLAG_SOURCE_FEEDBACK))
return;
setFlag(FLAG_SOURCE_FEEDBACK, false);
List editParts = getOperationSet();
for (int i = 0; i < editParts.size(); i++) {
EditPart editPart = (EditPart) editParts.get(i);
editPart.eraseSourceFeedback(getTargetRequest());
}
}
/**
* Asks each edit part in the {@link AbstractTool#getOperationSet()
* operation set} to contribute to a {@link CompoundCommand} after first
* setting the request type to either {@link RequestConstants#REQ_MOVE} or
* {@link RequestConstants#REQ_ORPHAN}, depending on the result of
* {@link #isMove()}.
*
* @see org.eclipse.gef.tools.AbstractTool#getCommand()
*/
protected Command getCommand() {
CompoundCommand command = new CompoundCommand();
command.setDebugLabel("Drag Object Tracker");//$NON-NLS-1$
Iterator iter = getOperationSet().iterator();
Request request = getTargetRequest();
if (isCloneActive())
request.setType(REQ_CLONE);
else if (isMove())
request.setType(REQ_MOVE);
else
request.setType(REQ_ORPHAN);
if (!isCloneActive()) {
while (iter.hasNext()) {
EditPart editPart = (EditPart) iter.next();
command.add(editPart.getCommand(request));
}
}
if (!isMove() || isCloneActive()) {
if (!isCloneActive())
request.setType(REQ_ADD);
if (getTargetEditPart() == null)
command.add(UnexecutableCommand.INSTANCE);
else
command.add(getTargetEditPart().getCommand(getTargetRequest()));
}
return command.unwrap();
}
/**
* @see org.eclipse.gef.tools.AbstractTool#getCommandName()
*/
protected String getCommandName() {
if (isCloneActive())
return REQ_CLONE;
else if (isMove())
return REQ_MOVE;
else
return REQ_ADD;
}
/**
* @see org.eclipse.gef.tools.AbstractTool#getDebugName()
*/
protected String getDebugName() {
return "DragEditPartsTracker:" + getCommandName();//$NON-NLS-1$
}
/**
* Returns a list of all the edit parts in the
* {@link AbstractTool#getOperationSet() operation set}, plus the
* {@link org.eclipse.draw2d.ConnectionLayer}.
*
* @see org.eclipse.gef.tools.TargetingTool#getExclusionSet()
*/
protected Collection getExclusionSet() {
if (exclusionSet == null) {
List set = getOperationSet();
exclusionSet = new ArrayList(set.size() + 1);
for (int i = 0; i < set.size(); i++) {
GraphicalEditPart editpart = (GraphicalEditPart) set.get(i);
exclusionSet.add(editpart.getFigure());
}
LayerManager layerManager = (LayerManager) getCurrentViewer()
.getEditPartRegistry().get(LayerManager.ID);
if (layerManager != null) {
exclusionSet.add(layerManager
.getLayer(LayerConstants.CONNECTION_LAYER));
}
}
return exclusionSet;
}
/**
* @see org.eclipse.gef.tools.TargetingTool#handleAutoexpose()
*/
protected void handleAutoexpose() {
updateTargetRequest();
updateTargetUnderMouse();
showTargetFeedback();
showSourceFeedback();
setCurrentCommand(getCommand());
}
/**
* Erases feedback and calls {@link #performDrag()}.
*
* @see org.eclipse.gef.tools.AbstractTool#handleButtonUp(int)
*/
protected boolean handleButtonUp(int button) {
if (stateTransition(STATE_DRAG_IN_PROGRESS, STATE_TERMINAL)) {
eraseSourceFeedback();
eraseTargetFeedback();
performDrag();
return true;
}
return super.handleButtonUp(button);
}
/**
* Updates the target request and mouse target, asks to show feedback, and
* sets the current command.
*
* @see org.eclipse.gef.tools.AbstractTool#handleDragInProgress()
*/
protected boolean handleDragInProgress() {
if (isInDragInProgress()) {
updateTargetRequest();
if (updateTargetUnderMouse())
updateTargetRequest();
showTargetFeedback();
showSourceFeedback();
setCurrentCommand(getCommand());
}
return true;
}
/**
* Calls {@link TargetingTool#updateAutoexposeHelper()} if a drag is in
* progress.
*
* @see org.eclipse.gef.tools.TargetingTool#handleHover()
*/
protected boolean handleHover() {
if (isInDragInProgress())
updateAutoexposeHelper();
return true;
}
/**
* Erases source feedback.
*
* @see org.eclipse.gef.tools.TargetingTool#handleInvalidInput()
*/
protected boolean handleInvalidInput() {
super.handleInvalidInput();
eraseSourceFeedback();
return true;
}
/**
* Processes arrow keys used to move edit parts.
*
* @see org.eclipse.gef.tools.AbstractTool#handleKeyDown(org.eclipse.swt.events.KeyEvent)
*/
protected boolean handleKeyDown(KeyEvent e) {
setAutoexposeHelper(null);
if (acceptArrowKey(e)) {
accStepIncrement();
if (stateTransition(STATE_INITIAL,
STATE_ACCESSIBLE_DRAG_IN_PROGRESS))
setStartLocation(getLocation());
switch (e.keyCode) {
case SWT.ARROW_DOWN:
placeMouseInViewer(getLocation().getTranslated(0, accGetStep()));
break;
case SWT.ARROW_UP:
placeMouseInViewer(getLocation()
.getTranslated(0, -accGetStep()));
break;
case SWT.ARROW_RIGHT:
int stepping = accGetStep();
if (isCurrentViewerMirrored())
stepping = -stepping;
placeMouseInViewer(getLocation().getTranslated(stepping, 0));
break;
case SWT.ARROW_LEFT:
int step = -accGetStep();
if (isCurrentViewerMirrored())
step = -step;
placeMouseInViewer(getLocation().getTranslated(step, 0));
break;
}
return true;
} else if (acceptClone(e)) {
setCloneActive(true);
handleDragInProgress();
return true;
} else if (acceptSHIFT(e)) {
handleDragInProgress();
return true;
}
return false;
}
/**
* Interprets and processes clone deactivation, constrained move
* deactivation, and accessibility navigation reset.
*
* @see org.eclipse.gef.tools.AbstractTool#handleKeyUp(org.eclipse.swt.events.KeyEvent)
*/
protected boolean handleKeyUp(KeyEvent e) {
if (acceptArrowKey(e)) {
accStepReset();
return true;
} else if (acceptClone(e)) {
setCloneActive(false);
handleDragInProgress();
return true;
} else if (acceptSHIFT(e)) {
handleDragInProgress();
return true;
}
return false;
}
/**
* Returns true if the current drag is a clone operation.
*
* @return true if cloning is enabled and is currently active.
*/
protected boolean isCloneActive() {
return cloneActive;
}
/**
* Returns <code>true</code> if the source edit part is being moved within
* its parent. If the source edit part is being moved to another parent,
* this returns <code>false</code>.
*
* @return <code>true</code> if the source edit part is not being reparented
*/
protected boolean isMove() {
EditPart part = getSourceEditPart();
while (part != getTargetEditPart() && part != null) {
if (part.getParent() == getTargetEditPart()
&& part.getSelected() != EditPart.SELECTED_NONE)
return true;
part = part.getParent();
}
return false;
}
/**
* Calls {@link AbstractTool#executeCurrentCommand()}.
*/
protected void performDrag() {
executeCurrentCommand();
}
/**
* If auto scroll (also called auto expose) is being performed, the start
* location moves during the scroll. This method updates that location.
*/
protected void repairStartLocation() {
if (sourceRelativeStartPoint == null)
return;
IFigure figure = ((GraphicalEditPart) getSourceEditPart()).getFigure();
PrecisionPoint newStart = (PrecisionPoint) sourceRelativeStartPoint
.getCopy();
figure.translateToAbsolute(newStart);
Point delta = new Point(newStart.x - getStartLocation().x, newStart.y
- getStartLocation().y);
setStartLocation(newStart);
// sourceRectangle and compoundSrcRect need to be updated as well when
// auto-scrolling
if (sourceRectangle != null)
sourceRectangle.translate(delta);
if (compoundSrcRect != null)
compoundSrcRect.translate(delta);
}
/**
* @see org.eclipse.gef.tools.TargetingTool#setAutoexposeHelper(org.eclipse.gef.AutoexposeHelper)
*/
protected void setAutoexposeHelper(AutoexposeHelper helper) {
super.setAutoexposeHelper(helper);
if (helper != null && sourceRelativeStartPoint == null
&& isInDragInProgress()) {
IFigure figure = ((GraphicalEditPart) getSourceEditPart())
.getFigure();
sourceRelativeStartPoint = new PrecisionPoint(getStartLocation());
figure.translateToRelative(sourceRelativeStartPoint);
}
}
/**
* Enables cloning if the value is true.
*
* @param cloneActive
* <code>true</code> if cloning should be active
*/
protected void setCloneActive(boolean cloneActive) {
if (this.cloneActive == cloneActive)
return;
eraseSourceFeedback();
eraseTargetFeedback();
this.cloneActive = cloneActive;
}
/**
* Extended to update the current snap-to strategy.
*
* @see org.eclipse.gef.tools.TargetingTool#setTargetEditPart(org.eclipse.gef.EditPart)
*/
protected void setTargetEditPart(EditPart editpart) {
if (getTargetEditPart() == editpart)
return;
super.setTargetEditPart(editpart);
snapToHelper = null;
if (getTargetEditPart() != null && getOperationSet().size() > 0)
snapToHelper = (SnapToHelper) getTargetEditPart().getAdapter(
SnapToHelper.class);
}
/**
* Asks the edit parts in the {@link AbstractTool#getOperationSet()
* operation set} to show source feedback.
*/
protected void showSourceFeedback() {
List editParts = getOperationSet();
for (int i = 0; i < editParts.size(); i++) {
EditPart editPart = (EditPart) editParts.get(i);
editPart.showSourceFeedback(getTargetRequest());
}
setFlag(FLAG_SOURCE_FEEDBACK, true);
}
/**
* Extended to activate cloning and to update the captured source dimensions
* when applicable.
*
* @see org.eclipse.gef.tools.AbstractTool#setState(int)
*/
protected void setState(int state) {
boolean check = isInState(STATE_INITIAL);
super.setState(state);
if (isInState(STATE_ACCESSIBLE_DRAG | STATE_DRAG_IN_PROGRESS
| STATE_ACCESSIBLE_DRAG_IN_PROGRESS)) {
if (getCurrentInput().isModKeyDown(MODIFIER_CLONE)) {
setCloneActive(true);
handleDragInProgress();
}
}
if (check
&& isInState(STATE_DRAG | STATE_ACCESSIBLE_DRAG
| STATE_ACCESSIBLE_DRAG_IN_PROGRESS))
captureSourceDimensions();
}
/**
* Calls {@link #repairStartLocation()} in case auto scroll is being
* performed. Updates the request with the current
* {@link AbstractTool#getOperationSet() operation set}, move delta,
* location and type.
*
* @see org.eclipse.gef.tools.TargetingTool#updateTargetRequest()
*/
protected void updateTargetRequest() {
repairStartLocation();
ChangeBoundsRequest request = (ChangeBoundsRequest) getTargetRequest();
request.setEditParts(getOperationSet());
Dimension delta = getDragMoveDelta();
request.setConstrainedMove(getCurrentInput().isModKeyDown(
MODIFIER_CONSTRAINED_MOVE));
request.setSnapToEnabled(!getCurrentInput().isModKeyDown(
MODIFIER_NO_SNAPPING));
// constrains the move to dx=0, dy=0, or dx=dy if shift is depressed
if (request.isConstrainedMove()) {
float ratio = 0;
if (delta.width != 0)
ratio = (float) delta.height / (float) delta.width;
ratio = Math.abs(ratio);
if (ratio > 0.5 && ratio < 1.5) {
if (Math.abs(delta.height) > Math.abs(delta.width)) {
if (delta.height > 0)
delta.height = Math.abs(delta.width);
else
delta.height = -Math.abs(delta.width);
} else {
if (delta.width > 0)
delta.width = Math.abs(delta.height);
else
delta.width = -Math.abs(delta.height);
}
} else {
if (Math.abs(delta.width) > Math.abs(delta.height))
delta.height = 0;
else
delta.width = 0;
}
}
Point moveDelta = new Point(delta.width, delta.height);
request.getExtendedData().clear();
request.setMoveDelta(moveDelta);
snapPoint(request);
request.setLocation(getLocation());
request.setType(getCommandName());
}
/**
* This method can be overridden by clients to customize the snapping
* behavior.
*
* @param request
* the <code>ChangeBoundsRequest</code> from which the move delta
* can be extracted and updated
* @since 3.4
*/
protected void snapPoint(ChangeBoundsRequest request) {
Point moveDelta = request.getMoveDelta();
if (snapToHelper != null && request.isSnapToEnabled()) {
PrecisionRectangle baseRect = sourceRectangle.getPreciseCopy();
PrecisionRectangle jointRect = compoundSrcRect.getPreciseCopy();
baseRect.translate(moveDelta);
jointRect.translate(moveDelta);
PrecisionPoint preciseDelta = new PrecisionPoint(moveDelta);
snapToHelper.snapPoint(request, PositionConstants.HORIZONTAL
| PositionConstants.VERTICAL, new PrecisionRectangle[] {
baseRect, jointRect }, preciseDelta);
request.setMoveDelta(preciseDelta);
}
}
}