1 package org.simantics.debug.graphical;
4 import java.awt.GradientPaint;
5 import java.awt.Graphics;
6 import java.awt.Graphics2D;
7 import java.awt.RenderingHints;
8 import java.awt.datatransfer.DataFlavor;
9 import java.awt.datatransfer.Transferable;
10 import java.awt.dnd.DnDConstants;
11 import java.awt.dnd.DropTarget;
12 import java.awt.dnd.DropTargetAdapter;
13 import java.awt.dnd.DropTargetDropEvent;
14 import java.awt.event.KeyAdapter;
15 import java.awt.event.KeyEvent;
16 import java.awt.event.MouseAdapter;
17 import java.awt.event.MouseEvent;
18 import java.awt.event.MouseMotionAdapter;
19 import java.awt.event.MouseWheelEvent;
20 import java.awt.event.MouseWheelListener;
21 import java.awt.geom.AffineTransform;
22 import java.awt.geom.Point2D;
23 import java.awt.geom.Rectangle2D;
24 import java.io.Reader;
25 import java.util.ArrayList;
26 import java.util.Collection;
27 import java.util.Random;
29 import javax.swing.JPanel;
30 import javax.swing.SwingUtilities;
32 import org.eclipse.core.runtime.IAdaptable;
33 import org.eclipse.jface.viewers.IStructuredSelection;
34 import org.eclipse.jface.viewers.StructuredSelection;
35 import org.simantics.Simantics;
36 import org.simantics.db.ChangeSet;
37 import org.simantics.db.ChangeSetIdentifier;
38 import org.simantics.db.ReadGraph;
39 import org.simantics.db.Resource;
40 import org.simantics.db.Session;
41 import org.simantics.db.Statement;
42 import org.simantics.db.common.request.ReadRequest;
43 import org.simantics.db.common.utils.NameUtils;
44 import org.simantics.db.exception.DatabaseException;
45 import org.simantics.db.request.Read;
46 import org.simantics.db.service.ManagementSupport;
47 import org.simantics.db.service.SerialisationSupport;
48 import org.simantics.debug.graphical.layout.ExtensionLayoutAlgorithm;
49 import org.simantics.debug.graphical.layout.LayoutGraph;
50 import org.simantics.debug.graphical.model.Edge;
51 import org.simantics.debug.graphical.model.LabelContent;
52 import org.simantics.debug.graphical.model.Node;
53 import org.simantics.debug.graphical.model.NodeData;
54 import org.simantics.layer0.Layer0;
55 import org.simantics.scl.runtime.SCLContext;
56 import org.simantics.scl.runtime.function.Function;
57 import org.simantics.ui.dnd.LocalObjectTransfer;
58 import org.simantics.ui.dnd.LocalObjectTransferable;
59 import org.simantics.ui.selection.AnyResource;
60 import org.simantics.ui.selection.WorkbenchSelectionElement;
61 import org.slf4j.Logger;
62 import org.slf4j.LoggerFactory;
64 import com.fasterxml.jackson.databind.JsonNode;
65 import com.fasterxml.jackson.databind.ObjectMapper;
66 import com.fasterxml.jackson.databind.node.NumericNode;
68 import gnu.trove.list.array.TDoubleArrayList;
69 import gnu.trove.map.hash.THashMap;
70 import gnu.trove.map.hash.TObjectIntHashMap;
72 public class DebuggerCanvas extends JPanel {
73 private static final Logger LOGGER = LoggerFactory.getLogger(DebuggerCanvas.class);
75 private static final long serialVersionUID = -718678297301786379L;
77 ArrayList<Node> nodes = new ArrayList<Node>();
78 THashMap<Resource, Node> nodeMap = new THashMap<Resource, Node>();
79 ArrayList<Edge> edges = new ArrayList<Edge>();
80 ArrayList<Node> extensionNodes = new ArrayList<Node>();
81 ArrayList<Edge> extensionEdges = new ArrayList<Edge>();
82 ArrayList<Edge> addedEdges = new ArrayList<Edge>();
83 ArrayList<Edge> removedEdges = new ArrayList<Edge>();
84 double canvasPosX = 0.0;
85 double canvasPosY = 0.0;
86 double canvasZoom = 1.0;
87 boolean extensionMode = false;
88 Random random = new Random();
89 public Function statementFilter;
91 LabelingPreferences labelingPreferences =
92 new LabelingPreferences();
94 public void setStatementFilter(Function statementFilter) {
95 this.statementFilter = statementFilter;
98 public void removeStatementFilter() {
99 this.statementFilter = null;
103 public void paint(Graphics _g) {
104 Graphics2D g = (Graphics2D)_g;
106 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
107 RenderingHints.VALUE_ANTIALIAS_ON);
108 g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
109 RenderingHints.VALUE_FRACTIONALMETRICS_ON);
110 g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
111 RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
113 g.setPaint(new GradientPaint(0.0f, 0.0f, new Color(200, 200, 200), getWidth(), getHeight(), Color.WHITE));
114 g.fill(new Rectangle2D.Double(0, 0, getWidth(), getHeight()));
115 g.setColor(Color.BLACK);
116 g.setTransform(new AffineTransform(
119 -canvasPosX*canvasZoom, -canvasPosY*canvasZoom));
120 for(Node node : nodes)
122 for(Edge edge : edges)
125 for(Node node : extensionNodes)
127 for(Edge edge : extensionEdges)
130 g.setColor(Color.GREEN);
131 for(Edge edge : addedEdges)
133 g.setColor(Color.RED);
134 for(Edge edge : removedEdges)
138 public Node pick(double x, double y) {
139 for(Node node : nodes)
145 public Node pickExtension(double x, double y) {
146 for(Node node : extensionNodes)
153 addMouseListener(new MouseAdapter() {
155 public void mousePressed(MouseEvent e) {
156 double x = canvasPosX+e.getX()/canvasZoom;
157 double y = canvasPosY+e.getY()/canvasZoom;
158 if(e.getButton() == MouseEvent.BUTTON1)
159 handleMouseLeftPressed(x, y);
160 else if(e.getButton() == MouseEvent.BUTTON2)
161 handleMouseMiddlePressed(x, y);
164 public void mouseMoved(MouseEvent e) {
165 handleMouseMoved(canvasPosX+e.getX()/canvasZoom,
166 canvasPosY+e.getY()/canvasZoom);
169 public void mouseReleased(MouseEvent e) {
170 handleMouseReleased(canvasPosX+e.getX()/canvasZoom,
171 canvasPosY+e.getY()/canvasZoom);
174 public void mouseWheelMoved(MouseWheelEvent e) {
175 double x = canvasPosX+e.getX()/canvasZoom;
176 double y = canvasPosY+e.getY()/canvasZoom;
177 handleMouseWheelMoved(x, y, e.getWheelRotation());
180 addMouseMotionListener(new MouseMotionAdapter() {
182 public void mouseDragged(MouseEvent e) {
183 handleMouseMoved(canvasPosX+e.getX()/canvasZoom,
184 canvasPosY+e.getY()/canvasZoom);
187 addMouseWheelListener(new MouseWheelListener() {
189 public void mouseWheelMoved(MouseWheelEvent e) {
190 double x = canvasPosX+e.getX()/canvasZoom;
191 double y = canvasPosY+e.getY()/canvasZoom;
192 handleMouseWheelMoved(x, y, e.getWheelRotation());
195 addKeyListener(new UsefulKeyAdapter(new KeyAdapter() {
197 public void keyPressed(KeyEvent e) {
198 DebuggerCanvas.this.keyPressed(e);
201 public void keyReleased(KeyEvent e) {
202 DebuggerCanvas.this.keyReleased(e);
205 DropTarget dropTarget = new DropTarget(this, new DropTargetAdapter() {
207 public void drop(DropTargetDropEvent dtde) {
209 Transferable transferable = dtde.getTransferable();
210 if( transferable.isDataFlavorSupported(
211 LocalObjectTransferable.FLAVOR ) ) {
212 dtde.acceptDrop( DnDConstants.ACTION_MOVE );
214 transferable.getTransferData(LocalObjectTransferable.FLAVOR);
215 Object obj = LocalObjectTransfer.getTransfer().getObject();
216 handleDrop(dtde, obj);
219 DataFlavor textFlavor = DataFlavor.selectBestTextFlavor(transferable.getTransferDataFlavors());
220 if(textFlavor != null) {
221 // Try to read the textual content of the drop event as a JSON object containing a field named resourceId
222 try(Reader reader = textFlavor.getReaderForText(transferable)) {
223 ObjectMapper mapper = new ObjectMapper();
224 JsonNode node = mapper.readTree(reader);
225 Object resourceId = node.get("resourceId");
226 if(resourceId instanceof NumericNode) {
227 dtde.acceptDrop( DnDConstants.ACTION_MOVE );
229 transferable.getTransferData(LocalObjectTransferable.FLAVOR);
230 Object obj = new StructuredSelection(Simantics.getSession().syncRequest(new Read<Resource>() {
231 public Resource perform(ReadGraph graph) throws DatabaseException {
232 SerialisationSupport ss = graph.getService(SerialisationSupport.class);
233 return ss.getResource(((NumericNode)resourceId).longValue());
236 handleDrop(dtde, obj);
243 } catch( Exception exception ) {
244 LOGGER.warn("Drop failed.", exception);
251 private void handleDrop(DropTargetDropEvent dtde, Object obj) {
252 double x = canvasPosX+dtde.getLocation().getX()/canvasZoom;
253 double y = canvasPosY+dtde.getLocation().getY()/canvasZoom;
254 handleDrop(x, y, obj);
256 dtde.getDropTargetContext().dropComplete( true );
259 public void keyPressed(KeyEvent e) {
260 switch(e.getKeyCode()) {
267 case KeyEvent.VK_CONTROL:
269 initializeExtension();
270 extensionMode = true;
275 findPreviousChangeset();
277 case KeyEvent.VK_DELETE:
278 if (!extensionMode && dragging != null) {
279 nodes.remove(dragging);
287 public void keyReleased(KeyEvent e) {
288 if(e.getKeyCode() == KeyEvent.VK_CONTROL) {
289 extensionMode = false;
295 private static Resource extractResource(Object obj) {
296 System.out.println("- " + obj.getClass().getName());
297 if(obj instanceof WorkbenchSelectionElement) {
298 Resource resource = ((WorkbenchSelectionElement)obj).getContent(new AnyResource(Simantics.getSession()));
302 if(obj instanceof IAdaptable) {
303 Resource resource = (Resource)((IAdaptable)obj).getAdapter(Resource.class);
307 if(obj instanceof Resource)
308 return (Resource)obj;
312 private void handleDrop(double x, double y, Object obj) {
313 //System.out.println(obj.getClass().getName());
314 if(obj instanceof IStructuredSelection) {
315 for(Object element : ((IStructuredSelection)obj).toArray()) {
316 Resource resource = extractResource(element);
317 if(resource != null && !nodeMap.containsKey(resource)) {
318 addResource(x, y, resource);
325 private Node addResource(double x, double y, Resource resource) {
326 Node a = new Node(new NodeData(resource));
333 public void addResource(Resource resource) {
335 if(nodes.isEmpty()) {
340 double xMin=Double.POSITIVE_INFINITY, yMin=Double.POSITIVE_INFINITY;
341 double xMax=Double.NEGATIVE_INFINITY, yMax=Double.NEGATIVE_INFINITY;
342 for(Node node : nodes) {
343 xMin = Math.min(node.getMinX(), xMin);
344 yMin = Math.min(node.getMinY(), yMin);
345 xMax = Math.max(node.getMaxX(), xMax);
346 yMax = Math.max(node.getMaxY(), yMax);
348 x = xMin + (xMax - xMin) * random.nextDouble();
349 y = yMin + (yMax - yMin) * random.nextDouble();
352 addResource(x, y, resource);
356 private void scheduleUpdate() {
357 Simantics.getSession().asyncRequest(new ReadRequest() {
359 public void run(ReadGraph graph) throws DatabaseException {
362 SwingUtilities.invokeLater(new Runnable() {
373 public void layoutGraph() {
374 ArrayList<Edge> allEdges = new ArrayList<Edge>(
375 edges.size() + addedEdges.size() + removedEdges.size()
377 allEdges.addAll(edges);
378 allEdges.addAll(addedEdges);
379 allEdges.addAll(removedEdges);
381 nodes.toArray(new Node[nodes.size()]),
382 allEdges.toArray(new Edge[edges.size()]));
386 private void updateNodes(ReadGraph graph) throws DatabaseException {
387 for(Node node : nodes) {
388 node.getData().updateData(graph, labelingPreferences);
389 node.setContent(new LabelContent(node.getData().getLabels()));
392 for(Node node : nodes) {
393 NodeData data = node.getData();
394 nodeMap.put(data.getResource(), node);
398 private void updateEdges(ReadGraph graph) throws DatabaseException {
399 ArrayList<Edge> edges = new ArrayList<Edge>();
400 for(Node node : nodes) {
401 NodeData data = node.getData();
402 Resource subject = data.getResource();
403 ArrayList<Statement> filteredStatements = new ArrayList<Statement>(data.getStatements().size());
404 for(Statement stat : data.getStatements()) {
405 Resource object = stat.getObject();
406 Node node2 = nodeMap.get(object);
408 if(object.getResourceId() > subject.getResourceId() ||
409 graph.getPossibleInverse(stat.getPredicate()) == null) {
410 edges.add(createEdge(graph, stat, node, node2));
414 filteredStatements.add(stat);
416 data.setStatements(filteredStatements);
419 this.addedEdges = filterEdgesWithoutNodes( this.addedEdges );
420 this.removedEdges = filterEdgesWithoutNodes( this.removedEdges );
423 private ArrayList<Edge> filterEdgesWithoutNodes(Collection<Edge> edges) {
424 ArrayList<Edge> result = new ArrayList<Edge>(edges.size());
425 for (Edge e : edges) {
426 if (!nodeMap.containsValue(e.getA()) || !nodeMap.containsValue(e.getB()))
433 private Edge createEdge(ReadGraph graph, Statement stat, Node n1, Node n2) throws DatabaseException {
434 Resource predicate = stat.getPredicate();
435 Resource inverse = graph.getPossibleInverse(predicate);
436 if(inverse != null) {
437 Layer0 L0 = Layer0.getInstance(graph);
438 if(graph.hasStatement(predicate, L0.PartOf, inverse)
439 || predicate.equals(L0.PartOf)
440 || predicate.equals(L0.SuperrelationOf)
441 || predicate.equals(L0.SupertypeOf)) {
448 Edge edge = new Edge(n1, n2);
449 edge.setContent(new LabelContent(new String[] {
450 NameUtils.getSafeName(graph, predicate)}));
454 Node dragging = null;
455 double dragDX, dragDY;
456 private void handleMouseLeftPressed(double x, double y) {
459 node = pickExtension(x, y);
462 extensionNodes.remove(node);
468 dragDX = x - node.getX();
469 dragDY = y - node.getY();
474 Point2D panningStartMouse;
475 private void handleMouseMiddlePressed(double x, double y) {
476 panningStartMouse = new Point2D.Double(x, y);
479 private void handleMouseMoved(double x, double y) {
480 if(dragging != null) {
481 dragging.setPos(x-dragDX, y-dragDY);
484 if(panningStartMouse != null) {
485 canvasPosX -= x - panningStartMouse.getX();
486 canvasPosY -= y - panningStartMouse.getY();
491 private void handleMouseWheelMoved(double x, double y, double amount) {
492 double s = Math.exp(-0.2*amount);
494 canvasPosX = x - (x-canvasPosX)/s;
495 canvasPosY = y - (y-canvasPosY)/s;
499 private void handleMouseReleased(double x, double y) {
501 panningStartMouse = null;
504 public void zoomToFit() {
505 if(!nodes.isEmpty()) {
506 double minX = Double.POSITIVE_INFINITY;
507 double minY = Double.POSITIVE_INFINITY;
508 double maxX = Double.NEGATIVE_INFINITY;
509 double maxY = Double.NEGATIVE_INFINITY;
510 System.out.println("(" + minX + "," + minY + ") - (" + maxX + "," + maxY + ")");
511 for(Node node : nodes) {
512 minX = Math.min(minX, node.getMinX());
513 minY = Math.min(minY, node.getMinY());
514 maxX = Math.max(maxX, node.getMaxX());
515 maxY = Math.max(maxY, node.getMaxY());
517 canvasZoom = Math.min(getWidth()/(maxX-minX), getHeight()/(maxY-minY));
519 canvasPosX = minX - 0.5 * (getWidth()/canvasZoom - maxX+minX);
520 canvasPosY = minY - 0.5 * (getHeight()/canvasZoom - maxY+minY);
525 THashMap<Resource, Node> extensionNodeMap = new THashMap<Resource, Node>();
526 public void initializeExtension() {
527 extensionNodes.clear();
528 extensionEdges.clear();
530 Simantics.getSession().syncRequest(new ReadRequest() {
532 public void run(ReadGraph graph) throws DatabaseException {
533 SCLContext sclContext = SCLContext.getCurrent();
534 Object oldGraph = sclContext.put("graph", graph);
536 THashMap<Resource, Node> oldExtensionNodeMap = DebuggerCanvas.this.extensionNodeMap;
537 THashMap<Resource, Node> extensionNodeMap = new THashMap<Resource, Node>();
538 for(Node node : nodes) {
539 for(Statement stat : node.getData().getStatements()) {
540 Resource object = stat.getObject();
541 Node node2 = extensionNodeMap.get(object);
543 if(statementFilter != null && Boolean.FALSE.equals(statementFilter.apply(stat)))
545 node2 = oldExtensionNodeMap.get(object);
547 node2 = new Node(new NodeData(object));
548 double angle = random.nextDouble() * Math.PI * 2.0;
549 double dx = Math.cos(angle);
550 double dy = Math.sin(angle);
552 node2.setPos(node.getX() + dx*len, node.getY() + dy*len);
554 node2.getData().updateData(graph, labelingPreferences);
555 node2.setContent(new LabelContent(node2.getData().getLabels()));
556 extensionNodeMap.put(object, node2);
557 extensionNodes.add(node2);
559 extensionEdges.add(createEdge(graph, stat, node, node2));
562 } catch (Throwable t) {
563 if (t instanceof DatabaseException)
564 throw (DatabaseException) t;
565 throw new DatabaseException(t);
567 sclContext.put("graph", oldGraph);
569 DebuggerCanvas.this.extensionNodeMap = extensionNodeMap;
573 } catch (DatabaseException e) {
578 private void layoutExtension() {
579 TObjectIntHashMap<Node> extensionNodeIds = new TObjectIntHashMap<Node>();
580 for(int i=0;i<extensionNodes.size();++i)
581 extensionNodeIds.put(extensionNodes.get(i), i);
583 double[][] neighbors = new double[extensionNodes.size()][];
585 TDoubleArrayList[] neighborLists = new TDoubleArrayList[neighbors.length];
586 for(int i=0;i<neighborLists.length;++i)
587 neighborLists[i] = new TDoubleArrayList();
588 for(Edge edge : extensionEdges) {
591 if(extensionNodeIds.containsKey(edge.getA())) {
592 id = extensionNodeIds.get(edge.getA());
596 id = extensionNodeIds.get(edge.getB());
599 TDoubleArrayList list = neighborLists[id];
600 list.add(node.getX());
601 list.add(node.getY());
603 for(int i=0;i<neighborLists.length;++i) {
604 neighbors[i] = neighborLists[i].toArray();
608 double[] fixedRepulsiveX = new double[nodes.size()];
609 double[] fixedRepulsiveY = new double[nodes.size()];
610 for(int i=0;i<nodes.size();++i) {
611 Node node = nodes.get(i);
612 fixedRepulsiveX[i] = node.getX();
613 fixedRepulsiveY[i] = node.getY();
615 ExtensionLayoutAlgorithm algo =
616 new ExtensionLayoutAlgorithm(neighbors, fixedRepulsiveX, fixedRepulsiveY);
617 double[] posX = algo.getPosX();
618 double[] posY = algo.getPosY();
619 for(int i=0;i<extensionNodes.size();++i) {
620 posX[i] = extensionNodes.get(i).getX();
621 posY[i] = extensionNodes.get(i).getY();
624 for(int i=0;i<extensionNodes.size();++i) {
625 extensionNodes.get(i).setPos(posX[i], posY[i]);
629 private Node getNode(Resource resource) {
630 Node node = nodeMap.get(resource);
632 node = addResource(random.nextDouble()*200.0-100.0,
633 random.nextDouble()*200.0-100.0,
635 nodeMap.put(resource, node);
640 public void findPreviousChangeset() {
642 Session session = Simantics.getSession();
643 final ManagementSupport ms = session.getService(ManagementSupport.class);
644 final long lastId = ms.getHeadRevisionId();
645 final long firstId = getOpId(ms, lastId);
646 //System.out.println(firstId + "-" + lastId);
648 removedEdges.clear();
649 session.asyncRequest(new ReadRequest() {
651 public void run(ReadGraph graph) throws DatabaseException {
652 final Collection<ChangeSet> css =
653 ms.fetchChangeSets(graph, firstId, lastId);
654 Layer0 L0 = Layer0.getInstance(graph);
655 for(ChangeSet cs : css) {
656 for(ChangeSet.StatementChange stat : cs.changedStatements()) {
657 Resource predicate = stat.getPredicate();
658 if(predicate.equals(L0.InstanceOf) ||
659 predicate.equals(L0.HasName) ||
660 predicate.equals(L0.NameOf))
662 Edge edge = createEdge(graph, stat,
663 getNode(stat.getSubject()),
664 getNode(stat.getObject()));
666 addedEdges.add(edge);
668 removedEdges.add(edge);
674 } catch(DatabaseException e) {
679 private static long getOpId(ManagementSupport ms, long revisionId) throws DatabaseException {
680 Collection<ChangeSetIdentifier> ids = ms.getChangeSetIdentifiers(revisionId, revisionId);
681 ChangeSetIdentifier curId = ids.iterator().next();
682 byte[] opIdData = curId.getMetadata().get("opid");
683 System.out.println(new String(opIdData));
684 long opId = Long.parseLong(new String(opIdData));