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.Transferable;
9 import java.awt.datatransfer.UnsupportedFlavorException;
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.IOException;
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.simantics.Simantics;
35 import org.simantics.db.ChangeSet;
36 import org.simantics.db.ChangeSetIdentifier;
37 import org.simantics.db.ReadGraph;
38 import org.simantics.db.Resource;
39 import org.simantics.db.Session;
40 import org.simantics.db.Statement;
41 import org.simantics.db.common.request.ReadRequest;
42 import org.simantics.db.common.utils.NameUtils;
43 import org.simantics.db.exception.DatabaseException;
44 import org.simantics.db.service.ManagementSupport;
45 import org.simantics.debug.graphical.layout.ExtensionLayoutAlgorithm;
46 import org.simantics.debug.graphical.layout.LayoutGraph;
47 import org.simantics.debug.graphical.model.Edge;
48 import org.simantics.debug.graphical.model.LabelContent;
49 import org.simantics.debug.graphical.model.Node;
50 import org.simantics.debug.graphical.model.NodeData;
51 import org.simantics.layer0.Layer0;
52 import org.simantics.scl.runtime.SCLContext;
53 import org.simantics.scl.runtime.function.Function;
54 import org.simantics.ui.dnd.LocalObjectTransfer;
55 import org.simantics.ui.dnd.LocalObjectTransferable;
56 import org.simantics.ui.selection.AnyResource;
57 import org.simantics.ui.selection.WorkbenchSelectionElement;
59 import gnu.trove.list.array.TDoubleArrayList;
60 import gnu.trove.map.hash.THashMap;
61 import gnu.trove.map.hash.TObjectIntHashMap;
63 public class DebuggerCanvas extends JPanel {
65 private static final long serialVersionUID = -718678297301786379L;
67 ArrayList<Node> nodes = new ArrayList<Node>();
68 THashMap<Resource, Node> nodeMap = new THashMap<Resource, Node>();
69 ArrayList<Edge> edges = new ArrayList<Edge>();
70 ArrayList<Node> extensionNodes = new ArrayList<Node>();
71 ArrayList<Edge> extensionEdges = new ArrayList<Edge>();
72 ArrayList<Edge> addedEdges = new ArrayList<Edge>();
73 ArrayList<Edge> removedEdges = new ArrayList<Edge>();
74 double canvasPosX = 0.0;
75 double canvasPosY = 0.0;
76 double canvasZoom = 1.0;
77 boolean extensionMode = false;
78 Random random = new Random();
79 public Function statementFilter;
81 LabelingPreferences labelingPreferences =
82 new LabelingPreferences();
84 public void setStatementFilter(Function statementFilter) {
85 this.statementFilter = statementFilter;
88 public void removeStatementFilter() {
89 this.statementFilter = null;
93 public void paint(Graphics _g) {
94 Graphics2D g = (Graphics2D)_g;
96 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
97 RenderingHints.VALUE_ANTIALIAS_ON);
98 g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
99 RenderingHints.VALUE_FRACTIONALMETRICS_ON);
100 g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
101 RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
103 g.setPaint(new GradientPaint(0.0f, 0.0f, new Color(200, 200, 200), getWidth(), getHeight(), Color.WHITE));
104 g.fill(new Rectangle2D.Double(0, 0, getWidth(), getHeight()));
105 g.setColor(Color.BLACK);
106 g.setTransform(new AffineTransform(
109 -canvasPosX*canvasZoom, -canvasPosY*canvasZoom));
110 for(Node node : nodes)
112 for(Edge edge : edges)
115 for(Node node : extensionNodes)
117 for(Edge edge : extensionEdges)
120 g.setColor(Color.GREEN);
121 for(Edge edge : addedEdges)
123 g.setColor(Color.RED);
124 for(Edge edge : removedEdges)
128 public Node pick(double x, double y) {
129 for(Node node : nodes)
135 public Node pickExtension(double x, double y) {
136 for(Node node : extensionNodes)
143 addMouseListener(new MouseAdapter() {
145 public void mousePressed(MouseEvent e) {
146 double x = canvasPosX+e.getX()/canvasZoom;
147 double y = canvasPosY+e.getY()/canvasZoom;
148 if(e.getButton() == MouseEvent.BUTTON1)
149 handleMouseLeftPressed(x, y);
150 else if(e.getButton() == MouseEvent.BUTTON2)
151 handleMouseMiddlePressed(x, y);
154 public void mouseMoved(MouseEvent e) {
155 handleMouseMoved(canvasPosX+e.getX()/canvasZoom,
156 canvasPosY+e.getY()/canvasZoom);
159 public void mouseReleased(MouseEvent e) {
160 handleMouseReleased(canvasPosX+e.getX()/canvasZoom,
161 canvasPosY+e.getY()/canvasZoom);
164 public void mouseWheelMoved(MouseWheelEvent e) {
165 double x = canvasPosX+e.getX()/canvasZoom;
166 double y = canvasPosY+e.getY()/canvasZoom;
167 handleMouseWheelMoved(x, y, e.getWheelRotation());
170 addMouseMotionListener(new MouseMotionAdapter() {
172 public void mouseDragged(MouseEvent e) {
173 handleMouseMoved(canvasPosX+e.getX()/canvasZoom,
174 canvasPosY+e.getY()/canvasZoom);
177 addMouseWheelListener(new MouseWheelListener() {
179 public void mouseWheelMoved(MouseWheelEvent e) {
180 double x = canvasPosX+e.getX()/canvasZoom;
181 double y = canvasPosY+e.getY()/canvasZoom;
182 handleMouseWheelMoved(x, y, e.getWheelRotation());
185 addKeyListener(new UsefulKeyAdapter(new KeyAdapter() {
187 public void keyPressed(KeyEvent e) {
188 DebuggerCanvas.this.keyPressed(e);
191 public void keyReleased(KeyEvent e) {
192 DebuggerCanvas.this.keyReleased(e);
195 DropTarget dropTarget = new DropTarget(this, new DropTargetAdapter() {
197 public void drop(DropTargetDropEvent dtde) {
199 Transferable transferable = dtde.getTransferable();
201 if( transferable.isDataFlavorSupported(
202 LocalObjectTransferable.FLAVOR ) ) {
203 dtde.acceptDrop( DnDConstants.ACTION_MOVE );
205 transferable.getTransferData(LocalObjectTransferable.FLAVOR );
206 Object obj = LocalObjectTransfer.getTransfer().getObject();
207 double x = canvasPosX+dtde.getLocation().getX()/canvasZoom;
208 double y = canvasPosY+dtde.getLocation().getY()/canvasZoom;
209 handleDrop(x, y, obj);
211 dtde.getDropTargetContext().dropComplete( true );
216 } catch( IOException exception ) {
217 exception.printStackTrace();
219 } catch( UnsupportedFlavorException ufException ) {
220 ufException.printStackTrace();
227 public void keyPressed(KeyEvent e) {
228 switch(e.getKeyCode()) {
235 case KeyEvent.VK_CONTROL:
237 initializeExtension();
238 extensionMode = true;
243 findPreviousChangeset();
245 case KeyEvent.VK_DELETE:
246 if (!extensionMode && dragging != null) {
247 nodes.remove(dragging);
255 public void keyReleased(KeyEvent e) {
256 if(e.getKeyCode() == KeyEvent.VK_CONTROL) {
257 extensionMode = false;
263 private static Resource extractResource(Object obj) {
264 System.out.println("- " + obj.getClass().getName());
265 if(obj instanceof WorkbenchSelectionElement) {
266 Resource resource = ((WorkbenchSelectionElement)obj).getContent(new AnyResource(Simantics.getSession()));
270 if(obj instanceof IAdaptable) {
271 Resource resource = (Resource)((IAdaptable)obj).getAdapter(Resource.class);
278 private void handleDrop(double x, double y, Object obj) {
279 //System.out.println(obj.getClass().getName());
280 if(obj instanceof IStructuredSelection) {
281 for(Object element : ((IStructuredSelection)obj).toArray()) {
282 Resource resource = extractResource(element);
283 if(resource != null && !nodeMap.containsKey(resource)) {
284 addResource(x, y, resource);
291 private Node addResource(double x, double y, Resource resource) {
292 Node a = new Node(new NodeData(resource));
299 public void addResource(Resource resource) {
301 if(nodes.isEmpty()) {
306 double xMin=Double.POSITIVE_INFINITY, yMin=Double.POSITIVE_INFINITY;
307 double xMax=Double.NEGATIVE_INFINITY, yMax=Double.NEGATIVE_INFINITY;
308 for(Node node : nodes) {
309 xMin = Math.min(node.getMinX(), xMin);
310 yMin = Math.min(node.getMinY(), yMin);
311 xMax = Math.max(node.getMaxX(), xMax);
312 yMax = Math.max(node.getMaxY(), yMax);
314 x = xMin + (xMax - xMin) * random.nextDouble();
315 y = yMin + (yMax - yMin) * random.nextDouble();
318 addResource(x, y, resource);
322 private void scheduleUpdate() {
323 Simantics.getSession().asyncRequest(new ReadRequest() {
325 public void run(ReadGraph graph) throws DatabaseException {
328 SwingUtilities.invokeLater(new Runnable() {
339 public void layoutGraph() {
340 ArrayList<Edge> allEdges = new ArrayList<Edge>(
341 edges.size() + addedEdges.size() + removedEdges.size()
343 allEdges.addAll(edges);
344 allEdges.addAll(addedEdges);
345 allEdges.addAll(removedEdges);
347 nodes.toArray(new Node[nodes.size()]),
348 allEdges.toArray(new Edge[edges.size()]));
352 private void updateNodes(ReadGraph graph) throws DatabaseException {
353 for(Node node : nodes) {
354 node.getData().updateData(graph, labelingPreferences);
355 node.setContent(new LabelContent(node.getData().getLabels()));
358 for(Node node : nodes) {
359 NodeData data = node.getData();
360 nodeMap.put(data.getResource(), node);
364 private void updateEdges(ReadGraph graph) throws DatabaseException {
365 ArrayList<Edge> edges = new ArrayList<Edge>();
366 for(Node node : nodes) {
367 NodeData data = node.getData();
368 Resource subject = data.getResource();
369 ArrayList<Statement> filteredStatements = new ArrayList<Statement>(data.getStatements().size());
370 for(Statement stat : data.getStatements()) {
371 Resource object = stat.getObject();
372 Node node2 = nodeMap.get(object);
374 if(object.getResourceId() > subject.getResourceId() ||
375 graph.getPossibleInverse(stat.getPredicate()) == null) {
376 edges.add(createEdge(graph, stat, node, node2));
380 filteredStatements.add(stat);
382 data.setStatements(filteredStatements);
385 this.addedEdges = filterEdgesWithoutNodes( this.addedEdges );
386 this.removedEdges = filterEdgesWithoutNodes( this.removedEdges );
389 private ArrayList<Edge> filterEdgesWithoutNodes(Collection<Edge> edges) {
390 ArrayList<Edge> result = new ArrayList<Edge>(edges.size());
391 for (Edge e : edges) {
392 if (!nodeMap.containsValue(e.getA()) || !nodeMap.containsValue(e.getB()))
399 private Edge createEdge(ReadGraph graph, Statement stat, Node n1, Node n2) throws DatabaseException {
400 Resource predicate = stat.getPredicate();
401 Resource inverse = graph.getPossibleInverse(predicate);
402 if(inverse != null) {
403 Layer0 L0 = Layer0.getInstance(graph);
404 if(graph.hasStatement(predicate, L0.PartOf, inverse)
405 || predicate.equals(L0.PartOf)
406 || predicate.equals(L0.SuperrelationOf)
407 || predicate.equals(L0.SupertypeOf)) {
414 Edge edge = new Edge(n1, n2);
415 edge.setContent(new LabelContent(new String[] {
416 NameUtils.getSafeName(graph, predicate)}));
420 Node dragging = null;
421 double dragDX, dragDY;
422 private void handleMouseLeftPressed(double x, double y) {
425 node = pickExtension(x, y);
428 extensionNodes.remove(node);
434 dragDX = x - node.getX();
435 dragDY = y - node.getY();
440 Point2D panningStartMouse;
441 private void handleMouseMiddlePressed(double x, double y) {
442 panningStartMouse = new Point2D.Double(x, y);
445 private void handleMouseMoved(double x, double y) {
446 if(dragging != null) {
447 dragging.setPos(x-dragDX, y-dragDY);
450 if(panningStartMouse != null) {
451 canvasPosX -= x - panningStartMouse.getX();
452 canvasPosY -= y - panningStartMouse.getY();
457 private void handleMouseWheelMoved(double x, double y, double amount) {
458 double s = Math.exp(-0.2*amount);
460 canvasPosX = x - (x-canvasPosX)/s;
461 canvasPosY = y - (y-canvasPosY)/s;
465 private void handleMouseReleased(double x, double y) {
467 panningStartMouse = null;
470 public void zoomToFit() {
471 if(!nodes.isEmpty()) {
472 double minX = Double.POSITIVE_INFINITY;
473 double minY = Double.POSITIVE_INFINITY;
474 double maxX = Double.NEGATIVE_INFINITY;
475 double maxY = Double.NEGATIVE_INFINITY;
476 System.out.println("(" + minX + "," + minY + ") - (" + maxX + "," + maxY + ")");
477 for(Node node : nodes) {
478 minX = Math.min(minX, node.getMinX());
479 minY = Math.min(minY, node.getMinY());
480 maxX = Math.max(maxX, node.getMaxX());
481 maxY = Math.max(maxY, node.getMaxY());
483 canvasZoom = Math.min(getWidth()/(maxX-minX), getHeight()/(maxY-minY));
485 canvasPosX = minX - 0.5 * (getWidth()/canvasZoom - maxX+minX);
486 canvasPosY = minY - 0.5 * (getHeight()/canvasZoom - maxY+minY);
491 THashMap<Resource, Node> extensionNodeMap = new THashMap<Resource, Node>();
492 public void initializeExtension() {
493 extensionNodes.clear();
494 extensionEdges.clear();
496 Simantics.getSession().syncRequest(new ReadRequest() {
498 public void run(ReadGraph graph) throws DatabaseException {
499 SCLContext sclContext = SCLContext.getCurrent();
500 Object oldGraph = sclContext.put("graph", graph);
502 THashMap<Resource, Node> oldExtensionNodeMap = DebuggerCanvas.this.extensionNodeMap;
503 THashMap<Resource, Node> extensionNodeMap = new THashMap<Resource, Node>();
504 for(Node node : nodes) {
505 for(Statement stat : node.getData().getStatements()) {
506 Resource object = stat.getObject();
507 Node node2 = extensionNodeMap.get(object);
509 if(statementFilter != null && Boolean.FALSE.equals(statementFilter.apply(stat)))
511 node2 = oldExtensionNodeMap.get(object);
513 node2 = new Node(new NodeData(object));
514 double angle = random.nextDouble() * Math.PI * 2.0;
515 double dx = Math.cos(angle);
516 double dy = Math.sin(angle);
518 node2.setPos(node.getX() + dx*len, node.getY() + dy*len);
520 node2.getData().updateData(graph, labelingPreferences);
521 node2.setContent(new LabelContent(node2.getData().getLabels()));
522 extensionNodeMap.put(object, node2);
523 extensionNodes.add(node2);
525 extensionEdges.add(createEdge(graph, stat, node, node2));
528 } catch (Throwable t) {
529 if (t instanceof DatabaseException)
530 throw (DatabaseException) t;
531 throw new DatabaseException(t);
533 sclContext.put("graph", oldGraph);
535 DebuggerCanvas.this.extensionNodeMap = extensionNodeMap;
539 } catch (DatabaseException e) {
544 private void layoutExtension() {
545 TObjectIntHashMap<Node> extensionNodeIds = new TObjectIntHashMap<Node>();
546 for(int i=0;i<extensionNodes.size();++i)
547 extensionNodeIds.put(extensionNodes.get(i), i);
549 double[][] neighbors = new double[extensionNodes.size()][];
551 TDoubleArrayList[] neighborLists = new TDoubleArrayList[neighbors.length];
552 for(int i=0;i<neighborLists.length;++i)
553 neighborLists[i] = new TDoubleArrayList();
554 for(Edge edge : extensionEdges) {
557 if(extensionNodeIds.containsKey(edge.getA())) {
558 id = extensionNodeIds.get(edge.getA());
562 id = extensionNodeIds.get(edge.getB());
565 TDoubleArrayList list = neighborLists[id];
566 list.add(node.getX());
567 list.add(node.getY());
569 for(int i=0;i<neighborLists.length;++i) {
570 neighbors[i] = neighborLists[i].toArray();
574 double[] fixedRepulsiveX = new double[nodes.size()];
575 double[] fixedRepulsiveY = new double[nodes.size()];
576 for(int i=0;i<nodes.size();++i) {
577 Node node = nodes.get(i);
578 fixedRepulsiveX[i] = node.getX();
579 fixedRepulsiveY[i] = node.getY();
581 ExtensionLayoutAlgorithm algo =
582 new ExtensionLayoutAlgorithm(neighbors, fixedRepulsiveX, fixedRepulsiveY);
583 double[] posX = algo.getPosX();
584 double[] posY = algo.getPosY();
585 for(int i=0;i<extensionNodes.size();++i) {
586 posX[i] = extensionNodes.get(i).getX();
587 posY[i] = extensionNodes.get(i).getY();
590 for(int i=0;i<extensionNodes.size();++i) {
591 extensionNodes.get(i).setPos(posX[i], posY[i]);
595 private Node getNode(Resource resource) {
596 Node node = nodeMap.get(resource);
598 node = addResource(random.nextDouble()*200.0-100.0,
599 random.nextDouble()*200.0-100.0,
601 nodeMap.put(resource, node);
606 public void findPreviousChangeset() {
608 Session session = Simantics.getSession();
609 final ManagementSupport ms = session.getService(ManagementSupport.class);
610 final long lastId = ms.getHeadRevisionId();
611 final long firstId = getOpId(ms, lastId);
612 //System.out.println(firstId + "-" + lastId);
614 removedEdges.clear();
615 session.asyncRequest(new ReadRequest() {
617 public void run(ReadGraph graph) throws DatabaseException {
618 final Collection<ChangeSet> css =
619 ms.fetchChangeSets(graph, firstId, lastId);
620 Layer0 L0 = Layer0.getInstance(graph);
621 for(ChangeSet cs : css) {
622 for(ChangeSet.StatementChange stat : cs.changedStatements()) {
623 Resource predicate = stat.getPredicate();
624 if(predicate.equals(L0.InstanceOf) ||
625 predicate.equals(L0.HasName) ||
626 predicate.equals(L0.NameOf))
628 Edge edge = createEdge(graph, stat,
629 getNode(stat.getSubject()),
630 getNode(stat.getObject()));
632 addedEdges.add(edge);
634 removedEdges.add(edge);
640 } catch(DatabaseException e) {
645 private static long getOpId(ManagementSupport ms, long revisionId) throws DatabaseException {
646 Collection<ChangeSetIdentifier> ids = ms.getChangeSetIdentifiers(revisionId, revisionId);
647 ChangeSetIdentifier curId = ids.iterator().next();
648 byte[] opIdData = curId.getMetadata().get("opid");
649 System.out.println(new String(opIdData));
650 long opId = Long.parseLong(new String(opIdData));