blob: 967a4655aa2c257fabbabd2fd0d29e68f227af37 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2003, 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;
import java.util.Map;
import org.eclipse.draw2d.geometry.PrecisionRectangle;
import org.eclipse.gef.requests.GroupRequest;
import org.eclipse.gef.rulers.RulerProvider;
/**
* A helper used to perform snapping to guides. The guides are obtained from the
* viewer's horizontal and vertical {@link RulerProvider RulerProviders}. If
* snapping is performed, the request's extended data will contain keyed values
* indicating which guides were snapped to, and which side of the part should be
* attached. Generally snapping to a guide should attach the part to that guide,
* but application behavior may differ.
* <P>
* Snapping (and attaching) to a guide is only possible if a single part is
* being dragged. The current implementation will not snap if a request contains
* multiple parts. This may be relaxed in the future to allow snapping, but
* without setting the attachment extended data.
* <P>
* This helper does not keep up with changes in guides. Clients should
* instantiate a new helper each time one is requested and not hold on to
* instances of the helper.
*
* @since 3.0
* @author Randy Hudson
* @author Pratik Shah
*/
public class SnapToGuides extends SnapToHelper {
/**
* The key used to identify the Vertical Guide. This key is used with the
* request's extended data map to store an Integer. The integer value is the
* location of the guide that is being snapped to.
*/
public static final String KEY_VERTICAL_GUIDE = "SnapToGuides.VerticalGuide"; //$NON-NLS-1$
/**
* The key used to identify the Horizontal Guide. This key is used with the
* request's extended data map to store an Integer. The integer value is the
* location of the guide that is being snapped to.
*/
public static final String KEY_HORIZONTAL_GUIDE = "SnapToGuides.HorizontalGuide"; //$NON-NLS-1$
/**
* The key used to identify the vertical anchor point. This key is used with
* the request's extended data map to store an Integer. If the
* VERTICAL_GUIDE has been set, then this integer is a number identifying
* which side of the dragged object is being snapped to that guide.
* <UL>
* <LI><code>-1</code> indicates the left side should be attached.
* <LI><code> 0</code> indicates the center should be attached.
* <LI><code> 1</code> indicates the right side should be attached.
* </UL>
*/
public static final String KEY_VERTICAL_ANCHOR = "SnapToGuides.VerticalAttachment"; //$NON-NLS-1$
/**
* The key used to identify the horizontal anchor point. This key is used
* with the request's extended data map to store an Integer. If the
* HORIZONTAL_GUIDE has been set, then this integer is a number identifying
* which side of the dragged object is being snapped to that guide.
* <UL>
* <LI><code>-1</code> indicates the top side should be attached.
* <LI><code> 0</code> indicates the middle should be attached.
* <LI><code> 1</code> indicates the bottom side should be attached.
* </UL>
*/
public static final String KEY_HORIZONTAL_ANCHOR = "SnapToGuides.HorizontalAttachment";//$NON-NLS-1$
/**
* The threshold for snapping to guides. The rectangle being snapped must be
* within +/- the THRESHOLD. The default value is 5.001;
*/
protected static final double THRESHOLD = 5.001;
private double threshold = THRESHOLD;
/**
* The graphical editpart to which guides are relative. This should also the
* parent of the parts being snapped to guides.
*/
protected GraphicalEditPart container;
/**
* The locations of the vertical guides in the container's coordinates. Use
* {@link #getVerticalGuides()}.
*/
protected int[] verticalGuides;
/**
* The locations of the horizontal guides in the container's coordinates.
* Use {@link #getHorizontalGuides()}.
*/
protected int[] horizontalGuides;
/**
* Constructs a new snap-to-guides helper using the given container as the
* basis.
*
* @param container
* the container editpart
*/
public SnapToGuides(GraphicalEditPart container) {
this.container = container;
}
/**
* Get the sensitivity of the snapping. Corrections greater than this value
* will not occur.
*
* @return the snapping threshold
* @since 3.4
*/
protected double getThreshold() {
return this.threshold;
}
/**
* Set the sensitivity of the snapping.
*
* @see #getThreshold()
* @param newThreshold
* the new snapping threshold
* @since 3.4
*/
protected void setThreshold(double newThreshold) {
this.threshold = newThreshold;
}
/**
* Returns the correction for the given near and far sides of a rectangle or
* {@link #getThreshold()} if no correction was found. The near side
* represents the top or left side of a rectangle being snapped. Similar for
* far. If snapping occurs, the extendedData will have the guide and
* attachment point set.
*
* @param guides
* the location of the guides
* @param near
* the top or left location
* @param far
* the bottom or right location
* @param extendedData
* the map for storing snap details
* @param isVertical
* <code>true</code> if for vertical guides, <code>false</code>
* for horizontal.
* @return the correction amount or getThreshold() if no correction was made
*/
protected double getCorrectionFor(int[] guides, double near, double far,
Map extendedData, boolean isVertical) {
far -= 1.0;
double total = near + far;
// If the width is even, there is no middle pixel so favor the left -
// most pixel.
if ((int) (near - far) % 2 == 0)
total -= 1.0;
double result = getCorrectionFor(guides, total / 2, extendedData,
isVertical, 0);
if (result == getThreshold())
result = getCorrectionFor(guides, near, extendedData, isVertical,
-1);
if (result == getThreshold())
result = getCorrectionFor(guides, far, extendedData, isVertical, 1);
return result;
}
/**
* Returns the correction for the given location or {@link #getThreshold()}
* if no correction was found. If correction occurs, the extendedData will
* have the guide and attachment point set. The attachment point is
* identified by the <code>side</code> parameter.
* <P>
* The correction's magnitude will be less than getThreshold().
*
* @param guides
* the location of the guides
* @param value
* the location being tested
* @param extendedData
* the map for storing snap details
* @param vert
* <code>true</code> if for vertical guides, <code>false</code>
* @param side
* the integer indicating which side is being snapped
* @return a correction amount or getThreshold() if no correction was made
*/
protected double getCorrectionFor(int[] guides, double value,
Map extendedData, boolean vert, int side) {
double resultMag = getThreshold();
double result = getThreshold();
for (int i = 0; i < guides.length; i++) {
int offset = guides[i];
double magnitude;
magnitude = Math.abs(value - offset);
if (magnitude < resultMag) {
extendedData.put(vert ? KEY_VERTICAL_GUIDE
: KEY_HORIZONTAL_GUIDE, new Integer(guides[i]));
extendedData.put(vert ? KEY_VERTICAL_ANCHOR
: KEY_HORIZONTAL_ANCHOR, new Integer(side));
resultMag = magnitude;
result = offset - value;
}
}
return result;
}
/**
* Returns the horizontal guides in the coordinates of the container's
* contents pane.
*
* @return the horizontal guides
*/
protected int[] getHorizontalGuides() {
if (horizontalGuides == null) {
RulerProvider rProvider = ((RulerProvider) container.getViewer()
.getProperty(RulerProvider.PROPERTY_VERTICAL_RULER));
if (rProvider != null)
horizontalGuides = rProvider.getGuidePositions();
else
horizontalGuides = new int[0];
}
return horizontalGuides;
}
/**
* Returns the vertical guides in the coordinates of the container's
* contents pane.
*
* @return the vertical guides
*/
protected int[] getVerticalGuides() {
if (verticalGuides == null) {
RulerProvider rProvider = ((RulerProvider) container.getViewer()
.getProperty(RulerProvider.PROPERTY_HORIZONTAL_RULER));
if (rProvider != null)
verticalGuides = rProvider.getGuidePositions();
else
verticalGuides = new int[0];
}
return verticalGuides;
}
/**
* @see SnapToHelper#snapRectangle(Request, int, PrecisionRectangle,
* PrecisionRectangle)
*/
public int snapRectangle(Request request, int snapOrientation,
PrecisionRectangle baseRect, PrecisionRectangle result) {
if (request instanceof GroupRequest
&& ((GroupRequest) request).getEditParts().size() > 1)
return snapOrientation;
baseRect = baseRect.getPreciseCopy();
makeRelative(container.getContentPane(), baseRect);
PrecisionRectangle correction = new PrecisionRectangle();
makeRelative(container.getContentPane(), correction);
if ((snapOrientation & HORIZONTAL) != 0) {
double xcorrect = getCorrectionFor(getVerticalGuides(),
baseRect.preciseX(), baseRect.preciseRight(),
request.getExtendedData(), true);
if (xcorrect != getThreshold()) {
snapOrientation &= ~HORIZONTAL;
correction.setPreciseX(correction.preciseX() + xcorrect);
}
}
if ((snapOrientation & VERTICAL) != 0) {
double ycorrect = getCorrectionFor(getHorizontalGuides(),
baseRect.preciseY(), baseRect.preciseBottom(),
request.getExtendedData(), false);
if (ycorrect != getThreshold()) {
snapOrientation &= ~VERTICAL;
correction.setPreciseY(correction.preciseY() + ycorrect);
}
}
boolean snapped = false;
if (!snapped && (snapOrientation & WEST) != 0) {
double leftCorrection = getCorrectionFor(getVerticalGuides(),
baseRect.preciseX(), request.getExtendedData(), true, -1);
if (leftCorrection != getThreshold()) {
snapOrientation &= ~WEST;
correction.setPreciseWidth(correction.preciseWidth()
- leftCorrection);
correction.setPreciseX(correction.preciseX() + leftCorrection);
}
}
if (!snapped && (snapOrientation & EAST) != 0) {
double rightCorrection = getCorrectionFor(getVerticalGuides(),
baseRect.preciseRight() - 1, request.getExtendedData(),
true, 1);
if (rightCorrection != getThreshold()) {
snapped = true;
snapOrientation &= ~EAST;
correction.setPreciseWidth(correction.preciseWidth()
+ rightCorrection);
}
}
snapped = false;
if (!snapped && (snapOrientation & NORTH) != 0) {
double topCorrection = getCorrectionFor(getHorizontalGuides(),
baseRect.preciseY(), request.getExtendedData(), false, -1);
if (topCorrection != getThreshold()) {
snapOrientation &= ~NORTH;
correction.setPreciseHeight(correction.preciseHeight()
- topCorrection);
correction.setPreciseY(correction.preciseY() + topCorrection);
}
}
if (!snapped && (snapOrientation & SOUTH) != 0) {
double bottom = getCorrectionFor(getHorizontalGuides(),
baseRect.preciseBottom() - 1, request.getExtendedData(),
false, 1);
if (bottom != getThreshold()) {
snapped = true;
snapOrientation &= ~SOUTH;
correction
.setPreciseHeight(correction.preciseHeight() + bottom);
}
}
makeAbsolute(container.getContentPane(), correction);
result.setPreciseX(result.preciseX() + correction.preciseX());
result.setPreciseY(result.preciseY() + correction.preciseY());
result.setPreciseWidth(result.preciseWidth()
+ correction.preciseWidth());
result.setPreciseHeight(result.preciseHeight()
+ correction.preciseHeight());
return snapOrientation;
}
}