blob: 55d04e7a1dc13c903a0a544be43e0b0af64f8f61 [file] [log] [blame]
/*******************************************************************************
* <copyright>
*
* Copyright (c) 2005, 2016 SAP AG.
* 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:
* SAP AG - initial API, implementation and documentation
* mwenz - Bug 352440 - Fixed deprecation warnings - contributed by Felix Velasco
* mwenz - Bug 363796 - Make setting of selection width of connections public
* mwenz - Bug 497669 - IndexOutOfBoundsException below GFPolyline.createPath (thrown in ArrayList.rangeCheck)
*
* </copyright>
*
*******************************************************************************/
/*
* Created on 14.10.2005
*/
package org.eclipse.graphiti.ui.internal.figures;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PointList;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.graphiti.mm.algorithms.GraphicsAlgorithm;
import org.eclipse.graphiti.mm.algorithms.Polyline;
import org.eclipse.graphiti.tb.DefaultToolBehaviorProvider;
import org.eclipse.graphiti.ui.internal.parts.IPictogramElementDelegate;
import org.eclipse.swt.graphics.Path;
/**
* A Graphiti Polyline Figure. Most functionality is handled in the super-class. This
* class only has to define the outline-path and fill-path for the
* figure-specific painting.
*
* @noinstantiate This class is not intended to be instantiated by clients.
* @noextend This class is not intended to be subclassed by clients.
*/
public class GFPolyline extends GFAbstractPointListShape {
/**
* The minimum selection line-width which is set in
* {@link #setMinimumSelectionLineWidth(int)}.
*/
private int minimumSelectionLineWidth = DefaultToolBehaviorProvider.DEFAULT_LINE_SELECTION_WIDTH;
/**
* If filling is suppressed as described in
* {@link #setSuppressFilling(boolean)}.
*/
private boolean suppressFilling = false;
/**
* The distance used to calculate the rounding of the bezier-curve as
* described in {@link #getGeneralBezierDistance()}.
*/
private int generalBezierDistance = 0; // no rounding by default
/**
* The distances used to calculate the rounding of the bezier-curve as
* described in {@link #getSpecificBezierDistances()}.
*/
private int specificBezierDistances[] = null; // no rounding by default
/**
* The adjusted point-list as described in
* {@link #getAdjustedPointListWithoutZoom()}.
*/
private PointList adjustedPointsWithoutZoom = new PointList();
/**
* Creates a new GFPolyline.
*
* @param pictogramElementDelegate
* The PictogramElementDelegate which provides the
* GraphicsAlgorithm.
* @param graphicsAlgorithm
* The GraphicsAlgorithm which provides the values to paint this
* Shape.
*/
public GFPolyline(IPictogramElementDelegate pictogramElementDelegate, GraphicsAlgorithm graphicsAlgorithm) {
super(pictogramElementDelegate, graphicsAlgorithm);
if (graphicsAlgorithm instanceof Polyline) {
// Ask the tool behavior provider for the selection tolerance for
// the line object
minimumSelectionLineWidth = pictogramElementDelegate.getConfigurationProvider().getDiagramTypeProvider()
.getCurrentToolBehaviorProvider().getLineSelectionWidth((Polyline) graphicsAlgorithm);
}
// A Polyline is NEVER filled
setSuppressFilling(true);
}
/**
* Sets the selection tolerance. This is the minimum line-width, which is
* still considered to be a selection of the polyline, independent from the
* real line-width. For a small line-width this makes it easier to select
* the polyline.
*
* @param minimumSelectionLineWidth
* The new minimum selection line-width to set.
*/
public void setMinimumSelectionLineWidth(int minimumSelectionLineWidth) {
this.minimumSelectionLineWidth = minimumSelectionLineWidth;
}
/**
* Set, if the filling should be suppressed under all circumstances (even if
* {@link #setFill(boolean)} was set to true).
*
* @param suppressFilling
* If true, then the filling is suppressed under all
* circumstances.
*/
public void setSuppressFilling(boolean suppressFilling) {
this.suppressFilling = suppressFilling;
}
/**
* Sets the general distance used to calculate the rounding of the
* bezier-curve. This means, that all bezier-distances for this polyline are
* identical. This is different to
* {@link #setSpecificBezierDistances(int[])}. See
* {@link GFFigureUtil#getBezierPath(List, boolean)} for a more details.
*
* @param bezierDistance
* The general distance used to calculate the rounding of the
* bezier-curve.
*/
public void setGeneralBezierDistance(int bezierDistance) {
this.generalBezierDistance = bezierDistance;
}
/**
* Returns the general bezier distance used to calculate the rounding of the
* bezier-curve. See {@link #setGeneralBezierDistance(int)} for more
* details.
*
* @return The general bezier distance used to calculate the rounding of the
* bezier-curve.
*/
protected int getGeneralBezierDistance() {
return generalBezierDistance;
}
/**
* Sets the specific distances used to calculate the rounding of the
* bezier-curve. This means, that for each point of the polyline the
* before-distance and the after distance have to be specified. If this
* attribute is null, then the value of the attribute in
* {@link #setGeneralBezierDistance(int)} will be used. See
* {@link GFFigureUtil#getBezierPath(List, boolean)} for a more details.
*
* @param bezierDistances
* The specific distance used to calculate the rounding of the
* bezier-curve.
*/
public void setSpecificBezierDistances(int[] bezierDistances) {
// TODO This interface should be improved: bezierDistances.length == 2 * points.length is quite difficult to use.
this.specificBezierDistances = bezierDistances;
}
/**
* Returns the specific bezier distance used to calculate the rounding of
* the bezier-curve. See {@link #setSpecificBezierDistances(int[])} for more
* details.
*
* @return The specific bezier distance used to calculate the rounding of
* the bezier-curve.
*/
protected int[] getSpecificBezierDistances() {
// TODO This interface should be improved: bezierDistances.length == 2 * points.length is quite difficult to use.
return specificBezierDistances;
}
/**
* Returns the points of this polyline, which are adjusted regarding their
* line-width but which are not zoomed (see
* {@link #getAdjustedPointList(PointList, double, double)}). This is a
* buffered value, which is used to check, if a point is contained on this
* polyline. It is re-calculated whenever the "real" point-list of this
* polyline changes.
*
* @return Returns the points of this polyline, which are translated
* regarding their line-width.
*/
protected PointList getAdjustedPointListWithoutZoom() {
getBounds(); // this will initialize the tranlatedPoints
return adjustedPointsWithoutZoom;
}
/**
* Returns a new instance of the input point-list, which is adjusted
* regarding the given zoom-factor and line-width. This is the point-list
* which will be outlined on the graphics. So this allows to draw along
* slightly different point-list than the original point-list.
* <p>
* The implementation of this method just forwards to
* {@link GFFigureUtil#getAdjustedPointList(PointList, double, double)}.
*
* @param points
* The point-list which to adjust.
* @param zoom
* The zoom-factor to regard.
* @param lw
* The line-width to regard.
*/
protected PointList getAdjustedPointList(PointList points, double zoom, double lw) {
return GFFigureUtil.getAdjustedPointList(points, zoom, lw);
}
// =================== overwritten functional methods =====================
/**
* Returns the bounding box of the Polyline. This is actually the bounding
* box of the point-list which defines the Polyline. That means, that the
* bounding box changes whenever the point-list changes. Also note, that
* calling {@link #setBounds(Rectangle)} does not change the dimension of
* the bounding box, but it may translate the bounding box (see
* {@link #primTranslate(int, int)}).
*
* @return The bounding box of the Polyline.
*/
@Override
public Rectangle getBounds() {
boolean boundsChanged = false;
if (bounds == null) {
boundsChanged = true;
bounds = getPoints().getBounds().getCopy();
int lw = getLineWidth();
adjustedPointsWithoutZoom = getAdjustedPointList(getPoints(), 1.0, lw);
}
// special handling, if bounds < linewidth (can easily happen for horizontal/vertical lines)
int lw = getLineWidth();
if (bounds.height < lw) {
boundsChanged = true;
bounds.y -= (lw - bounds.height + 1) / 2;
bounds.height = lw + 1;
}
if (bounds.width < lw) {
boundsChanged = true;
bounds.x -= (lw - bounds.width + 1) / 2;
bounds.width = lw + 1;
}
// Usually the following refresh is done in setBounds(), but in this class the bounds are changed in getBounds().
// Mostly it works without this explicit refresh, except that the selection-handles were not moved.
if (boundsChanged) {
invalidate();
fireFigureMoved();
repaint();
}
return bounds;
}
/**
* Sets the line-width of the Polyline. Note that changing the line-width
* might change the bounds of the Polyline. This is the reason, why this
* method is overwritten for the Polyline.
*
* @see #getBounds()
*
* @param lw
* The line-width to set.
*/
@Override
public void setLineWidth(int lw) {
if (getLineWidth() == lw)
return;
if (lw < getLineWidth()) // The bounds will become smaller, so erase
// must
// occur first.
erase();
bounds = null;
super.setLineWidth(lw);
}
/**
* Returns true, if the given point is contained in this Polyline. This is
* the case if
* <ul>
* <li>The point is located on the line, taking into account a certain
* tolerance (see {@link #setSelectionTolerance(int)})</li>
* <li>The point is contained in a child of this Shape</li>
* </ul>
*
* @param x
* The x-coordinate of the point to check.
* @param y
* The y-coordinate of the point to check.
*
* @return true, if the given point is contained in this Polyline.
*/
@Override
public boolean containsPointInFigure(int x, int y) {
int tolerance = Math.max(getLineWidth(), minimumSelectionLineWidth);
// check the translated-points, because the drawing will happen along
// those lines
Boolean inFigure = GFFigureUtil.containsPointInPolyline(getAdjustedPointListWithoutZoom(), x, y, tolerance);
if (inFigure != null) // clear result, no need for further checking
return inFigure.booleanValue();
// check if point inside children (if existing)
@SuppressWarnings("unchecked")
List<IFigure> children = getChildren();
for (int i = 0; i < children.size(); i++) {
if (children.get(i).containsPoint(x, y)) {
return true;
}
}
return false;
}
/**
* Translates the shape by the given x/y dimensions. This will translate all
* points of the Polyline by the given values. Note, that it is necessary to
* overwrite this method, because the super-class implementation only
* changes the bounding box, which has no affect on the coordinates of the
* Polyline.
*
* @param dx
* The amount to translate horizontally
* @param dy
* The amount to translate vertically
*
* @see #getBounds()
*/
@Override
public void primTranslate(int dx, int dy) {
setPoints(GFFigureUtil.getTranslatedPointList(getPoints(), dx, dy));
// copied from the super-class
if (useLocalCoordinates()) {
fireCoordinateSystemChanged();
return;
}
for (int i = 0; i < getChildren().size(); i++)
((IFigure) getChildren().get(i)).translate(dx, dy);
}
// ==================== overwritten drawing methods =======================
/**
* Suppresses the filling if specified in
* {@link #setSuppressFilling(boolean)}. Otherwise it just forwards to the
* super-class.
*/
@Override
protected void fillShape(Graphics graphics) {
if (!suppressFilling) {
super.fillShape(graphics);
}
}
/**
* Returns the Path specifying a polyline for the adjusted point-list (see
* {@link #getAdjustedPointList(PointList, double, double)}) of this Shape.
* Note, that the outer bounds are ignored to calculate the Path, in
* contrast to most other Shapes. It does not differenciate between a fill
* Path and an outline Path.
*
* @param outerBounds
* The outer bounds which shall contain the Path. They are
* calculated from the bounds of this figure by
* {@link GFFigureUtil#getAdjustedRectangle(Rectangle, double, int)}
* . Note, that those outline-bounds are just a suggestion which
* works fine for many cases.
* @param graphics
* The Graphics on which the outline Path shall be painted. It
* can be used to react on Graphics specific values, like the
* zoom-level of the Graphics.
* @param isFill
* if true, the Path is used for filling the Shape, otherwise for
* outlining the Shape.
*
* @return The Path specifying a polyline for the point-list of this Shape.
*/
@Override
protected Path createPath(Rectangle outerBoundss, Graphics graphics, boolean isFill) {
// instead of just zooming the translated-points (see
// getTranslatedPoints()),
// better do the calculation again by first zooming and then translating
// to avoid rounding errors.
double zoom = getZoomLevel(graphics);
double lw = getLineWidth(graphics);
PointList points = getAdjustedPointList(getPoints(), zoom, lw);
List<BezierPoint> bezierPoints = getBezierPoints(points, zoom);
boolean isClosed = false;
if (bezierPoints != null && bezierPoints.size() > 0) {
isClosed = bezierPoints.get(0).equals(bezierPoints.get(bezierPoints.size() - 1));
}
Path path = GFFigureUtil.getBezierPath(bezierPoints, isClosed);
return path;
}
/**
* Returns a new list of bezier-points, which is calculated from the given
* point list.
*
* @param points
* The point list, from which to calculate the bezier-points.
* @param zoom
* The zoom-level used to adjust the bezier distances.
*
* @return a new list of bezier-points, which is calculated from the given
* point list.
*/
protected List<BezierPoint> getBezierPoints(PointList points, double zoom) {
List<BezierPoint> ret = new ArrayList<BezierPoint>(points.size());
if (specificBezierDistances != null) {
for (int i = 0; i < points.size(); i++) {
int bezierDistanceBefore = (int) (getSpecificBezierDistances()[2 * i] * zoom);
int bezierDistanceAfter = (int) (getSpecificBezierDistances()[2 * i + 1] * zoom);
Point point = points.getPoint(i);
ret.add(new BezierPoint(point.x, point.y, bezierDistanceBefore, bezierDistanceAfter));
}
} else {
for (int i = 0; i < points.size(); i++) {
int bezierDistance = (int) (getGeneralBezierDistance() * zoom);
Point point = points.getPoint(i);
ret.add(new BezierPoint(point.x, point.y, bezierDistance, bezierDistance));
}
}
return ret;
}
}