1
14 package demo.view.advanced;
15
16 import demo.view.DemoBase;
17 import y.base.Edge;
18 import y.base.EdgeCursor;
19 import y.base.EdgeList;
20 import y.base.Graph;
21 import y.base.GraphEvent;
22 import y.base.GraphListener;
23 import y.base.Node;
24 import y.base.NodeCursor;
25 import y.base.NodeList;
26 import y.base.YList;
27 import y.geom.AffineLine;
28 import y.geom.YPoint;
29 import y.geom.YVector;
30 import y.util.Tuple;
31 import y.view.Bend;
32 import y.view.BendCursor;
33 import y.view.BendList;
34 import y.view.CreateEdgeMode;
35 import y.view.DefaultGraph2DRenderer;
36 import y.view.EditMode;
37 import y.view.Graph2D;
38 import y.view.Graph2DViewActions;
39 import y.view.HitInfo;
40 import y.view.MovePortMode;
41 import y.view.MoveSelectionMode;
42 import y.view.NodeRealizer;
43 import y.view.Port;
44 import y.view.ShapeNodeRealizer;
45 import y.view.Util;
46
47 import java.awt.Color;
48 import java.awt.Graphics2D;
49 import java.awt.geom.GeneralPath;
50 import java.awt.geom.PathIterator;
51 import java.awt.geom.Point2D;
52 import java.util.Iterator;
53 import java.util.HashMap;
54 import java.util.Map;
55 import java.util.WeakHashMap;
56
57 import javax.swing.ActionMap;
58 import javax.swing.InputMap;
59 import javax.swing.JComponent;
60
61
72 public class EdgeConnectorDemo extends DemoBase {
73 protected void initialize() {
74 super.initialize();
75 view.setAntialiasedPainting(true);
76 EdgeConnectorGraph2DRenderer r = new EdgeConnectorGraph2DRenderer();
77 r.setDrawEdgesFirst(true);
78 view.setGraph2DRenderer(r);
79
80 view.getGraph2D().addGraphListener(new EdgeConnectorListener());
81 }
82
83 protected void registerViewModes() {
84 EditMode editMode = new EdgeConnectorEditMode();
85 editMode.setCreateEdgeMode(new CreateEdgeConnectorMode());
86 editMode.setMoveSelectionMode(new EdgeConnectorMoveSelectionMode());
87 editMode.setMovePortMode(new EdgeConnectorMovePortMode());
88 view.addViewMode(editMode);
89 }
90
91
95 static class EdgeConnectorGraph2DRenderer extends DefaultGraph2DRenderer {
96 public void paint(final Graphics2D gfx, final Graph2D graph) {
97 for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
98 Node n = nc.node();
99 if(graph.getRealizer(n) instanceof EdgeConnectorRealizer) {
100 EdgeConnectorRealizer edgeConnectorRealizer = (EdgeConnectorRealizer) graph.getRealizer(n);
101 edgeConnectorRealizer.updateLocation();
102 }
103 }
104 super.paint(gfx, graph);
105 }
106 }
107
108 protected void registerViewActions() {
109 Graph2DViewActions actions = new Graph2DViewActions(view);
111 ActionMap amap = actions.createActionMap();
112 InputMap imap = actions.createDefaultInputMap(amap);
113 if (!isDeletionEnabled()) {
114 amap.remove(Graph2DViewActions.DELETE_SELECTION);
115 }
116 view.getCanvasComponent().setActionMap(amap);
117 view.getCanvasComponent().setInputMap(JComponent.WHEN_FOCUSED, imap);
118 }
119
120
123 static class EdgeConnectorManager {
124 static final Map map = new WeakHashMap();
125
126 private EdgeConnectorManager() {
127 }
128
129 static void addEdgeConnection(Node connector, Edge edge, double pathRatio) {
130 map.put(connector, Tuple.create(edge, new Double(pathRatio)));
131 }
132
133 static Edge getEdgeConnection(Node connector) {
134 Tuple tuple = (Tuple) map.get(connector);
135 if (tuple != null) {
136 return (Edge) tuple.o1;
137 }
138 return null;
139 }
140
141 static double getEdgeConnectionRatio(Node connector) {
142 Tuple tuple = (Tuple) map.get(connector);
143 if (tuple != null) {
144 return ((Double) tuple.o2).doubleValue();
145 }
146 return 0.0; }
148
149 static NodeList getConnectorNodes(Edge edge) {
150 NodeList result = new NodeList();
151 for (Iterator iter = map.entrySet().iterator(); iter.hasNext();) {
152 Map.Entry entry = (Map.Entry) iter.next();
153 Tuple value = (Tuple) entry.getValue();
154 if (value.o1 == edge) {
155 result.add(entry.getKey());
156 }
157 }
158 return result;
159 }
160 }
161
162
169 static class EdgeConnectorListener implements GraphListener {
170
171 private int block;
172
173 private Map block2edges;
174
175 private boolean armed;
176
177 EdgeConnectorListener() {
178 armed = true;
179 }
180
181 public void onGraphEvent(final GraphEvent e) {
182 if (!armed) {
183 return;
184 }
185
186 switch (e.getType()) {
187 case GraphEvent.PRE_EVENT:
188 ++block;
189 break;
190 case GraphEvent.POST_EVENT:
191 handleBlock();
192 --block;
193 break;
194 case GraphEvent.POST_EDGE_REMOVAL:
195 storeForHandleBlock((Edge) e.getData());
196 break;
197 }
198 }
199
200
204 private void storeForHandleBlock( final Edge e ) {
205 if (block2edges == null) {
206 block2edges = new HashMap();
207 }
208 final Integer key = new Integer(block);
209 EdgeList edges = (EdgeList) block2edges.get(key);
210 if (edges == null) {
211 edges = new EdgeList();
212 block2edges.put(key, edges);
213 }
214 edges.add(e);
215 }
216
217
221 private void handleBlock() {
222 if (block2edges == null) {
223 return;
224 }
225
226 final EdgeList el = (EdgeList) block2edges.remove(new Integer(block));
227 if (el == null) {
228 return;
229 }
230
231 armed = false;
232 handleRecursive(el);
233 armed = true;
234
235 if (block2edges.isEmpty()) {
236 block2edges = null;
237 }
238 }
239
240 private void handleRecursive( final EdgeList el ) {
241 final EdgeList cascade = new EdgeList();
242 for (EdgeCursor ec = el.edges(); ec.ok(); ec.next()) {
243 final Edge edge = ec.edge();
244 Node node;
245 node = edge.source();
246 if (EdgeConnectorManager.getEdgeConnection(node) != null) {
247 final Graph graph = node.getGraph();
248 if (graph != null && node.degree() == 0) {
249 graph.removeNode(node);
250 }
251 }
252 node = edge.target();
253 if (EdgeConnectorManager.getEdgeConnection(node) != null) {
254 final Graph graph = node.getGraph();
255 if (graph != null && node.degree() == 0) {
256 graph.removeNode(node);
257 }
258 }
259 final NodeList connectors = EdgeConnectorManager.getConnectorNodes(edge);
260 if (connectors != null) {
261 for (NodeCursor nc = connectors.nodes(); nc.ok(); nc.next()) {
262 node = nc.node();
263 final Graph graph = node.getGraph();
264 if (graph != null) {
265 for (EdgeCursor nec = node.edges(); nec.ok(); nec.next()) {
266 cascade.add(nec.edge());
267 }
268 graph.removeNode(node);
269 }
270 }
271 }
272 }
273
274 if (!cascade.isEmpty()) {
275 handleRecursive(cascade);
276 }
277 }
278 }
279
280
286 static class EdgeConnectorRealizer extends ShapeNodeRealizer {
287 public EdgeConnectorRealizer() {
288 setShapeType(ELLIPSE);
289 setSize(5,5);
290 setFillColor(Color.yellow);
291 }
292
293 public EdgeConnectorRealizer(NodeRealizer nr) {
294 super(nr);
295 }
296 public NodeRealizer createCopy(NodeRealizer nr) {
297 return new EdgeConnectorRealizer(nr);
298 }
299
300 public void updateLocation() {
301 Node node = getNode();
302 if(node != null) {
303 Edge edge = EdgeConnectorManager.getEdgeConnection(node);
304 if(edge != null) {
305 Graph2D graph = (Graph2D) node.getGraph();
306 double ratio = EdgeConnectorManager.getEdgeConnectionRatio(node);
307 try {
308 Point2D point = PointPathProjector.getPointForGlobalRatio(graph.getRealizer(edge).getPath(), ratio);
309 setCenter(point.getX(), point.getY());
310 }catch(IllegalStateException isex) {}
311 }
312 }
313 }
314
315
320 }
325
326
329 static class EdgeConnectorMoveSelectionMode extends MoveSelectionMode {
330 protected NodeList getNodesToBeMoved() {
331 NodeList result = super.getNodesToBeMoved();
332 for(NodeCursor nc = result.nodes(); nc.ok(); nc.next()) {
333 Node n = nc.node();
334 for(EdgeCursor ec = n.edges(); ec.ok(); ec.next()) {
335 Edge edge = ec.edge();
336 NodeList connectors = EdgeConnectorManager.getConnectorNodes(edge);
337 result.splice(connectors);
338 }
339 }
340 BendList bends = getBendsToBeMoved();
341 for (BendCursor bc = bends.bends(); bc.ok(); bc.next()) {
342 Bend b = bc.bend();
343 NodeList connectors = EdgeConnectorManager.getConnectorNodes(b.getEdge());
344 result.splice(connectors);
345 }
346 return result;
347 }
348 }
349
350
353 static class CreateEdgeConnectorMode extends CreateEdgeMode {
354 private Node startNode;
355
356 public void mouseShiftPressedLeft(double x, double y) {
357 if(isEditing()) {
358 super.mouseShiftPressedLeft(x,y);
359 }
360 else {
361 Graph2D graph = getGraph2D();
362 Edge edge = getHitInfo(x,y).getHitEdge();
363 if (edge != null) {
364 EdgeConnectorRealizer ecNR = new EdgeConnectorRealizer();
365 Point2D p = new Point2D.Double(x, y);
366 double[] result = PointPathProjector.calculateClosestPathPoint(graph.getRealizer(edge).getPath(), p);
367 ecNR.setCenter(result[0], result[1]);
368 startNode = getGraph2D().createNode(ecNR);
370 view.updateView();
371 super.mouseShiftPressedLeft(result[0], result[1]);
372 EdgeConnectorManager.addEdgeConnection(startNode, edge, result[5]);
373 }
374 else {
375 startNode = null;
376 super.mouseShiftPressedLeft(x, y);
377 }
378 }
379 }
380
381 public void mouseShiftReleasedLeft(double x, double y) {
382 Graph2D graph = getGraph2D();
383 Edge edge = getHitInfo(x, y).getHitEdge();
384 if (edge != null) {
385 EdgeConnectorRealizer ecNR = new EdgeConnectorRealizer();
386 Point2D p = new Point2D.Double(x, y);
387 double[] result = PointPathProjector.calculateClosestPathPoint(graph.getRealizer(edge).getPath(), p);
388 ecNR.setCenter(result[0], result[1]);
389 Node endNode = getGraph2D().createNode(ecNR);
390 view.updateView();
391 super.mouseShiftReleasedLeft(result[0], result[1]);
392 EdgeConnectorManager.addEdgeConnection(endNode, edge, result[5]);
393 } else {
394 super.mouseShiftReleasedLeft(x, y);
395 }
396 }
397
398 public HitInfo getHitInfo(double x, double y) {
399 return new HitInfo(view, x, y, false,
400 HitInfo.PORT,
401 HitInfo.BEND,
402 HitInfo.ELABEL,
403 HitInfo.EDGE,
404 HitInfo.NODE,
405 HitInfo.NLABEL);
406 }
407
408 protected void cancelEdgeCreation() {
409 if(startNode != null) {
410 getGraph2D().removeNode(startNode);
411 }
412 super.cancelEdgeCreation();
413 }
414
415 public void setEditing(boolean active) {
416 if (!active) {
417 startNode = null;
418 }
419 super.setEditing(active);
420 }
421 }
422
423 static class EdgeConnectorEditMode extends EditMode {
424 public void mouseDraggedLeft(double x, double y) {
425 if(isModifierPressed(lastPressEvent)) {
426 double px = translateX(lastPressEvent.getX());
427 double py = translateY(lastPressEvent.getY());
428 Edge edge = getHitInfo(px,py).getHitEdge();
429 if(edge != null) {
430 setChild(getCreateEdgeMode(), lastPressEvent, lastDragEvent);
431 return;
432 }
433 }
434 super.mouseDraggedLeft(x, y);
435 }
436 }
437
438
442 static class EdgeConnectorMovePortMode extends MovePortMode {
443
444 protected YList getPortCandidates(Node v, Edge e, double gridSpacing) {
445 Edge connectedEdge = EdgeConnectorManager.getEdgeConnection(v);
446 if(connectedEdge != null) {
447 Graph2D graph = getGraph2D();
448 YList result = new YList();
450 YPoint yport = e.source() == v ? graph.getSourcePointAbs(e) : graph.getTargetPointAbs(e);
451 Point2D p = new Point2D.Double(yport.x, yport.y);
452 double[] pppResult = PointPathProjector.calculateClosestPathPoint(getGraph2D().getRealizer(connectedEdge).getPath(), p);
453 result.add(new YPoint(pppResult[0], pppResult[1]));
454 return result;
455 }
456 return super.getPortCandidates(v,e,gridSpacing);
457 }
458
459 public void mouseReleasedLeft(double x, double y) {
460 Port p = this.port;
461 if(p != null) {
462 Edge e = p.getOwner().getEdge();
463 Node v = null;
464 if(p == p.getOwner().getTargetPort()) {
465 v = e.target();
466 }
467 else {
468 v = e.source();
469 }
470 Edge connectedEdge = EdgeConnectorManager.getEdgeConnection(v);
471 if(connectedEdge == null) {
472 super.mouseReleasedLeft(x,y);
473 return;
474 }
475 else {
476 double[] result = PointPathProjector.calculateClosestPathPoint(getGraph2D().getRealizer(connectedEdge).getPath(), x, y);
477 double ratio = result[5];
478 EdgeConnectorManager.addEdgeConnection(v, connectedEdge, ratio);
479 super.mouseReleasedLeft(x,y);
480 getGraph2D().setCenter(v, result[0], result[1]);
481 p.setOffsets(0,0);
482 }
483 getGraph2D().updateViews();
484 }
485 }
486 }
487
488
491 static class PointPathProjector {
492 private PointPathProjector() {
493 }
494
495 static double[] calculateClosestPathPoint(GeneralPath path, double px, double py) {
496 return calculateClosestPathPoint(path, new Point2D.Double(px,py));
497 }
498
499
516 static double[] calculateClosestPathPoint(GeneralPath path, Point2D p) {
517 double[] result = new double[6];
518 double px = p.getX();
519 double py = p.getY();
520 YPoint point = new YPoint(px, py);
521 double pathLength = 0;
522
523 CustomPathIterator pi = new CustomPathIterator(path, 1.0);
524 double[] curSeg = new double[4];
525 double minDist;
526 if (pi.ok()) {
527 curSeg = pi.segment();
528 minDist = YPoint.distance(px, py, curSeg[0], curSeg[1]);
529 result[0] = curSeg[0];
530 result[1] = curSeg[1];
531 result[2] = minDist;
532 result[3] = 0.0;
533 result[4] = 0.0;
534 result[5] = 0.0;
535 } else {
536 throw new IllegalStateException("path without any coordinates");
538 }
539
540 int segmentIndex = 0;
541 double lastPathLength = 0.0;
542 do {
543 YPoint segmentStart = new YPoint(curSeg[0], curSeg[1]);
544 YPoint segmentEnd = new YPoint(curSeg[2], curSeg[3]);
545 YVector segmentDirection = new YVector(segmentEnd, segmentStart);
546 double segmentLength = segmentDirection.length();
547 pathLength += segmentLength;
548 segmentDirection.norm();
549
550 AffineLine currentSegment = new AffineLine(segmentStart, segmentDirection);
551 AffineLine throughPoint = new AffineLine(point, YVector.orthoNormal(segmentDirection));
552 YPoint crossing = AffineLine.getCrossing(currentSegment, throughPoint);
553 YVector crossingVector = new YVector(crossing, segmentStart);
554
555 YVector segmentVector = new YVector(segmentEnd, segmentStart);
556 double indexEnd = YVector.scalarProduct(segmentVector, segmentDirection);
557 double indexCrossing = YVector.scalarProduct(crossingVector, segmentDirection);
558
559 double dist;
560 double segmentRatio;
561 YPoint nearestOnSegment;
562 if (indexCrossing <= 0.0) {
563 dist = YPoint.distance(point, segmentStart);
564 nearestOnSegment = segmentStart;
565 segmentRatio = 0.0;
566 } else if (indexCrossing >= indexEnd) {
567 dist = YPoint.distance(point, segmentEnd);
568 nearestOnSegment = segmentEnd;
569 segmentRatio = 1.0;
570 } else {
571 dist = YPoint.distance(point, crossing);
572 nearestOnSegment = crossing;
573 segmentRatio = indexCrossing / indexEnd;
574 }
575
576 if (dist < minDist) {
577 minDist = dist;
578 result[0] = nearestOnSegment.getX();
579 result[1] = nearestOnSegment.getY();
580 result[2] = minDist;
581 result[3] = segmentIndex;
582 result[4] = segmentRatio;
583 result[5] = segmentLength * segmentRatio + lastPathLength;
584 }
585
586 segmentIndex++;
587 lastPathLength = pathLength;
588 pi.next();
589 } while (pi.ok());
590
591 if(pathLength > 0) {
592 result[5] = result[5] / pathLength;
593 } else {
594 result[5] = 0.0;
595 }
596 return result;
597 }
598
599 static Point2D getPointForGlobalRatio(GeneralPath path, double globalRatio) {
600 if(globalRatio > 1.0 || globalRatio < 0.0) {
601 throw new IllegalArgumentException("globalRatio outside of [0,1]");
602 }
603 double totalPathLength = getPathLength(path);
604 double targetPathLength = totalPathLength * globalRatio;
605 CustomPathIterator pi = new CustomPathIterator(path, 1.0);
606 YPoint segmentStart = null, segmentEnd = null;
607 if (pi.isDone()) {
608 throw new IllegalStateException("path without any coordinates");
610 } else {
611 segmentStart = pi.segmentStart();
612 segmentEnd = pi.segmentEnd();
613 }
614
615 double currentPathLength = 0.0;
616 double lastPathLength = 0.0;
617 while (pi.ok()) {
618 YVector segmentDirection = new YVector(segmentEnd, segmentStart);
619 double segmentLength = segmentDirection.length();
620 currentPathLength += segmentLength;
621 if(currentPathLength / totalPathLength >= globalRatio) {
622 double remainingLength = targetPathLength - lastPathLength;
623 double localRatio = remainingLength / segmentLength;
624 segmentDirection.scale(localRatio);
625 YPoint targetPoint = YVector.add(segmentStart, segmentDirection);
626 return new Point2D.Double(targetPoint.getX(),targetPoint.getY());
627 }
628
629 lastPathLength = currentPathLength;
630 pi.next();
631 segmentStart = pi.segmentStart();
632 segmentEnd = pi.segmentEnd();
633 }
634
635 return new Point2D.Double(segmentStart.getX(), segmentStart.getY());
637 }
638
639 static Point2D getPointForLocalRatio(GeneralPath path, int segmentIndex, double segmentRatio) {
640 if (segmentRatio > 1.0 || segmentRatio < 0.0) {
641 throw new IllegalArgumentException("segmentRatio outside of [0,1]");
642 }
643 CustomPathIterator pi = new CustomPathIterator(path, 1.0);
644 if (pi.isDone()) {
645 throw new IllegalStateException("path without any coordinates");
647 }
648 int currentIndex = 0;
649 while (pi.ok() && currentIndex < segmentIndex) {
650 pi.next();
651 currentIndex++;
652 }
653 if(currentIndex < segmentIndex)
654 {
655 throw new IllegalArgumentException("found no segment for given segmentIndex");
656 }
657
658 YPoint segmentStart = pi.segmentStart();
659 YPoint segmentEnd = pi.segmentEnd();
660 YVector segmentDirection = new YVector(segmentEnd, segmentStart);
661 segmentDirection.scale(segmentRatio);
662 YPoint targetPoint = YVector.add(segmentStart, segmentDirection);
663 return new Point2D.Double(targetPoint.getX(), targetPoint.getY());
664 }
665
666 private static double getPathLength(GeneralPath path) {
667 double length = 0.0;
668 for(CustomPathIterator pi = new CustomPathIterator(path, 1.0); pi.ok(); pi.next()) {
669 length += pi.segmentDirection().length();
670 }
671 return length;
672 }
673 }
674
675
678 static class CustomPathIterator {
679 private double[] cachedSegment;
680 private boolean moreToGet;
681 private PathIterator pathIterator;
682
683 public CustomPathIterator(GeneralPath path, double flatness) {
684 pathIterator = (new GeneralPath(path)).getPathIterator(Util.TRANSFORM, flatness);
686 cachedSegment = new double[4];
687 getFirstSegment();
688 }
689
690 public boolean ok()
691 {
692 return moreToGet;
693 }
694
695 public boolean isDone() {
696 return !moreToGet;
697 }
698
699 public final double[] segment() {
700 if (moreToGet) {
701 return cachedSegment;
702 } else {
703 return null;
704 }
705 }
706
707 public YPoint segmentStart() {
708 if(moreToGet) {
709 return new YPoint(cachedSegment[0], cachedSegment[1]);
710 } else {
711 return null;
712 }
713 }
714
715 public YPoint segmentEnd() {
716 if(moreToGet) {
717 return new YPoint(cachedSegment[2], cachedSegment[3]);
718 } else {
719 return null;
720 }
721 }
722
723 public YVector segmentDirection() {
724 if(moreToGet) {
725 return new YVector(segmentEnd(), segmentStart());
726 } else {
727 return null;
728 }
729 }
730
731 public void next() {
732 if (!pathIterator.isDone()) {
733 float[] curSeg = new float[2];
734 cachedSegment[0] = cachedSegment[2];
735 cachedSegment[1] = cachedSegment[3];
736 pathIterator.currentSegment(curSeg);
737 cachedSegment[2] = curSeg[0];
738 cachedSegment[3] = curSeg[1];
739 pathIterator.next();
740 } else {
741 moreToGet = false;
742 }
743 }
744
745 private void getFirstSegment() {
746 float[] curSeg = new float[2];
747 if (!pathIterator.isDone()) {
748 pathIterator.currentSegment(curSeg);
749 cachedSegment[0] = curSeg[0];
750 cachedSegment[1] = curSeg[1];
751 pathIterator.next();
752 moreToGet = true;
753 } else {
754 moreToGet = false;
755 }
756 if (!pathIterator.isDone()) {
757 pathIterator.currentSegment(curSeg);
758 cachedSegment[2] = curSeg[0];
759 cachedSegment[3] = curSeg[1];
760 pathIterator.next();
761 moreToGet = true;
762 } else {
763 moreToGet = false;
764 }
765 }
766 }
767
768 public static void main(String[] args) {
769 initLnF();
770 EdgeConnectorDemo demo = new EdgeConnectorDemo();
771 demo.start(demo.getClass().getName());
772 }
773 }
774