1 package org.simantics.debug.graphical;
3 import gnu.trove.list.array.TDoubleArrayList;
4 import gnu.trove.map.hash.THashMap;
5 import gnu.trove.map.hash.TObjectIntHashMap;
8 import java.awt.GradientPaint;
9 import java.awt.Graphics;
10 import java.awt.Graphics2D;
11 import java.awt.RenderingHints;
12 import java.awt.datatransfer.Transferable;
13 import java.awt.datatransfer.UnsupportedFlavorException;
14 import java.awt.dnd.DnDConstants;
15 import java.awt.dnd.DropTarget;
16 import java.awt.dnd.DropTargetAdapter;
17 import java.awt.dnd.DropTargetDropEvent;
18 import java.awt.event.KeyAdapter;
19 import java.awt.event.KeyEvent;
20 import java.awt.event.MouseAdapter;
21 import java.awt.event.MouseEvent;
22 import java.awt.event.MouseMotionAdapter;
23 import java.awt.event.MouseWheelEvent;
24 import java.awt.event.MouseWheelListener;
25 import java.awt.geom.AffineTransform;
26 import java.awt.geom.Point2D;
27 import java.awt.geom.Rectangle2D;
28 import java.io.IOException;
29 import java.util.ArrayList;
30 import java.util.Collection;
31 import java.util.Random;
33 import javax.swing.JPanel;
34 import javax.swing.SwingUtilities;
36 import org.eclipse.core.runtime.IAdaptable;
37 import org.eclipse.jface.viewers.IStructuredSelection;
38 import org.simantics.Simantics;
39 import org.simantics.db.ChangeSet;
40 import org.simantics.db.ChangeSetIdentifier;
41 import org.simantics.db.ReadGraph;
42 import org.simantics.db.Resource;
43 import org.simantics.db.Session;
44 import org.simantics.db.Statement;
45 import org.simantics.db.common.request.ReadRequest;
46 import org.simantics.db.common.utils.NameUtils;
47 import org.simantics.db.exception.DatabaseException;
48 import org.simantics.db.service.ManagementSupport;
49 import org.simantics.debug.graphical.layout.ExtensionLayoutAlgorithm;
50 import org.simantics.debug.graphical.layout.LayoutGraph;
51 import org.simantics.debug.graphical.model.Edge;
52 import org.simantics.debug.graphical.model.LabelContent;
53 import org.simantics.debug.graphical.model.Node;
54 import org.simantics.debug.graphical.model.NodeData;
55 import org.simantics.layer0.Layer0;
56 import org.simantics.ui.dnd.LocalObjectTransfer;
57 import org.simantics.ui.dnd.LocalObjectTransferable;
58 import org.simantics.ui.selection.AnyResource;
59 import org.simantics.ui.selection.WorkbenchSelectionElement;
61 public class DebuggerCanvas extends JPanel {
63 private static final long serialVersionUID = -718678297301786379L;
65 ArrayList<Node> nodes = new ArrayList<Node>();
66 THashMap<Resource, Node> nodeMap = new THashMap<Resource, Node>();
67 ArrayList<Edge> edges = new ArrayList<Edge>();
68 ArrayList<Node> extensionNodes = new ArrayList<Node>();
69 ArrayList<Edge> extensionEdges = new ArrayList<Edge>();
70 ArrayList<Edge> addedEdges = new ArrayList<Edge>();
71 ArrayList<Edge> removedEdges = new ArrayList<Edge>();
72 double canvasPosX = 0.0;
73 double canvasPosY = 0.0;
74 double canvasZoom = 1.0;
75 boolean extensionMode = false;
76 Random random = new Random();
78 LabelingPreferences labelingPreferences =
79 new LabelingPreferences();
82 public void paint(Graphics _g) {
83 Graphics2D g = (Graphics2D)_g;
85 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
86 RenderingHints.VALUE_ANTIALIAS_ON);
87 g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
88 RenderingHints.VALUE_FRACTIONALMETRICS_ON);
89 g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
90 RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
92 g.setPaint(new GradientPaint(0.0f, 0.0f, new Color(200, 200, 200), getWidth(), getHeight(), Color.WHITE));
93 g.fill(new Rectangle2D.Double(0, 0, getWidth(), getHeight()));
94 g.setColor(Color.BLACK);
95 g.setTransform(new AffineTransform(
98 -canvasPosX*canvasZoom, -canvasPosY*canvasZoom));
99 for(Node node : nodes)
101 for(Edge edge : edges)
104 for(Node node : extensionNodes)
106 for(Edge edge : extensionEdges)
109 g.setColor(Color.GREEN);
110 for(Edge edge : addedEdges)
112 g.setColor(Color.RED);
113 for(Edge edge : removedEdges)
117 public Node pick(double x, double y) {
118 for(Node node : nodes)
124 public Node pickExtension(double x, double y) {
125 for(Node node : extensionNodes)
132 addMouseListener(new MouseAdapter() {
134 public void mousePressed(MouseEvent e) {
135 double x = canvasPosX+e.getX()/canvasZoom;
136 double y = canvasPosY+e.getY()/canvasZoom;
137 if(e.getButton() == MouseEvent.BUTTON1)
138 handleMouseLeftPressed(x, y);
139 else if(e.getButton() == MouseEvent.BUTTON2)
140 handleMouseMiddlePressed(x, y);
143 public void mouseMoved(MouseEvent e) {
144 handleMouseMoved(canvasPosX+e.getX()/canvasZoom,
145 canvasPosY+e.getY()/canvasZoom);
148 public void mouseReleased(MouseEvent e) {
149 handleMouseReleased(canvasPosX+e.getX()/canvasZoom,
150 canvasPosY+e.getY()/canvasZoom);
153 public void mouseWheelMoved(MouseWheelEvent e) {
154 double x = canvasPosX+e.getX()/canvasZoom;
155 double y = canvasPosY+e.getY()/canvasZoom;
156 handleMouseWheelMoved(x, y, e.getWheelRotation());
159 addMouseMotionListener(new MouseMotionAdapter() {
161 public void mouseDragged(MouseEvent e) {
162 handleMouseMoved(canvasPosX+e.getX()/canvasZoom,
163 canvasPosY+e.getY()/canvasZoom);
166 addMouseWheelListener(new MouseWheelListener() {
168 public void mouseWheelMoved(MouseWheelEvent e) {
169 double x = canvasPosX+e.getX()/canvasZoom;
170 double y = canvasPosY+e.getY()/canvasZoom;
171 handleMouseWheelMoved(x, y, e.getWheelRotation());
174 addKeyListener(new UsefulKeyAdapter(new KeyAdapter() {
176 public void keyPressed(KeyEvent e) {
177 DebuggerCanvas.this.keyPressed(e);
180 public void keyReleased(KeyEvent e) {
181 DebuggerCanvas.this.keyReleased(e);
184 DropTarget dropTarget = new DropTarget(this, new DropTargetAdapter() {
186 public void drop(DropTargetDropEvent dtde) {
188 Transferable transferable = dtde.getTransferable();
190 if( transferable.isDataFlavorSupported(
191 LocalObjectTransferable.FLAVOR ) ) {
192 dtde.acceptDrop( DnDConstants.ACTION_MOVE );
194 transferable.getTransferData(LocalObjectTransferable.FLAVOR );
195 Object obj = LocalObjectTransfer.getTransfer().getObject();
196 double x = canvasPosX+dtde.getLocation().getX()/canvasZoom;
197 double y = canvasPosY+dtde.getLocation().getY()/canvasZoom;
198 handleDrop(x, y, obj);
200 dtde.getDropTargetContext().dropComplete( true );
205 } catch( IOException exception ) {
206 exception.printStackTrace();
208 } catch( UnsupportedFlavorException ufException ) {
209 ufException.printStackTrace();
216 public void keyPressed(KeyEvent e) {
217 switch(e.getKeyCode()) {
224 case KeyEvent.VK_CONTROL:
226 initializeExtension();
227 extensionMode = true;
232 findPreviousChangeset();
234 case KeyEvent.VK_DELETE:
235 if (!extensionMode && dragging != null) {
236 nodes.remove(dragging);
244 public void keyReleased(KeyEvent e) {
245 if(e.getKeyCode() == KeyEvent.VK_CONTROL) {
246 extensionMode = false;
252 private static Resource extractResource(Object obj) {
253 System.out.println("- " + obj.getClass().getName());
254 if(obj instanceof WorkbenchSelectionElement) {
255 Resource resource = ((WorkbenchSelectionElement)obj).getContent(new AnyResource(Simantics.getSession()));
259 if(obj instanceof IAdaptable) {
260 Resource resource = (Resource)((IAdaptable)obj).getAdapter(Resource.class);
267 private void handleDrop(double x, double y, Object obj) {
268 //System.out.println(obj.getClass().getName());
269 if(obj instanceof IStructuredSelection) {
270 for(Object element : ((IStructuredSelection)obj).toArray()) {
271 Resource resource = extractResource(element);
272 if(resource != null && !nodeMap.containsKey(resource)) {
273 addResource(x, y, resource);
280 private Node addResource(double x, double y, Resource resource) {
281 Node a = new Node(new NodeData(resource));
288 private void scheduleUpdate() {
289 Simantics.getSession().asyncRequest(new ReadRequest() {
291 public void run(ReadGraph graph) throws DatabaseException {
294 SwingUtilities.invokeLater(new Runnable() {
305 public void layoutGraph() {
306 ArrayList<Edge> allEdges = new ArrayList<Edge>(
307 edges.size() + addedEdges.size() + removedEdges.size()
309 allEdges.addAll(edges);
310 allEdges.addAll(addedEdges);
311 allEdges.addAll(removedEdges);
313 nodes.toArray(new Node[nodes.size()]),
314 allEdges.toArray(new Edge[edges.size()]));
318 private void updateNodes(ReadGraph graph) throws DatabaseException {
319 for(Node node : nodes) {
320 node.getData().updateData(graph, labelingPreferences);
321 node.setContent(new LabelContent(node.getData().getLabels()));
324 for(Node node : nodes) {
325 NodeData data = node.getData();
326 nodeMap.put(data.getResource(), node);
330 private void updateEdges(ReadGraph graph) throws DatabaseException {
331 ArrayList<Edge> edges = new ArrayList<Edge>();
332 for(Node node : nodes) {
333 NodeData data = node.getData();
334 Resource subject = data.getResource();
335 ArrayList<Statement> filteredStatements = new ArrayList<Statement>(data.getStatements().size());
336 for(Statement stat : data.getStatements()) {
337 Resource object = stat.getObject();
338 Node node2 = nodeMap.get(object);
340 if(object.getResourceId() > subject.getResourceId() ||
341 graph.getPossibleInverse(stat.getPredicate()) == null) {
342 edges.add(createEdge(graph, stat, node, node2));
346 filteredStatements.add(stat);
348 data.setStatements(filteredStatements);
351 this.addedEdges = filterEdgesWithoutNodes( this.addedEdges );
352 this.removedEdges = filterEdgesWithoutNodes( this.removedEdges );
355 private ArrayList<Edge> filterEdgesWithoutNodes(Collection<Edge> edges) {
356 ArrayList<Edge> result = new ArrayList<Edge>(edges.size());
357 for (Edge e : edges) {
358 if (!nodeMap.containsValue(e.getA()) || !nodeMap.containsValue(e.getB()))
365 private Edge createEdge(ReadGraph graph, Statement stat, Node n1, Node n2) throws DatabaseException {
366 Resource predicate = stat.getPredicate();
367 Resource inverse = graph.getPossibleInverse(predicate);
368 if(inverse != null) {
369 Layer0 L0 = Layer0.getInstance(graph);
370 if(graph.hasStatement(predicate, L0.PartOf, inverse)
371 || predicate.equals(L0.PartOf)
372 || predicate.equals(L0.SuperrelationOf)
373 || predicate.equals(L0.SupertypeOf)) {
380 Edge edge = new Edge(n1, n2);
381 edge.setContent(new LabelContent(new String[] {
382 NameUtils.getSafeName(graph, predicate)}));
386 Node dragging = null;
387 double dragDX, dragDY;
388 private void handleMouseLeftPressed(double x, double y) {
391 node = pickExtension(x, y);
394 extensionNodes.remove(node);
400 dragDX = x - node.getX();
401 dragDY = y - node.getY();
406 Point2D panningStartMouse;
407 private void handleMouseMiddlePressed(double x, double y) {
408 panningStartMouse = new Point2D.Double(x, y);
411 private void handleMouseMoved(double x, double y) {
412 if(dragging != null) {
413 dragging.setPos(x-dragDX, y-dragDY);
416 if(panningStartMouse != null) {
417 canvasPosX -= x - panningStartMouse.getX();
418 canvasPosY -= y - panningStartMouse.getY();
423 private void handleMouseWheelMoved(double x, double y, double amount) {
424 double s = Math.exp(-0.2*amount);
426 canvasPosX = x - (x-canvasPosX)/s;
427 canvasPosY = y - (y-canvasPosY)/s;
431 private void handleMouseReleased(double x, double y) {
433 panningStartMouse = null;
436 public void zoomToFit() {
437 if(!nodes.isEmpty()) {
438 double minX = Double.POSITIVE_INFINITY;
439 double minY = Double.POSITIVE_INFINITY;
440 double maxX = Double.NEGATIVE_INFINITY;
441 double maxY = Double.NEGATIVE_INFINITY;
442 System.out.println("(" + minX + "," + minY + ") - (" + maxX + "," + maxY + ")");
443 for(Node node : nodes) {
444 minX = Math.min(minX, node.getMinX());
445 minY = Math.min(minY, node.getMinY());
446 maxX = Math.max(maxX, node.getMaxX());
447 maxY = Math.max(maxY, node.getMaxY());
449 canvasZoom = Math.min(getWidth()/(maxX-minX), getHeight()/(maxY-minY));
451 canvasPosX = minX - 0.5 * (getWidth()/canvasZoom - maxX+minX);
452 canvasPosY = minY - 0.5 * (getHeight()/canvasZoom - maxY+minY);
457 THashMap<Resource, Node> extensionNodeMap = new THashMap<Resource, Node>();
458 public void initializeExtension() {
459 extensionNodes.clear();
460 extensionEdges.clear();
462 Simantics.getSession().syncRequest(new ReadRequest() {
464 public void run(ReadGraph graph) throws DatabaseException {
465 THashMap<Resource, Node> oldExtensionNodeMap = DebuggerCanvas.this.extensionNodeMap;
466 THashMap<Resource, Node> extensionNodeMap = new THashMap<Resource, Node>();
467 for(Node node : nodes) {
468 for(Statement stat : node.getData().getStatements()) {
469 Resource object = stat.getObject();
470 Node node2 = extensionNodeMap.get(object);
472 node2 = oldExtensionNodeMap.get(object);
474 node2 = new Node(new NodeData(object));
475 double angle = random.nextDouble() * Math.PI * 2.0;
476 double dx = Math.cos(angle);
477 double dy = Math.sin(angle);
479 node2.setPos(node.getX() + dx*len, node.getY() + dy*len);
481 node2.getData().updateData(graph, labelingPreferences);
482 node2.setContent(new LabelContent(node2.getData().getLabels()));
483 extensionNodeMap.put(object, node2);
484 extensionNodes.add(node2);
486 extensionEdges.add(createEdge(graph, stat, node, node2));
489 DebuggerCanvas.this.extensionNodeMap = extensionNodeMap;
493 } catch (DatabaseException e) {
498 private void layoutExtension() {
499 TObjectIntHashMap<Node> extensionNodeIds = new TObjectIntHashMap<Node>();
500 for(int i=0;i<extensionNodes.size();++i)
501 extensionNodeIds.put(extensionNodes.get(i), i);
503 double[][] neighbors = new double[extensionNodes.size()][];
505 TDoubleArrayList[] neighborLists = new TDoubleArrayList[neighbors.length];
506 for(int i=0;i<neighborLists.length;++i)
507 neighborLists[i] = new TDoubleArrayList();
508 for(Edge edge : extensionEdges) {
511 if(extensionNodeIds.containsKey(edge.getA())) {
512 id = extensionNodeIds.get(edge.getA());
516 id = extensionNodeIds.get(edge.getB());
519 TDoubleArrayList list = neighborLists[id];
520 list.add(node.getX());
521 list.add(node.getY());
523 for(int i=0;i<neighborLists.length;++i) {
524 neighbors[i] = neighborLists[i].toArray();
528 double[] fixedRepulsiveX = new double[nodes.size()];
529 double[] fixedRepulsiveY = new double[nodes.size()];
530 for(int i=0;i<nodes.size();++i) {
531 Node node = nodes.get(i);
532 fixedRepulsiveX[i] = node.getX();
533 fixedRepulsiveY[i] = node.getY();
535 ExtensionLayoutAlgorithm algo =
536 new ExtensionLayoutAlgorithm(neighbors, fixedRepulsiveX, fixedRepulsiveY);
537 double[] posX = algo.getPosX();
538 double[] posY = algo.getPosY();
539 for(int i=0;i<extensionNodes.size();++i) {
540 posX[i] = extensionNodes.get(i).getX();
541 posY[i] = extensionNodes.get(i).getY();
544 for(int i=0;i<extensionNodes.size();++i) {
545 extensionNodes.get(i).setPos(posX[i], posY[i]);
549 private Node getNode(Resource resource) {
550 Node node = nodeMap.get(resource);
552 node = addResource(random.nextDouble()*200.0-100.0,
553 random.nextDouble()*200.0-100.0,
555 nodeMap.put(resource, node);
560 public void findPreviousChangeset() {
562 Session session = Simantics.getSession();
563 final ManagementSupport ms = session.getService(ManagementSupport.class);
564 final long lastId = ms.getHeadRevisionId();
565 final long firstId = getOpId(ms, lastId);
566 //System.out.println(firstId + "-" + lastId);
568 removedEdges.clear();
569 session.asyncRequest(new ReadRequest() {
571 public void run(ReadGraph graph) throws DatabaseException {
572 final Collection<ChangeSet> css =
573 ms.fetchChangeSets(graph, firstId, lastId);
574 Layer0 L0 = Layer0.getInstance(graph);
575 for(ChangeSet cs : css) {
576 for(ChangeSet.StatementChange stat : cs.changedStatements()) {
577 Resource predicate = stat.getPredicate();
578 if(predicate.equals(L0.InstanceOf) ||
579 predicate.equals(L0.HasName) ||
580 predicate.equals(L0.NameOf))
582 Edge edge = createEdge(graph, stat,
583 getNode(stat.getSubject()),
584 getNode(stat.getObject()));
586 addedEdges.add(edge);
588 removedEdges.add(edge);
594 } catch(DatabaseException e) {
599 private static long getOpId(ManagementSupport ms, long revisionId) throws DatabaseException {
600 Collection<ChangeSetIdentifier> ids = ms.getChangeSetIdentifiers(revisionId, revisionId);
601 ChangeSetIdentifier curId = ids.iterator().next();
602 byte[] opIdData = curId.getMetadata().get("opid");
603 System.out.println(new String(opIdData));
604 long opId = Long.parseLong(new String(opIdData));