/* * $Id$ * * Copyright (c) 1998-2005 The Regents of the University of California. * All rights reserved. See the file COPYRIGHT for details. */ package ptolemy.apps.vergil.graph; import ptolemy.kernel.*; import ptolemy.kernel.util.*; import diva.canvas.CanvasComponent; import diva.canvas.CompositeFigure; import diva.canvas.Figure; import diva.canvas.FigureLayer; import diva.canvas.GraphicsPane; import diva.canvas.Site; import diva.canvas.connector.AutonomousSite; import diva.canvas.connector.CenterSite; import diva.canvas.connector.Connector; import diva.canvas.connector.ConnectorAdapter; import diva.canvas.connector.ConnectorEvent; import diva.canvas.connector.ConnectorListener; import diva.canvas.connector.ConnectorManipulator; import diva.canvas.connector.ConnectorTarget; import diva.canvas.connector.PerimeterSite; import diva.canvas.connector.PerimeterTarget; import diva.canvas.connector.Terminal; import diva.canvas.event.LayerAdapter; import diva.canvas.event.LayerEvent; import diva.canvas.event.MouseFilter; import diva.canvas.interactor.AbstractInteractor; import diva.canvas.interactor.BasicSelectionRenderer; import diva.canvas.interactor.GrabHandle; import diva.canvas.interactor.Interactor; import diva.canvas.interactor.SelectionDragger; import diva.canvas.interactor.SelectionInteractor; import diva.canvas.interactor.SelectionModel; import diva.graph.*; import diva.util.Filter; import java.awt.event.InputEvent; import java.awt.geom.*; import java.util.HashMap; import java.util.Iterator; import java.util.List; /** * A basic implementation of GraphController, which works with * simple graphs that have edges connecting simple nodes. It * sets up some simple interaction on its view's pane. * * @author Michael Shilman * @version $Revision$ * @Pt.AcceptedRating Red */ public class MetaEdgeController extends CompositeEntity implements EdgeController { /** The selection interactor for drag-selecting nodes */ private SelectionDragger _selectionDragger; /** The connector target */ private ConnectorTarget _connectorTarget; /** The filter for control operations */ private MouseFilter _controlFilter = new MouseFilter(InputEvent.BUTTON1_MASK, InputEvent.CTRL_MASK); /** * Create a new edge controller with basic interaction. Specifically, * this method creates an edge interactor and initializes its menipulator * so that edges get attached appropriately. Furthermore, the edge * interactor is initialized with the selection model of the graph * controller. The manipulator is activated by either a regular click * or a control click. Also initialize a basic connector target that * generally attaches to the perimeter of nodes, except that it is * smart enough to properly handle terminals. */ public MetaEdgeController(CompositeEntity container, String name) throws IllegalActionException, NameDuplicationException { super(container, name); //SelectionModel sm = getController().getSelectionModel(); setEdgeInteractor(new MetaInteractor(this, "_interactor")); // Create and set up the manipulator for connectors // ConnectorManipulator manipulator = new ConnectorManipulator(); //manipulator.setSnapHalo(4.0); //manipulator.addConnectorListener(new EdgeDropper()); //getEdgeInteractor().setPrototypeDecorator(manipulator); // The mouse filter needs to accept regular click or control click // MouseFilter handleFilter = new MouseFilter(1, 0, 0); //manipulator.setHandleFilter(handleFilter); // Create and set up the target for connectors /* PerimeterTarget ct = new PerimeterTarget() { // Accept the head if the model graph model allows it. public boolean acceptHead (Connector c, Figure f) { Object node = f.getUserObject(); Object edge = c.getUserObject(); MutableGraphModel model = (MutableGraphModel)getController().getGraphModel(); if (model.isNode(node) && model.isEdge(edge) && model.acceptHead(edge, node)) { return super.acceptHead(c, f); } else return false; } // Accept the tail if the model graph model allows it. public boolean acceptTail (Connector c, Figure f) { Object node = f.getUserObject(); Object edge = c.getUserObject(); MutableGraphModel model = (MutableGraphModel)getController().getGraphModel(); if (model.isNode(node) && model.isEdge(edge) && model.acceptTail(edge, node)) { return super.acceptTail(c, f); } else return false; } // If we have any terminals, then return the connection // site of the terminal instead of a new perimeter site. public Site getHeadSite(Figure f, double x, double y) { if (f instanceof Terminal) { Site site = ((Terminal)f).getConnectSite(); return site; } else { return super.getHeadSite(f, x, y); } } }; setConnectorTarget(ct); */ } /** Add an edge to this graph editor and render it * from the given tail node to an autonomous site at the * given location. Give the new edge the given semanticObject. * The "end" flag is either HEAD_END * or TAIL_END, from diva.canvas.connector.ConnectorEvent. * @return The new edge. * @exception GraphException If the connector target cannot return a * valid site on the node's figure. */ public void addEdge(Object edge, Object node, int end, double x, double y) { MutableGraphModel model = (MutableGraphModel) getController() .getGraphModel(); Figure nf = getController().getFigure(node); FigureLayer layer = getController().getGraphPane().getForegroundLayer(); Site headSite; Site tailSite; // Temporary sites. One of these will get blown away later. headSite = new AutonomousSite(layer, x, y); tailSite = new AutonomousSite(layer, x, y); // Render the edge. Connector c = render(edge, layer, tailSite, headSite); try { //Attach the appropriate end of the edge to the node. if (end == ConnectorEvent.TAIL_END) { tailSite = getConnectorTarget().getTailSite(c, nf, x, y); if (tailSite == null) { throw new RuntimeException("Invalid connector target: " + "no valid site found for tail of new connector."); } model.setEdgeTail(getController(), edge, node); c.setTailSite(tailSite); } else { headSite = getConnectorTarget().getHeadSite(c, nf, x, y); if (headSite == null) { throw new RuntimeException("Invalid connector target: " + "no valid site found for head of new connector."); } model.setEdgeHead(getController(), edge, node); c.setHeadSite(headSite); } } catch (GraphException ex) { // If an error happened then blow away the edge, and rethrow // the exception removeEdge(edge); throw ex; } } /** * Add an edge to this graph between the given tail and head * nodes. Give the new edge the given semanticObject. */ public void addEdge(Object edge, Object tail, Object head) { // Connect the edge MutableGraphModel model = (MutableGraphModel) getController() .getGraphModel(); model.connectEdge(getController(), edge, tail, head); drawEdge(edge); } /** * Remove the figure for the given edge, but do not remove the * edge from the graph model. */ public void clearEdge(Object edge) { Figure f = getController().getFigure(edge); if (f != null) { CanvasComponent container = f.getParent(); f.setUserObject(null); getController().setFigure(edge, null); if (container instanceof FigureLayer) { ((FigureLayer) container).remove(f); } else if (container instanceof CompositeFigure) { ((CompositeFigure) container).remove(f); } } } /** * Draw the edge and add it to the layer, establishing * a two-way correspondence between the model and the * view. If the edge already has been associated with some figure in * the view, then use any information in that figure to help draw the * edge. */ public Figure drawEdge(Object edge) { GraphModel model = getController().getGraphModel(); FigureLayer layer = getController().getGraphPane().getForegroundLayer(); Object tail = model.getTail(edge); Object head = model.getHead(edge); Connector connector = (Connector) getController().getFigure(edge); Figure tailFigure = getController().getFigure(tail); Figure headFigure = getController().getFigure(head); Site tailSite; Site headSite; // If the tail is not attached, if (tailFigure == null) { // Then try to find the old tail site. if (connector != null) { tailSite = connector.getTailSite(); } else { // FIXME try to manufacture a site. //throw new RuntimeException("drawEdge failed: could not find" + // " a tail site."); return null; } } else { // Get a new tail site based on the tail figure. Rectangle2D bounds = tailFigure.getBounds(); tailSite = getConnectorTarget().getTailSite(tailFigure, bounds.getCenterX(), bounds.getCenterY()); } // If the head is not attached, if (headFigure == null) { // Then try to find the old head site. if (connector != null) { headSite = connector.getHeadSite(); } else { // FIXME try to manufacture a site. //throw new RuntimeException("drawEdge failed: could not find" + // " a head site."); return null; } } else { // Get a new head site based on the head figure. Rectangle2D bounds = headFigure.getBounds(); headSite = getConnectorTarget().getHeadSite(headFigure, bounds.getCenterX(), bounds.getCenterY()); } // If we did have an old figure, throw it away. if (connector != null) { clearEdge(edge); } // Create the figure Connector c = render(edge, layer, tailSite, headSite); getController().dispatch(new GraphViewEvent(this, GraphViewEvent.EDGE_DRAWN, edge)); return c; } /** * Get the target used to find sites on nodes to connect to. */ public ConnectorTarget getConnectorTarget() { return _connectorTarget; } /** * Get the graph controller that this controller is contained in. */ public GraphController getController() { return (GraphController) getContainer(); } /** * Get the interactor given to edge figures. */ public Interactor getEdgeInteractor() { List list = entityList(Interactor.class); if (list.size() > 0) { return (Interactor) list.get(0); } return null; } /** * Return the edge renderer for this view. */ public EdgeRenderer getEdgeRenderer() { List list = entityList(NodeRenderer.class); if (list.size() > 0) { return (EdgeRenderer) list.get(0); } return null; } /** * Initialize all interaction on the graph pane. This method * is called by the setGraphPane() method of the superclass. * This initialization cannot be done in the constructor because * the controller does not yet have a reference to its pane * at that time. */ protected void initializeInteraction() { GraphPane pane = getController().getGraphPane(); } /** * Remove the edge. */ public void removeEdge(Object edge) { clearEdge(edge); MutableGraphModel model = (MutableGraphModel) getController() .getGraphModel(); model.setEdgeHead(getController(), edge, null); model.setEdgeTail(getController(), edge, null); getController().getGraphPane().repaint(); } /** * Set the target used to find sites on nodes to connect to. This * sets the local connector target (which is often used to find the * starting point of an edge) and the manipulator's connector target, which * is used after the connector is being dragged. */ public void setConnectorTarget(ConnectorTarget t) { _connectorTarget = t; // FIXME: This is rather dangerous because it assumes a // basic selection renderer. /* BasicSelectionRenderer selectionRenderer = (BasicSelectionRenderer) getEdgeInteractor().getSelectionRenderer(); ConnectorManipulator manipulator = (ConnectorManipulator) selectionRenderer.getDecorator(); manipulator.setConnectorTarget(t); */ } /** * Set the interactor given to edge figures. */ public void setEdgeInteractor(Interactor interactor) { if (!(interactor instanceof ComponentEntity)) { throw new RuntimeException("Interactor " + interactor + " is not a component entity"); } try { Iterator iterator = entityList(Interactor.class).iterator(); while (iterator.hasNext()) { ((ComponentEntity) iterator.next()).setContainer(null); } ((ComponentEntity) interactor).setContainer(this); } catch (KernelException ex) { throw new RuntimeException("Interactor " + interactor + " could not be set"); } } /** * Set the edge renderer for this view. */ public void setEdgeRenderer(EdgeRenderer renderer) { if (!(renderer instanceof ComponentEntity)) { throw new RuntimeException("Renderer " + renderer + " is not a component entity"); } try { Iterator iterator = entityList(EdgeRenderer.class).iterator(); while (iterator.hasNext()) { ((ComponentEntity) iterator.next()).setContainer(null); } ((ComponentEntity) renderer).setContainer(this); } catch (KernelException ex) { throw new RuntimeException("Renderer " + renderer + " could not be set"); } } /** Render the edge on the given layer between the two sites. */ public Connector render(Object edge, FigureLayer layer, Site tailSite, Site headSite) { Connector ef = getEdgeRenderer().render(edge, tailSite, headSite); ef.setInteractor(getEdgeInteractor()); ef.setUserObject(edge); getController().setFigure(edge, ef); layer.add(ef); ef.route(); return ef; } /////////////////////////////////////////////////////////////////// //// EdgeDropper /** An inner class that handles interactive changes to connectivity. */ protected class EdgeDropper extends ConnectorAdapter { /** * Called when a connector end is dropped--attach or * detach the edge as appropriate. */ public void connectorDropped(ConnectorEvent evt) { Connector c = evt.getConnector(); Figure f = evt.getTarget(); Object edge = c.getUserObject(); Object node = (f == null) ? null : f.getUserObject(); MutableGraphModel model = (MutableGraphModel) getController() .getGraphModel(); try { switch (evt.getEnd()) { case ConnectorEvent.HEAD_END: model.setEdgeHead(getController(), edge, node); break; case ConnectorEvent.TAIL_END: model.setEdgeTail(getController(), edge, node); break; default: throw new IllegalStateException( "Cannot handle both ends of an edge being dragged."); } } catch (GraphException ex) { SelectionModel selectionModel = getController() .getSelectionModel(); // If it is illegal then blow away the edge. if (selectionModel.containsSelection(c)) { selectionModel.removeSelection(c); } removeEdge(edge); throw ex; } } } }