blob: 66a686bb8e0701a67574f6807106275945b1f854 [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.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.eclipse.draw2d.Animation;
import org.eclipse.draw2d.ColorConstants;
import org.eclipse.draw2d.FigureCanvas;
import org.eclipse.draw2d.FreeformLayer;
import org.eclipse.draw2d.FreeformLayout;
import org.eclipse.draw2d.FreeformViewport;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.LayoutAnimator;
import org.eclipse.draw2d.MouseMotionListener;
import org.eclipse.draw2d.PolylineConnection;
import org.eclipse.draw2d.SWTEventDispatcher;
import org.eclipse.draw2d.ScalableFigure;
import org.eclipse.draw2d.ScalableFreeformLayeredPane;
import org.eclipse.draw2d.ScrollPane;
import org.eclipse.draw2d.TreeSearch;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.ControlPaintHandler;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Item;
import org.eclipse.zest.core.widgets.internal.ContainerFigure;
import org.eclipse.zest.core.widgets.internal.RevealListener;
import org.eclipse.zest.core.widgets.internal.ZestRootLayer;
import org.eclipse.zest.layouts.InvalidLayoutConfiguration;
import org.eclipse.zest.layouts.LayoutAlgorithm;
import org.eclipse.zest.layouts.LayoutEntity;
import org.eclipse.zest.layouts.LayoutRelationship;
import org.eclipse.zest.layouts.LayoutStyles;
import org.eclipse.zest.layouts.algorithms.TreeLayoutAlgorithm;
import org.eclipse.zest.layouts.constraints.LayoutConstraint;
/*
* Holds the nodes and connections for the graph.
*
* @author Chris Callendar
*
* @author Ian Bull
*/
public class Graph extends FigureCanvas implements IContainer {
// CLASS CONSTANTS
public static final int ANIMATION_TIME = 500;
public static final int FISHEYE_ANIMATION_TIME = 100;
// @tag CGraph.Colors : These are the colour constants for the graph, they
// are disposed on clean-up
public Color LIGHT_BLUE = null;
public Color LIGHT_BLUE_CYAN = null;
public Color GREY_BLUE = null;
public Color DARK_BLUE = null;
public Color LIGHT_YELLOW = null;
public Color HIGHLIGHT_COLOR = ColorConstants.yellow;
public Color HIGHLIGHT_ADJACENT_COLOR = ColorConstants.orange;
public Color DEFAULT_NODE_COLOR = LIGHT_BLUE;
/**
* These are all the children of this graph. These lists contains all nodes
* and connections that have added themselves to this graph.
*/
private final List nodes;
protected List connections;
private List selectedItems = null;
IFigure fisheyedFigure = null;
private List /* SelectionListener */selectionListeners = null;
/** This maps all visible nodes to their model element. */
private HashMap figure2ItemMap = null;
/** Maps user nodes to internal nodes */
private int connectionStyle;
private int nodeStyle;
private List constraintAdapters;
private List revealListeners = null;
private ScalableFreeformLayeredPane fishEyeLayer = null;
LayoutAlgorithm layoutAlgorithm = null;
private Dimension preferredSize = null;
int style = 0;
private ScalableFreeformLayeredPane rootlayer;
private ZestRootLayer zestRootLayer;
private boolean hasPendingLayoutRequest;
/**
* Constructor for a Graph. This widget represents the root of the graph,
* and can contain graph items such as graph nodes and graph connections.
*
* @param parent
* @param style
*/
public Graph(Composite parent, int style) {
super(parent, style | SWT.DOUBLE_BUFFERED);
this.style = style;
this.setBackground(ColorConstants.white);
LIGHT_BLUE = new Color(Display.getDefault(), 216, 228, 248);
LIGHT_BLUE_CYAN = new Color(Display.getDefault(), 213, 243, 255);
GREY_BLUE = new Color(Display.getDefault(), 139, 150, 171);
DARK_BLUE = new Color(Display.getDefault(), 1, 70, 122);
LIGHT_YELLOW = new Color(Display.getDefault(), 255, 255, 206);
this.setViewport(new FreeformViewport());
this.getVerticalBar().addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
Graph.this.redraw();
}
});
this.getHorizontalBar().addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
Graph.this.redraw();
}
});
// @tag CGraph.workaround : this allows me to handle mouse events
// outside of the canvas
this.getLightweightSystem().setEventDispatcher(new SWTEventDispatcher() {
public void dispatchMouseMoved(org.eclipse.swt.events.MouseEvent me) {
super.dispatchMouseMoved(me);
// If the current event is null, return
if (getCurrentEvent() == null) {
return;
}
if (getMouseTarget() == null) {
setMouseTarget(getRoot());
}
if ((me.stateMask & SWT.BUTTON_MASK) != 0) {
// Sometimes getCurrentEvent() returns null
getMouseTarget().handleMouseDragged(getCurrentEvent());
} else {
getMouseTarget().handleMouseMoved(getCurrentEvent());
}
}
});
this.setContents(createLayers());
DragSupport dragSupport = new DragSupport(this);
this.getLightweightSystem().getRootFigure().addMouseListener(dragSupport);
this.getLightweightSystem().getRootFigure().addMouseMotionListener(dragSupport);
this.nodes = new ArrayList();
this.preferredSize = new Dimension(-1, -1);
this.connectionStyle = ZestStyles.NONE;
this.nodeStyle = ZestStyles.NONE;
this.connections = new ArrayList();
this.constraintAdapters = new ArrayList();
this.selectedItems = new ArrayList();
this.selectionListeners = new ArrayList();
this.figure2ItemMap = new HashMap();
revealListeners = new ArrayList(1);
ControlPaintHandler helper = new ControlPaintHandler(this);
helper.addPaintListener(new PaintListener() {
public void paintControl(PaintEvent e) {
if (!revealListeners.isEmpty()) {
// Go through the reveal list and let everyone know that the
// view is now available. Remove the listeners so they are
// only
// called once!
Iterator iterator = revealListeners.iterator();
while (iterator.hasNext()) {
RevealListener reveallisetner = (RevealListener) iterator.next();
reveallisetner.revealed(Graph.this);
iterator.remove();
}
}
/*
Iterator iterator = getNodes().iterator();
while (iterator.hasNext()) {
GraphNode node = (GraphNode) iterator.next();
node.paint();
}
*/
}
});
}
/**
* This adds a listener to the set of listeners that will be called when a
* selection event occurs.
*
* @param selectionListener
*/
public void addSelectionListener(SelectionListener selectionListener) {
if (!selectionListeners.contains(selectionListener)) {
selectionListeners.add(selectionListener);
}
}
public void removeSelectionListener(SelectionListener selectionListener) {
if (selectionListeners.contains(selectionListener)) {
selectionListeners.remove(selectionListener);
}
}
/**
* Gets a list of the GraphModelNode children objects under the root node in
* this diagram. If the root node is null then all the top level nodes are
* returned.
*
* @return List of GraphModelNode objects
*/
public List getNodes() {
return nodes;
}
/**
* Adds a new constraint adapter to the list of constraint adapters
* @param constraintAdapter
*/
public void addConstraintAdapter(ConstraintAdapter constraintAdapter) {
this.constraintAdapters.add(constraintAdapter);
}
/**
* Sets the constraint adapters on this model
*
* @param constraintAdapters
*/
public void setConstraintAdapters(List /* ConstraintAdapters */constraintAdapters) {
this.constraintAdapters = constraintAdapters;
}
/**
* Gets the root layer for this graph
*
* @return
*/
public ScalableFigure getRootLayer() {
return rootlayer;
}
/**
* Sets the default connection style.
*
* @param connection
* style the connection style to set
* @see org.eclipse.mylar.zest.core.widgets.ZestStyles
*/
public void setConnectionStyle(int connectionStyle) {
this.connectionStyle = connectionStyle;
}
/**
* Gets the default connection style.
*
* @return the connection style
* @see org.eclipse.mylar.zest.core.widgets.ZestStyles
*/
public int getConnectionStyle() {
return connectionStyle;
}
/**
* Sets the default node style.
*
* @param nodeStyle
* the node style to set
* @see org.eclipse.mylar.zest.core.widgets.ZestStyles
*/
public void setNodeStyle(int nodeStyle) {
this.nodeStyle = nodeStyle;
}
/**
* Gets the default node style.
*
* @return the node style
* @see org.eclipse.mylar.zest.core.widgets.ZestStyles
*/
public int getNodeStyle() {
return nodeStyle;
}
/**
* Gets the list of GraphModelConnection objects.
*
* @return list of GraphModelConnection objects
*/
public List getConnections() {
return this.connections;
}
/**
* Changes the selection to the list of items
*
* @param l
*/
public void setSelection(GraphItem[] items) {
clearSelection();
if (items != null) {
for (int i = 0; i < items.length; i++) {
if (items[i] != null) {
select(items[i]);
}
}
}
}
public void selectAll() {
setSelection((GraphItem[]) nodes.toArray(new GraphItem[] {}));
}
/**
* Gets the list of currently selected GraphNodes
*
* @return Currently selected graph node
*/
public List getSelection() {
return selectedItems;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.swt.widgets.Widget#toString()
*/
public String toString() {
return "GraphModel {" + nodes.size() + " nodes, " + connections.size() + " connections}";
}
/*
* (non-Javadoc)
*
* @see org.eclipse.mylar.zest.core.internal.graphmodel.IGraphItem#getGraphModel()
*/
public Graph getGraphModel() {
return this;
}
/**
* Dispose of the nodes and edges when the graph is disposed.
*/
public void dispose() {
while (nodes.size() > 0) {
GraphNode node = (GraphNode) nodes.get(0);
if (node != null && !node.isDisposed()) {
node.dispose();
}
}
while (connections.size() > 0) {
GraphConnection connection = (GraphConnection) connections.get(0);
if (connection != null && !connection.isDisposed()) {
connection.dispose();
}
}
super.dispose();
if (LIGHT_BLUE != null) {
LIGHT_BLUE.dispose();
}
if (LIGHT_BLUE_CYAN != null) {
LIGHT_BLUE_CYAN.dispose();
}
if (GREY_BLUE != null) {
GREY_BLUE.dispose();
}
if (DARK_BLUE != null) {
DARK_BLUE.dispose();
}
if (LIGHT_YELLOW != null) {
LIGHT_YELLOW.dispose();
}
}
/**
* Runs the layout on this graph. It uses the reveal listener to run the
* layout only if the view is visible. Otherwise it will be deferred until
* after the view is available.
*/
public void applyLayout() {
if (!hasPendingLayoutRequest) {
hasPendingLayoutRequest = true;
this.addRevealListener(new RevealListener() {
public void revealed(Control c) {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
applyLayoutInternal();
}
});
}
});
}
}
/**
* Sets the preferred size of the layout area. Size of ( -1, -1) uses the
* current canvas size.
*
* @param width
* @param height
*/
public void setPreferredSize(int width, int height) {
this.preferredSize = new Dimension(width, height);
}
/**
* @param algorithm
*/
public void setLayoutAlgorithm(LayoutAlgorithm algorithm, boolean applyLayout) {
this.layoutAlgorithm = algorithm;
if (applyLayout) {
applyLayout();
}
}
public LayoutAlgorithm getLayoutAlgorithm() {
return this.layoutAlgorithm;
}
/**
* Finds a figure at the location X, Y in the graph
*
* This point should be translated to relative before calling findFigureAt
*/
public IFigure getFigureAt(int x, int y) {
IFigure figureUnderMouse = this.getContents().findFigureAt(x, y, new TreeSearch() {
public boolean accept(IFigure figure) {
return true;
}
public boolean prune(IFigure figure) {
IFigure parent = figure.getParent();
// @tag TODO Zest : change these to from getParent to their actual layer names
if (parent == fishEyeLayer) {
// If it node is on the fish eye layer, don't worry about it.
return true;
}
if (parent instanceof ContainerFigure && figure instanceof PolylineConnection) {
return false;
}
if (parent == zestRootLayer || parent == zestRootLayer.getParent() || parent == zestRootLayer.getParent().getParent()) {
return false;
}
GraphItem item = (GraphItem) figure2ItemMap.get(figure);
if (item != null && item.getItemType() == GraphItem.CONTAINER) {
return false;
} else if (figure instanceof FreeformLayer || parent instanceof FreeformLayer || figure instanceof ScrollPane || parent instanceof ScrollPane || parent instanceof ScalableFreeformLayeredPane || figure instanceof ScalableFreeformLayeredPane || figure instanceof FreeformViewport ||
parent instanceof FreeformViewport) {
return false;
}
return true;
}
});
return figureUnderMouse;
}
// /////////////////////////////////////////////////////////////////////////////////
// PRIVATE METHODS. These are NON API
// /////////////////////////////////////////////////////////////////////////////////
class DragSupport implements MouseMotionListener, org.eclipse.draw2d.MouseListener {
/**
*
*/
Graph graph = null;
Point lastLocation = null;
GraphItem fisheyedItem = null;
boolean isDragging = false;
DragSupport(Graph graph) {
this.graph = graph;
}
public void mouseDragged(org.eclipse.draw2d.MouseEvent me) {
if (!isDragging) {
return;
}
Point mousePoint = new Point(me.x, me.y);
Point tempPoint = mousePoint.getCopy();
if (selectedItems.size() > 0) {
Iterator iterator = selectedItems.iterator();
while (iterator.hasNext()) {
GraphItem item = (GraphItem) iterator.next();
if ((item.getItemType() == GraphItem.NODE) || (item.getItemType() == GraphItem.CONTAINER)) {
// @tag Zest.selection Zest.move : This is where the node movement is tracked
Point pointCopy = mousePoint.getCopy();
Point tempLastLocation = lastLocation.getCopy();
item.getFigure().getParent().translateToRelative(tempLastLocation);
item.getFigure().getParent().translateFromParent(tempLastLocation);
item.getFigure().getParent().translateToRelative(pointCopy);
item.getFigure().getParent().translateFromParent(pointCopy);
Point delta = new Point(pointCopy.x - tempLastLocation.x, pointCopy.y - tempLastLocation.y);
if (item.getItemType() == GraphItem.NODE || item.getItemType() == GraphItem.CONTAINER) {
GraphNode node = (GraphNode) item;
node.setLocation(node.getLocation().x + delta.x, node.getLocation().y + delta.y);
}
/*
else if (item.getItemType() == GraphItem.CONTAINER) {
GraphContainer container = (GraphContainer) item;
container.setLocation(container.getLocation().x + delta.x, container.getLocation().y + delta.y);
}
*/
} else {
// There is no movement for connection
}
}
if (fisheyedFigure != null) {
Point pointCopy = mousePoint.getCopy();
Point tempLastLocation = lastLocation.getCopy();
fisheyedFigure.translateToRelative(tempLastLocation);
fisheyedFigure.translateFromParent(tempLastLocation);
fisheyedFigure.translateToRelative(pointCopy);
fisheyedFigure.translateFromParent(pointCopy);
Point delta = new Point(pointCopy.x - tempLastLocation.x, pointCopy.y - tempLastLocation.y);
Point point = new Point(fisheyedFigure.getBounds().x + delta.x, fisheyedFigure.getBounds().y + delta.y);
fishEyeLayer.setConstraint(fisheyedFigure, new Rectangle(point, fisheyedFigure.getSize()));
fishEyeLayer.getUpdateManager().performUpdate();
//fisheyedFigure.setBounds(new Rectangle(point2, fisheyedFigure.getSize()));
//fisheyedFigure.setLocation(new Point(fisheyedFigure.getBounds().x + delta.x, fisheyedFigure.getBounds().y + delta.y));
}
}
lastLocation = tempPoint;
//oldLocation = mousePoint;
}
public void mouseEntered(org.eclipse.draw2d.MouseEvent me) {
}
public void mouseExited(org.eclipse.draw2d.MouseEvent me) {
}
public void mouseHover(org.eclipse.draw2d.MouseEvent me) {
}
/**
* This tracks whenever a mouse moves. The only thing we care about is
* fisheye(ing) nodes. This means whenever the mouse moves we check if
* we need to fisheye on a node or not.
*/
public void mouseMoved(org.eclipse.draw2d.MouseEvent me) {
Point mousePoint = new Point(me.x, me.y);
getRootLayer().translateToRelative(mousePoint);
IFigure figureUnderMouse = getFigureAt(mousePoint.x, mousePoint.y);
if (figureUnderMouse != null) {
// There is a figure under this mouse
GraphItem itemUnderMouse = (GraphItem) figure2ItemMap.get(figureUnderMouse);
if (itemUnderMouse == fisheyedItem) {
} else if (itemUnderMouse != null && itemUnderMouse.getItemType() == GraphItem.NODE) {
fisheyedItem = itemUnderMouse;
fisheyedFigure = ((GraphNode) itemUnderMouse).fishEye(true, true);
if (fisheyedFigure == null) {
// If there is no fisheye figure (this means that the node does not support a fish eye)
// then remove the fisheyed item
fisheyedItem = null;
}
} else if (fisheyedItem != null) {
((GraphNode) fisheyedItem).fishEye(false, true);
fisheyedItem = null;
fisheyedFigure = null;
}
} else {
if (fisheyedItem != null) {
((GraphNode) fisheyedItem).fishEye(false, true);
fisheyedItem = null;
fisheyedFigure = null;
}
}
}
public void mouseDoubleClicked(org.eclipse.draw2d.MouseEvent me) {
}
public void mousePressed(org.eclipse.draw2d.MouseEvent me) {
isDragging = true;
Point mousePoint = new Point(me.x, me.y);
lastLocation = mousePoint.getCopy();
getRootLayer().translateToRelative(mousePoint);
if (me.getState() == org.eclipse.draw2d.MouseEvent.ALT) {
double scale = getRootLayer().getScale();
scale *= 1.05;
getRootLayer().setScale(scale);
Point newMousePoint = mousePoint.getCopy().scale(1.05);
Point delta = new Point(newMousePoint.x - mousePoint.x, newMousePoint.y - mousePoint.y);
Point newViewLocation = getViewport().getViewLocation().getCopy().translate(delta);
getViewport().setViewLocation(newViewLocation);
lastLocation.scale(scale);
clearSelection();
return;
} else if (me.getState() == (org.eclipse.draw2d.MouseEvent.ALT | org.eclipse.draw2d.MouseEvent.SHIFT)) {
double scale = getRootLayer().getScale();
scale /= 1.05;
getRootLayer().setScale(scale);
Point newMousePoint = mousePoint.getCopy().scale(1 / 1.05);
Point delta = new Point(newMousePoint.x - mousePoint.x, newMousePoint.y - mousePoint.y);
Point newViewLocation = getViewport().getViewLocation().getCopy().translate(delta);
getViewport().setViewLocation(newViewLocation);
clearSelection();
return;
} else {
boolean hasSelection = selectedItems.size() > 0;
IFigure figureUnderMouse = getFigureAt(mousePoint.x, mousePoint.y);
getRootLayer().translateFromParent(mousePoint);
if (figureUnderMouse != null) {
figureUnderMouse.getParent().translateFromParent(mousePoint);
}
// If the figure under the mouse is the canvas, and CTRL is not being held down, then select
// nothing
if (figureUnderMouse == null || figureUnderMouse == graph) {
if (me.getState() != org.eclipse.draw2d.MouseEvent.CONTROL) {
clearSelection();
if (hasSelection) {
fireWidgetSelectedEvent(null);
hasSelection = false;
}
}
return;
}
GraphItem itemUnderMouse = (GraphItem) figure2ItemMap.get(figureUnderMouse);
if (itemUnderMouse == null) {
if (me.getState() != org.eclipse.draw2d.MouseEvent.CONTROL) {
clearSelection();
if (hasSelection) {
fireWidgetSelectedEvent(null);
hasSelection = false;
}
}
return;
}
if (selectedItems.contains(itemUnderMouse)) {
// We have already selected this node, and CTRL is being held down, remove this selection
// @tag Zest.selection : This deselects when you have CTRL pressed
if (me.getState() == org.eclipse.draw2d.MouseEvent.CONTROL) {
selectedItems.remove(itemUnderMouse);
(itemUnderMouse).unhighlight();
fireWidgetSelectedEvent(itemUnderMouse);
}
return;
}
if (me.getState() != org.eclipse.draw2d.MouseEvent.CONTROL) {
clearSelection();
}
if (itemUnderMouse.getItemType() == GraphItem.NODE) {
// @tag Zest.selection : This is where the nodes are selected
selectedItems.add(itemUnderMouse);
((GraphNode) itemUnderMouse).highlight();
fireWidgetSelectedEvent(itemUnderMouse);
} else if (itemUnderMouse.getItemType() == GraphItem.CONNECTION) {
selectedItems.add(itemUnderMouse);
((GraphConnection) itemUnderMouse).highlight();
fireWidgetSelectedEvent(itemUnderMouse);
} else if (itemUnderMouse.getItemType() == GraphItem.CONTAINER) {
selectedItems.add(itemUnderMouse);
((GraphContainer) itemUnderMouse).highlight();
fireWidgetSelectedEvent(itemUnderMouse);
}
}
}
public void mouseReleased(org.eclipse.draw2d.MouseEvent me) {
isDragging = false;
}
}
/* (non-Javadoc)
* @see org.eclipse.swt.widgets.Widget#notifyListeners(int, org.eclipse.swt.widgets.Event)
*/
public void notifyListeners(int eventType, Event event) {
if (eventType == SWT.Selection && event != null) {
notifySelectionListeners(new SelectionEvent(event));
}
}
private void clearSelection() {
Iterator iterator = new ArrayList(selectedItems).iterator();
while (iterator.hasNext()) {
deselect((GraphItem) iterator.next());
}
}
private void fireWidgetSelectedEvent(Item item) {
Event swtEvent = new Event();
swtEvent.item = item;
swtEvent.widget = this;
notifySelectionListeners(new SelectionEvent(swtEvent));
}
private void notifySelectionListeners(SelectionEvent event) {
Iterator iterator = selectionListeners.iterator();
while (iterator.hasNext()) {
((SelectionListener) iterator.next()).widgetSelected(event);
}
}
private void deselect(GraphItem item) {
selectedItems.remove(item);
item.unhighlight();
setNodeSelected(item, false);
}
private void select(GraphItem item) {
selectedItems.add(item);
item.highlight();
setNodeSelected(item, true);
}
private void setNodeSelected(GraphItem item, boolean selected) {
if (item instanceof GraphNode) {
((GraphNode) item).setSelected(selected);
}
}
/**
* Moves the edge to the highlight layer. This moves the edge above the
* nodes
*
* @param connection
*/
void highlightEdge(GraphConnection connection) {
IFigure figure = connection.getConnectionFigure();
if (figure != null && !connection.isHighlighted()) {
zestRootLayer.highlightConnection(figure);
}
}
/**
* Moves the edge from the edge feedback layer back to the edge layer
*
* @param graphConnection
*/
void unhighlightEdge(GraphConnection connection) {
IFigure figure = connection.getConnectionFigure();
if (figure != null && connection.isHighlighted()) {
zestRootLayer.unHighlightConnection(figure);
}
}
/**
* Moves the node onto the node feedback layer
*
* @param node
*/
void highlightNode(GraphNode node) {
IFigure figure = node.getNodeFigure();
if (figure != null && !node.isHighlighted()) {
zestRootLayer.highlightNode(figure);
}
}
/**
* Moves the node onto the node feedback layer
*
* @param node
*/
void highlightNode(GraphContainer node) {
IFigure figure = node.getNodeFigure();
if (figure != null && !node.isHighlighted()) {
zestRootLayer.highlightNode(figure);
}
}
/**
* Moves the node off the node feedback layer
*
* @param node
*/
void unhighlightNode(GraphContainer node) {
IFigure figure = node.getNodeFigure();
if (figure != null && node.isHighlighted()) {
zestRootLayer.unHighlightNode(figure);
}
}
/**
* Moves the node off the node feedback layer
*
* @param node
*/
void unhighlightNode(GraphNode node) {
IFigure figure = node.getNodeFigure();
if (figure != null && node.isHighlighted()) {
zestRootLayer.unHighlightNode(figure);
}
}
/**
* Converts the list of GraphModelConnection objects into an array and
* returns it.
*
* @return GraphModelConnection[]
*/
GraphConnection[] getConnectionsArray() {
GraphConnection[] connsArray = new GraphConnection[connections.size()];
connsArray = (GraphConnection[]) connections.toArray(connsArray);
return connsArray;
}
LayoutRelationship[] getConnectionsToLayout(List nodesToLayout) {
// @tag zest.bug.156528-Filters.follows : make sure not to layout
// filtered connections, if the style says so.
LayoutRelationship[] entities;
if (ZestStyles.checkStyle(style, ZestStyles.IGNORE_INVISIBLE_LAYOUT)) {
LinkedList connectionList = new LinkedList();
for (Iterator i = this.getConnections().iterator(); i.hasNext();) {
GraphConnection next = (GraphConnection) i.next();
if (next.isVisible() && nodesToLayout.contains(next.getSource()) && nodesToLayout.contains(next.getDestination())) {
connectionList.add(next.getLayoutRelationship());
}
}
entities = (LayoutRelationship[]) connectionList.toArray(new LayoutRelationship[] {});
} else {
LinkedList nodeList = new LinkedList();
for (Iterator i = this.getConnections().iterator(); i.hasNext();) {
GraphConnection next = (GraphConnection) i.next();
if (nodesToLayout.contains(next.getSource()) && nodesToLayout.contains(next.getDestination())) {
nodeList.add(next.getLayoutRelationship());
}
}
entities = (LayoutRelationship[]) nodeList.toArray(new LayoutRelationship[] {});
}
return entities;
}
LayoutEntity[] getNodesToLayout(List nodes) {
// @tag zest.bug.156528-Filters.follows : make sure not to layout
// filtered nodes, if the style says so.
LayoutEntity[] entities;
if (ZestStyles.checkStyle(style, ZestStyles.IGNORE_INVISIBLE_LAYOUT)) {
LinkedList nodeList = new LinkedList();
for (Iterator i = nodes.iterator(); i.hasNext();) {
GraphNode next = (GraphNode) i.next();
if (next.isVisible()) {
nodeList.add(next.getLayoutEntity());
}
}
entities = (LayoutEntity[]) nodeList.toArray(new LayoutEntity[] {});
} else {
LinkedList nodeList = new LinkedList();
for (Iterator i = nodes.iterator(); i.hasNext();) {
GraphNode next = (GraphNode) i.next();
nodeList.add(next.getLayoutEntity());
}
entities = (LayoutEntity[]) nodeList.toArray(new LayoutEntity[] {});
}
return entities;
}
void removeConnection(GraphConnection connection) {
IFigure figure = connection.getConnectionFigure();
PolylineConnection sourceContainerConnectionFigure = connection.getSourceContainerConnectionFigure();
PolylineConnection targetContainerConnectionFigure = connection.getTargetContainerConnectionFigure();
connection.removeFigure();
this.getConnections().remove(connection);
figure2ItemMap.remove(figure);
if (sourceContainerConnectionFigure != null) {
figure2ItemMap.remove(sourceContainerConnectionFigure);
}
if (targetContainerConnectionFigure != null) {
figure2ItemMap.remove(targetContainerConnectionFigure);
}
}
void removeNode(GraphNode node) {
IFigure figure = node.getNodeFigure();
if (figure.getParent() != null) {
if (figure.getParent() instanceof ZestRootLayer) {
((ZestRootLayer) figure.getParent()).removeNode(figure);
} else {
figure.getParent().remove(figure);
}
}
this.getNodes().remove(node);
if (this.getSelection() != null) {
this.getSelection().remove(node);
}
figure2ItemMap.remove(figure);
}
void addConnection(GraphConnection connection, boolean addToEdgeLayer) {
this.getConnections().add(connection);
if (addToEdgeLayer) {
zestRootLayer.addConnection(connection.getFigure());
}
}
/*
public void redraw() {
Iterator iterator = this.getConnections().iterator();
while (iterator.hasNext()) {
GraphConnection connection = (GraphConnection) iterator.next();
IFigure figure = connection.getFigure();
if (!zestRootLayer.getChildren().contains(figure)) {
if (true || false || false) {
zestRootLayer.addConnection(connection.getFigure());
}
}
}
iterator = this.getNodes().iterator();
while (iterator.hasNext()) {
GraphNode graphNode = (GraphNode) iterator.next();
IFigure figure = graphNode.getFigure();
if (!zestRootLayer.getChildren().contains(figure)) {
zestRootLayer.addNode(graphNode.getFigure());
}
}
super.redraw();
}
*/
void addNode(GraphNode node) {
this.getNodes().add(node);
zestRootLayer.addNode(node.getFigure());
}
void addNode(GraphContainer graphContainer) {
this.getNodes().add(graphContainer);
zestRootLayer.addNode(graphContainer.getFigure());
}
void registerItem(GraphItem item) {
if (item.getItemType() == GraphItem.NODE) {
IFigure figure = item.getFigure();
figure2ItemMap.put(figure, item);
} else if (item.getItemType() == GraphItem.CONNECTION) {
IFigure figure = item.getFigure();
figure2ItemMap.put(figure, item);
if (((GraphConnection) item).getSourceContainerConnectionFigure() != null) {
figure2ItemMap.put(((GraphConnection) item).getSourceContainerConnectionFigure(), item);
}
if (((GraphConnection) item).getTargetContainerConnectionFigure() != null) {
figure2ItemMap.put(((GraphConnection) item).getTargetContainerConnectionFigure(), item);
}
} else if (item.getItemType() == GraphItem.CONTAINER) {
IFigure figure = item.getFigure();
figure2ItemMap.put(figure, item);
} else {
throw new RuntimeException("Unknown item type: " + item.getItemType());
}
}
/*
/**
* Changes the figure for a particular node
*/
void changeNodeFigure(IFigure oldValue, IFigure newFigure, GraphNode graphItem) {
if (zestRootLayer.getChildren().contains(oldValue)) {
zestRootLayer.remove(oldValue);
figure2ItemMap.remove(oldValue);
}
figure2ItemMap.put(newFigure, graphItem);
zestRootLayer.add(newFigure);
}
/**
* Invoke all the constraint adapaters for this constraints
*
* @param object
* @param constraint
*/
void invokeConstraintAdapters(Object object, LayoutConstraint constraint) {
if (constraintAdapters == null) {
return;
}
Iterator iterator = this.constraintAdapters.iterator();
while (iterator.hasNext()) {
ConstraintAdapter constraintAdapter = (ConstraintAdapter) iterator.next();
constraintAdapter.populateConstraint(object, constraint);
}
}
private void applyLayoutInternal() {
hasPendingLayoutRequest = false;
if ((this.getNodes().size() == 0)) {
return;
}
int layoutStyle = 0;
if ((nodeStyle & ZestStyles.NODES_NO_LAYOUT_RESIZE) > 0) {
layoutStyle = LayoutStyles.NO_LAYOUT_NODE_RESIZING;
}
if (layoutAlgorithm == null) {
layoutAlgorithm = new TreeLayoutAlgorithm(layoutStyle);
}
layoutAlgorithm.setStyle(layoutAlgorithm.getStyle() | layoutStyle);
// calculate the size for the layout algorithm
Dimension d = this.getViewport().getSize();
d.width = d.width - 10;
d.height = d.height - 10;
if (this.preferredSize.width >= 0) {
d.width = preferredSize.width;
}
if (this.preferredSize.height >= 0) {
d.height = preferredSize.height;
}
if (d.isEmpty()) {
return;
}
LayoutRelationship[] connectionsToLayout = getConnectionsToLayout(nodes);
LayoutEntity[] nodesToLayout = getNodesToLayout(getNodes());
try {
if ((nodeStyle & ZestStyles.NODES_NO_LAYOUT_ANIMATION) == 0) {
Animation.markBegin();
}
layoutAlgorithm.applyLayout(nodesToLayout, connectionsToLayout, 0, 0, d.width, d.height, false, false);
if ((nodeStyle & ZestStyles.NODES_NO_LAYOUT_ANIMATION) == 0) {
Animation.run(ANIMATION_TIME);
}
getLightweightSystem().getUpdateManager().performUpdate();
} catch (InvalidLayoutConfiguration e) {
e.printStackTrace();
}
}
interface MyRunnable extends Runnable {
public boolean isVisible();
}
/**
* Adds a reveal listener to the view. Note: A reveal listener will only
* every be called ONCE!!! even if a view comes and goes. There is no remove
* reveal listener. This is used to defer some events until after the view
* is revealed.
*
* @param revealListener
*/
private void addRevealListener(final RevealListener revealListener) {
MyRunnable myRunnable = new MyRunnable() {
boolean isVisible;
public boolean isVisible() {
return this.isVisible;
}
public void run() {
isVisible = Graph.this.isVisible();
}
};
Display.getDefault().syncExec(myRunnable);
if (myRunnable.isVisible()) {
revealListener.revealed(this);
} else {
revealListeners.add(revealListener);
}
}
private ScalableFigure createLayers() {
rootlayer = new ScalableFreeformLayeredPane();
rootlayer.setLayoutManager(new FreeformLayout());
zestRootLayer = new ZestRootLayer();
zestRootLayer.setLayoutManager(new FreeformLayout());
fishEyeLayer = new ScalableFreeformLayeredPane();
fishEyeLayer.setLayoutManager(new FreeformLayout());
rootlayer.add(zestRootLayer);
rootlayer.add(fishEyeLayer);
zestRootLayer.addLayoutListener(LayoutAnimator.getDefault());
fishEyeLayer.addLayoutListener(LayoutAnimator.getDefault());
return rootlayer;
}
/**
* This removes the fisheye from the graph. It uses an animation to make the fisheye
* shrink, and then it finally clears the fisheye layer. This assumes that there
* is ever only 1 node on the fisheye layer at any time.
*
* @param fishEyeFigure The fisheye figure
* @param regularFigure The regular figure (i.e. the non fisheye version)
*/
void removeFishEye(final IFigure fishEyeFigure, final IFigure regularFigure, boolean animate) {
if (!fishEyeLayer.getChildren().contains(fishEyeFigure)) {
return;
}
if (animate) {
Animation.markBegin();
}
Rectangle bounds = regularFigure.getBounds().getCopy();
regularFigure.translateToAbsolute(bounds);
double scale = rootlayer.getScale();
fishEyeLayer.setScale(1 / scale);
fishEyeLayer.translateToRelative(bounds);
fishEyeLayer.translateFromParent(bounds);
fishEyeLayer.setConstraint(fishEyeFigure, bounds);
if (animate) {
Animation.run(FISHEYE_ANIMATION_TIME * 2);
}
this.getRootLayer().getUpdateManager().performUpdate();
fishEyeLayer.removeAll();
this.fisheyedFigure = null;
}
/**
* Replaces the old fisheye figure with a new one.
* @param oldFigure
* @param newFigure
*/
boolean replaceFishFigure(IFigure oldFigure, IFigure newFigure) {
if (this.fishEyeLayer.getChildren().contains(oldFigure)) {
Rectangle bounds = oldFigure.getBounds();
newFigure.setBounds(bounds);
//this.fishEyeLayer.getChildren().remove(oldFigure);
this.fishEyeLayer.remove(oldFigure);
this.fishEyeLayer.add(newFigure);
//this.fishEyeLayer.getChildren().add(newFigure);
//this.fishEyeLayer.invalidate();
//this.fishEyeLayer.repaint();
this.fisheyedFigure = newFigure;
return true;
}
return false;
}
/**
* Add a fisheye version of the node. This works by animating the change from the original node
* to the fisheyed one, and then placing the fisheye node on the fisheye layer.
* @param startFigure The original node
* @param endFigure The fisheye figure
* @param newBounds The final size of the fisheyed figure
*/
void fishEye(IFigure startFigure, IFigure endFigure, Rectangle newBounds, boolean animate) {
fishEyeLayer.removeAll();
fisheyedFigure = null;
if (animate) {
Animation.markBegin();
}
double scale = rootlayer.getScale();
fishEyeLayer.setScale(1 / scale);
fishEyeLayer.translateToRelative(newBounds);
fishEyeLayer.translateFromParent(newBounds);
Rectangle bounds = startFigure.getBounds().getCopy();
startFigure.translateToAbsolute(bounds);
//startFigure.translateToRelative(bounds);
fishEyeLayer.translateToRelative(bounds);
fishEyeLayer.translateFromParent(bounds);
endFigure.setLocation(bounds.getLocation());
endFigure.setSize(bounds.getSize());
fishEyeLayer.add(endFigure);
fishEyeLayer.setConstraint(endFigure, newBounds);
if (animate) {
Animation.run(FISHEYE_ANIMATION_TIME);
}
this.getRootLayer().getUpdateManager().performUpdate();
}
public Graph getGraph() {
// @tag refactor : Is this method really needed
return this.getGraphModel();
}
public int getItemType() {
return GraphItem.GRAPH;
}
GraphItem getGraphItem(IFigure figure) {
return (GraphItem) figure2ItemMap.get(figure);
}
}