/******************************************************************************* * Copyright (c) 2000, 2010, 2012 IBM Corporation, Gerhardt Informatics Kft. 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 * Gerhardt Informatics Kft. - GEFGWT port *******************************************************************************/ package org.eclipse.gef.tools; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; 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; import org.eclipse.swt.SWT; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.graphics.Cursor; /** * 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); } } }