-package org.simantics.debug.graphical;\r
-\r
-import gnu.trove.list.array.TDoubleArrayList;\r
-import gnu.trove.map.hash.THashMap;\r
-import gnu.trove.map.hash.TObjectIntHashMap;\r
-\r
-import java.awt.Color;\r
-import java.awt.GradientPaint;\r
-import java.awt.Graphics;\r
-import java.awt.Graphics2D;\r
-import java.awt.RenderingHints;\r
-import java.awt.datatransfer.Transferable;\r
-import java.awt.datatransfer.UnsupportedFlavorException;\r
-import java.awt.dnd.DnDConstants;\r
-import java.awt.dnd.DropTarget;\r
-import java.awt.dnd.DropTargetAdapter;\r
-import java.awt.dnd.DropTargetDropEvent;\r
-import java.awt.event.KeyAdapter;\r
-import java.awt.event.KeyEvent;\r
-import java.awt.event.MouseAdapter;\r
-import java.awt.event.MouseEvent;\r
-import java.awt.event.MouseMotionAdapter;\r
-import java.awt.event.MouseWheelEvent;\r
-import java.awt.event.MouseWheelListener;\r
-import java.awt.geom.AffineTransform;\r
-import java.awt.geom.Point2D;\r
-import java.awt.geom.Rectangle2D;\r
-import java.io.IOException;\r
-import java.util.ArrayList;\r
-import java.util.Collection;\r
-import java.util.Random;\r
-\r
-import javax.swing.JPanel;\r
-import javax.swing.SwingUtilities;\r
-\r
-import org.eclipse.core.runtime.IAdaptable;\r
-import org.eclipse.jface.viewers.IStructuredSelection;\r
-import org.simantics.Simantics;\r
-import org.simantics.db.ChangeSet;\r
-import org.simantics.db.ChangeSetIdentifier;\r
-import org.simantics.db.ReadGraph;\r
-import org.simantics.db.Resource;\r
-import org.simantics.db.Session;\r
-import org.simantics.db.Statement;\r
-import org.simantics.db.common.request.ReadRequest;\r
-import org.simantics.db.common.utils.NameUtils;\r
-import org.simantics.db.exception.DatabaseException;\r
-import org.simantics.db.service.ManagementSupport;\r
-import org.simantics.debug.graphical.layout.ExtensionLayoutAlgorithm;\r
-import org.simantics.debug.graphical.layout.LayoutGraph;\r
-import org.simantics.debug.graphical.model.Edge;\r
-import org.simantics.debug.graphical.model.LabelContent;\r
-import org.simantics.debug.graphical.model.Node;\r
-import org.simantics.debug.graphical.model.NodeData;\r
-import org.simantics.layer0.Layer0;\r
-import org.simantics.ui.dnd.LocalObjectTransfer;\r
-import org.simantics.ui.dnd.LocalObjectTransferable;\r
-import org.simantics.ui.selection.AnyResource;\r
-import org.simantics.ui.selection.WorkbenchSelectionElement;\r
-\r
-public class DebuggerCanvas extends JPanel {\r
-\r
- private static final long serialVersionUID = -718678297301786379L;\r
- \r
- ArrayList<Node> nodes = new ArrayList<Node>();\r
- THashMap<Resource, Node> nodeMap = new THashMap<Resource, Node>(); \r
- ArrayList<Edge> edges = new ArrayList<Edge>();\r
- ArrayList<Node> extensionNodes = new ArrayList<Node>();\r
- ArrayList<Edge> extensionEdges = new ArrayList<Edge>();\r
- ArrayList<Edge> addedEdges = new ArrayList<Edge>();\r
- ArrayList<Edge> removedEdges = new ArrayList<Edge>();\r
- double canvasPosX = 0.0;\r
- double canvasPosY = 0.0;\r
- double canvasZoom = 1.0;\r
- boolean extensionMode = false;\r
- Random random = new Random();\r
-\r
- LabelingPreferences labelingPreferences = \r
- new LabelingPreferences();\r
- \r
- @Override\r
- public void paint(Graphics _g) {\r
- Graphics2D g = (Graphics2D)_g;\r
- \r
- g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, \r
- RenderingHints.VALUE_ANTIALIAS_ON);\r
- g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, \r
- RenderingHints.VALUE_FRACTIONALMETRICS_ON);\r
- g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, \r
- RenderingHints.VALUE_TEXT_ANTIALIAS_ON);\r
- \r
- g.setPaint(new GradientPaint(0.0f, 0.0f, new Color(200, 200, 200), getWidth(), getHeight(), Color.WHITE));\r
- g.fill(new Rectangle2D.Double(0, 0, getWidth(), getHeight()));\r
- g.setColor(Color.BLACK);\r
- g.setTransform(new AffineTransform(\r
- canvasZoom, 0.0, \r
- 0.0, canvasZoom, \r
- -canvasPosX*canvasZoom, -canvasPosY*canvasZoom));\r
- for(Node node : nodes)\r
- node.render(g);\r
- for(Edge edge : edges)\r
- edge.render(g);\r
- if(extensionMode) {\r
- for(Node node : extensionNodes)\r
- node.render(g);\r
- for(Edge edge : extensionEdges)\r
- edge.render(g);\r
- }\r
- g.setColor(Color.GREEN);\r
- for(Edge edge : addedEdges)\r
- edge.render(g);\r
- g.setColor(Color.RED);\r
- for(Edge edge : removedEdges)\r
- edge.render(g);\r
- }\r
- \r
- public Node pick(double x, double y) {\r
- for(Node node : nodes)\r
- if(node.pick(x, y))\r
- return node;\r
- return null;\r
- }\r
- \r
- public Node pickExtension(double x, double y) {\r
- for(Node node : extensionNodes)\r
- if(node.pick(x, y))\r
- return node;\r
- return null;\r
- }\r
- \r
- {\r
- addMouseListener(new MouseAdapter() {\r
- @Override\r
- public void mousePressed(MouseEvent e) { \r
- double x = canvasPosX+e.getX()/canvasZoom;\r
- double y = canvasPosY+e.getY()/canvasZoom;\r
- if(e.getButton() == MouseEvent.BUTTON1)\r
- handleMouseLeftPressed(x, y);\r
- else if(e.getButton() == MouseEvent.BUTTON2)\r
- handleMouseMiddlePressed(x, y);\r
- }\r
- @Override\r
- public void mouseMoved(MouseEvent e) {\r
- handleMouseMoved(canvasPosX+e.getX()/canvasZoom, \r
- canvasPosY+e.getY()/canvasZoom);\r
- }\r
- @Override\r
- public void mouseReleased(MouseEvent e) {\r
- handleMouseReleased(canvasPosX+e.getX()/canvasZoom, \r
- canvasPosY+e.getY()/canvasZoom);\r
- }\r
- @Override\r
- public void mouseWheelMoved(MouseWheelEvent e) {\r
- double x = canvasPosX+e.getX()/canvasZoom;\r
- double y = canvasPosY+e.getY()/canvasZoom;\r
- handleMouseWheelMoved(x, y, e.getWheelRotation());\r
- }\r
- });\r
- addMouseMotionListener(new MouseMotionAdapter() {\r
- @Override\r
- public void mouseDragged(MouseEvent e) {\r
- handleMouseMoved(canvasPosX+e.getX()/canvasZoom, \r
- canvasPosY+e.getY()/canvasZoom);\r
- } \r
- });\r
- addMouseWheelListener(new MouseWheelListener() { \r
- @Override\r
- public void mouseWheelMoved(MouseWheelEvent e) {\r
- double x = canvasPosX+e.getX()/canvasZoom;\r
- double y = canvasPosY+e.getY()/canvasZoom;\r
- handleMouseWheelMoved(x, y, e.getWheelRotation());\r
- }\r
- });\r
- addKeyListener(new UsefulKeyAdapter(new KeyAdapter() {\r
- @Override\r
- public void keyPressed(KeyEvent e) {\r
- DebuggerCanvas.this.keyPressed(e);\r
- }\r
- @Override\r
- public void keyReleased(KeyEvent e) {\r
- DebuggerCanvas.this.keyReleased(e);\r
- }\r
- }));\r
- DropTarget dropTarget = new DropTarget(this, new DropTargetAdapter() {\r
- @Override\r
- public void drop(DropTargetDropEvent dtde) {\r
- try {\r
- Transferable transferable = dtde.getTransferable();\r
- \r
- if( transferable.isDataFlavorSupported( \r
- LocalObjectTransferable.FLAVOR ) ) {\r
- dtde.acceptDrop( DnDConstants.ACTION_MOVE );\r
- \r
- transferable.getTransferData(LocalObjectTransferable.FLAVOR );\r
- Object obj = LocalObjectTransfer.getTransfer().getObject();\r
- double x = canvasPosX+dtde.getLocation().getX()/canvasZoom;\r
- double y = canvasPosY+dtde.getLocation().getY()/canvasZoom;\r
- handleDrop(x, y, obj);\r
- \r
- dtde.getDropTargetContext().dropComplete( true );\r
- }\r
- else {\r
- dtde.rejectDrop();\r
- }\r
- } catch( IOException exception ) {\r
- exception.printStackTrace();\r
- dtde.rejectDrop();\r
- } catch( UnsupportedFlavorException ufException ) {\r
- ufException.printStackTrace();\r
- dtde.rejectDrop();\r
- }\r
- } \r
- });\r
- } \r
- \r
- public void keyPressed(KeyEvent e) {\r
- switch(e.getKeyCode()) {\r
- case KeyEvent.VK_1:\r
- zoomToFit();\r
- break;\r
- case KeyEvent.VK_L:\r
- layoutGraph();\r
- break;\r
- case KeyEvent.VK_CONTROL:\r
- if(!extensionMode) {\r
- initializeExtension();\r
- extensionMode = true;\r
- repaint();\r
- }\r
- break;\r
- case KeyEvent.VK_C:\r
- findPreviousChangeset();\r
- break;\r
- case KeyEvent.VK_DELETE:\r
- if (!extensionMode && dragging != null) {\r
- nodes.remove(dragging);\r
- scheduleUpdate();\r
- repaint();\r
- }\r
- break;\r
- }\r
- }\r
- \r
- public void keyReleased(KeyEvent e) {\r
- if(e.getKeyCode() == KeyEvent.VK_CONTROL) {\r
- extensionMode = false;\r
- scheduleUpdate();\r
- repaint();\r
- }\r
- }\r
-\r
- private static Resource extractResource(Object obj) {\r
- System.out.println("- " + obj.getClass().getName());\r
- if(obj instanceof WorkbenchSelectionElement) {\r
- Resource resource = ((WorkbenchSelectionElement)obj).getContent(new AnyResource(Simantics.getSession()));\r
- if(resource != null)\r
- return resource;\r
- }\r
- if(obj instanceof IAdaptable) {\r
- Resource resource = (Resource)((IAdaptable)obj).getAdapter(Resource.class);\r
- if(resource != null)\r
- return resource;\r
- }\r
- return null;\r
- }\r
- \r
- private void handleDrop(double x, double y, Object obj) {\r
- //System.out.println(obj.getClass().getName());\r
- if(obj instanceof IStructuredSelection) {\r
- for(Object element : ((IStructuredSelection)obj).toArray()) {\r
- Resource resource = extractResource(element);\r
- if(resource != null && !nodeMap.containsKey(resource)) {\r
- addResource(x, y, resource); \r
- repaint();\r
- }\r
- }\r
- }\r
- } \r
- \r
- private Node addResource(double x, double y, Resource resource) {\r
- Node a = new Node(new NodeData(resource));\r
- a.setPos(x, y);\r
- scheduleUpdate();\r
- nodes.add(a);\r
- return a;\r
- }\r
-\r
- private void scheduleUpdate() {\r
- Simantics.getSession().asyncRequest(new ReadRequest() { \r
- @Override\r
- public void run(ReadGraph graph) throws DatabaseException { \r
- updateNodes(graph);\r
- updateEdges(graph);\r
- SwingUtilities.invokeLater(new Runnable() {\r
- @Override\r
- public void run() {\r
- repaint();\r
- }\r
- \r
- });\r
- } \r
- }); \r
- }\r
- \r
- public void layoutGraph() {\r
- ArrayList<Edge> allEdges = new ArrayList<Edge>(\r
- edges.size() + addedEdges.size() + removedEdges.size()\r
- );\r
- allEdges.addAll(edges);\r
- allEdges.addAll(addedEdges);\r
- allEdges.addAll(removedEdges);\r
- LayoutGraph.layout(\r
- nodes.toArray(new Node[nodes.size()]), \r
- allEdges.toArray(new Edge[edges.size()]));\r
- repaint(); \r
- }\r
- \r
- private void updateNodes(ReadGraph graph) throws DatabaseException {\r
- for(Node node : nodes) {\r
- node.getData().updateData(graph, labelingPreferences);\r
- node.setContent(new LabelContent(node.getData().getLabels()));\r
- }\r
- nodeMap.clear();\r
- for(Node node : nodes) {\r
- NodeData data = node.getData();\r
- nodeMap.put(data.getResource(), node);\r
- }\r
- }\r
- \r
- private void updateEdges(ReadGraph graph) throws DatabaseException {\r
- ArrayList<Edge> edges = new ArrayList<Edge>();\r
- for(Node node : nodes) {\r
- NodeData data = node.getData();\r
- Resource subject = data.getResource();\r
- ArrayList<Statement> filteredStatements = new ArrayList<Statement>(data.getStatements().size());\r
- for(Statement stat : data.getStatements()) {\r
- Resource object = stat.getObject();\r
- Node node2 = nodeMap.get(object);\r
- if(node2 != null) {\r
- if(object.getResourceId() > subject.getResourceId() ||\r
- graph.getPossibleInverse(stat.getPredicate()) == null) {\r
- edges.add(createEdge(graph, stat, node, node2));\r
- }\r
- }\r
- else\r
- filteredStatements.add(stat);\r
- }\r
- data.setStatements(filteredStatements);\r
- }\r
- this.edges = edges;\r
- this.addedEdges = filterEdgesWithoutNodes( this.addedEdges );\r
- this.removedEdges = filterEdgesWithoutNodes( this.removedEdges );\r
- }\r
-\r
- private ArrayList<Edge> filterEdgesWithoutNodes(Collection<Edge> edges) {\r
- ArrayList<Edge> result = new ArrayList<Edge>(edges.size());\r
- for (Edge e : edges) {\r
- if (!nodeMap.containsValue(e.getA()) || !nodeMap.containsValue(e.getB()))\r
- continue;\r
- result.add(e);\r
- }\r
- return result;\r
- }\r
-\r
- private Edge createEdge(ReadGraph graph, Statement stat, Node n1, Node n2) throws DatabaseException {\r
- Resource predicate = stat.getPredicate();\r
- Resource inverse = graph.getPossibleInverse(predicate); \r
- if(inverse != null) {\r
- Layer0 L0 = Layer0.getInstance(graph);\r
- if(graph.hasStatement(predicate, L0.PartOf, inverse)\r
- || predicate.equals(L0.PartOf)\r
- || predicate.equals(L0.SuperrelationOf)\r
- || predicate.equals(L0.SupertypeOf)) {\r
- predicate = inverse;\r
- Node temp = n1;\r
- n1 = n2;\r
- n2 = temp;\r
- }\r
- }\r
- Edge edge = new Edge(n1, n2);\r
- edge.setContent(new LabelContent(new String[] {\r
- NameUtils.getSafeName(graph, predicate)}));\r
- return edge;\r
- }\r
- \r
- Node dragging = null;\r
- double dragDX, dragDY;\r
- private void handleMouseLeftPressed(double x, double y) {\r
- Node node;\r
- if(extensionMode) {\r
- node = pickExtension(x, y);\r
- if(node != null) {\r
- nodes.add(node);\r
- extensionNodes.remove(node);\r
- }\r
- }\r
- else\r
- node = pick(x, y);\r
- if(node != null) {\r
- dragDX = x - node.getX();\r
- dragDY = y - node.getY();\r
- dragging = node;\r
- }\r
- }\r
- \r
- Point2D panningStartMouse;\r
- private void handleMouseMiddlePressed(double x, double y) {\r
- panningStartMouse = new Point2D.Double(x, y);\r
- }\r
- \r
- private void handleMouseMoved(double x, double y) {\r
- if(dragging != null) {\r
- dragging.setPos(x-dragDX, y-dragDY);\r
- repaint();\r
- }\r
- if(panningStartMouse != null) {\r
- canvasPosX -= x - panningStartMouse.getX();\r
- canvasPosY -= y - panningStartMouse.getY();\r
- repaint();\r
- }\r
- }\r
- \r
- private void handleMouseWheelMoved(double x, double y, double amount) {\r
- double s = Math.exp(-0.2*amount);\r
- canvasZoom *= s;\r
- canvasPosX = x - (x-canvasPosX)/s;\r
- canvasPosY = y - (y-canvasPosY)/s;\r
- repaint();\r
- }\r
-\r
- private void handleMouseReleased(double x, double y) {\r
- dragging = null;\r
- panningStartMouse = null;\r
- }\r
- \r
- public void zoomToFit() {\r
- if(!nodes.isEmpty()) {\r
- double minX = Double.POSITIVE_INFINITY;\r
- double minY = Double.POSITIVE_INFINITY;\r
- double maxX = Double.NEGATIVE_INFINITY;\r
- double maxY = Double.NEGATIVE_INFINITY;\r
- System.out.println("(" + minX + "," + minY + ") - (" + maxX + "," + maxY + ")");\r
- for(Node node : nodes) {\r
- minX = Math.min(minX, node.getMinX());\r
- minY = Math.min(minY, node.getMinY());\r
- maxX = Math.max(maxX, node.getMaxX());\r
- maxY = Math.max(maxY, node.getMaxY());\r
- } \r
- canvasZoom = Math.min(getWidth()/(maxX-minX), getHeight()/(maxY-minY));\r
- canvasZoom *= 0.9;\r
- canvasPosX = minX - 0.5 * (getWidth()/canvasZoom - maxX+minX);\r
- canvasPosY = minY - 0.5 * (getHeight()/canvasZoom - maxY+minY);\r
- repaint();\r
- }\r
- }\r
-\r
- THashMap<Resource, Node> extensionNodeMap = new THashMap<Resource, Node>();\r
- public void initializeExtension() {\r
- extensionNodes.clear();\r
- extensionEdges.clear();\r
- try {\r
- Simantics.getSession().syncRequest(new ReadRequest() {\r
- @Override\r
- public void run(ReadGraph graph) throws DatabaseException {\r
- THashMap<Resource, Node> oldExtensionNodeMap = DebuggerCanvas.this.extensionNodeMap;\r
- THashMap<Resource, Node> extensionNodeMap = new THashMap<Resource, Node>();\r
- for(Node node : nodes) {\r
- for(Statement stat : node.getData().getStatements()) {\r
- Resource object = stat.getObject();\r
- Node node2 = extensionNodeMap.get(object);\r
- if(node2 == null) {\r
- node2 = oldExtensionNodeMap.get(object);\r
- if(node2 == null) {\r
- node2 = new Node(new NodeData(object));\r
- double angle = random.nextDouble() * Math.PI * 2.0;\r
- double dx = Math.cos(angle);\r
- double dy = Math.sin(angle);\r
- double len = 150.0;\r
- node2.setPos(node.getX() + dx*len, node.getY() + dy*len);\r
- }\r
- node2.getData().updateData(graph, labelingPreferences); \r
- node2.setContent(new LabelContent(node2.getData().getLabels()));\r
- extensionNodeMap.put(object, node2);\r
- extensionNodes.add(node2);\r
- }\r
- extensionEdges.add(createEdge(graph, stat, node, node2));\r
- }\r
- }\r
- DebuggerCanvas.this.extensionNodeMap = extensionNodeMap;\r
- layoutExtension();\r
- } \r
- });\r
- } catch (DatabaseException e) {\r
- e.printStackTrace();\r
- }\r
- }\r
-\r
- private void layoutExtension() { \r
- TObjectIntHashMap<Node> extensionNodeIds = new TObjectIntHashMap<Node>();\r
- for(int i=0;i<extensionNodes.size();++i)\r
- extensionNodeIds.put(extensionNodes.get(i), i);\r
- \r
- double[][] neighbors = new double[extensionNodes.size()][];\r
- {\r
- TDoubleArrayList[] neighborLists = new TDoubleArrayList[neighbors.length]; \r
- for(int i=0;i<neighborLists.length;++i)\r
- neighborLists[i] = new TDoubleArrayList();\r
- for(Edge edge : extensionEdges) {\r
- int id;\r
- Node node;\r
- if(extensionNodeIds.containsKey(edge.getA())) {\r
- id = extensionNodeIds.get(edge.getA());\r
- node = edge.getB();\r
- }\r
- else {\r
- id = extensionNodeIds.get(edge.getB());\r
- node = edge.getA();\r
- }\r
- TDoubleArrayList list = neighborLists[id];\r
- list.add(node.getX());\r
- list.add(node.getY());\r
- } \r
- for(int i=0;i<neighborLists.length;++i) {\r
- neighbors[i] = neighborLists[i].toArray();\r
- }\r
- }\r
- \r
- double[] fixedRepulsiveX = new double[nodes.size()];\r
- double[] fixedRepulsiveY = new double[nodes.size()];\r
- for(int i=0;i<nodes.size();++i) {\r
- Node node = nodes.get(i);\r
- fixedRepulsiveX[i] = node.getX();\r
- fixedRepulsiveY[i] = node.getY();\r
- }\r
- ExtensionLayoutAlgorithm algo = \r
- new ExtensionLayoutAlgorithm(neighbors, fixedRepulsiveX, fixedRepulsiveY);\r
- double[] posX = algo.getPosX();\r
- double[] posY = algo.getPosY();\r
- for(int i=0;i<extensionNodes.size();++i) {\r
- posX[i] = extensionNodes.get(i).getX();\r
- posY[i] = extensionNodes.get(i).getY();\r
- } \r
- algo.optimize();\r
- for(int i=0;i<extensionNodes.size();++i) {\r
- extensionNodes.get(i).setPos(posX[i], posY[i]);\r
- } \r
- } \r
- \r
- private Node getNode(Resource resource) {\r
- Node node = nodeMap.get(resource);\r
- if(node == null) {\r
- node = addResource(random.nextDouble()*200.0-100.0, \r
- random.nextDouble()*200.0-100.0, \r
- resource);\r
- nodeMap.put(resource, node);\r
- }\r
- return node;\r
- }\r
- \r
- public void findPreviousChangeset() {\r
- try {\r
- Session session = Simantics.getSession();\r
- final ManagementSupport ms = session.getService(ManagementSupport.class);\r
- final long lastId = ms.getHeadRevisionId();\r
- final long firstId = getOpId(ms, lastId);\r
- //System.out.println(firstId + "-" + lastId);\r
- addedEdges.clear();\r
- removedEdges.clear(); \r
- session.asyncRequest(new ReadRequest() {\r
- @Override\r
- public void run(ReadGraph graph) throws DatabaseException {\r
- final Collection<ChangeSet> css = \r
- ms.fetchChangeSets(graph, firstId, lastId);\r
- Layer0 L0 = Layer0.getInstance(graph);\r
- for(ChangeSet cs : css) {\r
- for(ChangeSet.StatementChange stat : cs.changedStatements()) {\r
- Resource predicate = stat.getPredicate();\r
- if(predicate.equals(L0.InstanceOf) ||\r
- predicate.equals(L0.HasName) ||\r
- predicate.equals(L0.NameOf))\r
- continue;\r
- Edge edge = createEdge(graph, stat, \r
- getNode(stat.getSubject()),\r
- getNode(stat.getObject()));\r
- if(stat.isClaim())\r
- addedEdges.add(edge);\r
- else\r
- removedEdges.add(edge);\r
- }\r
- } \r
- scheduleUpdate();\r
- } \r
- }); \r
- } catch(DatabaseException e) {\r
- e.printStackTrace();\r
- }\r
- } \r
- \r
- private static long getOpId(ManagementSupport ms, long revisionId) throws DatabaseException {\r
- Collection<ChangeSetIdentifier> ids = ms.getChangeSetIdentifiers(revisionId, revisionId);\r
- ChangeSetIdentifier curId = ids.iterator().next();\r
- byte[] opIdData = curId.getMetadata().get("opid");\r
- System.out.println(new String(opIdData));\r
- long opId = Long.parseLong(new String(opIdData));\r
- if(opId == 0)\r
- opId = revisionId;\r
- return opId;\r
- }\r
-\r
-}\r
+package org.simantics.debug.graphical;
+
+import gnu.trove.list.array.TDoubleArrayList;
+import gnu.trove.map.hash.THashMap;
+import gnu.trove.map.hash.TObjectIntHashMap;
+
+import java.awt.Color;
+import java.awt.GradientPaint;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.awt.dnd.DnDConstants;
+import java.awt.dnd.DropTarget;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionAdapter;
+import java.awt.event.MouseWheelEvent;
+import java.awt.event.MouseWheelListener;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Random;
+
+import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
+
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.simantics.Simantics;
+import org.simantics.db.ChangeSet;
+import org.simantics.db.ChangeSetIdentifier;
+import org.simantics.db.ReadGraph;
+import org.simantics.db.Resource;
+import org.simantics.db.Session;
+import org.simantics.db.Statement;
+import org.simantics.db.common.request.ReadRequest;
+import org.simantics.db.common.utils.NameUtils;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.service.ManagementSupport;
+import org.simantics.debug.graphical.layout.ExtensionLayoutAlgorithm;
+import org.simantics.debug.graphical.layout.LayoutGraph;
+import org.simantics.debug.graphical.model.Edge;
+import org.simantics.debug.graphical.model.LabelContent;
+import org.simantics.debug.graphical.model.Node;
+import org.simantics.debug.graphical.model.NodeData;
+import org.simantics.layer0.Layer0;
+import org.simantics.ui.dnd.LocalObjectTransfer;
+import org.simantics.ui.dnd.LocalObjectTransferable;
+import org.simantics.ui.selection.AnyResource;
+import org.simantics.ui.selection.WorkbenchSelectionElement;
+
+public class DebuggerCanvas extends JPanel {
+
+ private static final long serialVersionUID = -718678297301786379L;
+
+ ArrayList<Node> nodes = new ArrayList<Node>();
+ THashMap<Resource, Node> nodeMap = new THashMap<Resource, Node>();
+ ArrayList<Edge> edges = new ArrayList<Edge>();
+ ArrayList<Node> extensionNodes = new ArrayList<Node>();
+ ArrayList<Edge> extensionEdges = new ArrayList<Edge>();
+ ArrayList<Edge> addedEdges = new ArrayList<Edge>();
+ ArrayList<Edge> removedEdges = new ArrayList<Edge>();
+ double canvasPosX = 0.0;
+ double canvasPosY = 0.0;
+ double canvasZoom = 1.0;
+ boolean extensionMode = false;
+ Random random = new Random();
+
+ LabelingPreferences labelingPreferences =
+ new LabelingPreferences();
+
+ @Override
+ public void paint(Graphics _g) {
+ Graphics2D g = (Graphics2D)_g;
+
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
+ RenderingHints.VALUE_FRACTIONALMETRICS_ON);
+ g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+ RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+
+ g.setPaint(new GradientPaint(0.0f, 0.0f, new Color(200, 200, 200), getWidth(), getHeight(), Color.WHITE));
+ g.fill(new Rectangle2D.Double(0, 0, getWidth(), getHeight()));
+ g.setColor(Color.BLACK);
+ g.setTransform(new AffineTransform(
+ canvasZoom, 0.0,
+ 0.0, canvasZoom,
+ -canvasPosX*canvasZoom, -canvasPosY*canvasZoom));
+ for(Node node : nodes)
+ node.render(g);
+ for(Edge edge : edges)
+ edge.render(g);
+ if(extensionMode) {
+ for(Node node : extensionNodes)
+ node.render(g);
+ for(Edge edge : extensionEdges)
+ edge.render(g);
+ }
+ g.setColor(Color.GREEN);
+ for(Edge edge : addedEdges)
+ edge.render(g);
+ g.setColor(Color.RED);
+ for(Edge edge : removedEdges)
+ edge.render(g);
+ }
+
+ public Node pick(double x, double y) {
+ for(Node node : nodes)
+ if(node.pick(x, y))
+ return node;
+ return null;
+ }
+
+ public Node pickExtension(double x, double y) {
+ for(Node node : extensionNodes)
+ if(node.pick(x, y))
+ return node;
+ return null;
+ }
+
+ {
+ addMouseListener(new MouseAdapter() {
+ @Override
+ public void mousePressed(MouseEvent e) {
+ double x = canvasPosX+e.getX()/canvasZoom;
+ double y = canvasPosY+e.getY()/canvasZoom;
+ if(e.getButton() == MouseEvent.BUTTON1)
+ handleMouseLeftPressed(x, y);
+ else if(e.getButton() == MouseEvent.BUTTON2)
+ handleMouseMiddlePressed(x, y);
+ }
+ @Override
+ public void mouseMoved(MouseEvent e) {
+ handleMouseMoved(canvasPosX+e.getX()/canvasZoom,
+ canvasPosY+e.getY()/canvasZoom);
+ }
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ handleMouseReleased(canvasPosX+e.getX()/canvasZoom,
+ canvasPosY+e.getY()/canvasZoom);
+ }
+ @Override
+ public void mouseWheelMoved(MouseWheelEvent e) {
+ double x = canvasPosX+e.getX()/canvasZoom;
+ double y = canvasPosY+e.getY()/canvasZoom;
+ handleMouseWheelMoved(x, y, e.getWheelRotation());
+ }
+ });
+ addMouseMotionListener(new MouseMotionAdapter() {
+ @Override
+ public void mouseDragged(MouseEvent e) {
+ handleMouseMoved(canvasPosX+e.getX()/canvasZoom,
+ canvasPosY+e.getY()/canvasZoom);
+ }
+ });
+ addMouseWheelListener(new MouseWheelListener() {
+ @Override
+ public void mouseWheelMoved(MouseWheelEvent e) {
+ double x = canvasPosX+e.getX()/canvasZoom;
+ double y = canvasPosY+e.getY()/canvasZoom;
+ handleMouseWheelMoved(x, y, e.getWheelRotation());
+ }
+ });
+ addKeyListener(new UsefulKeyAdapter(new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent e) {
+ DebuggerCanvas.this.keyPressed(e);
+ }
+ @Override
+ public void keyReleased(KeyEvent e) {
+ DebuggerCanvas.this.keyReleased(e);
+ }
+ }));
+ DropTarget dropTarget = new DropTarget(this, new DropTargetAdapter() {
+ @Override
+ public void drop(DropTargetDropEvent dtde) {
+ try {
+ Transferable transferable = dtde.getTransferable();
+
+ if( transferable.isDataFlavorSupported(
+ LocalObjectTransferable.FLAVOR ) ) {
+ dtde.acceptDrop( DnDConstants.ACTION_MOVE );
+
+ transferable.getTransferData(LocalObjectTransferable.FLAVOR );
+ Object obj = LocalObjectTransfer.getTransfer().getObject();
+ double x = canvasPosX+dtde.getLocation().getX()/canvasZoom;
+ double y = canvasPosY+dtde.getLocation().getY()/canvasZoom;
+ handleDrop(x, y, obj);
+
+ dtde.getDropTargetContext().dropComplete( true );
+ }
+ else {
+ dtde.rejectDrop();
+ }
+ } catch( IOException exception ) {
+ exception.printStackTrace();
+ dtde.rejectDrop();
+ } catch( UnsupportedFlavorException ufException ) {
+ ufException.printStackTrace();
+ dtde.rejectDrop();
+ }
+ }
+ });
+ }
+
+ public void keyPressed(KeyEvent e) {
+ switch(e.getKeyCode()) {
+ case KeyEvent.VK_1:
+ zoomToFit();
+ break;
+ case KeyEvent.VK_L:
+ layoutGraph();
+ break;
+ case KeyEvent.VK_CONTROL:
+ if(!extensionMode) {
+ initializeExtension();
+ extensionMode = true;
+ repaint();
+ }
+ break;
+ case KeyEvent.VK_C:
+ findPreviousChangeset();
+ break;
+ case KeyEvent.VK_DELETE:
+ if (!extensionMode && dragging != null) {
+ nodes.remove(dragging);
+ scheduleUpdate();
+ repaint();
+ }
+ break;
+ }
+ }
+
+ public void keyReleased(KeyEvent e) {
+ if(e.getKeyCode() == KeyEvent.VK_CONTROL) {
+ extensionMode = false;
+ scheduleUpdate();
+ repaint();
+ }
+ }
+
+ private static Resource extractResource(Object obj) {
+ System.out.println("- " + obj.getClass().getName());
+ if(obj instanceof WorkbenchSelectionElement) {
+ Resource resource = ((WorkbenchSelectionElement)obj).getContent(new AnyResource(Simantics.getSession()));
+ if(resource != null)
+ return resource;
+ }
+ if(obj instanceof IAdaptable) {
+ Resource resource = (Resource)((IAdaptable)obj).getAdapter(Resource.class);
+ if(resource != null)
+ return resource;
+ }
+ return null;
+ }
+
+ private void handleDrop(double x, double y, Object obj) {
+ //System.out.println(obj.getClass().getName());
+ if(obj instanceof IStructuredSelection) {
+ for(Object element : ((IStructuredSelection)obj).toArray()) {
+ Resource resource = extractResource(element);
+ if(resource != null && !nodeMap.containsKey(resource)) {
+ addResource(x, y, resource);
+ repaint();
+ }
+ }
+ }
+ }
+
+ private Node addResource(double x, double y, Resource resource) {
+ Node a = new Node(new NodeData(resource));
+ a.setPos(x, y);
+ scheduleUpdate();
+ nodes.add(a);
+ return a;
+ }
+
+ private void scheduleUpdate() {
+ Simantics.getSession().asyncRequest(new ReadRequest() {
+ @Override
+ public void run(ReadGraph graph) throws DatabaseException {
+ updateNodes(graph);
+ updateEdges(graph);
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ repaint();
+ }
+
+ });
+ }
+ });
+ }
+
+ public void layoutGraph() {
+ ArrayList<Edge> allEdges = new ArrayList<Edge>(
+ edges.size() + addedEdges.size() + removedEdges.size()
+ );
+ allEdges.addAll(edges);
+ allEdges.addAll(addedEdges);
+ allEdges.addAll(removedEdges);
+ LayoutGraph.layout(
+ nodes.toArray(new Node[nodes.size()]),
+ allEdges.toArray(new Edge[edges.size()]));
+ repaint();
+ }
+
+ private void updateNodes(ReadGraph graph) throws DatabaseException {
+ for(Node node : nodes) {
+ node.getData().updateData(graph, labelingPreferences);
+ node.setContent(new LabelContent(node.getData().getLabels()));
+ }
+ nodeMap.clear();
+ for(Node node : nodes) {
+ NodeData data = node.getData();
+ nodeMap.put(data.getResource(), node);
+ }
+ }
+
+ private void updateEdges(ReadGraph graph) throws DatabaseException {
+ ArrayList<Edge> edges = new ArrayList<Edge>();
+ for(Node node : nodes) {
+ NodeData data = node.getData();
+ Resource subject = data.getResource();
+ ArrayList<Statement> filteredStatements = new ArrayList<Statement>(data.getStatements().size());
+ for(Statement stat : data.getStatements()) {
+ Resource object = stat.getObject();
+ Node node2 = nodeMap.get(object);
+ if(node2 != null) {
+ if(object.getResourceId() > subject.getResourceId() ||
+ graph.getPossibleInverse(stat.getPredicate()) == null) {
+ edges.add(createEdge(graph, stat, node, node2));
+ }
+ }
+ else
+ filteredStatements.add(stat);
+ }
+ data.setStatements(filteredStatements);
+ }
+ this.edges = edges;
+ this.addedEdges = filterEdgesWithoutNodes( this.addedEdges );
+ this.removedEdges = filterEdgesWithoutNodes( this.removedEdges );
+ }
+
+ private ArrayList<Edge> filterEdgesWithoutNodes(Collection<Edge> edges) {
+ ArrayList<Edge> result = new ArrayList<Edge>(edges.size());
+ for (Edge e : edges) {
+ if (!nodeMap.containsValue(e.getA()) || !nodeMap.containsValue(e.getB()))
+ continue;
+ result.add(e);
+ }
+ return result;
+ }
+
+ private Edge createEdge(ReadGraph graph, Statement stat, Node n1, Node n2) throws DatabaseException {
+ Resource predicate = stat.getPredicate();
+ Resource inverse = graph.getPossibleInverse(predicate);
+ if(inverse != null) {
+ Layer0 L0 = Layer0.getInstance(graph);
+ if(graph.hasStatement(predicate, L0.PartOf, inverse)
+ || predicate.equals(L0.PartOf)
+ || predicate.equals(L0.SuperrelationOf)
+ || predicate.equals(L0.SupertypeOf)) {
+ predicate = inverse;
+ Node temp = n1;
+ n1 = n2;
+ n2 = temp;
+ }
+ }
+ Edge edge = new Edge(n1, n2);
+ edge.setContent(new LabelContent(new String[] {
+ NameUtils.getSafeName(graph, predicate)}));
+ return edge;
+ }
+
+ Node dragging = null;
+ double dragDX, dragDY;
+ private void handleMouseLeftPressed(double x, double y) {
+ Node node;
+ if(extensionMode) {
+ node = pickExtension(x, y);
+ if(node != null) {
+ nodes.add(node);
+ extensionNodes.remove(node);
+ }
+ }
+ else
+ node = pick(x, y);
+ if(node != null) {
+ dragDX = x - node.getX();
+ dragDY = y - node.getY();
+ dragging = node;
+ }
+ }
+
+ Point2D panningStartMouse;
+ private void handleMouseMiddlePressed(double x, double y) {
+ panningStartMouse = new Point2D.Double(x, y);
+ }
+
+ private void handleMouseMoved(double x, double y) {
+ if(dragging != null) {
+ dragging.setPos(x-dragDX, y-dragDY);
+ repaint();
+ }
+ if(panningStartMouse != null) {
+ canvasPosX -= x - panningStartMouse.getX();
+ canvasPosY -= y - panningStartMouse.getY();
+ repaint();
+ }
+ }
+
+ private void handleMouseWheelMoved(double x, double y, double amount) {
+ double s = Math.exp(-0.2*amount);
+ canvasZoom *= s;
+ canvasPosX = x - (x-canvasPosX)/s;
+ canvasPosY = y - (y-canvasPosY)/s;
+ repaint();
+ }
+
+ private void handleMouseReleased(double x, double y) {
+ dragging = null;
+ panningStartMouse = null;
+ }
+
+ public void zoomToFit() {
+ if(!nodes.isEmpty()) {
+ double minX = Double.POSITIVE_INFINITY;
+ double minY = Double.POSITIVE_INFINITY;
+ double maxX = Double.NEGATIVE_INFINITY;
+ double maxY = Double.NEGATIVE_INFINITY;
+ System.out.println("(" + minX + "," + minY + ") - (" + maxX + "," + maxY + ")");
+ for(Node node : nodes) {
+ minX = Math.min(minX, node.getMinX());
+ minY = Math.min(minY, node.getMinY());
+ maxX = Math.max(maxX, node.getMaxX());
+ maxY = Math.max(maxY, node.getMaxY());
+ }
+ canvasZoom = Math.min(getWidth()/(maxX-minX), getHeight()/(maxY-minY));
+ canvasZoom *= 0.9;
+ canvasPosX = minX - 0.5 * (getWidth()/canvasZoom - maxX+minX);
+ canvasPosY = minY - 0.5 * (getHeight()/canvasZoom - maxY+minY);
+ repaint();
+ }
+ }
+
+ THashMap<Resource, Node> extensionNodeMap = new THashMap<Resource, Node>();
+ public void initializeExtension() {
+ extensionNodes.clear();
+ extensionEdges.clear();
+ try {
+ Simantics.getSession().syncRequest(new ReadRequest() {
+ @Override
+ public void run(ReadGraph graph) throws DatabaseException {
+ THashMap<Resource, Node> oldExtensionNodeMap = DebuggerCanvas.this.extensionNodeMap;
+ THashMap<Resource, Node> extensionNodeMap = new THashMap<Resource, Node>();
+ for(Node node : nodes) {
+ for(Statement stat : node.getData().getStatements()) {
+ Resource object = stat.getObject();
+ Node node2 = extensionNodeMap.get(object);
+ if(node2 == null) {
+ node2 = oldExtensionNodeMap.get(object);
+ if(node2 == null) {
+ node2 = new Node(new NodeData(object));
+ double angle = random.nextDouble() * Math.PI * 2.0;
+ double dx = Math.cos(angle);
+ double dy = Math.sin(angle);
+ double len = 150.0;
+ node2.setPos(node.getX() + dx*len, node.getY() + dy*len);
+ }
+ node2.getData().updateData(graph, labelingPreferences);
+ node2.setContent(new LabelContent(node2.getData().getLabels()));
+ extensionNodeMap.put(object, node2);
+ extensionNodes.add(node2);
+ }
+ extensionEdges.add(createEdge(graph, stat, node, node2));
+ }
+ }
+ DebuggerCanvas.this.extensionNodeMap = extensionNodeMap;
+ layoutExtension();
+ }
+ });
+ } catch (DatabaseException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void layoutExtension() {
+ TObjectIntHashMap<Node> extensionNodeIds = new TObjectIntHashMap<Node>();
+ for(int i=0;i<extensionNodes.size();++i)
+ extensionNodeIds.put(extensionNodes.get(i), i);
+
+ double[][] neighbors = new double[extensionNodes.size()][];
+ {
+ TDoubleArrayList[] neighborLists = new TDoubleArrayList[neighbors.length];
+ for(int i=0;i<neighborLists.length;++i)
+ neighborLists[i] = new TDoubleArrayList();
+ for(Edge edge : extensionEdges) {
+ int id;
+ Node node;
+ if(extensionNodeIds.containsKey(edge.getA())) {
+ id = extensionNodeIds.get(edge.getA());
+ node = edge.getB();
+ }
+ else {
+ id = extensionNodeIds.get(edge.getB());
+ node = edge.getA();
+ }
+ TDoubleArrayList list = neighborLists[id];
+ list.add(node.getX());
+ list.add(node.getY());
+ }
+ for(int i=0;i<neighborLists.length;++i) {
+ neighbors[i] = neighborLists[i].toArray();
+ }
+ }
+
+ double[] fixedRepulsiveX = new double[nodes.size()];
+ double[] fixedRepulsiveY = new double[nodes.size()];
+ for(int i=0;i<nodes.size();++i) {
+ Node node = nodes.get(i);
+ fixedRepulsiveX[i] = node.getX();
+ fixedRepulsiveY[i] = node.getY();
+ }
+ ExtensionLayoutAlgorithm algo =
+ new ExtensionLayoutAlgorithm(neighbors, fixedRepulsiveX, fixedRepulsiveY);
+ double[] posX = algo.getPosX();
+ double[] posY = algo.getPosY();
+ for(int i=0;i<extensionNodes.size();++i) {
+ posX[i] = extensionNodes.get(i).getX();
+ posY[i] = extensionNodes.get(i).getY();
+ }
+ algo.optimize();
+ for(int i=0;i<extensionNodes.size();++i) {
+ extensionNodes.get(i).setPos(posX[i], posY[i]);
+ }
+ }
+
+ private Node getNode(Resource resource) {
+ Node node = nodeMap.get(resource);
+ if(node == null) {
+ node = addResource(random.nextDouble()*200.0-100.0,
+ random.nextDouble()*200.0-100.0,
+ resource);
+ nodeMap.put(resource, node);
+ }
+ return node;
+ }
+
+ public void findPreviousChangeset() {
+ try {
+ Session session = Simantics.getSession();
+ final ManagementSupport ms = session.getService(ManagementSupport.class);
+ final long lastId = ms.getHeadRevisionId();
+ final long firstId = getOpId(ms, lastId);
+ //System.out.println(firstId + "-" + lastId);
+ addedEdges.clear();
+ removedEdges.clear();
+ session.asyncRequest(new ReadRequest() {
+ @Override
+ public void run(ReadGraph graph) throws DatabaseException {
+ final Collection<ChangeSet> css =
+ ms.fetchChangeSets(graph, firstId, lastId);
+ Layer0 L0 = Layer0.getInstance(graph);
+ for(ChangeSet cs : css) {
+ for(ChangeSet.StatementChange stat : cs.changedStatements()) {
+ Resource predicate = stat.getPredicate();
+ if(predicate.equals(L0.InstanceOf) ||
+ predicate.equals(L0.HasName) ||
+ predicate.equals(L0.NameOf))
+ continue;
+ Edge edge = createEdge(graph, stat,
+ getNode(stat.getSubject()),
+ getNode(stat.getObject()));
+ if(stat.isClaim())
+ addedEdges.add(edge);
+ else
+ removedEdges.add(edge);
+ }
+ }
+ scheduleUpdate();
+ }
+ });
+ } catch(DatabaseException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private static long getOpId(ManagementSupport ms, long revisionId) throws DatabaseException {
+ Collection<ChangeSetIdentifier> ids = ms.getChangeSetIdentifiers(revisionId, revisionId);
+ ChangeSetIdentifier curId = ids.iterator().next();
+ byte[] opIdData = curId.getMetadata().get("opid");
+ System.out.println(new String(opIdData));
+ long opId = Long.parseLong(new String(opIdData));
+ if(opId == 0)
+ opId = revisionId;
+ return opId;
+ }
+
+}