| /******************************************************************************* |
| * 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.List; |
| |
| import org.eclipse.swt.graphics.Cursor; |
| |
| 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.PrecisionDimension; |
| import org.eclipse.draw2d.geometry.PrecisionPoint; |
| import org.eclipse.draw2d.geometry.PrecisionRectangle; |
| import org.eclipse.swt.SWT; |
| |
| import org.eclipse.gef.EditPart; |
| import org.eclipse.gef.GraphicalEditPart; |
| import org.eclipse.gef.Request; |
| 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.handles.HandleBounds; |
| import org.eclipse.gef.requests.ChangeBoundsRequest; |
| |
| /** |
| * A Tracker for dragging a resize handle. The ResizeTracker will resize all of |
| * the selected editparts in the viewer which understand a RESIZE request. A |
| * {@link ChangeBoundsRequest} is sent to each member of the operation set. The |
| * tracker allows for the resize direction to be specified in the constructor. |
| * |
| * @author hudsonr |
| */ |
| public class ResizeTracker extends SimpleDragTracker { |
| |
| /** |
| * Key modifier for centered resizing. It's ALT on all platforms. |
| */ |
| static final int MODIFIER_CENTERED_RESIZE = SWT.ALT; |
| |
| /** |
| * Key modifier for constrained resizing. It's SHIFT on all platforms. |
| */ |
| static final int MODIFIER_CONSTRAINED_RESIZE = SWT.SHIFT; |
| |
| private static int FLAG_TARGET_FEEDBACK = SimpleDragTracker.MAX_FLAG << 1; |
| |
| /** |
| * The maximum flag used by this class. |
| */ |
| protected static final int MAX_FLAG = new Integer(FLAG_TARGET_FEEDBACK) |
| .intValue(); |
| |
| private int direction; |
| private GraphicalEditPart owner; |
| private PrecisionRectangle sourceRect; |
| private SnapToHelper snapToHelper; |
| |
| /** |
| * Constructs a resize tracker that resizes in the specified direction. The |
| * direction is specified using {@link PositionConstants#NORTH}, |
| * {@link PositionConstants#NORTH_EAST}, etc. |
| * |
| * @deprecated use ResizeTracker(GraphicalEditPart, int) instead |
| * @param direction |
| * the direction |
| */ |
| public ResizeTracker(int direction) { |
| this(null, direction); |
| } |
| |
| /** |
| * Constructs a resize tracker that resizes in the specified direction. The |
| * direction is specified using {@link PositionConstants#NORTH}, |
| * {@link PositionConstants#NORTH_EAST}, etc. |
| * |
| * @param owner |
| * of the resize handle which returned this tracker |
| * @param direction |
| * the direction |
| */ |
| public ResizeTracker(GraphicalEditPart owner, int direction) { |
| this.owner = owner; |
| this.direction = direction; |
| setDisabledCursor(SharedCursors.NO); |
| } |
| |
| /** |
| * @see org.eclipse.gef.Tool#activate() |
| */ |
| public void activate() { |
| super.activate(); |
| if (owner != null) { |
| if (getTargetEditPart() != null) |
| snapToHelper = (SnapToHelper) getTargetEditPart().getAdapter( |
| SnapToHelper.class); |
| |
| IFigure figure = owner.getFigure(); |
| if (figure instanceof HandleBounds) |
| sourceRect = new PrecisionRectangle( |
| ((HandleBounds) figure).getHandleBounds()); |
| else |
| sourceRect = new PrecisionRectangle(figure.getBounds()); |
| figure.translateToAbsolute(sourceRect); |
| } |
| } |
| |
| /** |
| * @see org.eclipse.gef.tools.AbstractTool#commitDrag() |
| */ |
| public void commitDrag() { |
| eraseTargetFeedback(); |
| super.commitDrag(); |
| } |
| |
| /** |
| * Returns all selected parts which understand resizing. |
| * |
| * @see org.eclipse.gef.tools.AbstractTool#createOperationSet() |
| */ |
| protected List createOperationSet() { |
| List list = super.createOperationSet(); |
| ToolUtilities.filterEditPartsUnderstanding(list, getSourceRequest()); |
| return list; |
| } |
| |
| /** |
| * @see org.eclipse.gef.tools.SimpleDragTracker#createSourceRequest() |
| */ |
| protected Request createSourceRequest() { |
| ChangeBoundsRequest request; |
| request = new ChangeBoundsRequest(REQ_RESIZE); |
| request.setResizeDirection(getResizeDirection()); |
| return request; |
| } |
| |
| /** |
| * @see org.eclipse.gef.tools.AbstractTool#deactivate() |
| */ |
| public void deactivate() { |
| // For the case where ESC key was hit while resizing |
| eraseTargetFeedback(); |
| |
| sourceRect = null; |
| snapToHelper = null; |
| super.deactivate(); |
| } |
| |
| /** |
| * This method is invoked when the resize operation is complete. It notifies |
| * the {@link #getTargetEditPart() target} to erase target feedback. |
| */ |
| protected void eraseTargetFeedback() { |
| if (!getFlag(FLAG_TARGET_FEEDBACK)) |
| return; |
| if (getTargetEditPart() != null) |
| getTargetEditPart().eraseTargetFeedback(getSourceRequest()); |
| setFlag(FLAG_TARGET_FEEDBACK, false); |
| } |
| |
| /** |
| * @see org.eclipse.gef.tools.AbstractTool#getCommand() |
| */ |
| protected Command getCommand() { |
| List editparts = getOperationSet(); |
| EditPart part; |
| CompoundCommand command = new CompoundCommand(); |
| command.setDebugLabel("Resize Handle Tracker");//$NON-NLS-1$ |
| for (int i = 0; i < editparts.size(); i++) { |
| part = (EditPart) editparts.get(i); |
| command.add(part.getCommand(getSourceRequest())); |
| } |
| return command.unwrap(); |
| } |
| |
| /** |
| * @see org.eclipse.gef.tools.AbstractTool#getCommandName() |
| */ |
| protected String getCommandName() { |
| return REQ_RESIZE; |
| } |
| |
| /** |
| * @see org.eclipse.gef.tools.AbstractTool#getDefaultCursor() |
| */ |
| protected Cursor getDefaultCursor() { |
| return SharedCursors.getDirectionalCursor(direction, |
| getTargetEditPart().getFigure().isMirrored()); |
| } |
| |
| /** |
| * @see org.eclipse.gef.tools.AbstractTool#getDebugName() |
| */ |
| protected String getDebugName() { |
| return "Resize Handle Tracker";//$NON-NLS-1$ |
| } |
| |
| /** |
| * Returns the owner of this resize tracker. |
| * |
| * @since 3.7 |
| */ |
| protected GraphicalEditPart getOwner() { |
| return owner; |
| } |
| |
| /** |
| * Returns the direction of the resize (NORTH, EAST, NORTH_EAST, etc.). |
| * These constants are from {@link PositionConstants}. |
| * |
| * @return the resize direction. |
| */ |
| protected int getResizeDirection() { |
| return direction; |
| } |
| |
| /** |
| * The TargetEditPart is the parent of the EditPart being resized. |
| * |
| * @return The target EditPart; may be <code>null</code> in 2.1 applications |
| * that use the now deprecated |
| * {@link ResizeTracker#ResizeTracker(int) constructor}. |
| */ |
| protected GraphicalEditPart getTargetEditPart() { |
| if (owner != null) |
| return (GraphicalEditPart) owner.getParent(); |
| return null; |
| } |
| |
| /** |
| * If dragging is in progress, cleans up feedback and calls performDrag(). |
| * |
| * @see org.eclipse.gef.tools.SimpleDragTracker#handleButtonUp(int) |
| */ |
| protected boolean handleButtonUp(int button) { |
| if (stateTransition(STATE_DRAG_IN_PROGRESS, STATE_TERMINAL)) { |
| eraseSourceFeedback(); |
| eraseTargetFeedback(); |
| performDrag(); |
| } |
| return true; |
| } |
| |
| /** |
| * Updates the command and the source request, and shows feedback. |
| * |
| * @see org.eclipse.gef.tools.SimpleDragTracker#handleDragInProgress() |
| */ |
| protected boolean handleDragInProgress() { |
| if (isInDragInProgress()) { |
| updateSourceRequest(); |
| showSourceFeedback(); |
| showTargetFeedback(); |
| setCurrentCommand(getCommand()); |
| } |
| return true; |
| } |
| |
| /** |
| * This method is invoked as the drag is happening. It notifies the |
| * {@link #getTargetEditPart() target} to show target feedback. |
| */ |
| protected void showTargetFeedback() { |
| setFlag(FLAG_TARGET_FEEDBACK, true); |
| if (getTargetEditPart() != null) |
| getTargetEditPart().showTargetFeedback(getSourceRequest()); |
| } |
| |
| /** |
| * @see org.eclipse.gef.tools.SimpleDragTracker#updateSourceRequest() |
| */ |
| protected void updateSourceRequest() { |
| ChangeBoundsRequest request = (ChangeBoundsRequest) getSourceRequest(); |
| Dimension d = getDragMoveDelta(); |
| |
| Point location = new Point(getLocation()); |
| Point moveDelta = new Point(0, 0); |
| Dimension resizeDelta = new Dimension(0, 0); |
| |
| request.setConstrainedResize(getCurrentInput().isModKeyDown( |
| MODIFIER_CONSTRAINED_RESIZE)); |
| request.setCenteredResize(getCurrentInput().isModKeyDown( |
| MODIFIER_CENTERED_RESIZE)); |
| request.setSnapToEnabled(!getCurrentInput().isModKeyDown( |
| MODIFIER_NO_SNAPPING)); |
| |
| if (request.isConstrainedResize() && owner != null) { |
| request.setConstrainedResize(true); |
| |
| int origHeight = owner.getFigure().getBounds().height; |
| int origWidth = owner.getFigure().getBounds().width; |
| float ratio = 1; |
| |
| if (origWidth != 0 && origHeight != 0) |
| ratio = ((float) origHeight / (float) origWidth); |
| |
| if (getResizeDirection() == PositionConstants.SOUTH_EAST) { |
| if (d.height > (d.width * ratio)) |
| d.width = (int) (d.height / ratio); |
| else |
| d.height = (int) (d.width * ratio); |
| } else if (getResizeDirection() == PositionConstants.NORTH_WEST) { |
| if (d.height < (d.width * ratio)) |
| d.width = (int) (d.height / ratio); |
| else |
| d.height = (int) (d.width * ratio); |
| } else if (getResizeDirection() == PositionConstants.NORTH_EAST) { |
| if (-(d.height) > (d.width * ratio)) |
| d.width = -(int) (d.height / ratio); |
| else |
| d.height = -(int) (d.width * ratio); |
| } else if (getResizeDirection() == PositionConstants.SOUTH_WEST) { |
| if (-(d.height) < (d.width * ratio)) |
| d.width = -(int) (d.height / ratio); |
| else |
| d.height = -(int) (d.width * ratio); |
| } |
| } |
| |
| if ((getResizeDirection() & PositionConstants.NORTH) != 0) { |
| if (request.isCenteredResize()) { |
| resizeDelta.height -= d.height; |
| } |
| moveDelta.y += d.height; |
| resizeDelta.height -= d.height; |
| } |
| if ((getResizeDirection() & PositionConstants.SOUTH) != 0) { |
| if (request.isCenteredResize()) { |
| moveDelta.y -= d.height; |
| resizeDelta.height += d.height; |
| } |
| resizeDelta.height += d.height; |
| } |
| if ((getResizeDirection() & PositionConstants.WEST) != 0) { |
| if (request.isCenteredResize()) { |
| resizeDelta.width -= d.width; |
| } |
| moveDelta.x += d.width; |
| resizeDelta.width -= d.width; |
| } |
| if ((getResizeDirection() & PositionConstants.EAST) != 0) { |
| if (request.isCenteredResize()) { |
| moveDelta.x -= d.width; |
| resizeDelta.width += d.width; |
| } |
| resizeDelta.width += d.width; |
| } |
| |
| request.setMoveDelta(moveDelta); |
| request.setSizeDelta(resizeDelta); |
| request.setLocation(location); |
| request.setEditParts(getOperationSet()); |
| request.getExtendedData().clear(); |
| request.setResizeDirection(getResizeDirection()); |
| |
| if (request.isSnapToEnabled() && snapToHelper != null) { |
| PrecisionRectangle rect = sourceRect.getPreciseCopy(); |
| rect.translate(moveDelta); |
| rect.resize(resizeDelta); |
| PrecisionRectangle result = new PrecisionRectangle(); |
| |
| snapToHelper.snapRectangle(request, request.getResizeDirection(), |
| rect, result); |
| if (request.isCenteredResize()) { |
| if (result.preciseX() != 0.0) |
| result.setPreciseWidth(result.preciseWidth() |
| - result.preciseX()); |
| else if (result.preciseWidth() != 0.0) { |
| result.setPreciseX(-result.preciseWidth()); |
| result.setPreciseWidth(result.preciseWidth() * 2.0); |
| } |
| |
| if (result.preciseY() != 0.0) |
| result.setPreciseHeight(result.preciseHeight() |
| - result.preciseY()); |
| else if (result.preciseHeight() != 0.0) { |
| result.setPreciseY(-result.preciseHeight()); |
| result.setPreciseHeight(result.preciseHeight() * 2.0); |
| } |
| } |
| |
| PrecisionPoint preciseMove = new PrecisionPoint(result.preciseX() |
| + moveDelta.x, result.preciseY() + moveDelta.y); |
| |
| PrecisionDimension preciseResize = new PrecisionDimension( |
| result.preciseWidth() + resizeDelta.width, |
| result.preciseHeight() + resizeDelta.height); |
| |
| request.setMoveDelta(preciseMove); |
| request.setSizeDelta(preciseResize); |
| } |
| |
| enforceConstraintsForResize(request); |
| } |
| |
| /** |
| * Ensures size constraints (by default minimum and maximum) are respected |
| * by the given request. May be overwritten by clients to enforce additional |
| * constraints. |
| * |
| * @param changeBoundsRequest |
| * The request to validate |
| * @since 3.7 |
| */ |
| protected void enforceConstraintsForResize( |
| ChangeBoundsRequest changeBoundsRequest) { |
| // adjust request, so that minimum and maximum size constraints are |
| // respected |
| if (owner != null) { |
| PrecisionRectangle originalConstraint = new PrecisionRectangle( |
| owner.getFigure().getBounds()); |
| owner.getFigure().translateToAbsolute(originalConstraint); |
| PrecisionRectangle manipulatedConstraint = new PrecisionRectangle( |
| changeBoundsRequest |
| .getTransformedRectangle(originalConstraint)); |
| owner.getFigure().translateToRelative(manipulatedConstraint); |
| // validate constraint (maximum and minimum size are regarded to be |
| // 'normalized', i.e. relative to this figure's bounds coordinates). |
| manipulatedConstraint.setSize(Dimension.max( |
| manipulatedConstraint.getSize(), |
| getMinimumSizeFor(changeBoundsRequest))); |
| manipulatedConstraint.setSize(Dimension.min( |
| manipulatedConstraint.getSize(), |
| getMaximumSizeFor(changeBoundsRequest))); |
| // translate back to absolute |
| owner.getFigure().translateToAbsolute(manipulatedConstraint); |
| Dimension newSizeDelta = manipulatedConstraint.getSize() |
| .getShrinked(originalConstraint.getSize()); |
| changeBoundsRequest.setSizeDelta(newSizeDelta); |
| } |
| } |
| |
| /** |
| * Determines the <em>maximum</em> size that the host can be resized to for |
| * a given request. By default, a default value is returned. The value is |
| * interpreted to be a dimension in the host figure's coordinate system |
| * (i.e. relative to its bounds), so it is not affected by zooming affects. |
| * |
| * @param request |
| * the ChangeBoundsRequest |
| * @return the minimum size |
| * @since 3.7 |
| */ |
| protected Dimension getMaximumSizeFor(ChangeBoundsRequest request) { |
| return IFigure.MAX_DIMENSION; |
| } |
| |
| /** |
| * Determines the <em>minimum</em> size that the specified child can be |
| * resized to.By default, a default value is returned. The value is |
| * interpreted to be a dimension in the host figure's coordinate system |
| * (i.e. relative to its bounds), so it is not affected by zooming effects. |
| * |
| * @param request |
| * the ChangeBoundsRequest |
| * @return the minimum size |
| * @since 3.7 |
| */ |
| protected Dimension getMinimumSizeFor(ChangeBoundsRequest request) { |
| return IFigure.MIN_DIMENSION; |
| } |
| |
| } |