/******************************************************************************* * 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.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.preciseX += xcorrect; } } if ((snapOrientation & VERTICAL) != 0) { double ycorrect = getCorrectionFor(getHorizontalGuides(), baseRect.preciseY, baseRect.preciseBottom(), request.getExtendedData(), false); if (ycorrect != getThreshold()) { snapOrientation &= ~VERTICAL; 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.preciseWidth -= leftCorrection; 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.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.preciseHeight -= topCorrection; 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.preciseHeight += bottom; } } correction.updateInts(); makeAbsolute(container.getContentPane(), correction); result.preciseX += correction.preciseX; result.preciseY += correction.preciseY; result.preciseWidth += correction.preciseWidth; result.preciseHeight += correction.preciseHeight; result.updateInts(); return snapOrientation; } }