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 nodes = new ArrayList(); THashMap nodeMap = new THashMap(); ArrayList edges = new ArrayList(); ArrayList extensionNodes = new ArrayList(); ArrayList extensionEdges = new ArrayList(); ArrayList addedEdges = new ArrayList(); ArrayList removedEdges = new ArrayList(); 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 allEdges = new ArrayList( 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 edges = new ArrayList(); for(Node node : nodes) { NodeData data = node.getData(); Resource subject = data.getResource(); ArrayList filteredStatements = new ArrayList(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 filterEdgesWithoutNodes(Collection edges) { ArrayList result = new ArrayList(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 extensionNodeMap = new THashMap(); public void initializeExtension() { extensionNodes.clear(); extensionEdges.clear(); try { Simantics.getSession().syncRequest(new ReadRequest() { @Override public void run(ReadGraph graph) throws DatabaseException { THashMap oldExtensionNodeMap = DebuggerCanvas.this.extensionNodeMap; THashMap extensionNodeMap = new THashMap(); 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 extensionNodeIds = new TObjectIntHashMap(); for(int i=0;i 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 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; } }