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);
218 dtde.getDropTargetContext().dropComplete( true );
221 DataFlavor textFlavor = DataFlavor.selectBestTextFlavor(transferable.getTransferDataFlavors());
222 if(textFlavor != null) {
223 try(Reader reader = textFlavor.getReaderForText(transferable)) {
224 ObjectMapper mapper = new ObjectMapper();
225 JsonNode node = mapper.readTree(reader);
226 Object resourceId = node.get("resourceId");
227 if(resourceId instanceof NumericNode) {
228 dtde.acceptDrop( DnDConstants.ACTION_MOVE );
230 transferable.getTransferData(LocalObjectTransferable.FLAVOR);
231 Object obj = new StructuredSelection(Simantics.getSession().syncRequest(new Read<Resource>() {
232 public Resource perform(ReadGraph graph) throws DatabaseException {
233 SerialisationSupport ss = graph.getService(SerialisationSupport.class);
234 return ss.getResource(((NumericNode)resourceId).longValue());
237 handleDrop(dtde, obj);
239 dtde.getDropTargetContext().dropComplete( true );
246 } catch( Exception exception ) {
247 LOGGER.warn("Drop failed.", exception);
254 private void handleDrop(DropTargetDropEvent dtde, Object obj) {
255 double x = canvasPosX+dtde.getLocation().getX()/canvasZoom;
256 double y = canvasPosY+dtde.getLocation().getY()/canvasZoom;
257 handleDrop(x, y, obj);
260 public void keyPressed(KeyEvent e) {
261 switch(e.getKeyCode()) {
268 case KeyEvent.VK_CONTROL:
270 initializeExtension();
271 extensionMode = true;
276 findPreviousChangeset();
278 case KeyEvent.VK_DELETE:
279 if (!extensionMode && dragging != null) {
280 nodes.remove(dragging);
288 public void keyReleased(KeyEvent e) {
289 if(e.getKeyCode() == KeyEvent.VK_CONTROL) {
290 extensionMode = false;
296 private static Resource extractResource(Object obj) {
297 System.out.println("- " + obj.getClass().getName());
298 if(obj instanceof WorkbenchSelectionElement) {
299 Resource resource = ((WorkbenchSelectionElement)obj).getContent(new AnyResource(Simantics.getSession()));
303 if(obj instanceof IAdaptable) {
304 Resource resource = (Resource)((IAdaptable)obj).getAdapter(Resource.class);
308 if(obj instanceof Resource)
309 return (Resource)obj;
313 private void handleDrop(double x, double y, Object obj) {
314 //System.out.println(obj.getClass().getName());
315 if(obj instanceof IStructuredSelection) {
316 for(Object element : ((IStructuredSelection)obj).toArray()) {
317 Resource resource = extractResource(element);
318 if(resource != null && !nodeMap.containsKey(resource)) {
319 addResource(x, y, resource);
326 private Node addResource(double x, double y, Resource resource) {
327 Node a = new Node(new NodeData(resource));
334 public void addResource(Resource resource) {
336 if(nodes.isEmpty()) {
341 double xMin=Double.POSITIVE_INFINITY, yMin=Double.POSITIVE_INFINITY;
342 double xMax=Double.NEGATIVE_INFINITY, yMax=Double.NEGATIVE_INFINITY;
343 for(Node node : nodes) {
344 xMin = Math.min(node.getMinX(), xMin);
345 yMin = Math.min(node.getMinY(), yMin);
346 xMax = Math.max(node.getMaxX(), xMax);
347 yMax = Math.max(node.getMaxY(), yMax);
349 x = xMin + (xMax - xMin) * random.nextDouble();
350 y = yMin + (yMax - yMin) * random.nextDouble();
353 addResource(x, y, resource);
357 private void scheduleUpdate() {
358 Simantics.getSession().asyncRequest(new ReadRequest() {
360 public void run(ReadGraph graph) throws DatabaseException {
363 SwingUtilities.invokeLater(new Runnable() {
374 public void layoutGraph() {
375 ArrayList<Edge> allEdges = new ArrayList<Edge>(
376 edges.size() + addedEdges.size() + removedEdges.size()
378 allEdges.addAll(edges);
379 allEdges.addAll(addedEdges);
380 allEdges.addAll(removedEdges);
382 nodes.toArray(new Node[nodes.size()]),
383 allEdges.toArray(new Edge[edges.size()]));
387 private void updateNodes(ReadGraph graph) throws DatabaseException {
388 for(Node node : nodes) {
389 node.getData().updateData(graph, labelingPreferences);
390 node.setContent(new LabelContent(node.getData().getLabels()));
393 for(Node node : nodes) {
394 NodeData data = node.getData();
395 nodeMap.put(data.getResource(), node);
399 private void updateEdges(ReadGraph graph) throws DatabaseException {
400 ArrayList<Edge> edges = new ArrayList<Edge>();
401 for(Node node : nodes) {
402 NodeData data = node.getData();
403 Resource subject = data.getResource();
404 ArrayList<Statement> filteredStatements = new ArrayList<Statement>(data.getStatements().size());
405 for(Statement stat : data.getStatements()) {
406 Resource object = stat.getObject();
407 Node node2 = nodeMap.get(object);
409 if(object.getResourceId() > subject.getResourceId() ||
410 graph.getPossibleInverse(stat.getPredicate()) == null) {
411 edges.add(createEdge(graph, stat, node, node2));
415 filteredStatements.add(stat);
417 data.setStatements(filteredStatements);
420 this.addedEdges = filterEdgesWithoutNodes( this.addedEdges );
421 this.removedEdges = filterEdgesWithoutNodes( this.removedEdges );
424 private ArrayList<Edge> filterEdgesWithoutNodes(Collection<Edge> edges) {
425 ArrayList<Edge> result = new ArrayList<Edge>(edges.size());
426 for (Edge e : edges) {
427 if (!nodeMap.containsValue(e.getA()) || !nodeMap.containsValue(e.getB()))
434 private Edge createEdge(ReadGraph graph, Statement stat, Node n1, Node n2) throws DatabaseException {
435 Resource predicate = stat.getPredicate();
436 Resource inverse = graph.getPossibleInverse(predicate);
437 if(inverse != null) {
438 Layer0 L0 = Layer0.getInstance(graph);
439 if(graph.hasStatement(predicate, L0.PartOf, inverse)
440 || predicate.equals(L0.PartOf)
441 || predicate.equals(L0.SuperrelationOf)
442 || predicate.equals(L0.SupertypeOf)) {
449 Edge edge = new Edge(n1, n2);
450 edge.setContent(new LabelContent(new String[] {
451 NameUtils.getSafeName(graph, predicate)}));
455 Node dragging = null;
456 double dragDX, dragDY;
457 private void handleMouseLeftPressed(double x, double y) {
460 node = pickExtension(x, y);
463 extensionNodes.remove(node);
469 dragDX = x - node.getX();
470 dragDY = y - node.getY();
475 Point2D panningStartMouse;
476 private void handleMouseMiddlePressed(double x, double y) {
477 panningStartMouse = new Point2D.Double(x, y);
480 private void handleMouseMoved(double x, double y) {
481 if(dragging != null) {
482 dragging.setPos(x-dragDX, y-dragDY);
485 if(panningStartMouse != null) {
486 canvasPosX -= x - panningStartMouse.getX();
487 canvasPosY -= y - panningStartMouse.getY();
492 private void handleMouseWheelMoved(double x, double y, double amount) {
493 double s = Math.exp(-0.2*amount);
495 canvasPosX = x - (x-canvasPosX)/s;
496 canvasPosY = y - (y-canvasPosY)/s;
500 private void handleMouseReleased(double x, double y) {
502 panningStartMouse = null;
505 public void zoomToFit() {
506 if(!nodes.isEmpty()) {
507 double minX = Double.POSITIVE_INFINITY;
508 double minY = Double.POSITIVE_INFINITY;
509 double maxX = Double.NEGATIVE_INFINITY;
510 double maxY = Double.NEGATIVE_INFINITY;
511 System.out.println("(" + minX + "," + minY + ") - (" + maxX + "," + maxY + ")");
512 for(Node node : nodes) {
513 minX = Math.min(minX, node.getMinX());
514 minY = Math.min(minY, node.getMinY());
515 maxX = Math.max(maxX, node.getMaxX());
516 maxY = Math.max(maxY, node.getMaxY());
518 canvasZoom = Math.min(getWidth()/(maxX-minX), getHeight()/(maxY-minY));
520 canvasPosX = minX - 0.5 * (getWidth()/canvasZoom - maxX+minX);
521 canvasPosY = minY - 0.5 * (getHeight()/canvasZoom - maxY+minY);
526 THashMap<Resource, Node> extensionNodeMap = new THashMap<Resource, Node>();
527 public void initializeExtension() {
528 extensionNodes.clear();
529 extensionEdges.clear();
531 Simantics.getSession().syncRequest(new ReadRequest() {
533 public void run(ReadGraph graph) throws DatabaseException {
534 SCLContext sclContext = SCLContext.getCurrent();
535 Object oldGraph = sclContext.put("graph", graph);
537 THashMap<Resource, Node> oldExtensionNodeMap = DebuggerCanvas.this.extensionNodeMap;
538 THashMap<Resource, Node> extensionNodeMap = new THashMap<Resource, Node>();
539 for(Node node : nodes) {
540 for(Statement stat : node.getData().getStatements()) {
541 Resource object = stat.getObject();
542 Node node2 = extensionNodeMap.get(object);
544 if(statementFilter != null && Boolean.FALSE.equals(statementFilter.apply(stat)))
546 node2 = oldExtensionNodeMap.get(object);
548 node2 = new Node(new NodeData(object));
549 double angle = random.nextDouble() * Math.PI * 2.0;
550 double dx = Math.cos(angle);
551 double dy = Math.sin(angle);
553 node2.setPos(node.getX() + dx*len, node.getY() + dy*len);
555 node2.getData().updateData(graph, labelingPreferences);
556 node2.setContent(new LabelContent(node2.getData().getLabels()));
557 extensionNodeMap.put(object, node2);
558 extensionNodes.add(node2);
560 extensionEdges.add(createEdge(graph, stat, node, node2));
563 } catch (Throwable t) {
564 if (t instanceof DatabaseException)
565 throw (DatabaseException) t;
566 throw new DatabaseException(t);
568 sclContext.put("graph", oldGraph);
570 DebuggerCanvas.this.extensionNodeMap = extensionNodeMap;
574 } catch (DatabaseException e) {
579 private void layoutExtension() {
580 TObjectIntHashMap<Node> extensionNodeIds = new TObjectIntHashMap<Node>();
581 for(int i=0;i<extensionNodes.size();++i)
582 extensionNodeIds.put(extensionNodes.get(i), i);
584 double[][] neighbors = new double[extensionNodes.size()][];
586 TDoubleArrayList[] neighborLists = new TDoubleArrayList[neighbors.length];
587 for(int i=0;i<neighborLists.length;++i)
588 neighborLists[i] = new TDoubleArrayList();
589 for(Edge edge : extensionEdges) {
592 if(extensionNodeIds.containsKey(edge.getA())) {
593 id = extensionNodeIds.get(edge.getA());
597 id = extensionNodeIds.get(edge.getB());
600 TDoubleArrayList list = neighborLists[id];
601 list.add(node.getX());
602 list.add(node.getY());
604 for(int i=0;i<neighborLists.length;++i) {
605 neighbors[i] = neighborLists[i].toArray();
609 double[] fixedRepulsiveX = new double[nodes.size()];
610 double[] fixedRepulsiveY = new double[nodes.size()];
611 for(int i=0;i<nodes.size();++i) {
612 Node node = nodes.get(i);
613 fixedRepulsiveX[i] = node.getX();
614 fixedRepulsiveY[i] = node.getY();
616 ExtensionLayoutAlgorithm algo =
617 new ExtensionLayoutAlgorithm(neighbors, fixedRepulsiveX, fixedRepulsiveY);
618 double[] posX = algo.getPosX();
619 double[] posY = algo.getPosY();
620 for(int i=0;i<extensionNodes.size();++i) {
621 posX[i] = extensionNodes.get(i).getX();
622 posY[i] = extensionNodes.get(i).getY();
625 for(int i=0;i<extensionNodes.size();++i) {
626 extensionNodes.get(i).setPos(posX[i], posY[i]);
630 private Node getNode(Resource resource) {
631 Node node = nodeMap.get(resource);
633 node = addResource(random.nextDouble()*200.0-100.0,
634 random.nextDouble()*200.0-100.0,
636 nodeMap.put(resource, node);
641 public void findPreviousChangeset() {
643 Session session = Simantics.getSession();
644 final ManagementSupport ms = session.getService(ManagementSupport.class);
645 final long lastId = ms.getHeadRevisionId();
646 final long firstId = getOpId(ms, lastId);
647 //System.out.println(firstId + "-" + lastId);
649 removedEdges.clear();
650 session.asyncRequest(new ReadRequest() {
652 public void run(ReadGraph graph) throws DatabaseException {
653 final Collection<ChangeSet> css =
654 ms.fetchChangeSets(graph, firstId, lastId);
655 Layer0 L0 = Layer0.getInstance(graph);
656 for(ChangeSet cs : css) {
657 for(ChangeSet.StatementChange stat : cs.changedStatements()) {
658 Resource predicate = stat.getPredicate();
659 if(predicate.equals(L0.InstanceOf) ||
660 predicate.equals(L0.HasName) ||
661 predicate.equals(L0.NameOf))
663 Edge edge = createEdge(graph, stat,
664 getNode(stat.getSubject()),
665 getNode(stat.getObject()));
667 addedEdges.add(edge);
669 removedEdges.add(edge);
675 } catch(DatabaseException e) {
680 private static long getOpId(ManagementSupport ms, long revisionId) throws DatabaseException {
681 Collection<ChangeSetIdentifier> ids = ms.getChangeSetIdentifiers(revisionId, revisionId);
682 ChangeSetIdentifier curId = ids.iterator().next();
683 byte[] opIdData = curId.getMetadata().get("opid");
684 System.out.println(new String(opIdData));
685 long opId = Long.parseLong(new String(opIdData));