blob: 20d3d2436385cf27d75e4889260c034427ef3e51 [file] [log] [blame]
/******************************************************************************
* Copyright (c) 2003, 2007 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.gmf.runtime.diagram.ui.handles;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.Locator;
import org.eclipse.draw2d.PositionConstants;
import org.eclipse.draw2d.Viewport;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.handles.HandleBounds;
/**
* This locator is used to locate all the handles around the shape the closest
* distance to the mouse location. It spaces the handles a certain distance
* apart. Call setEastWestSouth(true) to indicate that the connection handle can
* only be located on the east, west, or south side of the reference figure.
*
* @author cmahoney
*/
public class ConnectionHandleLocator
implements Locator {
/** number of pixels between connection handles */
private static int HANDLE_SPACING = 20;
/** the MARGIN to leave by the edge of the parent figure */
private static int MARGIN = 2;
/**
* the margin outside the shape in which the handles will appear to
* determine if the handles will be outside the viewport
*/
private static Dimension HANDLE_MARGIN = new Dimension(25, 25);
/** list of connection handles for the shape */
private List<ConnectionHandle> handles = new ArrayList<ConnectionHandle>();
/** a point in the shape used when locating the handles */
private Point cursorPosition = null;
/** the parent figure */
private IFigure reference = null;
/** the side on which the handles will appear, value from PositionConstants */
private int side = PositionConstants.WEST;
/** the point on the border where the handles should appear */
private Point borderPoint = new Point(0, 0);
/**
* flag to indicate if the connection handle should only be on the east,
* west, or south side
*/
private boolean bEastWestSouth = false;
/**
* Constructor for ConnectionHandleLocator.
*
* @param reference
* the figure
* @param cursorPosition
* the handles will be located on the border of the shape as
* close to this position as possible
*/
public ConnectionHandleLocator(IFigure reference, Point cursorPosition) {
this.reference = reference;
this.cursorPosition = cursorPosition;
}
/**
* Adds a connection handle to the list of handles.
*
* @param handle
* the <code>ConnectionHandle</code> to add
*/
public void addHandle(ConnectionHandle handle) {
handles.add(handle);
}
/**
* Return the client area of the viewport. If a viewport is not found, a
* default rectangle is returned.
*
* @return the client area of the viewport
*/
private Rectangle getViewportArea() {
IFigure fig = getReference();
while (fig != null) {
if (fig instanceof Viewport) {
return fig.getClientArea();
}
fig = fig.getParent();
}
return new Rectangle(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE);
}
/**
* Resets the border point and side variables where the connection handles
* will appear, based on the cursor location.
*/
private void resetBorderPointAndSide() {
Rectangle bounds = getReferenceFigureBounds();
// Get the point on the edge of the reference figure nearest to the
// cursor position.
int westDiff = cursorPosition.x - bounds.x;
int eastDiff = bounds.getRight().x - cursorPosition.x;
int southDiff = bounds.getBottom().y - cursorPosition.y;
int northDiff = isEastWestSouth() ? Integer.MAX_VALUE
: cursorPosition.y - bounds.y;
// avoid having the handles appear outside the viewport
Rectangle viewportRect = getViewportArea();
Rectangle absBounds = bounds.getCopy();
getReference().translateToAbsolute(absBounds);
absBounds.translate(viewportRect.getLocation());
Dimension handleMargin = getHandleMargin();
if (absBounds.right() + handleMargin.width > viewportRect.right()) {
// don't use east side
eastDiff = Integer.MAX_VALUE;
} else if (absBounds.x - handleMargin.width < viewportRect.x) {
// don't use west side
westDiff = Integer.MAX_VALUE;
}
if (absBounds.bottom() + handleMargin.height > viewportRect.bottom()) {
// don't use south side
southDiff = Integer.MAX_VALUE;
} else if (absBounds.y - handleMargin.height < viewportRect.y) {
// don't use north side
northDiff = Integer.MAX_VALUE;
}
double minLR = Math.min(eastDiff, westDiff);
double minUD = Math.min(southDiff, northDiff);
// which directions is closest North/South or East/West?
boolean bUp = (minUD < minLR);
if (bUp) {
if (northDiff < southDiff) {
// re-evaluate if the north is closer than the east since the
// handles will be placed in the north-west corner because of
// the action bar.
if (eastDiff < westDiff
&& eastDiff < cursorPosition.getDistance(bounds
.getTopLeft())) {
borderPoint.setLocation(bounds.x + bounds.width,
cursorPosition.y);
side = PositionConstants.EAST;
} else {
borderPoint.setLocation(bounds.x, bounds.y);
side = PositionConstants.NORTH;
}
} else {
borderPoint.setLocation(cursorPosition.x, bounds.y
+ bounds.height);
side = PositionConstants.SOUTH;
}
} else {
if (westDiff < eastDiff) {
borderPoint.setLocation(bounds.x, cursorPosition.y);
side = PositionConstants.WEST;
} else {
//
borderPoint.setLocation(bounds.x + bounds.width,
cursorPosition.y);
side = PositionConstants.EAST;
}
}
}
/**
* Gets the side (West, South, or East) on which the handles will appear,
* using the cursor position passed in to the constructor.
*
* @return the side, a value in PositionContstants
*/
public int getBorderSide() {
resetBorderPointAndSide();
return side;
}
/**
* Locates the figure on the appropriate edge of the parent depending on
* which side is closest to the reference point. Then adjusts the figure
* depending on the number of handles that this locator has so that the
* handles are spaced out from the center along the edge of the shape.
*
* @see org.eclipse.draw2d.Locator#relocate(org.eclipse.draw2d.IFigure)
*/
public void relocate(IFigure target) {
Rectangle bounds = getReferenceFigureBounds();
resetBorderPointAndSide();
Point borderPointTranslated = borderPoint.getCopy();
reference.translateToAbsolute(bounds);
target.translateToRelative(bounds);
reference.translateToAbsolute(borderPointTranslated);
target.translateToRelative(borderPointTranslated);
int width = target.getBounds().width;
int halfWidth = width / 2;
int height = target.getBounds().height;
int halfHeight = height / 2;
// adjust border point if the handles will extend past the bounds of the
// shape
if (side == PositionConstants.WEST || side == PositionConstants.EAST) {
int halfTotalHeight = (height * handles.size() + HANDLE_SPACING
* (handles.size() - 1)) / 2;
if (borderPointTranslated.y - halfTotalHeight < bounds.getTop().y) {
borderPointTranslated.y = bounds.getTop().y + halfTotalHeight;
} else if (borderPointTranslated.y + halfTotalHeight > bounds
.getBottom().y) {
borderPointTranslated.y = bounds.getBottom().y
- halfTotalHeight;
}
} else {
int halfTotalWidth = (width * handles.size() + HANDLE_SPACING
* (handles.size() - 1)) / 2;
if (borderPointTranslated.x - halfTotalWidth < bounds.getLeft().x) {
borderPointTranslated.x = bounds.getLeft().x + halfTotalWidth;
} else if (borderPointTranslated.x + halfTotalWidth > bounds
.getRight().x) {
borderPointTranslated.x = bounds.getRight().x - halfTotalWidth;
}
}
// set location based on side
if (side == PositionConstants.WEST) {
target.setLocation(borderPointTranslated
.getTranslated(new Dimension(-(width + MARGIN), -halfHeight)));
} else if (side == PositionConstants.EAST) {
target.setLocation(borderPointTranslated
.getTranslated(new Dimension(MARGIN, -halfHeight)));
} else if (side == PositionConstants.SOUTH) {
target.setLocation(borderPointTranslated
.getTranslated(new Dimension(-halfWidth, MARGIN)));
} else if (side == PositionConstants.NORTH) {
target.setLocation(borderPointTranslated
.getTranslated(new Dimension(MARGIN, -height - MARGIN)));
}
// space out handles
int index = handles.indexOf(target);
double centerOffset = index - (handles.size() / 2.0) + 0.5;
if (side == PositionConstants.WEST || side == PositionConstants.EAST) {
target.translate(0, (int) (centerOffset * HANDLE_SPACING));
} else {
target.translate((int) (centerOffset * HANDLE_SPACING), 0);
}
}
/**
* Gets the bounding rectangle of the reference figure.
*
* @return the bounds
*/
private Rectangle getReferenceFigureBounds() {
Rectangle bounds = reference instanceof HandleBounds ? ((HandleBounds) reference)
.getHandleBounds().getCopy()
: reference.getBounds().getCopy();
return bounds;
}
/**
* Gets the reference figure.
*
* @return Returns the reference.
*/
protected IFigure getReference() {
return reference;
}
/**
* Gets the cursor position.
*
* @return Returns the cursorPosition.
*/
protected Point getCursorPosition() {
return cursorPosition;
}
/**
* Can the connection handle can only be located on the east, west, or south
* side of the reference figure.
*
* @return Returns true if the connection handle can only be located on the
* east, west, or south side of the reference figure; false
* otherwise.
*/
public boolean isEastWestSouth() {
return bEastWestSouth;
}
/**
* Set to true if the connection handle can only be located on the east,
* west, or south side of the reference figure.
*
* @param eastWestSouth
* The bEastWestSouth to set.
*/
public void setEastWestSouth(boolean eastWestSouth) {
bEastWestSouth = eastWestSouth;
}
/**
* Returns the margin outside the shape in which the handles will appear to
* determine if the handles will be outside the viewport.
*
* @return the margin
*/
protected Dimension getHandleMargin() {
return HANDLE_MARGIN;
}
}