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