| /******************************************************************************* |
| * 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.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.draw2d.rap.swt.SWT; |
| |
| 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); |
| } |
| } |
| |
| } |