/**
* Copyright (c) 2007 Borland Software Corp.
*
* 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:
* bblajer - initial API and implementation
*/
package org.eclipse.gmf.runtime.lite.figures;
import java.util.Collection;
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.Rectangle;
import org.eclipse.gef.handles.HandleBounds;
import org.eclipse.gmf.runtime.lite.edit.policies.SideAffixedLayoutEditPolicy;
/**
* Defines position for side-affixed elements. Used both for already created elements (by {@link BorderItemLocator}
* and for feedback positioning while creating the element (by {@link SideAffixedLayoutEditPolicy}).
* @author bblajer
*/
public abstract class SideAffixedElementPositioner {
private static final Dimension BORDER_ITEM_OFFSET_DEFAULT = new Dimension(1, 1);
private static final Dimension GAP_DEFAULT = new Dimension(8, 8);
private Dimension borderItemOffset = BORDER_ITEM_OFFSET_DEFAULT;
private Dimension myGap = GAP_DEFAULT;
/**
* @return Returns the borderItemOffset.
*/
public Dimension getBorderItemOffset() {
return borderItemOffset;
}
/**
* @param borderItemOffset
* The borderItemOffset to set.
*/
public void setBorderItemOffset(Dimension borderItemOffset) {
this.borderItemOffset = borderItemOffset;
}
/**
* Utility to calculate the parent bounds with consideration for the handle
* bounds inset.
*
* @return <code>Rectangle</code> that is the bounds of the parent.
*/
protected Rectangle getParentBorder() {
if (getHostFigure() instanceof HandleBounds) {
return ((HandleBounds) getHostFigure()).getHandleBounds().getCopy();
}
return getHostFigure().getBounds().getCopy();
}
/**
* Ensure the suggested location actually lies on the parent boundary. The
* side takes precedence.
*
* @param suggestedLocation
* @param suggestedSide
* @return point
*/
private Rectangle locateOnParent(Rectangle suggestedLocation, int suggestedSide) {
Rectangle bounds = getParentBorder();
Dimension borderItemOffset = getBorderItemOffset();
int parentFigureWidth = bounds.width;
int parentFigureHeight = bounds.height;
int parentFigureX = bounds.x;
int parentFigureY = bounds.y;
int westX = parentFigureX - suggestedLocation.width + borderItemOffset.width;
int eastX = parentFigureX + parentFigureWidth - borderItemOffset.width;
int southY = parentFigureY + parentFigureHeight - borderItemOffset.height;
int northY = parentFigureY - suggestedLocation.height + borderItemOffset.height;
int newX;
int newY;
if (suggestedSide == PositionConstants.WEST) {
newX = westX;
newY = constrainValue(northY + suggestedLocation.height, southY - suggestedLocation.height, suggestedLocation.y);
} else if (suggestedSide == PositionConstants.EAST) {
newX = eastX;
newY = constrainValue(northY + suggestedLocation.height, southY - suggestedLocation.height, suggestedLocation.y);
} else if (suggestedSide == PositionConstants.SOUTH) {
newY = southY;
newX = constrainValue(westX + suggestedLocation.width, eastX - suggestedLocation.width, suggestedLocation.x);
} else { // NORTH
newY = northY;
newX = constrainValue(westX + suggestedLocation.width, eastX - suggestedLocation.width, suggestedLocation.x);
}
return new Rectangle(newX, newY, suggestedLocation.width, suggestedLocation.height);
}
private static int constrainValue(int min, int max, int defaultValue) {
if (defaultValue < min) {
return min;
}
if (defaultValue > max) {
return max;
}
return defaultValue;
}
/**
* The preferred side takes precedence.
*
* @param suggestedLocation
* @param suggestedSide
* @param circuitCount recursion count to avoid an infinite loop
* @return point
*/
protected final Rectangle locateOnBorder(Rectangle suggestedLocation, int suggestedSide, int circuitCount) {
Rectangle recommendedLocation = locateOnParent(suggestedLocation, suggestedSide);
Dimension gap = getGap();
int vertical_gap = gap.height;
int horizontal_gap = gap.width;
if (circuitCount < 4 && conflicts(recommendedLocation)) {
if (suggestedSide == PositionConstants.WEST) {
do {
recommendedLocation.y += recommendedLocation.height + vertical_gap;
} while (conflicts(recommendedLocation));
if (recommendedLocation.y > getParentBorder().getBottomLeft().y - recommendedLocation.height) { // off the bottom,
// wrap south
return locateOnBorder(recommendedLocation, PositionConstants.SOUTH, circuitCount + 1);
}
} else if (suggestedSide == PositionConstants.SOUTH) {
do {
recommendedLocation.x += recommendedLocation.width + horizontal_gap;
} while (conflicts(recommendedLocation));
if (recommendedLocation.x > getParentBorder().getBottomRight().x - recommendedLocation.width) {
return locateOnBorder(recommendedLocation, PositionConstants.EAST, circuitCount + 1);
}
} else if (suggestedSide == PositionConstants.EAST) {
// move up the east side
do {
recommendedLocation.y -= (recommendedLocation.height + vertical_gap);
} while (conflicts(recommendedLocation));
if (recommendedLocation.y < getParentBorder().getTopRight().y) {
// east is full, try north.
return locateOnBorder(recommendedLocation, PositionConstants.NORTH, circuitCount + 1);
}
} else { // NORTH
do {
recommendedLocation.x -= (recommendedLocation.width + horizontal_gap);
} while (conflicts(recommendedLocation));
if (recommendedLocation.x < getParentBorder().getTopLeft().x) {
return locateOnBorder(recommendedLocation, PositionConstants.WEST, circuitCount + 1);
}
}
}
return recommendedLocation;
}
/**
* Determine if the the given rectangle conflicts with the position of an
* existing borderItemFigure.
*
* @param recommendedLocation
* @return <code>true</code> or <code>false</code>
*/
protected boolean conflicts(Rectangle recommendedLocation) {
for (IFigure borderItem : getSiblings()) {
if (borderItem.isVisible()) {
if (borderItem.getBounds().intersects(recommendedLocation)) {
return true;
}
}
}
return false;
}
protected abstract Collection<? extends IFigure> getSiblings();
/**
* Returns the gap to introduce if a side-affixed element conflicts to its sibling.
*/
protected Dimension getGap() {
return myGap;
}
/**
* Sets the gap to introduce if a side-affixed element conflicts to its sibling.
* @param gap
*/
protected void setGap(Dimension gap) {
myGap = gap;
}
/**
* Returns the valid location that is as close as possible to the given location. The given argument will not be modified.
* @param proposedLocation proposed location.
*/
public Rectangle getValidLocation(Rectangle proposedLocation) {
int side = findClosestSideOfParent(proposedLocation, getParentBorder());
Rectangle realLocation = new Rectangle(proposedLocation.getTopLeft(), getBorderItemSize());
return locateOnBorder(realLocation, side, 0);
}
/**
* Find the closest side when x,y is inside parent.
*
* @param proposedLocation
* @param parentBorder
* @return draw constant
*/
public static int findClosestSideOfParent(Rectangle proposedLocation, Rectangle parentBorder) {
// Rectangle parentBorder = getParentBorder();
Point parentCenter = parentBorder.getCenter();
Point childCenter = proposedLocation.getCenter();
if (childCenter.x < parentCenter.x) // West, North or South.
{
if (childCenter.y < parentCenter.y) // west or north
{
// closer to west or north?
Point parentTopLeft = parentBorder.getTopLeft();
if ((childCenter.x - parentTopLeft.x) <= (childCenter.y - parentTopLeft.y)) {
return PositionConstants.WEST;
} else {
return PositionConstants.NORTH;
}
} else { // west or south
Point parentBottomLeft = parentBorder.getBottomLeft();
if ((childCenter.x - parentBottomLeft.x) <= (parentBottomLeft.y - childCenter.y)) {
return PositionConstants.WEST;
} else {
return PositionConstants.SOUTH;
}
}
} else { // EAST, NORTH or SOUTH
if (childCenter.y < parentCenter.y) // north or east
{
Point parentTopRight = parentBorder.getTopRight();
if ((parentTopRight.x - childCenter.x) <= (childCenter.y - parentTopRight.y)) {
return PositionConstants.EAST;
} else {
return PositionConstants.NORTH;
}
} else { // south or east.
Point parentBottomRight = parentBorder.getBottomRight();
if ((parentBottomRight.x - childCenter.x) <= (parentBottomRight.y - childCenter.y)) {
return PositionConstants.EAST;
} else {
return PositionConstants.SOUTH;
}
}
}
}
protected abstract IFigure getHostFigure();
protected abstract Dimension getBorderItemSize();
}