blob: b8def67d9f43654d08a892e64ab08d2762833c18 [file] [log] [blame]
/*******************************************************************************
* Copyright 2005, CHISEL Group, University of Victoria, Victoria, BC, Canada.
* 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: The Chisel Group, University of Victoria
******************************************************************************/
package org.eclipse.zest.core.widgets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.draw2d.ColorConstants;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.Label;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Insets;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PrecisionPoint;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;
import org.eclipse.zest.core.widgets.internal.GraphLabel;
import org.eclipse.zest.layouts.LayoutEntity;
import org.eclipse.zest.layouts.constraints.LayoutConstraint;
/*
* Simple node class which has the following properties: color, size, location,
* and a label. It also has a list of connections and anchors.
*
* @author Chris Callendar
*
* @author Del Myers
*
* @author Ian Bull
*/
public class GraphNode extends GraphItem {
public static final int HIGHLIGHT_NONE = 0;
public static final int HIGHLIGHT_ON = 1;
// @tag ADJACENT : Removed highlight adjacent
//public static final int HIGHLIGHT_ADJACENT = 2;
private int nodeStyle;
private List /* IGraphModelConnection */sourceConnections;
private List /* IGraphModelConnection */targetConnections;
private Color foreColor;
private Color backColor;
private Color highlightColor;
// @tag ADJACENT : Removed highlight adjacent
//private Color highlightAdjacentColor;
private Color borderColor;
private Color borderHighlightColor;
private int borderWidth;
private Point currentLocation;
protected Dimension size;
private Font font;
private boolean cacheLabel;
private boolean visible = true;
private LayoutEntity layoutEntity;
protected Graph graph;
protected IContainer parent;
/** The internal node. */
protected Object internalNode;
private boolean selected;
protected int highlighted = HIGHLIGHT_NONE;
private IFigure tooltip;
protected IFigure nodeFigure;
private boolean isDisposed = false;
private boolean hasCustomTooltip;
public GraphNode(IContainer graphModel, int style) {
this(graphModel, style, null);
}
public GraphNode(IContainer graphModel, int style, Object data) {
this(graphModel.getGraph(), style, "" /*text*/, null /*image*/, data);
}
public GraphNode(IContainer graphModel, int style, String text) {
this(graphModel, style, text, null);
}
public GraphNode(IContainer graphModel, int style, String text, Object data) {
this(graphModel.getGraph(), style, text, null /*image*/, data);
}
public GraphNode(IContainer graphModel, int style, String text, Image image) {
this(graphModel, style, text, image, null);
}
public GraphNode(IContainer graphModel, int style, String text, Image image, Object data) {
super(graphModel.getGraph(), style, data);
initModel(graphModel, text, image);
if (nodeFigure == null) {
initFigure();
}
// This is a hack because JAVA sucks!
// I don't want to expose addNode so I can't put it in the
// IContainer interface.
if (this.parent.getItemType() == GRAPH) {
((Graph) this.parent).addNode(this);
} else if (this.parent.getItemType() == CONTAINER) {
((GraphContainer) this.parent).addNode(this);
}
this.parent.getGraph().registerItem(this);
}
protected void initFigure() {
nodeFigure = createFigureForModel();
}
static int count = 0;
protected void initModel(IContainer parent, String text, Image image) {
this.nodeStyle |= parent.getGraph().getNodeStyle();
this.parent = parent;
this.sourceConnections = new ArrayList();
this.targetConnections = new ArrayList();
this.foreColor = parent.getGraph().DARK_BLUE;
this.backColor = parent.getGraph().LIGHT_BLUE;
this.highlightColor = parent.getGraph().HIGHLIGHT_COLOR;
// @tag ADJACENT : Removed highlight adjacent
//this.highlightAdjacentColor = ColorConstants.orange();
this.nodeStyle = SWT.NONE;
this.borderColor = ColorConstants.lightGray();
this.borderHighlightColor = ColorConstants.blue();
this.borderWidth = 1;
this.currentLocation = new PrecisionPoint(0, 0);
this.size = new Dimension(-1, -1);
this.font = Display.getDefault().getSystemFont();
this.graph = parent.getGraph();
this.cacheLabel = false;
this.setText(text);
this.layoutEntity = new LayoutGraphNode();
if (image != null) {
this.setImage(image);
}
if (font == null) {
font = Display.getDefault().getSystemFont();
}
}
/**
* A simple toString that we can use for debugging
*/
public String toString() {
return "GraphModelNode: " + getText();
}
public LayoutEntity getLayoutEntity() {
return layoutEntity;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.mylar.zest.core.widgets.GraphItem#dispose()
*/
public void dispose() {
if (isFisheyeEnabled) {
this.fishEye(false, false);
}
super.dispose();
this.isDisposed = true;
while (getSourceConnections().size() > 0) {
GraphConnection connection = (GraphConnection) getSourceConnections().get(0);
if (!connection.isDisposed()) {
connection.dispose();
} else {
removeSourceConnection(connection);
}
}
while (getTargetConnections().size() > 0) {
GraphConnection connection = (GraphConnection) getTargetConnections().get(0);
if (!connection.isDisposed()) {
connection.dispose();
} else {
removeTargetConnection(connection);
}
}
graph.removeNode(this);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.swt.widgets.Widget#isDisposed()
*/
public boolean isDisposed() {
return isDisposed;
}
/**
* Determines if this node has a fixed size or if it is packed to the size of its contents.
* To set a node to pack, set its size (-1, -1)
* @return
*/
public boolean isSizeFixed() {
return !(this.size.width < 0 && this.size.height < 0);
}
/**
* Returns a new list of the source connections (GraphModelConnection
* objects).
*
* @return List a new list of GraphModelConnect objects
*/
public List getSourceConnections() {
return new ArrayList(sourceConnections);
}
/**
* Returns a new list of the target connections (GraphModelConnection
* objects).
*
* @return List a new list of GraphModelConnect objects
*/
public List getTargetConnections() {
return new ArrayList(targetConnections);
}
/**
* Returns the bounds of this node. It is just the combination of the
* location and the size.
*
* @return Rectangle
*/
Rectangle getBounds() {
return new Rectangle(getLocation(), getSize());
}
/**
* Returns a copy of the node's location.
*
* @return Point
*/
public Point getLocation() {
return currentLocation;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.mylar.zest.core.internal.graphmodel.IGraphModelNode#isSelected()
*/
public boolean isSelected() {
return selected;
}
/**
* Sets the current location for this node.
*/
public void setLocation(double x, double y) {
currentLocation.x = (int) x;
currentLocation.y = (int) y;
refreshLocation();
}
/**
* Returns a copy of the node's size.
*
* @return Dimension
*/
public Dimension getSize() {
if (size.height < 0 && size.width < 0 && nodeFigure != null) {
return nodeFigure.getSize().getCopy();
}
return size.getCopy();
}
/**
* Get the foreground colour for this node
*/
public Color getForegroundColor() {
return foreColor;
}
/**
* Set the foreground colour for this node
*/
public void setForegroundColor(Color c) {
this.foreColor = c;
updateFigureForModel(nodeFigure);
}
/**
* Get the background colour for this node. This is the color the node will
* be if it is not currently highlighted. This color is meaningless if a
* custom figure has been set.
*/
public Color getBackgroundColor() {
return backColor;
}
/**
* Permanently sets the background color (unhighlighted). This color has no
* effect if a custom figure has been set.
*
* @param c
*/
public void setBackgroundColor(Color c) {
backColor = c;
updateFigureForModel(nodeFigure);
}
/**
* Sets the tooltip on this node. This tooltip will display if the mouse
* hovers over the node. Setting the tooltip has no effect if a custom
* figure has been set.
*/
public void setTooltip(IFigure tooltip) {
hasCustomTooltip = true;
this.tooltip = tooltip;
updateFigureForModel(nodeFigure);
}
/**
* Gets the current tooltip for this node. The tooltip returned is
* meaningless if a custom figure has been set.
*/
public IFigure getTooltip() {
return this.tooltip;
}
/**
* Sets the border color.
*
* @param c
* the border color.
*/
public void setBorderColor(Color c) {
borderColor = c;
updateFigureForModel(nodeFigure);
}
/**
* Sets the highlighted border color.
*
* @param c
* the highlighted border color.
*/
public void setBorderHighlightColor(Color c) {
this.borderHighlightColor = c;
updateFigureForModel(nodeFigure);
}
/**
* Get the highlight colour for this node
*/
public Color getHighlightColor() {
return highlightColor;
}
/**
* Set the highlight colour for this node
*/
public void setHighlightColor(Color c) {
this.highlightColor = c;
}
/**
* Get the highlight adjacent colour for this node. This is the colour that
* adjacent nodes will get
*/
// @tag ADJACENT : Removed highlight adjacent
/*
public Color getHighlightAdjacentColor() {
return highlightAdjacentColor;
}
*/
/**
* Set the highlight adjacent colour for this node. This is the colour that
* adjacent node will get.
*/
// @tag ADJACENT : Removed highlight adjacent
/*
public void setHighlightAdjacentColor(Color c) {
this.highlightAdjacentColor = c;
}
*/
/**
* Highlights the node changing the background color and border color. The
* source and destination connections are also highlighted, and the adjacent
* nodes are highlighted too in a different color.
*/
public void highlight() {
if (highlighted == HIGHLIGHT_ON) {
return;
}
// @tag ADJACENT : Removed highlight adjacent
/*
if (ZestStyles.checkStyle(getNodeStyle(), ZestStyles.NODES_HIGHLIGHT_ADJACENT)) {
for (Iterator iter = sourceConnections.iterator(); iter.hasNext();) {
GraphConnection conn = (GraphConnection) iter.next();
conn.highlight();
conn.getDestination().highlightAdjacent();
}
for (Iterator iter = targetConnections.iterator(); iter.hasNext();) {
GraphConnection conn = (GraphConnection) iter.next();
conn.highlight();
conn.getSource().highlightAdjacent();
}
}
*/
if (parent.getItemType() == GraphItem.CONTAINER) {
((GraphContainer) parent).highlightNode(this);
} else {
((Graph) parent).highlightNode(this);
}
highlighted = HIGHLIGHT_ON;
updateFigureForModel(getNodeFigure());
}
/**
* Restores the nodes original background color and border width.
*/
public void unhighlight() {
// @tag ADJACENT : Removed highlight adjacent
//boolean highlightedAdjacently = (highlighted == HIGHLIGHT_ADJACENT);
if (highlighted == HIGHLIGHT_NONE) {
return;
}
// @tag ADJACENT : Removed highlight adjacent
/*
if (!highlightedAdjacently) {
// IF we are highlighted as an adjacent node, we don't need to deal
// with our connections.
if (ZestStyles.checkStyle(getNodeStyle(), ZestStyles.NODES_HIGHLIGHT_ADJACENT)) {
// unhighlight the adjacent edges
for (Iterator iter = sourceConnections.iterator(); iter.hasNext();) {
GraphConnection conn = (GraphConnection) iter.next();
conn.unhighlight();
if (conn.getDestination() != this) {
conn.getDestination().unhighlight();
}
}
for (Iterator iter = targetConnections.iterator(); iter.hasNext();) {
GraphConnection conn = (GraphConnection) iter.next();
conn.unhighlight();
if (conn.getSource() != this) {
conn.getSource().unhighlight();
}
}
}
}
*/
if (parent.getItemType() == GraphItem.CONTAINER) {
((GraphContainer) parent).unhighlightNode(this);
} else {
((Graph) parent).unhighlightNode(this);
}
highlighted = HIGHLIGHT_NONE;
updateFigureForModel(nodeFigure);
}
protected void refreshLocation() {
Point loc = this.getLocation();
Dimension size = this.getSize();
Rectangle bounds = new Rectangle(loc, size);
if (nodeFigure == null || nodeFigure.getParent() == null) {
return; // node figure has not been created yet
}
//nodeFigure.setBounds(bounds);
nodeFigure.getParent().setConstraint(nodeFigure, bounds);
}
/**
* Highlights this node using the adjacent highlight color. This only does
* something if highlighAdjacentNodes is set to true and if the node isn't
* already highlighted.
*
* @see #setHighlightAdjacentNodes(boolean)
*/
// @tag ADJACENT : removed highlight adjacent
/*
public void highlightAdjacent() {
if (highlighted > 0) {
return;
}
highlighted = HIGHLIGHT_ADJACENT;
updateFigureForModel(nodeFigure);
if (parent.getItemType() == GraphItem.CONTAINER) {
((GraphContainer) parent).highlightNode(this);
} else {
((Graph) parent).highlightNode(this);
}
}
*/
/**
* Returns if the nodes adjacent to this node will be highlighted when this
* node is selected.
*
* @return GraphModelNode
*/
// @tag ADJACENT : Removed highlight adjacent
/*
public boolean isHighlightAdjacentNodes() {
return ZestStyles.checkStyle(nodeStyle, ZestStyles.NODES_HIGHLIGHT_ADJACENT);
}
*/
/**
* Sets if the adjacent nodes to this one should be highlighted when this
* node is selected.
*
* @param highlightAdjacentNodes
* The highlightAdjacentNodes to set.
*/
// @tag ADJACENT : Removed highlight adjacent
/*
public void setHighlightAdjacentNodes(boolean highlightAdjacentNodes) {
if (!highlightAdjacentNodes) {
this.nodeStyle |= ZestStyles.NODES_HIGHLIGHT_ADJACENT;
this.nodeStyle ^= ZestStyles.NODES_HIGHLIGHT_ADJACENT;
return;
}
this.nodeStyle |= ZestStyles.NODES_HIGHLIGHT_ADJACENT;
}
*/
public Color getBorderColor() {
return borderColor;
}
public int getBorderWidth() {
return borderWidth;
}
public void setBorderWidth(int width) {
this.borderWidth = width;
updateFigureForModel(nodeFigure);
}
public Font getFont() {
return font;
}
public void setFont(Font font) {
this.font = font;
updateFigureForModel(nodeFigure);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.swt.widgets.Item#setText(java.lang.String)
*/
public void setText(String string) {
if (string == null) {
string = "";
}
super.setText(string);
if (nodeFigure != null) {
updateFigureForModel(this.nodeFigure);
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.swt.widgets.Item#setImage(org.eclipse.swt.graphics.Image)
*/
public void setImage(Image image) {
super.setImage(image);
if (nodeFigure != null) {
updateFigureForModel(nodeFigure);
}
}
/**
* Gets the graphModel that this node is contained in
*
* @return The graph model that this node is contained in
*/
public Graph getGraphModel() {
return this.graph;
}
/**
* @return the nodeStyle
*/
public int getNodeStyle() {
return nodeStyle;
}
/**
* @param nodeStyle
* the nodeStyle to set
*/
public void setNodeStyle(int nodeStyle) {
this.nodeStyle = nodeStyle;
this.cacheLabel = ((this.nodeStyle & ZestStyles.NODES_CACHE_LABEL) > 0) ? true : false;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.mylar.zest.core.internal.graphmodel.IGraphModelNode#setSize(double,
* double)
*/
public void setSize(double width, double height) {
if ((width != size.width) || (height != size.height)) {
size.width = (int) width;
size.height = (int) height;
refreshLocation();
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.mylar.zest.core.internal.graphmodel.IGraphModelNode#getBorderHighlightColor()
*/
public Color getBorderHighlightColor() {
return borderHighlightColor;
}
public boolean cacheLabel() {
return this.cacheLabel;
}
public void setCacheLabel(boolean cacheLabel) {
this.cacheLabel = cacheLabel;
}
public IFigure getNodeFigure() {
return this.nodeFigure;
}
public void setVisible(boolean visible) {
// graph.addRemoveFigure(this, visible);
this.visible = visible;
this.getFigure().setVisible(visible);
List sConnections = (this).getSourceConnections();
List tConnections = (this).getTargetConnections();
for (Iterator iterator2 = sConnections.iterator(); iterator2.hasNext();) {
GraphConnection connection = (GraphConnection) iterator2.next();
connection.setVisible(visible);
}
for (Iterator iterator2 = tConnections.iterator(); iterator2.hasNext();) {
GraphConnection connection = (GraphConnection) iterator2.next();
connection.setVisible(visible);
}
}
public int getStyle() {
return super.getStyle() | this.getNodeStyle();
}
/***************************************************************************
* PRIVATE MEMBERS
**************************************************************************/
private IFigure fishEyeFigure = null;
private Font fishEyeFont = null;
private boolean isFisheyeEnabled;
protected IFigure fishEye(boolean enable, boolean animate) {
if (isDisposed) {
// If a fisheyed figure is still left on the canvas, we could get
// called once more after the dispose is called. Since we cleaned
// up everything on dispose, we can just return null here.
return null;
}
if (!checkStyle(ZestStyles.NODES_FISHEYE)) {
return null;
}
if (enable) {
// Create the fish eye label
fishEyeFigure = createFishEyeFigure();
// Get the current Bounds
Rectangle rectangle = nodeFigure.getBounds().getCopy();
FontData fontData = Display.getCurrent().getSystemFont().getFontData()[0];
fontData.setHeight(12);
fishEyeFont = new Font(Display.getCurrent(), fontData);
fishEyeFigure.setFont(fishEyeFont);
// Calculate how much we have to expand the current bounds to get to the new bounds
Dimension newSize = fishEyeFigure.getPreferredSize();
Rectangle currentSize = rectangle.getCopy();
nodeFigure.translateToAbsolute(currentSize);
int expandedH = (newSize.height - currentSize.height) / 2 + 1;
int expandedW = (newSize.width - currentSize.width) / 2 + 1;
Dimension expandAmount = new Dimension(expandedW, expandedH);
nodeFigure.translateToAbsolute(rectangle);
rectangle.expand(new Insets(expandAmount.height, expandAmount.width, expandAmount.height, expandAmount.width));
if (expandedH <= 0 && expandedW <= 0) {
return null;
}
//Add the fisheye
this.getGraphModel().fishEye(nodeFigure, fishEyeFigure, rectangle, true);
if (fishEyeFigure != null) {
isFisheyeEnabled = true;
}
return fishEyeFigure;
} else {
// Remove the fisheye and dispose the font
this.getGraphModel().removeFishEye(fishEyeFigure, nodeFigure, animate);
if (fishEyeFont != null) {
this.fishEyeFont.dispose();
this.fishEyeFont = null;
}
isFisheyeEnabled = false;
return null;
}
}
IContainer getParent() {
return parent;
}
boolean isHighlighted() {
return highlighted > 0;
}
void invokeLayoutListeners(LayoutConstraint constraint) {
graph.invokeConstraintAdapters(this, constraint);
}
protected void updateFigureForModel(IFigure currentFigure) {
if (currentFigure == null) {
return;
}
if (!(currentFigure instanceof GraphLabel)) {
return;
}
GraphLabel figure = (GraphLabel) currentFigure;
IFigure toolTip;
if (!checkStyle(ZestStyles.NODES_HIDE_TEXT)) {
figure.setText(this.getText());
}
figure.setIcon(getImage());
if (highlighted == HIGHLIGHT_ON) {
figure.setForegroundColor(getForegroundColor());
figure.setBackgroundColor(getHighlightColor());
figure.setBorderColor(getBorderHighlightColor());
} else {
figure.setForegroundColor(getForegroundColor());
figure.setBackgroundColor(getBackgroundColor());
figure.setBorderColor(getBorderColor());
}
figure.setBorderWidth(getBorderWidth());
figure.setFont(getFont());
if (this.getTooltip() == null && hasCustomTooltip == false) {
// if we have a custom tooltip, don't try and create our own.
toolTip = new Label();
((Label) toolTip).setText(getText());
} else {
toolTip = this.getTooltip();
}
figure.setToolTip(toolTip);
refreshLocation();
if (isFisheyeEnabled) {
IFigure newFisheyeFigure = createFishEyeFigure();
if (graph.replaceFishFigure(this.fishEyeFigure, newFisheyeFigure)) {
this.fishEyeFigure = newFisheyeFigure;
}
}
}
protected IFigure createFigureForModel() {
GraphNode node = this;
boolean cacheLabel = (this).cacheLabel();
GraphLabel label = new GraphLabel(node.getText(), node.getImage(), cacheLabel);
label.setFont(this.font);
if (checkStyle(ZestStyles.NODES_HIDE_TEXT)) {
label.setText("");
}
updateFigureForModel(label);
return label;
}
private IFigure createFishEyeFigure() {
GraphNode node = this;
boolean cacheLabel = this.cacheLabel();
GraphLabel label = new GraphLabel(node.getText(), node.getImage(), cacheLabel);
if (!checkStyle(ZestStyles.NODES_HIDE_TEXT)) {
label.setText(this.getText());
}
label.setIcon(getImage());
// @tag TODO: Add border and foreground colours to highlight
// (this.borderColor)
if (highlighted == HIGHLIGHT_ON) {
label.setForegroundColor(getForegroundColor());
label.setBackgroundColor(getHighlightColor());
} else {
label.setForegroundColor(getForegroundColor());
label.setBackgroundColor(getBackgroundColor());
}
label.setFont(getFont());
return label;
}
public boolean isVisible() {
return visible;
}
void addSourceConnection(GraphConnection connection) {
this.sourceConnections.add(connection);
}
void addTargetConnection(GraphConnection connection) {
this.targetConnections.add(connection);
}
void removeSourceConnection(GraphConnection connection) {
this.sourceConnections.remove(connection);
}
void removeTargetConnection(GraphConnection connection) {
this.targetConnections.remove(connection);
}
/**
* Sets the node as selected.
*/
void setSelected(boolean selected) {
if (selected == isSelected()) {
return;
}
if (selected) {
highlight();
} else {
unhighlight();
}
this.selected = selected;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.mylar.zest.core.widgets.IGraphItem#getItemType()
*/
public int getItemType() {
return NODE;
}
class LayoutGraphNode implements LayoutEntity {
Object layoutInformation = null;
public double getHeightInLayout() {
return getSize().height;
}
public Object getLayoutInformation() {
return layoutInformation;
}
public String toString() {
return getText();
}
public double getWidthInLayout() {
return getSize().width;
}
public double getXInLayout() {
return getLocation().x;
}
public double getYInLayout() {
return getLocation().y;
}
public void populateLayoutConstraint(LayoutConstraint constraint) {
invokeLayoutListeners(constraint);
}
public void setLayoutInformation(Object internalEntity) {
this.layoutInformation = internalEntity;
}
public void setLocationInLayout(double x, double y) {
setLocation(x, y);
}
public void setSizeInLayout(double width, double height) {
setSize(width, height);
}
/**
* Compares two nodes.
*/
public int compareTo(Object otherNode) {
int rv = 0;
if (otherNode instanceof GraphNode) {
GraphNode node = (GraphNode) otherNode;
if (getText() != null) {
rv = getText().compareTo(node.getText());
}
}
return rv;
}
public Object getGraphData() {
return GraphNode.this;
}
public void setGraphData(Object o) {
// TODO Auto-generated method stub
}
}
IFigure getFigure() {
if (this.nodeFigure == null) {
initFigure();
}
return this.getNodeFigure();
}
void paint() {
}
}