package sonumina.swt.grappa; /************************************************************* $Id: GraphCanvas.java 154 2007-09-17 13:22:27Z sba $ This class is copyright 2006 by Sebastian Bauer and released under the terms of the CPL. See http://www.opensource.org/licenses/cpl.php for more information. *************************************************************/ import java.awt.geom.PathIterator; import java.io.File; import java.io.FileInputStream; import java.util.HashMap; import java.util.Map.Entry; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseListener; 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.graphics.GC; import org.eclipse.swt.graphics.Path; import org.eclipse.swt.graphics.Pattern; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Transform; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.swt.widgets.ScrollBar; import org.eclipse.swt.widgets.TypedListener; import att.grappa.Edge; import att.grappa.Element; import att.grappa.Graph; import att.grappa.GraphEnumeration; import att.grappa.GrappaConstants; import att.grappa.GrappaLine; import att.grappa.GrappaPoint; import att.grappa.GrappaStyle; import att.grappa.Node; import att.grappa.Parser; import att.grappa.Subgraph; /** * A canvas displaying a graph. * * @author Sebastian Bauer */ public class GraphCanvas extends Canvas { private Graph g; private HashMap node2Path; private Node selectedNode; private boolean scaleToFit; private float scale; private float xoffset; private float yoffset; private MenuItem zoomInItem; private MenuItem zoomOutItem; private MenuItem zoomReset; private MenuItem scaleToFitItem; private ScrollBar horizontalScrollBar; private ScrollBar verticalScrollBar; private Canvas thisCanvas; /** * Constructs a new graph canvas. * * @param parent * @param style */ public GraphCanvas(Composite parent, int style) { super(parent, style | SWT.NO_BACKGROUND | SWT.DOUBLE_BUFFERED | SWT.HORIZONTAL | SWT.VERTICAL); scaleToFit = true; horizontalScrollBar = getHorizontalBar(); verticalScrollBar = getVerticalBar(); thisCanvas = this; g = new Graph("empty"); prepareGraph(); /* Hide the scrollbars */ horizontalScrollBar.setVisible(false); verticalScrollBar.setVisible(false); /* Add our control listener */ addControlListener(new ControlAdapter() { @Override public void controlResized(ControlEvent e) { if (scaleToFit) updateTransformation(); updateScrollers(); } }); /* Add our mouse listener */ addMouseListener(new MouseAdapter() { @Override public void mouseDown(MouseEvent e) { /* Build the inverted transformation */ Transform transform = buildTransform(); transform.invert(); float [] points = new float[2]; points[0] = (float)e.x; points[1] = (float)e.y; transform.transform(points); float [] bounds = new float[4]; for (Entry entry : node2Path.entrySet()) { Path p = entry.getValue(); p.getBounds(bounds); if (points[0] > bounds[0] && points[0] < bounds[0] + bounds[2] && points[1] > bounds[1] && points[1] < bounds[1] + bounds[3]) { selectedNode = entry.getKey(); redraw(); /* emit event */ Event ev = new Event(); ev.widget = thisCanvas; ev.type = SWT.Selection; ev.text = getNameOfCurrentSelectedNode(); notifyListeners(SWT.Selection, ev); break; } } } }); /* Add our paint listener */ addPaintListener(new PaintListener() { public void paintControl(PaintEvent e) { GC gc = e.gc; gc.setAntialias(SWT.ON); gc.setBackground(getDisplay().getSystemColor(SWT.COLOR_WHITE)); gc.fillRectangle(getClientArea()); Transform transform = buildTransform(); gc.setTransform(transform); paintElement(g,gc); transform.dispose(); } void paintElement(Element e, GC gc) { switch (e.getType()) { case GrappaConstants.NODE: paintNode((Node)e,gc); break; case GrappaConstants.EDGE: paintEdge((Edge)e,gc); break; case GrappaConstants.SUBGRAPH: { GraphEnumeration en = ((Subgraph)e).elements(); while (en.hasMoreElements()) { Element ne = en.nextGraphElement(); if (e != ne) paintElement(ne,gc); } } break; } } /** * Paint the given edge into gc. * * @param e * @param gc */ void paintEdge(Edge e, GC gc) { GrappaLine line = (GrappaLine)e.getAttributeValue(Edge.POS_ATTR); java.awt.Color col = (java.awt.Color)e.getAttributeValue(Edge.COLOR_ATTR); Color swtCol = null; Color oldCol = null; if (col != null) { swtCol = new Color(getDisplay(),col.getRed(),col.getGreen(),col.getBlue()); oldCol = gc.getForeground(); gc.setForeground(swtCol); } PathIterator iter = line.getPathIterator(); Path path = pathIterator2Path(iter); gc.drawPath(path); path.dispose(); if (swtCol != null) { gc.setForeground(oldCol); swtCol.dispose(); } } /** * Paint the given node into gc. * * @param node * @param gc */ void paintNode(Node node, GC gc) { GrappaPoint center = node.getCenterPoint(); int x = (int)center.x; int y = (int)center.y; double w = (Double)node.getAttributeValue(Node.WIDTH_ATTR); double h = (Double)node.getAttributeValue(Node.HEIGHT_ATTR); String label = (String)node.getAttributeValue(Node.LABEL_ATTR); GrappaStyle style = (GrappaStyle)node.getAttributeValue(Node.STYLE_ATTR); java.awt.Color fillColorAWT = (java.awt.Color)node.getAttributeValue(Node.FILLCOLOR_ATTR); int shape = (Integer)node.getAttributeValue(Node.SHAPE_ATTR); /* Here, color is used for the first color of the gradient fill only */ java.awt.Color colorAWT = (java.awt.Color)node.getAttributeValue(Node.COLOR_ATTR); w *= 72; h *= 72; Path nodePath = node2Path.get(node); assert(nodePath != null); if (shape != GrappaConstants.PLAINTEXT_SHAPE) { if (fillColorAWT != null) { boolean gradient_fill = style.getGradientFill(); Color oldFillColor = gc.getBackground(); Color fillColor = new Color(getDisplay(),fillColorAWT.getRed(),fillColorAWT.getGreen(),fillColorAWT.getBlue()); Color color = null; gc.setBackground(fillColor); Pattern oldPat = gc.getBackgroundPattern(); Pattern pat = null; if (gradient_fill) { color = new Color(getDisplay(),colorAWT.getRed(),colorAWT.getGreen(),colorAWT.getBlue()); pat = new Pattern(getDisplay(),0,y - (float)h / 2,0,y+(float)h/2,color,fillColor); gc.setBackgroundPattern(pat); } gc.fillPath(nodePath); gc.setBackground(oldFillColor); if (gradient_fill) { gc.setBackgroundPattern(oldPat); pat.dispose(); color.dispose(); } fillColor.dispose(); } int oldLineWidth = gc.getLineWidth(); gc.setLineWidth((int)(style.getLineWidth())); if (node == selectedNode) { gc.setForeground(getDisplay().getSystemColor(SWT.COLOR_RED)); gc.setLineWidth(3); } else gc.setForeground(getDisplay().getSystemColor(SWT.COLOR_BLACK)); gc.drawPath(nodePath); gc.setLineWidth(oldLineWidth); if (node == selectedNode) gc.setForeground(getDisplay().getSystemColor(SWT.COLOR_BLACK)); } if (label != null) { String [] labelArray = label.split("\\\\n"); Point [] pointArray = new Point[labelArray.length]; int height = 0; for (int i=0;i(); prepareElement(g); } /** * Prepare the given element. * * @param e */ private void prepareElement(Element e) { switch (e.getType()) { case GrappaConstants.NODE: prepareNode((Node)e); break; case GrappaConstants.SUBGRAPH: { GraphEnumeration en = ((Subgraph)e).elements(); while (en.hasMoreElements()) { Element ne = en.nextGraphElement(); if (e != ne) prepareElement(ne); } } break; } } /** * Prepare the given node for displaying. * * @param node */ private void prepareNode(Node node) { GrappaPoint center = node.getCenterPoint(); int x = (int)center.x; int y = (int)center.y; double w = (Double)node.getAttributeValue(Node.WIDTH_ATTR); double h = (Double)node.getAttributeValue(Node.HEIGHT_ATTR); int shape = (Integer)node.getAttributeValue(Node.SHAPE_ATTR); w *= 72; h *= 72; Path nodePath = new Path(getDisplay()); if (shape != GrappaConstants.PLAINTEXT_SHAPE) nodePath.addArc((float)(x - w/2), (float)(y-h/2), (float)w, (float)h, 0f, 360f); node2Path.put(node,nodePath); } /** * Converts a java 2d path iterator to a SWT path. * * @param iter * @return */ private Path pathIterator2Path(PathIterator iter) { float [] coords = new float[6]; Path path = new Path(getDisplay()); while (!iter.isDone()) { int type = iter.currentSegment(coords); switch (type) { case PathIterator.SEG_MOVETO: path.moveTo(coords[0], coords[1]); break; case PathIterator.SEG_LINETO: path.lineTo(coords[0],coords[1]); break; case PathIterator.SEG_CLOSE: path.close(); break; case PathIterator.SEG_QUADTO: path.quadTo(coords[0],coords[1],coords[2],coords[3]); break; case PathIterator.SEG_CUBICTO: path.cubicTo(coords[0], coords[1],coords[2],coords[3],coords[4],coords[5]); break; } iter.next(); } return path; } /** * Short cut for getting the width of the displayed graph. * * @return */ private float getGraphWidth() { return (float)(g.getBoundingBox().getMaxX() - g.getBoundingBox().getMinX() + 1); } /** * Short cut for getting the height of the displayed graph. * * @return */ private float getGraphHeight() { return (float)(g.getBoundingBox().getMaxY() - g.getBoundingBox().getMinY() + 1); } /** * Updates transformation for scale to fit. */ private void updateTransformation() { /* Real graph Dimensions */ float graphWidth = getGraphWidth(); float graphHeight = getGraphHeight(); /* Zoom factor */ scale = Math.min(((float)getClientArea().width)/graphWidth,((float)getClientArea().height)/graphHeight)/1.005f; xoffset = 0; yoffset = 0; } /** * Updates the scroller (i.e. if they are visible and their values) */ private void updateScrollers() { getHorizontalBar().setValues((int)xoffset,0,(int)(getGraphWidth()*scale),getClientArea().width,1,getClientArea().width-1); getVerticalBar().setValues((int)yoffset,0,(int)(getGraphHeight()*scale),getClientArea().height,1,getClientArea().height-1); if (getGraphWidth() * scale > getClientArea().width) getHorizontalBar().setVisible(true); else getHorizontalBar().setVisible(false); if (getGraphHeight() * scale > getClientArea().height) getVerticalBar().setVisible(true); else getVerticalBar().setVisible(false); } /** * Returns the name of the currently selected Node. * * @return */ public String getNameOfCurrentSelectedNode() { if (selectedNode != null) return selectedNode.getName(); return null; } /** * Selectes the node with the given name. Other nodes are deselected. * * @param name */ public void selectNode(String name) { Node node = g.findNodeByName(name); if (node != selectedNode) { selectedNode = node; redraw(); } } public void setLayoutedDotFile(File file) throws Exception { Parser parser = new Parser(new FileInputStream(file), System.err); parser.parse(); selectedNode = null; cleanupGraph(); g = parser.getGraph(); g.setEditable(false); prepareGraph(); updateTransformation(); redraw(); } /** * Sets a new magnification. Ensures that the center point stays the same. * * @param newScale */ private void setScale(float newScale) { scaleToFit = false; scaleToFitItem.setSelection(false); /* Update the xoffset and yoffsets as follow: Deterimine the current * center and adapt the offsets after scaling to match the center. */ float centerX = (Math.min((float)getClientArea().width,getGraphWidth()*scale) / 2f + xoffset) / scale; float centerY = (Math.min((float)getClientArea().height,getGraphHeight()*scale) / 2f + yoffset) / scale; scale = newScale; xoffset = centerX * scale - Math.min((float)getClientArea().width,getGraphWidth()*scale) / 2f; yoffset = centerY * scale - Math.min((float)getClientArea().height,getGraphHeight()*scale) / 2f; updateScrollers(); redraw(); } /** * Zoom out. */ public void zoomOut() { setScale(scale / 1.5f); } /** * Zoom in. */ public void zoomIn() { setScale(scale * 1.5f); } /** * Reset the zoom. */ public void zoomReset() { setScale(1.0f); } /** * Set whether the magnification should be chosen such that, the complete * graph is visible (also after resizing). * * @param selection */ public void setScaleToFit(boolean selection) { this.scaleToFit = selection; if (selection) { updateTransformation(); updateScrollers(); redraw(); } } /** * Add a new selection listener. The text field is used * for the node's name. * * @param sel */ public void addSelectionListener(SelectionListener sel) { TypedListener listener = new TypedListener(sel); addListener(SWT.Selection, listener); } public void addMouseListener(MouseListener mouse) { TypedListener typedListener = new TypedListener(mouse); addListener(SWT.MouseDown, typedListener); addListener(SWT.MouseUp, typedListener); addListener(SWT.MouseDoubleClick, typedListener); } /** * Builds an transformation from the graph coordinate system to * the display coordinate system. * * @return */ private Transform buildTransform() { Transform transform = new Transform(getDisplay()); float alignedXOffset = -xoffset; float alignedYOffset = getClientArea().height - 2 - yoffset; if (getGraphWidth() * scale < getClientArea().width) alignedXOffset += (getClientArea().width - getGraphWidth() * scale)/2; if (getGraphHeight() * scale < getClientArea().height) alignedYOffset -= (getClientArea().height - getGraphHeight() * scale)/2; else { alignedYOffset += getGraphHeight() * scale - getClientArea().height; } transform.translate(alignedXOffset, alignedYOffset); transform.scale(scale,scale); return transform; } }