package de.tu_darmstadt.informatik.tk.scopviz.graphs; import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; import org.graphstream.graph.Element; import org.graphstream.ui.swingViewer.ViewPanel; import org.graphstream.ui.view.Viewer; import org.graphstream.ui.view.ViewerPipe; import de.tu_darmstadt.informatik.tk.scopviz.debug.Debug; import de.tu_darmstadt.informatik.tk.scopviz.main.CreationMode; import de.tu_darmstadt.informatik.tk.scopviz.main.Layer; import de.tu_darmstadt.informatik.tk.scopviz.main.Main; import de.tu_darmstadt.informatik.tk.scopviz.ui.GraphDisplayManager; import de.tu_darmstadt.informatik.tk.scopviz.ui.PropertiesManager; import de.tu_darmstadt.informatik.tk.scopviz.ui.handlers.MyMouseManager; /** * Interface between GUI and internal Graph representation. Manages internal * representation of the Graph to accommodate creation and deletion of nodes and * edges. * * @author Jascha Bohne * @version 3.0.0.0 * */ public class GraphManager { /** String for the processing enabled type of node. */ public static final String UI_CLASS_PROCESSING_ENABLED = "procEn"; /** The Graph this instance of GraphManager manages. */ protected MyGraph g; protected MyGraph activeSubGraph; /** The last Node that was deleted. */ protected MyNode deletedNode; /** The last Edge that was deleted. */ protected LinkedList deletedEdges = new LinkedList<>(); /** The currently selected Node, mutually exclusive with selectedEdgeID. */ protected String selectedNodeID = null; /** The currently selected Edge, mutually exclusive with selectedNodeID. */ protected String selectedEdgeID = null; /** The ViewPanel the Graph is drawn in. */ protected ViewPanel view; /** The Path on Disk the Graph will be saved to. */ protected String currentPath; /** The Viewer the Graph provides, grants Access to Camera Manipulation. */ protected Viewer viewer; /** * The Pipe that notifies the underlying Graph of any Changes within the * graphic Representation. */ protected ViewerPipe fromViewer; /** * The Id of the Node that was last clicked. */ protected String lastClickedID; /** * Creates a new Manager for the given graph. * * @param graph * the graph this visualizer should handle */ public GraphManager(MyGraph graph) { g = graph; /* Viewer */ viewer = new Viewer(g, Viewer.ThreadingModel.GRAPH_IN_ANOTHER_THREAD); view = viewer.addDefaultView(false); viewer.setCloseFramePolicy(Viewer.CloseFramePolicy.EXIT); /* ViewerPipe */fromViewer = viewer.newViewerPipe(); view.setMouseManager(new MyMouseManager(this)); fromViewer.addSink(graph); fromViewer.removeElementSink(graph); } /** * Deletes the Node corresponding to the given ID from the Graph. The * referenced Graph is modified directly. Will throw an * ElementNotFoundException, when the Node is not Found Will also remove all * Edges connected to the given Node * * @param id * the ID of the node that will be removed */ public void deleteNode(final String id) { deletedEdges.removeAll(deletedEdges); deletedNode = null; // Edges have to be deleted first because they clear deletedNode // and need the Node to still be in the Graph deleteEdgesOfNode(id); deletedNode = g.removeNode(id); } /** * Deletes the Edge corresponding to the given ID from the Graph. The * referenced Graph is modified directly. Will throw an * ElementNotFoundException, when the Edge is not Found * * @param id * the ID of the Edge that will be removed */ public void deleteEdge(final String id) { deselect(); deletedEdges.removeAll(deletedEdges); deletedNode = null; deletedEdges.add(g.removeEdge(id)); } /** * Deletes all Edges connected to the given Node. The referenced Graph is * modified directly. Will throw an ElementNotFoundException if the Node is * not Found * * @param id * the Id of the Node, whose Edges shall be removed */ protected void deleteEdgesOfNode(final String id) { deselect(); MyNode node = g.getNode(id); deletedEdges.removeAll(deletedEdges); deletedNode = null; MyEdge[] temp = new MyEdge[0]; temp = g.getEdgeSet().toArray(temp); for (MyEdge e : temp) { if (e.getSourceNode().equals(node) || e.getTargetNode().equals(node)) { // adds the Edge to the list of deleted Edges and remove sit // from the Graph deletedEdges.add(g.removeEdge(e)); } } GraphHelper.propagateElementDeletion(g, deletedEdges); } /** * Undoes the last deleting operation on the given Graph. Deleting * operations are: deleteNode, deleteEdge and deleteEdgesOfNode. Only undoes * the last deleting operation even if that operation didn't change the * Graph */ public void undelete() { String newId = ""; HashMap attributes = new HashMap(); if (deletedNode != null) { for (String s : deletedNode.getAttributeKeySet()) { attributes.put(s, deletedNode.getAttribute(s)); } newId = Main.getInstance().getUnusedID(); g.addNode(newId); g.getNode(newId).addAttributes(attributes); String origElement = GraphHelper.propagateElementUndeletion(g, deletedNode, null); if (origElement != null) { g.getNode(newId).addAttribute("originalElement", origElement); } } for (MyEdge e : deletedEdges) { String sourceId = null; String targetId = null; attributes = new HashMap(); for (String s : e.getAttributeKeySet()) { attributes.put(s, e.getAttribute(s)); } String id = Main.getInstance().getUnusedID(); if (deletedNode != null) { sourceId = (e.getSourceNode().getId().equals(deletedNode.getId())) ? newId : e.getSourceNode().getId(); targetId = (e.getTargetNode().getId().equals(deletedNode.getId())) ? newId : e.getTargetNode().getId(); } else { sourceId = e.getSourceNode().getId(); targetId = e.getTargetNode().getId(); } g.addEdge(id, sourceId, targetId, e.isDirected()); g.getEdge(id).addAttributes(attributes); if (g.getNode(Main.getInstance().getGraphManager().getActiveSubGraph() + newId) == null || g.getNode(newId).getAttribute("originalElement") == null) { continue; } String origElement = GraphHelper.propagateElementUndeletion(g, e, g.getNode(newId).getAttribute("originalElement")); if (origElement != null) { g.getEdge(id).addAttribute("originalElement", origElement); } } deletedEdges = new LinkedList<>(); deletedNode = null; } /** * @return the activeSubGraph */ public MyGraph getActiveSubGraph() { return activeSubGraph; } /** * returns a View of the Graph. The View lives in the Swing Thread and the * Graph in the Main thread. * * * @return a View of the Graph, inheriting from JPanel */ public ViewPanel getView() { return view; } /** * Returns the ID of the currently selected Node. * * @return the node's ID */ public String getSelectedNodeID() { return selectedNodeID; } /** * Returns the ID of the currently selected Edge. * * @return the edge's ID */ public String getSelectedEdgeID() { return selectedEdgeID; } /** * Selects the Node with the given ID, resets Edge selection. * * @param nodeID * the ID of the Node to select */ public void selectNode(String nodeID) { if (nodeID != null && g.getNode(nodeID) != null) { deselect(); this.selectedNodeID = nodeID; MyNode n = g.getNode(nodeID); n.addCSSClass("selected"); PropertiesManager.setItemsProperties(); } } /** * Selects the Edge with the given ID, resets Node selection. * * @param edgeID * the ID of the Edge to select */ public void selectEdge(String edgeID) { if (edgeID != null && g.getEdge(edgeID) != null) { deselect(); this.selectedEdgeID = edgeID; g. getEdge(edgeID).addCSSClass("selected"); PropertiesManager.setItemsProperties(); } } /** * Deselect any currently selected nodes or edges. */ // TODO call this before save public void deselect() { // Set last selected Edge Color to Black if (getSelectedEdgeID() != null && g.getEdge(getSelectedEdgeID()) != null) { g. getEdge(getSelectedEdgeID()).removeCSSClass("selected"); } // Set last selected Node color to black else if (getSelectedNodeID() != null && g.getNode(getSelectedNodeID()) != null) { g. getNode(getSelectedNodeID()).removeCSSClass("selected"); } PropertiesManager.setItemsProperties(); this.selectedNodeID = null; this.selectedEdgeID = null; } /** * Returns a reference to the Graph object managed by this visualizer. * * @return the graph */ public MyGraph getGraph() { return g; } /** * Zooms in the view of the graph by 5 percent. */ public void zoomIn() { zoom(-0.05); } /** * Zooms out the view of the graph by 5 percent. */ public void zoomOut() { zoom(0.05); } /** * Zooms the view by the given Amount, positive values zoom out, negative * values zoom in. * * @param amount * the amount of zoom, should usually be between -0.2 and 0.2 for * reasonable zoom. */ public void zoom(double amount) { view.getCamera().setViewPercent(view.getCamera().getViewPercent() * (1 + amount)); } /** * Pumps the Pipe from the graphical Representation to the underlying Graph, * propagating all Changes made. */ public void pumpIt() { fromViewer.pump(); } @Override public String toString() { return "Visualizer for Graph \"" + g.getId() + "\""; } /** * Returns the current Save Path on Disk for the Graph. * * @return the current Path */ public String getCurrentPath() { return currentPath; } /** * Sets the Save Path. * * @param currentPath * the new Path to set */ public void setCurrentPath(String currentPath) { this.currentPath = currentPath; } /** * Adds a Copy of the given Node to the graph. The Copy retains the * ID and all attributes. * * @param n * the Node to be added to the graph */ public void addNode(MyNode n) { HashMap attributes = new HashMap<>(); for (String s : n.getAttributeKeySet()) { attributes.put(s, n.getAttribute(s)); } String nodeId = n.getId(); if (activeSubGraph != null) { nodeId = activeSubGraph.getId() + nodeId; } g.addNode(nodeId); g.getNode(nodeId).addAttributes(attributes); if (activeSubGraph != null && !activeSubGraph.equals(g)) { activeSubGraph.addNode(n.getId()); activeSubGraph.getNode(n.getId()).addAttributes(attributes); g.getNode(nodeId).addAttribute("originalElement", activeSubGraph.getId() + "+#" + n.getId()); g.getNode(nodeId).addAttribute("originalGraph", activeSubGraph.getId()); } } /** * Returns the smallest X Coordinate of any Node in the Graph. * * @return the smallest X Coordinate in the Graph */ public double getMinX() { return g.getMinX(); } /** * Returns the biggest X Coordinate of any Node in the Graph. * * @return the biggest X Coordinate in the Graph */ public double getMaxX() { return g.getMaxX(); } /** * Returns the smallest Y Coordinate of any Node in the Graph. * * @return the smallest Y Coordinate in the Graph */ public double getMinY() { return g.getMinY(); } /** * Returns the biggest Y Coordinate of any Node in the Graph. * * @return the biggest Y Coordinate in the Graph */ public double getMaxY() { return g.getMaxY(); } /** * Sets the Stylesheet to be used by the Graph. */ public void setStylesheet() { g.addAttribute("ui.stylesheet", "edge{text-offset: 4px,-4px;}"); } /** * adds the given listener to the underlying graph the listener will be * notified, when an Edge is added. * * @param e * the EdgeCreatedListener */ public void addEdgeCreatedListener(EdgeCreatedListener e) { ((MyGraph) g).addEdgeCreatedListener(e); } /** * adds the given listener to the underlying graph the listener will be * notified, when a Node is added. * * @param n * the NodeCreatedListener */ public void addNodeCreatedListener(NodeCreatedListener n) { ((MyGraph) g).addNodeCreatedListener(n); } /** * Sets typeofNode as the ui.class of all Nodes. * */ public void convertUiClass() { Collection allNodes = g.getNodeSet(); for (MyNode n : allNodes) { if (n.hasAttribute("typeofNode")) { n.addAttribute("ui.class", n.getAttribute("typeofNode").toString()); } } } /** * Create Edges based on CreateMode. * * @param id * The ID for the newly created Edge */ public void createEdges(String id) { switch (Main.getInstance().getCreationMode()) { case CREATE_DIRECTED_EDGE: case CREATE_UNDIRECTED_EDGE: if (lastClickedID == null) { lastClickedID = id; if (!selectNodeForEdgeCreation(lastClickedID)) { lastClickedID = null; } } else if (id.equals(lastClickedID) || createEdge(id, lastClickedID)) { deselectNodesAfterEdgeCreation(lastClickedID); lastClickedID = null; } break; default: break; } PropertiesManager.setItemsProperties(); } /** * creates a edge between two nodes. * * @author MW * @param to * ID of the destination node * @param from * ID of the origin node * @return true if the edge was created. false otherwise */ protected boolean createEdge(String to, String from) { if (getGraph().getNode(from).hasEdgeBetween(to)) return false; String newID = Main.getInstance().getUnusedID(); boolean isDirected = (Main.getInstance().getCreationMode() == CreationMode.CREATE_DIRECTED_EDGE); MyNode sourceNode = g.getNode(from); MyNode targetNode = g.getNode(to); if (activeSubGraph != null && !activeSubGraph.equals(g)) { if (sourceNode.getAttribute("originalGraph").equals(activeSubGraph.getId()) && targetNode.getAttribute("originalGraph").equals(activeSubGraph.getId())) { g.addEdge(newID, from, to, isDirected); String newerID = Main.getInstance().getUnusedID(activeSubGraph); activeSubGraph.addEdge(newerID, sourceNode.getAttribute("originalElement").toString().split("\\+#")[1], targetNode.getAttribute("originalElement").toString().split("\\+#")[1], isDirected); g.getEdge(newID).addAttribute("originalElement", activeSubGraph.getId() + "+#" + newerID); } else { Debug.out(sourceNode.getAttribute("originalGraph").toString() + targetNode.getAttribute("originalGraph").toString()); if (sourceNode.getAttribute("originalGraph").equals(targetNode.getAttribute("originalGraph"))) { Debug.out("Can Only add edges to currently active Subgraph!", 2); } else { Debug.out("Can not create Edge between Nodes of different Subgraphs!", 2); } return false; } } else { g.addEdge(newID, from, to, isDirected); } selectEdge(newID); return true; } /** * Selects a Node as the starting point for creating a new Edge. * * @param nodeID * the ID of the Node to select */ protected boolean selectNodeForEdgeCreation(String nodeID) { deselect(); MyNode n = getGraph().getNode(nodeID); if (!hasClass(n, UI_CLASS_PROCESSING_ENABLED) || !GraphDisplayManager.getCurrentLayer().equals(Layer.MAPPING)) { n.addCSSClass("selectedForEdgeCreation"); } return true; } /** * Reset the Selection of the Node after Edge has been successfully created. * * @param nodeID * the Id of the node to deselect. */ protected void deselectNodesAfterEdgeCreation(String nodeID) { MyNode n = getGraph().getNode(nodeID); if (n == null) { return; } if (!hasClass(n, UI_CLASS_PROCESSING_ENABLED) || !GraphDisplayManager.getCurrentLayer().equals(Layer.MAPPING)) { n.removeCSSClass("selectedForEdgeCreation"); } } /** * Resets the selction of the Node for Edge selection */ public void deselectEdgeCreationNodes() { if (lastClickedID != null) deselectNodesAfterEdgeCreation(lastClickedID); lastClickedID = null; } public void setActiveSubGraph(String id) { for (MyGraph subGraph : g.getAllSubGraphs()) { if (subGraph.getId().equals(id)) { activeSubGraph = subGraph; return; } } } protected boolean addClass(String id, String className) { Element e = getGraph().getEdge(id); if (e == null) e = getGraph().getNode(id); if (e == null) return false; String eClass = e.getAttribute("ui.class"); if (eClass == null || eClass.equals("")) eClass = className; else if (!(eClass.equals(className) || eClass.startsWith(className.concat(", ")) || eClass.contains(", ".concat(className)))) eClass = className.concat(", ").concat(eClass); e.addAttribute("ui.class", eClass); Debug.out("added " + className + ": " + eClass); return true; } protected boolean removeClass(String id, String className) { Element e = getGraph().getEdge(id); if (e == null) e = getGraph().getNode(id); if (e == null) return false; String eClass = e.getAttribute("ui.class"); if (eClass == null || eClass.equals(className)) eClass = ""; else eClass = eClass.replace(className.concat(", "), "").replace(", ".concat(className), ""); e.addAttribute("ui.class", eClass); Debug.out("removed " + className + ": " + eClass); return true; } protected boolean toggleClass(String id, String className) { Element e = getGraph().getEdge(id); if (e == null) e = getGraph().getNode(id); if (e == null) return false; String eClass = e.getAttribute("ui.class"); if (eClass == null || !(eClass.equals(className) || eClass.startsWith(className.concat(", ")) || eClass.contains(", ".concat(className)))) return addClass(id, className); return removeClass(id, className); } protected boolean hasClass(String id, String className) { Element e = getGraph().getEdge(id); if (e == null) e = getGraph().getNode(id); if (e == null) return false; String eClass = e.getAttribute("ui.class"); return (eClass != null && (eClass.equals(className) || eClass.startsWith(className.concat(", ")) || eClass.contains(", ".concat(className)))); } protected boolean hasClass(MyEdge e, String className) { if (e == null) return false; String eClass = e.getAttribute("ui.class"); return (eClass != null && (eClass.equals(className) || eClass.startsWith(className.concat(", ")) || eClass.contains(", ".concat(className)))); } protected boolean hasClass(MyNode n, String className) { if (n == null) return false; String nClass = n.getAttribute("ui.class"); /* * TODO: nochmal angucken, wenn CSS Manager steht, ist gerade gehackt * damit, Vorführung läuft. return (nClass != null && * (nClass.equals(className) || nClass.startsWith(className.concat(", " * )) || nClass.contains(", ".concat(className)))); */ return (nClass != null && nClass.contains(className)); } }