| /******************************************************************************* |
| * 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; |
| } |
| |
| } |