| /******************************************************************************* |
| * Copyright 2005, 2011 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.viewers.internal; |
| |
| import java.util.ArrayList; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.TreeSet; |
| |
| import org.eclipse.draw2d.IFigure; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.SWTError; |
| import org.eclipse.swt.widgets.Widget; |
| import org.eclipse.zest.core.viewers.AbstractZoomableViewer; |
| import org.eclipse.zest.core.viewers.IGraphContentProvider; |
| import org.eclipse.zest.core.widgets.CGraphNode; |
| import org.eclipse.zest.core.widgets.ConstraintAdapter; |
| import org.eclipse.zest.core.widgets.Graph; |
| import org.eclipse.zest.core.widgets.GraphConnection; |
| import org.eclipse.zest.core.widgets.GraphContainer; |
| import org.eclipse.zest.core.widgets.GraphItem; |
| import org.eclipse.zest.core.widgets.GraphNode; |
| import org.eclipse.zest.core.widgets.IContainer; |
| import org.eclipse.zest.core.widgets.ZestStyles; |
| import org.eclipse.zest.layouts.LayoutAlgorithm; |
| |
| /** |
| * Abstraction of graph viewers to implement functionality used by all of them. |
| * Not intended to be implemented by clients. Use one of the provided children |
| * instead. |
| * |
| * @author Del Myers |
| */ |
| public abstract class AbstractStructuredGraphViewer extends |
| AbstractZoomableViewer { |
| /** |
| * Contains top-level styles for the entire graph. Set in the constructor. * |
| */ |
| private int graphStyle; |
| |
| /** |
| * Contains node-level styles for the graph. Set in setNodeStyle(). Defaults |
| * are used in the constructor. |
| */ |
| private int nodeStyle; |
| |
| /** |
| * Contains arc-level styles for the graph. Set in setConnectionStyle(). |
| * Defaults are used in the constructor. |
| */ |
| private int connectionStyle; |
| |
| private HashMap nodesMap = new HashMap(); |
| private HashMap connectionsMap = new HashMap(); |
| |
| /** |
| * The constraint adapters |
| */ |
| private List constraintAdapters = new ArrayList(); |
| |
| /** |
| * A simple graph comparator that orders graph elements based on their type |
| * (connection or node), and their unique object identification. |
| */ |
| private class SimpleGraphComparator implements Comparator { |
| TreeSet storedStrings; |
| |
| /** |
| * |
| */ |
| public SimpleGraphComparator() { |
| this.storedStrings = new TreeSet(); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) |
| */ |
| public int compare(Object arg0, Object arg1) { |
| if (arg0 instanceof GraphNode && arg1 instanceof GraphConnection) { |
| return 1; |
| } else if (arg0 instanceof GraphConnection |
| && arg1 instanceof GraphNode) { |
| return -1; |
| } |
| if (arg0.equals(arg1)) { |
| return 0; |
| } |
| return getObjectString(arg0).compareTo(getObjectString(arg1)); |
| } |
| |
| private String getObjectString(Object o) { |
| String s = o.getClass().getName() + "@" |
| + Integer.toHexString(o.hashCode()); |
| while (storedStrings.contains(s)) { |
| s = s + 'X'; |
| } |
| return s; |
| } |
| } |
| |
| protected AbstractStructuredGraphViewer(int graphStyle) { |
| this.graphStyle = graphStyle; |
| this.connectionStyle = SWT.NONE; |
| this.nodeStyle = SWT.NONE; |
| |
| } |
| |
| /** |
| * Sets the default style for nodes in this graph. Note: if an input is set |
| * on the viewer, a ZestException will be thrown. |
| * |
| * @param nodeStyle |
| * the style for the nodes. |
| * @see #ZestStyles |
| */ |
| public void setNodeStyle(int nodeStyle) { |
| if (getInput() != null) { |
| throw new SWTError(SWT.ERROR_UNSPECIFIED); |
| } |
| this.nodeStyle = nodeStyle; |
| } |
| |
| /** |
| * Sets the default style for connections in this graph. Note: if an input |
| * is set on the viewer, a ZestException will be thrown. |
| * |
| * @param connectionStyle |
| * the style for the connections. |
| * @see #ZestStyles |
| */ |
| public void setConnectionStyle(int connectionStyle) { |
| if (getInput() != null) { |
| throw new SWTError(SWT.ERROR_UNSPECIFIED); |
| } |
| if (!ZestStyles.validateConnectionStyle(connectionStyle)) { |
| throw new SWTError(SWT.ERROR_INVALID_ARGUMENT); |
| } |
| this.connectionStyle = connectionStyle; |
| } |
| |
| /** |
| * Returns the style set for the graph |
| * |
| * @return The style set of the graph |
| */ |
| public int getGraphStyle() { |
| return graphStyle; |
| } |
| |
| /** |
| * Returns the style set for the nodes. |
| * |
| * @return the style set for the nodes. |
| */ |
| public int getNodeStyle() { |
| return nodeStyle; |
| } |
| |
| public Graph getGraphControl() { |
| return (Graph) getControl(); |
| } |
| |
| /** |
| * @return the connection style. |
| */ |
| public int getConnectionStyle() { |
| return connectionStyle; |
| } |
| |
| /** |
| * Adds a new constraint adapter to the list of constraints |
| * |
| * @param constraintAdapter |
| */ |
| public void addConstraintAdapter(ConstraintAdapter constraintAdapter) { |
| this.constraintAdapters.add(constraintAdapter); |
| } |
| |
| /** |
| * Gets all the constraint adapters currently on the viewer |
| * |
| * @return |
| */ |
| public List getConstraintAdapters() { |
| return this.constraintAdapters; |
| } |
| |
| /** |
| * Sets the layout algorithm for this viewer. Subclasses may place |
| * restrictions on the algorithms that it accepts. |
| * |
| * @param algorithm |
| * the layout algorithm |
| * @param run |
| * true if the layout algorithm should be run immediately. This |
| * is a hint. |
| */ |
| public abstract void setLayoutAlgorithm(LayoutAlgorithm algorithm, |
| boolean run); |
| |
| /** |
| * Gets the current layout algorithm. |
| * |
| * @return the current layout algorithm. |
| */ |
| protected abstract LayoutAlgorithm getLayoutAlgorithm(); |
| |
| /** |
| * Equivalent to setLayoutAlgorithm(algorithm, false). |
| * |
| * @param algorithm |
| */ |
| public void setLayoutAlgorithm(LayoutAlgorithm algorithm) { |
| setLayoutAlgorithm(algorithm, false); |
| } |
| |
| public Object[] getNodeElements() { |
| return this.nodesMap.keySet().toArray(); |
| } |
| |
| public Object[] getConnectionElements() { |
| return this.connectionsMap.keySet().toArray(); |
| } |
| |
| HashMap getNodesMap() { |
| return this.nodesMap; |
| } |
| |
| GraphNode addGraphModelContainer(Object element) { |
| GraphNode node = this.getGraphModelNode(element); |
| if (node == null) { |
| node = new GraphContainer((Graph) getControl(), SWT.NONE); |
| this.nodesMap.put(element, node); |
| node.setData(element); |
| } |
| return node; |
| } |
| |
| GraphNode addGraphModelNode(IContainer container, Object element) { |
| GraphNode node = this.getGraphModelNode(element); |
| if (node == null) { |
| node = new GraphNode(container, SWT.NONE); |
| this.nodesMap.put(element, node); |
| node.setData(element); |
| } |
| return node; |
| } |
| |
| GraphNode addGraphModelNode(Object element, IFigure figure) { |
| GraphNode node = this.getGraphModelNode(element); |
| if (node == null) { |
| if (figure != null) { |
| node = new CGraphNode((Graph) getControl(), SWT.NONE, figure); |
| this.nodesMap.put(element, node); |
| node.setData(element); |
| } else { |
| node = new GraphNode((Graph) getControl(), SWT.NONE); |
| this.nodesMap.put(element, node); |
| node.setData(element); |
| } |
| } |
| return node; |
| } |
| |
| GraphConnection addGraphModelConnection(Object element, GraphNode source, |
| GraphNode target) { |
| GraphConnection connection = this.getGraphModelConnection(element); |
| if (connection == null) { |
| connection = new GraphConnection((Graph) getControl(), SWT.NONE, |
| source, target); |
| this.connectionsMap.put(element, connection); |
| connection.setData(element); |
| } |
| return connection; |
| |
| } |
| |
| GraphConnection getGraphModelConnection(Object obj) { |
| return (GraphConnection) this.connectionsMap.get(obj); |
| } |
| |
| GraphNode getGraphModelNode(Object obj) { |
| return (GraphNode) this.nodesMap.get(obj); |
| } |
| |
| void removeGraphModelConnection(Object obj) { |
| GraphConnection connection = (GraphConnection) connectionsMap.get(obj); |
| if (connection != null) { |
| connectionsMap.remove(obj); |
| if (!connection.isDisposed()) { |
| connection.dispose(); |
| } |
| } |
| } |
| |
| void removeGraphModelNode(Object obj) { |
| GraphNode node = (GraphNode) nodesMap.get(obj); |
| if (node != null) { |
| nodesMap.remove(obj); |
| if (!node.isDisposed()) { |
| node.dispose(); |
| } |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see |
| * org.eclipse.jface.viewers.StructuredViewer#internalRefresh(java.lang. |
| * Object) |
| */ |
| protected void internalRefresh(Object element) { |
| if (getInput() == null) { |
| return; |
| } |
| if (element == getInput()) { |
| getFactory().refreshGraph(getGraphControl()); |
| } else { |
| getFactory().refresh(getGraphControl(), element); |
| } |
| // After all the items are loaded, we call update to ensure drawing. |
| // This way the damaged area does not get too big if we start |
| // adding and removing more nodes |
| getGraphControl().getLightweightSystem().getUpdateManager() |
| .performUpdate(); |
| } |
| |
| protected void doUpdateItem(Widget item, Object element, boolean fullMap) { |
| if (item == getGraphControl()) { |
| getFactory().update(getNodesArray(getGraphControl())); |
| getFactory().update(getConnectionsArray(getGraphControl())); |
| } else if (item instanceof GraphItem) { |
| getFactory().update((GraphItem) item); |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see |
| * org.eclipse.jface.viewers.StructuredViewer#doFindInputItem(java.lang. |
| * Object) |
| */ |
| protected Widget doFindInputItem(Object element) { |
| |
| if (element == getInput() && element instanceof Widget) { |
| return (Widget) element; |
| } |
| return null; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see |
| * org.eclipse.jface.viewers.StructuredViewer#doFindItem(java.lang.Object) |
| */ |
| protected Widget doFindItem(Object element) { |
| Widget node = (Widget) nodesMap.get(element); |
| Widget connection = (Widget) connectionsMap.get(element); |
| return (node != null) ? node : connection; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jface.viewers.StructuredViewer#getSelectionFromWidget() |
| */ |
| protected List getSelectionFromWidget() { |
| List internalSelection = getWidgetSelection(); |
| LinkedList externalSelection = new LinkedList(); |
| for (Iterator i = internalSelection.iterator(); i.hasNext();) { |
| Object data = ((GraphItem) i.next()).getData(); |
| if (data != null) { |
| externalSelection.add(data); |
| } |
| } |
| return externalSelection; |
| } |
| |
| protected GraphItem[] /* GraphItem */findItems(List l) { |
| if (l == null) { |
| return new GraphItem[0]; |
| } |
| |
| ArrayList list = new ArrayList(); |
| Iterator iterator = l.iterator(); |
| |
| while (iterator.hasNext()) { |
| GraphItem w = (GraphItem) findItem(iterator.next()); |
| list.add(w); |
| } |
| return (GraphItem[]) list.toArray(new GraphItem[list.size()]); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see |
| * org.eclipse.jface.viewers.StructuredViewer#setSelectionToWidget(java. |
| * util.List, boolean) |
| */ |
| protected void setSelectionToWidget(List l, boolean reveal) { |
| Graph control = (Graph) getControl(); |
| List selection = new LinkedList(); |
| for (Iterator i = l.iterator(); i.hasNext();) { |
| Object obj = i.next(); |
| GraphNode node = (GraphNode) nodesMap.get(obj); |
| GraphConnection conn = (GraphConnection) connectionsMap.get(obj); |
| if (node != null) { |
| selection.add(node); |
| } |
| if (conn != null) { |
| selection.add(conn); |
| } |
| } |
| control.setSelection((GraphNode[]) selection |
| .toArray(new GraphNode[selection.size()])); |
| } |
| |
| /** |
| * Gets the internal model elements that are selected. |
| * |
| * @return |
| */ |
| protected List getWidgetSelection() { |
| Graph control = (Graph) getControl(); |
| return control.getSelection(); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jface.viewers.Viewer#inputChanged(java.lang.Object, |
| * java.lang.Object) |
| */ |
| protected void inputChanged(Object input, Object oldInput) { |
| IStylingGraphModelFactory factory = getFactory(); |
| factory.setConnectionStyle(getConnectionStyle()); |
| factory.setNodeStyle(getNodeStyle()); |
| |
| // Save the old map so we can set the size and position of any nodes |
| // that are the same |
| Map oldNodesMap = nodesMap; |
| Graph graph = (Graph) getControl(); |
| graph.setSelection(new GraphNode[0]); |
| |
| Iterator iterator = nodesMap.values().iterator(); |
| while (iterator.hasNext()) { |
| GraphNode node = (GraphNode) iterator.next(); |
| if (!node.isDisposed()) { |
| node.dispose(); |
| } |
| } |
| |
| iterator = connectionsMap.values().iterator(); |
| while (iterator.hasNext()) { |
| GraphConnection connection = (GraphConnection) iterator.next(); |
| if (!connection.isDisposed()) { |
| connection.dispose(); |
| } |
| } |
| |
| nodesMap = new HashMap(); |
| connectionsMap = new HashMap(); |
| |
| graph = factory.createGraphModel(graph); |
| |
| ((Graph) getControl()).setNodeStyle(getNodeStyle()); |
| ((Graph) getControl()).setConnectionStyle(getConnectionStyle()); |
| |
| // check if any of the pre-existing nodes are still present |
| // in this case we want them to keep the same location & size |
| for (Iterator iter = oldNodesMap.keySet().iterator(); iter.hasNext();) { |
| Object data = iter.next(); |
| GraphNode newNode = (GraphNode) nodesMap.get(data); |
| if (newNode != null) { |
| GraphNode oldNode = (GraphNode) oldNodesMap.get(data); |
| newNode.setLocation(oldNode.getLocation().x, |
| oldNode.getLocation().y); |
| if (oldNode.isSizeFixed()) { |
| newNode.setSize(oldNode.getSize().width, |
| oldNode.getSize().height); |
| } |
| } |
| } |
| |
| applyLayout(); |
| } |
| |
| /** |
| * Returns the factory used to create the model. This must not be called |
| * before the content provider is set. |
| * |
| * @return |
| */ |
| protected abstract IStylingGraphModelFactory getFactory(); |
| |
| protected void filterVisuals() { |
| if (getGraphControl() == null) { |
| return; |
| } |
| Object[] filtered = getFilteredChildren(getInput()); |
| SimpleGraphComparator comparator = new SimpleGraphComparator(); |
| TreeSet filteredElements = new TreeSet(comparator); |
| TreeSet unfilteredElements = new TreeSet(comparator); |
| List connections = getGraphControl().getConnections(); |
| List nodes = getGraphControl().getNodes(); |
| if (filtered.length == 0) { |
| // set everything to invisible. |
| // @tag zest.bug.156528-Filters.check : should we only filter out |
| // the nodes? |
| for (Iterator i = connections.iterator(); i.hasNext();) { |
| GraphConnection c = (GraphConnection) i.next(); |
| c.setVisible(false); |
| } |
| for (Iterator i = nodes.iterator(); i.hasNext();) { |
| GraphNode n = (GraphNode) i.next(); |
| n.setVisible(false); |
| } |
| return; |
| } |
| for (Iterator i = connections.iterator(); i.hasNext();) { |
| GraphConnection c = (GraphConnection) i.next(); |
| if (c.getExternalConnection() != null) { |
| unfilteredElements.add(c); |
| } |
| } |
| for (Iterator i = nodes.iterator(); i.hasNext();) { |
| GraphNode n = (GraphNode) i.next(); |
| if (n.getData() != null) { |
| unfilteredElements.add(n); |
| } |
| } |
| for (int i = 0; i < filtered.length; i++) { |
| Object modelElement = connectionsMap.get(filtered[i]); |
| if (modelElement == null) { |
| modelElement = nodesMap.get(filtered[i]); |
| } |
| if (modelElement != null) { |
| filteredElements.add(modelElement); |
| } |
| } |
| unfilteredElements.removeAll(filteredElements); |
| // set all the elements that did not pass the filters to invisible, and |
| // all the elements that passed to visible. |
| while (unfilteredElements.size() > 0) { |
| GraphItem i = (GraphItem) unfilteredElements.first(); |
| i.setVisible(false); |
| unfilteredElements.remove(i); |
| } |
| while (filteredElements.size() > 0) { |
| GraphItem i = (GraphItem) filteredElements.first(); |
| i.setVisible(true); |
| filteredElements.remove(i); |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see |
| * org.eclipse.jface.viewers.StructuredViewer#getRawChildren(java.lang.Object |
| * ) |
| */ |
| protected Object[] getRawChildren(Object parent) { |
| if (parent == getInput()) { |
| // get the children from the model. |
| LinkedList children = new LinkedList(); |
| if (getGraphControl() != null) { |
| List connections = getGraphControl().getConnections(); |
| List nodes = getGraphControl().getNodes(); |
| for (Iterator i = connections.iterator(); i.hasNext();) { |
| GraphConnection c = (GraphConnection) i.next(); |
| if (c.getExternalConnection() != null) { |
| children.add(c.getExternalConnection()); |
| } |
| } |
| for (Iterator i = nodes.iterator(); i.hasNext();) { |
| GraphNode n = (GraphNode) i.next(); |
| if (n.getData() != null) { |
| children.add(n.getData()); |
| } |
| } |
| return children.toArray(); |
| } |
| } |
| return super.getRawChildren(parent); |
| } |
| |
| /** |
| * |
| */ |
| public void reveal(Object element) { |
| Widget[] items = this.findItems(element); |
| for (int i = 0; i < items.length; i++) { |
| Widget item = items[i]; |
| if (item instanceof GraphNode) { |
| GraphNode graphModelNode = (GraphNode) item; |
| graphModelNode.highlight(); |
| } else if (item instanceof GraphConnection) { |
| GraphConnection graphModelConnection = (GraphConnection) item; |
| graphModelConnection.highlight(); |
| } |
| } |
| } |
| |
| public void unReveal(Object element) { |
| Widget[] items = this.findItems(element); |
| for (int i = 0; i < items.length; i++) { |
| Widget item = items[i]; |
| if (item instanceof GraphNode) { |
| GraphNode graphModelNode = (GraphNode) item; |
| graphModelNode.unhighlight(); |
| } else if (item instanceof GraphConnection) { |
| GraphConnection graphModelConnection = (GraphConnection) item; |
| graphModelConnection.unhighlight(); |
| } |
| } |
| } |
| |
| /** |
| * Applies the viewers layouts. |
| * |
| */ |
| public abstract void applyLayout(); |
| |
| /** |
| * Removes the given connection object from the layout algorithm and the |
| * model. |
| * |
| * @param connection |
| */ |
| public void removeRelationship(Object connection) { |
| GraphConnection relationship = (GraphConnection) connectionsMap |
| .get(connection); |
| |
| if (relationship != null) { |
| // remove the relationship from the layout algorithm |
| if (getLayoutAlgorithm() != null) { |
| getLayoutAlgorithm().removeRelationship( |
| relationship.getLayoutRelationship()); |
| } |
| // remove the relationship from the model |
| relationship.dispose(); |
| } |
| } |
| |
| /** |
| * Creates a new node and adds it to the graph. If it already exists nothing |
| * happens. |
| * |
| * @param newNode |
| */ |
| public void addNode(Object element) { |
| if (nodesMap.get(element) == null) { |
| // create the new node |
| getFactory().createNode(getGraphControl(), element); |
| |
| } |
| } |
| |
| /** |
| * Removes the given element from the layout algorithm and the model. |
| * |
| * @param element |
| * The node element to remove. |
| */ |
| public void removeNode(Object element) { |
| GraphNode node = (GraphNode) nodesMap.get(element); |
| |
| if (node != null) { |
| // remove the node from the layout algorithm and all the connections |
| if (getLayoutAlgorithm() != null) { |
| getLayoutAlgorithm().removeEntity(node.getLayoutEntity()); |
| getLayoutAlgorithm().removeRelationships( |
| node.getSourceConnections()); |
| getLayoutAlgorithm().removeRelationships( |
| node.getTargetConnections()); |
| } |
| // remove the node and it's connections from the model |
| node.dispose(); |
| } |
| } |
| |
| /** |
| * Creates a new relationship between the source node and the destination |
| * node. If either node doesn't exist then it will be created. |
| * |
| * @param connection |
| * The connection data object. |
| * @param srcNode |
| * The source node data object. |
| * @param destNode |
| * The destination node data object. |
| */ |
| public void addRelationship(Object connection, Object srcNode, |
| Object destNode) { |
| // create the new relationship |
| IStylingGraphModelFactory modelFactory = getFactory(); |
| modelFactory.createConnection(getGraphControl(), connection, srcNode, |
| destNode); |
| |
| } |
| |
| /** |
| * Adds a new relationship given the connection. It will use the content |
| * provider to determine the source and destination nodes. |
| * |
| * @param connection |
| * The connection data object. |
| */ |
| public void addRelationship(Object connection) { |
| IStylingGraphModelFactory modelFactory = getFactory(); |
| if (connectionsMap.get(connection) == null) { |
| if (modelFactory.getContentProvider() instanceof IGraphContentProvider) { |
| IGraphContentProvider content = ((IGraphContentProvider) modelFactory |
| .getContentProvider()); |
| Object source = content.getSource(connection); |
| Object dest = content.getDestination(connection); |
| // create the new relationship |
| modelFactory.createConnection(getGraphControl(), connection, |
| source, dest); |
| } else { |
| throw new UnsupportedOperationException(); |
| } |
| } |
| } |
| |
| /** |
| * Converts the list of GraphModelConnection objects into an array and |
| * returns it. |
| * |
| * @return GraphModelConnection[] |
| */ |
| protected GraphConnection[] getConnectionsArray(Graph graph) { |
| GraphConnection[] connsArray = new GraphConnection[graph |
| .getConnections().size()]; |
| connsArray = (GraphConnection[]) graph.getConnections().toArray( |
| connsArray); |
| return connsArray; |
| } |
| |
| /** |
| * Converts the list of GraphModelNode objects into an array an returns it. |
| * |
| * @return GraphModelNode[] |
| */ |
| protected GraphNode[] getNodesArray(Graph graph) { |
| GraphNode[] nodesArray = new GraphNode[graph.getNodes().size()]; |
| nodesArray = (GraphNode[]) graph.getNodes().toArray(nodesArray); |
| return nodesArray; |
| } |
| |
| } |