blob: 083622e674257141b506177f8ec176c2a7fb0e04 [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.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.draw2d.rap.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;
}
}