blob: 1fb3ed20261514ff6c7e672ea0c8b1ce09b8a00a [file] [log] [blame]
/*********************************************************************
* Copyright (c) 2005, 2019 SAP SE
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Contributors:
* SAP SE - initial API, implementation and documentation
* mwenz - Bug 352440 - Fixed deprecation warnings - contributed by Felix Velasco
* cbrand - Bug 369371 - ConnectionDecorator visualization cut
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipse.graphiti.ui.internal.figures;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.RotatableDecoration;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PrecisionPoint;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.graphiti.mm.algorithms.GraphicsAlgorithm;
import org.eclipse.graphiti.ui.internal.parts.IPictogramElementDelegate;
import org.eclipse.swt.graphics.Path;
/**
* This class is an abstract super-class for all RotatableDecorations in
* Graphiti. The main idea is, that the outline and fill-area of a
* RotatableDecoration is defined by a Path. Sub-classes usually only have to
* implement the abstract methods
* {@link #createPath(Rectangle, Graphics, boolean)} and
* {@link #getInitialTouchPoint()}.
*
* @noinstantiate This class is not intended to be instantiated by clients.
* @noextend This class is not intended to be subclassed by clients.
*/
public abstract class GFAbstractRotatableDecoration extends GFAbstractShape implements RotatableDecoration {
/**
* The initial bounds as described in {@link #getInitialBounds()}.
*/
private Rectangle initialBounds = null;
/**
* The bounds of this figure. They are calculated depending on the current
* decorator-location and decorator-reference-point.
*/
private Rectangle bounds = new Rectangle();
/**
* The decorator-location as described in {@link #getDecoratorLocation()}.
*/
private Point decoratorLocation = new Point();
/**
* The decorator-reference-point as described in
* {@link #getDecoratorReferencePoint()}.
*/
private Point decoratorReferencePoint = new Point();
/**
* The angle "phi" in degrees as described in {@link #getPhiDegrees()}.
*/
private double phiDegrees = 0;
/**
* The angle "phi" in radians as described in {@link #getPhiRadians()}.
*/
private double phiRadians = 0;
/**
* The distance which the decorator will be moved, so that the
* initialTouchPoint touches the decorator-location after the rotation.
*/
private Point touchPointDelta = new Point();
/**
* Creates a new GFAbstractRotatableDecoration.
*
* @param pictogramElementDelegate
* The PictogramElementDelegate which provides the
* GraphicsAlgorithm.
* @param graphicsAlgorithm
* The GraphicsAlgorithm which provides the values to paint this
* Shape.
*/
public GFAbstractRotatableDecoration(IPictogramElementDelegate pictogramElementDelegate, GraphicsAlgorithm graphicsAlgorithm) {
super(pictogramElementDelegate, graphicsAlgorithm);
// initialize values
initialBounds = new Rectangle(0, 0, graphicsAlgorithm.getWidth(), graphicsAlgorithm.getHeight());
}
// ============================= abstract methods =========================
/**
* Returns the initial touch-point of the decorator-shape. This is the point
* of the decorator-shape inside the initial-bounds (see
* {@link #getInitialBounds()}), which shall touch the decorated figure at
* the decorator-location (see {@link #getDecoratorLocation()}. It is called
* "inital" because it is the point before translation/rotation.
* <p>
* Example: for an Ellipse the point
* <code>(0, getInitialBounds().height / 2)</code> is returned.
*
* @return The initial touch-point of the decorator-shape.
*/
protected abstract Point getInitialTouchPoint();
// ======================= interface RotatableDecoration ==================
/**
* Sets the decorator-location.
*
* @param p
* The decorator-location to set.
* @see #getDecoratorLocation()
*/
@Override
public void setLocation(Point p) {
decoratorLocation.setLocation(p);
// probably not necessary, because afterwards always(?)
// setReferencePoint() is called.
processRotatableDecorationValues();
}
/**
* Sets the decorator-reference-point. Calls
* {@link #processRotatableDecorationValues()} afterwards to initialize the
* rotated rectangle values.
*
* @param ref
* The decorator-reference-point to set.
* @see #getDecoratorReferencePoint()
*/
public void setReferencePoint(Point ref) {
decoratorReferencePoint.setLocation(ref);
processRotatableDecorationValues();
}
/**
* After the decorator-location or decorator-reference-point changed, the
* translated/rotated rectangle has to be re-calculated. This includes
* {@link #getPhiDegrees()}, {@link #getPhiRadians()}, {@link #getBounds()},
* {@link #getTouchPointDelta()}.
*/
protected void processRotatableDecorationValues() {
// Calculate the angle between the x-axis and the line through
// [location, referencePoint].
// Add 180 degrees so that 0 degree is at 3 o'clock, increasing
// clockwise
Point tempRect = Point.SINGLETON.setLocation(getDecoratorReferencePoint());
tempRect.negate().translate(getDecoratorLocation());
phiDegrees = Math.toDegrees((Math.atan2(tempRect.y, tempRect.x))) + 180d;
phiRadians = Math.toRadians(phiDegrees);
// Compute the delta between the rotated touch-point and the location.
double sin = Math.sin(getPhiRadians());
double cos = Math.cos(getPhiRadians());
double rotatedX = getInitialTouchPoint().x * cos - getInitialTouchPoint().y * sin;
double rotatedY = getInitialTouchPoint().x * sin + getInitialTouchPoint().y * cos;
touchPointDelta = new PrecisionPoint(getDecoratorLocation().x - rotatedX, getDecoratorLocation().y - rotatedY);
// Calculate the bounds.
// This is a rough value, which is bigger than the smallest bounds would
// be.
int maxExtension = Math.max(getInitialBounds().width, getInitialBounds().height) + 10;
bounds = new Rectangle(touchPointDelta.x - maxExtension, touchPointDelta.y - maxExtension, 3 * maxExtension,
3 * maxExtension);
}
// ============================ overwritten methods =======================
/**
* Returns the bounds of this decorator shape. This implementation bases on
* a rough calculation, which returns a bigger rectangle than necessary.
*
* @return The bounds of this decorator shape.
*/
@Override
public Rectangle getBounds() {
return bounds;
}
/**
* Fills this Shape on the given Graphics. It enhances the implementation of
* the super-class by adding a rotation and translation according to the
* values set via {@link RotatableDecoration}.
*
* @param graphics
* The Graphics on which to outline this Shape.
*/
@Override
protected void paintShape(Graphics graphics, boolean isFill) {
// initialize Graphics
int oldLineWidth = graphics.getLineWidth();
graphics.setLineWidth(getLineWidth(graphics));
// get Path
double zoom = getZoomLevel(graphics);
int lw = getLineWidth(graphics);
Rectangle pathbounds = GFFigureUtil.getAdjustedRectangle(getInitialBounds(), zoom, lw);
// do not adjust the bounds for filling as is done in the super-class,
// because the rotation creates too much rounding-differences to get
// good results.
Path path = createPath(pathbounds, graphics, isFill);
// adjust Graphics
graphics.pushState();
// translation before rotation to avoid nullpointer
graphics.translate(getTouchPointDelta().x, getTouchPointDelta().y);
graphics.rotate((float) getPhiDegrees());
// outline or fill Path
if (isFill) {
fillPath(graphics, path);
} else {
graphics.drawPath(path);
}
// reset Graphics
path.dispose();
graphics.popState();
graphics.setLineWidth(oldLineWidth);
}
/**
* Returns true, if the point is inside the translated/rotated rectangle.
* Specifics of the decorator Shape are ignored, but can be added by
* overwriting {@link #containsPointInInitialFigure(int, int)}.
*/
@Override
public boolean containsPointInFigure(int x, int y) {
// Idea: translate and rotate the point (x, y) backwards into the intial
// bounds.
// There the real check is done.
PrecisionPoint backwards = rotateTargetToInitial(x, y);
// fast check
if (!getInitialBounds()
.contains((int) Math.round(backwards.preciseX()), (int) Math.round(backwards.preciseY()))) {
return false;
}
// forward to the shape-specific check
return containsPointInInitialFigure(backwards.x, backwards.y);
}
// ============================== helper methods ==========================
/**
* Returns true, if the point is inside the initial figure, meaning the
* figure in the {@link #getInitialBounds()} before translation/rotation.
* <p>
* This implementation just returns true, but sub-classes can overwrite it
* to make decorator-shape specific checks.
*/
public boolean containsPointInInitialFigure(int x, int y) {
return true;
}
/**
* Returns the initial point for the given target point. This means, that it
* translates/rotates the given target point from the coordinates of the
* target-rectangle backwards to the coordinates of the initial bounds.
*
* @param x
* The x-coordinate of the target point to translate/rotate.
* @param y
* The y-coordinate of the target point to translate/rotate.
* @return The initial point for the given target point.
*/
protected PrecisionPoint rotateTargetToInitial(double x, double y) {
double sin = Math.sin(-getPhiRadians());
double cos = Math.cos(-getPhiRadians());
double translatedX = x - getDecoratorLocation().x;
double translatedY = y - getDecoratorLocation().y;
double rotatedX = translatedX * cos - translatedY * sin;
double rotatedY = translatedX * sin + translatedY * cos;
rotatedX += getInitialTouchPoint().x;
rotatedY += getInitialTouchPoint().y;
return new PrecisionPoint(rotatedX, rotatedY);
}
/**
* Sets the initial-bounds. The initial-bounds are a rectangle which starts
* at (0,0) and has the (width, height) of the figure before rotation. The
* initial-bounds are set in the constructor and not changed afterwards.
*
* @param initialBounds
* The initial-bounds to set.
*/
protected final void setInitialBounds(Rectangle initialBounds) {
this.initialBounds = initialBounds;
}
/**
* Returns the initial-bounds. The initial-bounds are a rectangle which
* starts at (0,0) and has the (width, height) of the figure before
* rotation. The initial-bounds are set in the constructor and not changed
* afterwards.
*
* @return The initial-bounds.
*/
protected final Rectangle getInitialBounds() {
return initialBounds;
}
/**
* Returns the decorator-location. The decorator-location is the start-point
* of the connection. This means that at this point the decorator and the
* figure shall touch.
*
* @return The decorator-location.
* @see #setLocation(Point)
*/
protected final Point getDecoratorLocation() {
return decoratorLocation;
}
/**
* Returns the decorator-reference-point. The decorator-reference-point is
* the end-point of the connection. It is used to calculate the angle "phi".
*
* @return The decorator-reference-point.
* @see #setReferencePoint(Point)
* @see #getPhiDegrees()
* @see #getPhiRadians()
*/
protected final Point getDecoratorReferencePoint() {
return decoratorReferencePoint;
}
/**
* Returns the angle "phi" in degrees. This is the angle between the x-axis
* and the line through [decorator-location, decorator-reference-point].
*
* @return The angle "phi" in degrees.
* @see #getPhiRadians()
*/
protected final double getPhiDegrees() {
return phiDegrees;
}
/**
* Returns the angle "phi" in radians. This is the angle between the x-axis
* and the line through [decorator-location, decorator-reference-point].
*
* @return The angle "phi" in radians.
* @see #getPhiDegrees()
*/
protected final double getPhiRadians() {
return phiRadians;
}
/**
* Returns the touch-point delta. This is the distance which the decorator
* has to be moved, so that the initial touchpoint touches the
* decorator-location after the rotation.
*
* @return The touch-point delta.
*/
protected final Point getTouchPointDelta() {
return touchPointDelta;
}
}