blob: c7dc69b77413ac2549ea32f294a9c1825494e9ae [file] [log] [blame]
/******************************************************************************
* Copyright (c) 2002, 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.gmf.runtime.gef.ui.figures;
import java.util.Hashtable;
import java.util.Iterator;
import org.eclipse.draw2d.ConnectionAnchor;
import org.eclipse.draw2d.Figure;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.geometry.Insets;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PointList;
import org.eclipse.draw2d.geometry.PrecisionPoint;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.handles.HandleBounds;
import org.eclipse.gmf.runtime.draw2d.ui.figures.BaseSlidableAnchor;
import org.eclipse.gmf.runtime.draw2d.ui.figures.IAnchorableFigure;
import org.eclipse.gmf.runtime.draw2d.ui.figures.IPolygonAnchorableFigure;
import org.eclipse.gmf.runtime.draw2d.ui.graphics.ColorRegistry;
import org.eclipse.gmf.runtime.draw2d.ui.internal.figures.TransparentBorder;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Path;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.PlatformUI;
/**
* Base class that most shape figures should extend from to gain default connection anchor behavior.
*
* @author melaasar
*/
public class NodeFigure
extends Figure
implements HandleBounds, IAnchorableFigure, IPolygonAnchorableFigure {
private Hashtable connectionAnchors;
/**
* The width of this shape's outline. (a field from GEF Shape). Must be
* greater than zero.
* TODO: NodeFigure should have extended org.eclipse.draw2d.Shape
*/
private int lineWidth = 1;
/**
* The line style to be used for this shape's outline.
* TODO: NodeFigure should have extended org.eclipse.draw2d.Shape
*/
private int lineStyle = Graphics.LINE_SOLID;
private boolean isUsingGradient = false;
private int gradientColor1 = -1;
private int gradientColor2 = -1;
private int gradientStyle = 0;
/**
* The transparency of this shape in percent.
* Must be in [0, 100] range.
*/
private int transparency = 0;
/**
* <code>String</code> that is the identifier for the default anchor
*/
static public final String szAnchor = ""; //$NON-NLS-1$
/**
* Constructor - sets the default colors for all node figures.
*/
public NodeFigure() {
// empty constructor
}
/*
* (non-Javadoc)
* @see org.eclipse.gef.handles.HandleBounds#getHandleBounds()
*/
public Rectangle getHandleBounds() {
Insets insets = new Insets(0, 0, 0, 0);
if (getBorder() instanceof TransparentBorder) {
insets =
((TransparentBorder) getBorder()).getTransparentInsets(this);
}
// Ignore the insets when placing the handles
return new Rectangle(
getBounds().x + insets.left,
getBounds().y + insets.top,
getBounds().width - (insets.right + insets.left),
getBounds().height - (insets.bottom + insets.top));
}
/* (non-Javadoc)
* @see org.eclipse.gmf.runtime.draw2d.ui.figures.IAnchorableFigure#getConnectionAnchor(java.lang.String)
*/
public ConnectionAnchor getConnectionAnchor(String terminal) {
ConnectionAnchor connectAnchor =
(ConnectionAnchor) getConnectionAnchors().get(terminal);
if (connectAnchor == null) {
if (terminal.equals(szAnchor)) {
// get a new one - this figure doesn't support static anchors
connectAnchor = createDefaultAnchor();
getConnectionAnchors().put(terminal,connectAnchor);
}
else {
connectAnchor = createAnchor(SlidableAnchor.parseTerminalString(terminal));
}
}
return connectAnchor;
}
/* (non-Javadoc)
* @see org.eclipse.gmf.runtime.draw2d.ui.figures.IAnchorableFigure#getConnectionAnchorTerminal(org.eclipse.draw2d.ConnectionAnchor)
*/
public String getConnectionAnchorTerminal(ConnectionAnchor c) {
if (c instanceof SlidableAnchor) {
return ((SlidableAnchor) c).getTerminal();
}
if (getConnectionAnchors().containsValue(c)) {
Iterator iter = getConnectionAnchors().keySet().iterator();
String key;
while (iter.hasNext()) {
key = (String) iter.next();
if (getConnectionAnchors().get(key).equals(c))
return key;
}
}
getConnectionAnchor(szAnchor);
return szAnchor;
}
/* (non-Javadoc)
* @see org.eclipse.gmf.runtime.draw2d.ui.figures.IAnchorableFigure#getSourceConnectionAnchorAt(org.eclipse.draw2d.geometry.Point)
*/
public ConnectionAnchor getSourceConnectionAnchorAt(Point p) {
return createConnectionAnchor(p);
}
/* (non-Javadoc)
* @see org.eclipse.gmf.runtime.draw2d.ui.figures.IAnchorableFigure#getTargetConnectionAnchorAt(org.eclipse.draw2d.geometry.Point)
*/
public ConnectionAnchor getTargetConnectionAnchorAt(Point p) {
return createConnectionAnchor(p);
}
/**
* Returns a new anchor for this node figure.
*
* @param p <code>Point</code> on the figure that gives a hint which anchor to return.
* @return <code>ConnectionAnchor</code> reference to an anchor associated with the
* given point on the figure.
*/
protected ConnectionAnchor createConnectionAnchor(Point p) {
if (p == null) {
return getConnectionAnchor(szAnchor);
}
else {
Point temp = p.getCopy();
translateToRelative(temp);
PrecisionPoint pt = BaseSlidableAnchor.getAnchorRelativeLocation(temp, getBounds());
if (isDefaultAnchorArea(pt))
return getConnectionAnchor(szAnchor);
return createAnchor(pt);
}
}
/**
* Checks whether the <PrecisionPoint> p which is a candidate for a relative reference
* for the <Code>SlidableAnchor</Code> belongs to the area where the default anchor
* must be created
*
* @param p
* @return <code>boolean</code> <code>true</code> if <PrecisionPoint> belongs to the area where the default anchor must be
* created, <code>false</code> otherwise
*/
protected boolean isDefaultAnchorArea(PrecisionPoint p) {
return p.preciseX >= getSlidableAnchorArea()/2 && p.preciseX <= 1 - getSlidableAnchorArea()/2 &&
p.preciseY >= getSlidableAnchorArea()/2 && p.preciseY <= 1 - getSlidableAnchorArea()/2;
}
/*
* (non-Javadoc)
* @see org.eclipse.draw2d.Figure#paintFigure(org.eclipse.draw2d.Graphics)
*/
protected void paintFigure(Graphics graphics) {
if (isOpaque() && getBorder() != null) {
Rectangle tempRect = new Rectangle(getBounds());
tempRect.crop(getBorder().getInsets(this));
graphics.fillRectangle(tempRect);
return;
}
super.paintFigure(graphics);
}
/**
* Returns the connectionAnchors.
* @return Hashtable
*/
protected Hashtable getConnectionAnchors() {
if (connectionAnchors == null)
connectionAnchors = new Hashtable(1);
return connectionAnchors;
}
/**
* Specifies how large the area of the figure's bounds where <Code>SlidableAnchor</Code>
* will be created. The result number: 0<=result<=1
*
* @return the size of the area of the figure's bounds
*/
protected double getSlidableAnchorArea() {
return 0.5;
}
/**
* Creates the default Slidable anchor with a reference point at the center
* of the figure's bounds
*
* @return - default SlidableAnchor, relative reference the center of the figure
*/
protected ConnectionAnchor createDefaultAnchor() {
return new SlidableAnchor(this);
}
/**
* Creates a slidable anchor at the specified point (from the ratio of the
* reference's coordinates and bounds of the figure
*
* @param p - relative reference for the <Code>SlidableAnchor</Code>
* @return a <code>SlidableAnchor</code> for this figure with relative reference at p
*/
protected ConnectionAnchor createAnchor(PrecisionPoint p) {
if (p==null)
// If the old terminal for the connection anchor cannot be resolved (by SlidableAnchor) a null
// PrecisionPoint will passed in - this is handled here
return createDefaultAnchor();
return new SlidableAnchor(this, p);
}
/**
* @return <code>String</code> that is the identifier for the default anchor
*/
public static String getDefaultAnchorID() {
return szAnchor;
}
/* (non-Javadoc)
* @see org.eclipse.gmf.runtime.draw2d.ui.figures.IPolygonAnchorableFigure#getPolygonPoints()
*/
public PointList getPolygonPoints() {
PointList points = new PointList(5);
Rectangle anchorableRectangle = getHandleBounds();
points.addPoint(anchorableRectangle.x, anchorableRectangle.y);
points.addPoint(anchorableRectangle.x + anchorableRectangle.width,
anchorableRectangle.y);
points.addPoint(anchorableRectangle.x + anchorableRectangle.width,
anchorableRectangle.y + anchorableRectangle.height);
points.addPoint(anchorableRectangle.x, anchorableRectangle.y
+ anchorableRectangle.height);
points.addPoint(anchorableRectangle.x, anchorableRectangle.y);
return points;
}
/**
* Returns the line style used to outline this shape.
* @return the line style
* @since 2.1
*/
public int getLineStyle() {
return lineStyle;
}
/**
* Returns the line width of this shape's outline.
* @return the line width
* @since 2.1
*/
public int getLineWidth() {
return lineWidth;
}
/**
* Sets the line width to be used to outline the shape.
*
* @param w the new width
* @since 2.1
*/
public void setLineWidth(int w) {
if ((lineWidth == w) || (w < 0))
return;
lineWidth = w;
repaint();
}
/**
* Sets the style of line to be used by this shape.
*
* @param s the new line style
* @since 2.1
*/
public void setLineStyle(int s) {
if (lineStyle == s)
return;
lineStyle = s;
repaint();
}
/**
* @return the gradientColor1
* @since 1.2
*/
public int getGradientColor1() {
return gradientColor1;
}
/**
* @return the gradientColor2
* @since 1.2
*/
public int getGradientColor2() {
return gradientColor2;
}
/**
* @return the gradientStyle
* @since 1.2
*/
public int getGradientStyle() {
return gradientStyle;
}
/**
* Sets values defining gradient data.
*
* @param gradientColor1
* @param gradientColor2
* @param gradientStyle
* @since 1.2
*/
public void setGradientData(int gradientColor1, int gradientColor2, int gradientStyle) {
boolean doRepaint = false;
// When in high contrast mode, there is no gradient, so set bg color to null to allow inheriting
// the system color.
Display display = Display.getCurrent();
if (display == null && PlatformUI.isWorkbenchRunning()) {
display = PlatformUI.getWorkbench().getDisplay();
}
if (display != null && display.getHighContrast()) {
setBackgroundColor(null);
return;
}
if (gradientColor1 != this.gradientColor1 && gradientColor1 > -1) {
this.gradientColor1 = gradientColor1;
doRepaint = true;
}
if (gradientColor2 != this.gradientColor2 && gradientColor2 > -1) {
this.gradientColor2 = gradientColor2;
doRepaint = true;
}
if (gradientStyle != this.gradientStyle) {
this.gradientStyle = gradientStyle;
doRepaint = true;
}
if (doRepaint) {
repaint();
}
}
/**
* Utility method that indicates if gradient should be used as a fill style or not.
*
* @return true if gradient should be used, false otherwise (fill color should be used)
* @since 1.2
*/
public boolean isUsingGradient() {
// When in high contrast mode, there is no gradient, so return false.
Display display = Display.getCurrent();
if (display == null && PlatformUI.isWorkbenchRunning()) {
display = PlatformUI.getWorkbench().getDisplay();
}
if (display != null && display.getHighContrast()) {
return false;
}
return isUsingGradient && gradientColor1 > -1 && gradientColor2 > -1;
}
/**
* Sets the value of isUsingGradient
*
* @param b value for isUsingGradient
* @since 1.2
*/
public void setIsUsingGradient(boolean b) {
if (b != isUsingGradient) {
isUsingGradient = b;
// this is needed, e.g. when undoing gradient clearing from Advanced tab;
// in cases when repaint() is already called, it doesn't matter since
// the actual paint happens only once
repaint();
}
}
/**
* Fills given path by gradient using given fillMode
*
* @param g The Graphics used to paint
* @param path Path of shape to be filled with gradient
* @param fillMode One of SWT.FILL_EVEN_ODD and SWT.FILL_WINDING
* @since 1.2
*/
protected void fillGradient(Graphics g, Path path, int fillMode) {
if (path != null) {
g.pushState();
g.setForegroundColor(ColorRegistry.getInstance().getColor(Integer.valueOf(getGradientColor1())));
g.setBackgroundColor(ColorRegistry.getInstance().getColor(Integer.valueOf(getGradientColor2())));
if (fillMode == SWT.FILL_EVEN_ODD || fillMode == SWT.FILL_WINDING) {
g.setFillRule(fillMode);
}
g.clipPath(path);
g.fillGradient(getBounds(), getGradientStyle() == 0);
path.dispose();
g.popState();
}
}
/**
* Fills gradient using default mode SWT.FILL_EVEN_ODD and getPath() to
* obtain path to fill.
*
* @param g The Graphics used to paint
* @since 1.2
*/
protected void fillGradient(Graphics g) {
// use the default mode if one is not provided
fillGradient(g, getPath(), SWT.FILL_EVEN_ODD);
}
/**
* Fills gradient using default mode SWT.FILL_EVEN_ODD. Use this method when
* getPath() doesn't return desired path.
*
* @param g The Graphics used to paint
* @param path Path of shape to be filled with gradient
* @since 1.2
*/
protected void fillGradient(Graphics g, Path path) {
// use the default mode if one is not provided
fillGradient(g, path, SWT.FILL_EVEN_ODD);
}
/**
* Fills gradient using getPath() to obtain path to fill. Use this method
* when default fill mode SWT.FILL_EVEN_ODD is not appropriate.
*
* @param g The Graphics used to paint
* @param fillMode One of SWT.FILL_EVEN_ODD and SWT.FILL_WINDING
* @since 1.2
*/
protected void fillGradient(Graphics g, int fillMode) {
fillGradient(g, getPath(), fillMode);
}
/**
* This method creates and returns figure's path. Default implementation defines path
* based on figure's bounds and insets. Subclasses should override if
* needed.
*
* @return Created path
* @since 1.2
*/
protected Path getPath() {
if (!isOpaque()) {
return null;
}
Path path = new Path(null);
Rectangle tempRect = getClientArea();
path.addRectangle(tempRect.x, tempRect.y, tempRect.width, tempRect.height);
return path;
}
/**
* Returns transparency value (belongs to [0, 100] interval)
*
* @return transparency
* @since 1.2
*/
public int getTransparency() {
return transparency;
}
/**
* Sets the transparency if the given parameter is in [0, 100] range
*
* @param transparency The transparency to set
* @since 1.2
*/
public void setTransparency(int transparency) {
if (transparency != this.transparency &&
transparency >= 0 && transparency <= 100) {
this.transparency = transparency;
repaint();
}
}
/**
* Converts transparency value from percent range [0, 100] to alpha range
* [0, 255] and applies converted value. 0% corresponds to alpha 255 and
* 100% corresponds to alpha 0.
*
* @param g The Graphics used to paint
* @since 1.2
*/
protected void applyTransparency(Graphics g) {
g.setAlpha(255 - transparency * 255 / 100);
}
}