]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.debug.graphical/src/org/simantics/debug/graphical/DebuggerCanvas.java
00a539990a6c466ae42ac0788a254889cab68508
[simantics/platform.git] / bundles / org.simantics.debug.graphical / src / org / simantics / debug / graphical / DebuggerCanvas.java
1 package org.simantics.debug.graphical;
2
3 import gnu.trove.list.array.TDoubleArrayList;
4 import gnu.trove.map.hash.THashMap;
5 import gnu.trove.map.hash.TObjectIntHashMap;
6
7 import java.awt.Color;
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;
32
33 import javax.swing.JPanel;
34 import javax.swing.SwingUtilities;
35
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;
60
61 public class DebuggerCanvas extends JPanel {
62
63     private static final long serialVersionUID = -718678297301786379L;
64     
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();
77
78     LabelingPreferences labelingPreferences = 
79             new LabelingPreferences();
80     
81     @Override
82     public void paint(Graphics _g) {
83         Graphics2D g = (Graphics2D)_g;
84         
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);
91         
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(
96                 canvasZoom, 0.0, 
97                 0.0, canvasZoom, 
98                 -canvasPosX*canvasZoom, -canvasPosY*canvasZoom));
99         for(Node node : nodes)
100             node.render(g);
101         for(Edge edge : edges)
102             edge.render(g);
103         if(extensionMode) {
104             for(Node node : extensionNodes)
105                 node.render(g);
106             for(Edge edge : extensionEdges)
107                 edge.render(g);
108         }
109         g.setColor(Color.GREEN);
110         for(Edge edge : addedEdges)
111             edge.render(g);
112         g.setColor(Color.RED);
113         for(Edge edge : removedEdges)
114             edge.render(g);
115     }
116     
117     public Node pick(double x, double y) {
118         for(Node node : nodes)
119             if(node.pick(x, y))
120                 return node;
121         return null;
122     }
123     
124     public Node pickExtension(double x, double y) {
125         for(Node node : extensionNodes)
126             if(node.pick(x, y))
127                 return node;
128         return null;
129     }
130     
131     {
132         addMouseListener(new MouseAdapter() {
133             @Override
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);
141             }
142             @Override
143             public void mouseMoved(MouseEvent e) {
144                 handleMouseMoved(canvasPosX+e.getX()/canvasZoom, 
145                         canvasPosY+e.getY()/canvasZoom);
146             }
147             @Override
148             public void mouseReleased(MouseEvent e) {
149                 handleMouseReleased(canvasPosX+e.getX()/canvasZoom, 
150                         canvasPosY+e.getY()/canvasZoom);
151             }
152             @Override
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());
157             }
158         });
159         addMouseMotionListener(new MouseMotionAdapter() {
160             @Override
161             public void mouseDragged(MouseEvent e) {
162                 handleMouseMoved(canvasPosX+e.getX()/canvasZoom, 
163                         canvasPosY+e.getY()/canvasZoom);
164             }            
165         });
166         addMouseWheelListener(new MouseWheelListener() {            
167             @Override
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());
172             }
173         });
174         addKeyListener(new UsefulKeyAdapter(new KeyAdapter() {
175             @Override
176             public void keyPressed(KeyEvent e) {
177                 DebuggerCanvas.this.keyPressed(e);
178             }
179             @Override
180             public void keyReleased(KeyEvent e) {
181                 DebuggerCanvas.this.keyReleased(e);
182             }
183         }));
184         DropTarget dropTarget = new DropTarget(this, new DropTargetAdapter() {
185             @Override
186             public void drop(DropTargetDropEvent dtde) {
187                 try {
188                     Transferable transferable = dtde.getTransferable();
189                     
190                     if( transferable.isDataFlavorSupported( 
191                             LocalObjectTransferable.FLAVOR ) ) {
192                         dtde.acceptDrop( DnDConstants.ACTION_MOVE );
193                         
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);
199                         
200                         dtde.getDropTargetContext().dropComplete( true );
201                     }
202                     else {
203                         dtde.rejectDrop();
204                     }
205                 } catch( IOException exception ) {
206                     exception.printStackTrace();
207                     dtde.rejectDrop();
208                 } catch( UnsupportedFlavorException ufException ) {
209                     ufException.printStackTrace();
210                     dtde.rejectDrop();
211                 }
212             }     
213         });
214     }    
215     
216     public void keyPressed(KeyEvent e) {
217         switch(e.getKeyCode()) {
218         case KeyEvent.VK_1:
219             zoomToFit();
220             break;
221         case KeyEvent.VK_L:
222             layoutGraph();
223             break;
224         case KeyEvent.VK_CONTROL:
225             if(!extensionMode) {
226                 initializeExtension();
227                 extensionMode = true;
228                 repaint();
229             }
230             break;
231         case KeyEvent.VK_C:
232             findPreviousChangeset();
233             break;
234         case KeyEvent.VK_DELETE:
235             if (!extensionMode && dragging != null) {
236                 nodes.remove(dragging);
237                 scheduleUpdate();
238                 repaint();
239             }
240             break;
241         }
242     }
243     
244     public void keyReleased(KeyEvent e) {
245         if(e.getKeyCode() == KeyEvent.VK_CONTROL) {
246             extensionMode = false;
247             scheduleUpdate();
248             repaint();
249         }
250     }
251
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()));
256             if(resource != null)
257                 return resource;
258         }
259         if(obj instanceof IAdaptable) {
260             Resource resource = (Resource)((IAdaptable)obj).getAdapter(Resource.class);
261             if(resource != null)
262                 return resource;
263         }
264         return null;
265     }
266     
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);                    
274                     repaint();
275                 }
276             }
277         }
278     }       
279     
280     private Node addResource(double x, double y, Resource resource) {
281         Node a = new Node(new NodeData(resource));
282         a.setPos(x, y);
283         scheduleUpdate();
284         nodes.add(a);
285         return a;
286     }
287
288     private void scheduleUpdate() {
289         Simantics.getSession().asyncRequest(new ReadRequest() {            
290             @Override
291             public void run(ReadGraph graph) throws DatabaseException {                
292                 updateNodes(graph);
293                 updateEdges(graph);
294                 SwingUtilities.invokeLater(new Runnable() {
295                     @Override
296                     public void run() {
297                         repaint();
298                     }
299                     
300                 });
301             }            
302         });        
303     }
304     
305     public void layoutGraph() {
306         ArrayList<Edge> allEdges = new ArrayList<Edge>(
307                 edges.size() + addedEdges.size() + removedEdges.size()
308                 );
309         allEdges.addAll(edges);
310         allEdges.addAll(addedEdges);
311         allEdges.addAll(removedEdges);
312         LayoutGraph.layout(
313                 nodes.toArray(new Node[nodes.size()]), 
314                 allEdges.toArray(new Edge[edges.size()]));
315         repaint();                
316     }
317     
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()));
322         }
323         nodeMap.clear();
324         for(Node node : nodes) {
325             NodeData data = node.getData();
326             nodeMap.put(data.getResource(), node);
327         }
328     }
329     
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);
339                 if(node2 != null) {
340                     if(object.getResourceId() > subject.getResourceId() ||
341                             graph.getPossibleInverse(stat.getPredicate()) == null) {
342                         edges.add(createEdge(graph, stat, node, node2));
343                     }
344                 }
345                 else
346                     filteredStatements.add(stat);
347             }
348             data.setStatements(filteredStatements);
349         }
350         this.edges = edges;
351         this.addedEdges = filterEdgesWithoutNodes( this.addedEdges );
352         this.removedEdges = filterEdgesWithoutNodes( this.removedEdges );
353     }
354
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()))
359                 continue;
360             result.add(e);
361         }
362         return result;
363     }
364
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)) {
374                 predicate = inverse;
375                 Node temp = n1;
376                 n1 = n2;
377                 n2 = temp;
378             }
379         }
380         Edge edge = new Edge(n1, n2);
381         edge.setContent(new LabelContent(new String[] {
382                 NameUtils.getSafeName(graph, predicate)}));
383         return edge;
384     }
385     
386     Node dragging = null;
387     double dragDX, dragDY;
388     private void handleMouseLeftPressed(double x, double y) {
389         Node node;
390         if(extensionMode) {
391             node = pickExtension(x, y);
392             if(node != null) {
393                 nodes.add(node);
394                 extensionNodes.remove(node);
395             }
396         }
397         else
398             node = pick(x, y);
399         if(node != null) {
400             dragDX = x - node.getX();
401             dragDY = y - node.getY();
402             dragging = node;
403         }
404     }
405     
406     Point2D panningStartMouse;
407     private void handleMouseMiddlePressed(double x, double y) {
408         panningStartMouse = new Point2D.Double(x, y);
409     }
410     
411     private void handleMouseMoved(double x, double y) {
412         if(dragging != null) {
413             dragging.setPos(x-dragDX, y-dragDY);
414             repaint();
415         }
416         if(panningStartMouse != null) {
417             canvasPosX -= x - panningStartMouse.getX();
418             canvasPosY -= y - panningStartMouse.getY();
419             repaint();
420         }
421     }
422     
423     private void handleMouseWheelMoved(double x, double y, double amount) {
424         double s = Math.exp(-0.2*amount);
425         canvasZoom *= s;
426         canvasPosX = x - (x-canvasPosX)/s;
427         canvasPosY = y - (y-canvasPosY)/s;
428         repaint();
429     }
430
431     private void handleMouseReleased(double x, double y) {
432         dragging = null;
433         panningStartMouse = null;
434     }
435     
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());
448             }            
449             canvasZoom = Math.min(getWidth()/(maxX-minX), getHeight()/(maxY-minY));
450             canvasZoom *= 0.9;
451             canvasPosX = minX - 0.5 * (getWidth()/canvasZoom - maxX+minX);
452             canvasPosY = minY - 0.5 * (getHeight()/canvasZoom - maxY+minY);
453             repaint();
454         }
455     }
456
457     THashMap<Resource, Node> extensionNodeMap = new THashMap<Resource, Node>();
458     public void initializeExtension() {
459         extensionNodes.clear();
460         extensionEdges.clear();
461         try {
462             Simantics.getSession().syncRequest(new ReadRequest() {
463                 @Override
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);
471                             if(node2 == null) {
472                                 node2 = oldExtensionNodeMap.get(object);
473                                 if(node2 == null) {
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);
478                                     double len = 150.0;
479                                     node2.setPos(node.getX() + dx*len, node.getY() + dy*len);
480                                 }
481                                 node2.getData().updateData(graph, labelingPreferences);                                
482                                 node2.setContent(new LabelContent(node2.getData().getLabels()));
483                                 extensionNodeMap.put(object, node2);
484                                 extensionNodes.add(node2);
485                             }
486                             extensionEdges.add(createEdge(graph, stat, node, node2));
487                         }
488                     }
489                     DebuggerCanvas.this.extensionNodeMap = extensionNodeMap;
490                     layoutExtension();
491                 }      
492             });
493         } catch (DatabaseException e) {
494             e.printStackTrace();
495         }
496     }
497
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);
502         
503         double[][] neighbors = new double[extensionNodes.size()][];
504         {
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) {
509                 int id;
510                 Node node;
511                 if(extensionNodeIds.containsKey(edge.getA())) {
512                     id = extensionNodeIds.get(edge.getA());
513                     node = edge.getB();
514                 }
515                 else {
516                     id = extensionNodeIds.get(edge.getB());
517                     node = edge.getA();
518                 }
519                 TDoubleArrayList list = neighborLists[id];
520                 list.add(node.getX());
521                 list.add(node.getY());
522             }            
523             for(int i=0;i<neighborLists.length;++i) {
524                 neighbors[i] = neighborLists[i].toArray();
525             }
526         }
527         
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();
534         }
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();
542         }        
543         algo.optimize();
544         for(int i=0;i<extensionNodes.size();++i) {
545             extensionNodes.get(i).setPos(posX[i], posY[i]);
546         }        
547     }      
548     
549     private Node getNode(Resource resource) {
550         Node node = nodeMap.get(resource);
551         if(node == null) {
552             node = addResource(random.nextDouble()*200.0-100.0, 
553                     random.nextDouble()*200.0-100.0, 
554                     resource);
555             nodeMap.put(resource, node);
556         }
557         return node;
558     }
559     
560     public void findPreviousChangeset() {
561         try {
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);
567             addedEdges.clear();
568             removedEdges.clear();            
569             session.asyncRequest(new ReadRequest() {
570                 @Override
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))
581                                 continue;
582                             Edge edge = createEdge(graph, stat, 
583                                     getNode(stat.getSubject()),
584                                     getNode(stat.getObject()));
585                             if(stat.isClaim())
586                                 addedEdges.add(edge);
587                             else
588                                 removedEdges.add(edge);
589                         }
590                     }    
591                     scheduleUpdate();
592                 }                
593             });                  
594         } catch(DatabaseException e) {
595             e.printStackTrace();
596         }
597     } 
598     
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));
605         if(opId == 0)
606             opId = revisionId;
607         return opId;
608     }
609
610 }