1
14 package demo.view.advanced;
15
16 import demo.view.DemoBase;
17 import y.base.*;
18 import y.geom.AffineLine;
19 import y.geom.YPoint;
20 import y.geom.YVector;
21 import y.util.Tuple;
22 import y.view.*;
23
24 import java.awt.Color;
25 import java.awt.Graphics2D;
26 import java.awt.geom.GeneralPath;
27 import java.awt.geom.PathIterator;
28 import java.awt.geom.Point2D;
29 import java.awt.geom.Rectangle2D;
30 import java.util.Iterator;
31 import java.util.Map;
32 import java.util.WeakHashMap;
33
34
44 public class EdgeConnectorDemo extends DemoBase {
45 protected void initialize() {
46 super.initialize();
47 view.setAntialiasedPainting(true);
48 EdgeConnectorGraph2DRenderer r = new EdgeConnectorGraph2DRenderer();
49 r.setDrawEdgesFirst(true);
50 view.setGraph2DRenderer(r);
51
52 view.getGraph2D().addGraphListener(new EdgeConnectorListener());
53
54 }
56
57 protected void registerViewModes() {
58 EditMode editMode = new EdgeConnectorEditMode();
59 editMode.setCreateEdgeMode(new CreateEdgeConnectorMode());
60 editMode.setMoveSelectionMode(new EdgeConnectorMoveSelectionMode());
61 editMode.setMovePortMode(new EdgeConnectorMovePortMode());
62 view.addViewMode(editMode);
63
64 }
65
66
70 static class EdgeConnectorGraph2DRenderer extends DefaultGraph2DRenderer {
71 public void paint(final Graphics2D gfx, final Graph2D graph) {
72 for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
73 Node n = nc.node();
74 if(graph.getRealizer(n) instanceof EdgeConnectorRealizer) {
75 EdgeConnectorRealizer edgeConnectorRealizer = (EdgeConnectorRealizer) graph.getRealizer(n);
76 edgeConnectorRealizer.updateLocation();
77 }
78 }
79 super.paint(gfx, graph);
80 }
81 }
82
83
95
98 static class EdgeConnectorManager {
99 static final Map map = new WeakHashMap();
100
101 private EdgeConnectorManager() {
102 }
103
104 static void addEdgeConnection(Node connector, Edge edge, double pathRatio) {
105 map.put(connector, Tuple.create(edge, new Double(pathRatio)));
106 }
107
108 static Edge getEdgeConnection(Node connector) {
109 Tuple tuple = (Tuple) map.get(connector);
110 if (tuple != null) {
111 return (Edge) tuple.o1;
112 }
113 return null;
114 }
115
116 static double getEdgeConnectionRatio(Node connector) {
117 Tuple tuple = (Tuple) map.get(connector);
118 if (tuple != null) {
119 return ((Double) tuple.o2).doubleValue();
120 }
121 return 0.0; }
123
124 static NodeList getConnectorNodes(Edge edge) {
125 NodeList result = new NodeList();
126 for (Iterator iter = map.entrySet().iterator(); iter.hasNext();) {
127 Map.Entry entry = (Map.Entry) iter.next();
128 Tuple value = (Tuple) entry.getValue();
129 if (value.o1 == edge) {
130 result.add(entry.getKey());
131 }
132 }
133 return result;
134 }
135 }
136
137
140 static class EdgeConnectorListener implements GraphListener {
141 public void onGraphEvent(final GraphEvent e) {
142 switch (e.getType()) {
143 case GraphEvent.POST_EDGE_REMOVAL:
144 Edge edge = (Edge) e.getData();
145 Node node = edge.source();
146 if (EdgeConnectorManager.getEdgeConnection(node) != null) {
147 Graph graph = node.getGraph();
148 if (graph != null) {
149 graph.removeNode(node);
150 }
151 }
152 node = edge.target();
153 if (EdgeConnectorManager.getEdgeConnection(node) != null) {
154 Graph graph = node.getGraph();
155 if (graph != null) {
156 graph.removeNode(node);
157 }
158 }
159 NodeList connectors = EdgeConnectorManager.getConnectorNodes(edge);
160 if (connectors != null) {
161 for (NodeCursor nc = connectors.nodes(); nc.ok(); nc.next()) {
162 Node n = nc.node();
163 if (n.getGraph() != null) {
164 if (n.firstInEdge() != null) {
165 n.getGraph().removeEdge(n.firstInEdge());
166 } else if (n.firstOutEdge() != null) {
167 n.getGraph().removeEdge(n.firstOutEdge());
168 }
169 }
170 }
171 }
172 }
173 }
174 }
175
176
182 static class EdgeConnectorRealizer extends ShapeNodeRealizer {
183 public EdgeConnectorRealizer() {
184 setShapeType(ELLIPSE);
185 setSize(5,5);
186 setFillColor(Color.yellow);
187 }
188
189 public EdgeConnectorRealizer(NodeRealizer nr) {
190 super(nr);
191 }
192 public NodeRealizer createCopy(NodeRealizer nr) {
193 return new EdgeConnectorRealizer(nr);
194 }
195
196 public void updateLocation() {
197 Node node = getNode();
198 if(node != null) {
199 Edge edge = EdgeConnectorManager.getEdgeConnection(node);
200 if(edge != null) {
201 Graph2D graph = (Graph2D) node.getGraph();
202 double ratio = EdgeConnectorManager.getEdgeConnectionRatio(node);
203 try {
204 Point2D point = PointPathProjector.getPointForGlobalRatio(graph.getRealizer(edge).getPath(), ratio);
205 setCenter(point.getX(), point.getY());
206 }catch(IllegalStateException isex) {}
207 }
208 }
209 }
210
211
216 }
221
222
225 static class EdgeConnectorMoveSelectionMode extends MoveSelectionMode {
226 protected NodeList getNodesToBeMoved() {
227 NodeList result = super.getNodesToBeMoved();
228 for(NodeCursor nc = result.nodes(); nc.ok(); nc.next()) {
229 Node n = nc.node();
230 for(EdgeCursor ec = n.edges(); ec.ok(); ec.next()) {
231 Edge edge = ec.edge();
232 NodeList connectors = EdgeConnectorManager.getConnectorNodes(edge);
233 result.splice(connectors);
234 }
235 }
236 BendList bends = getBendsToBeMoved();
237 for (BendCursor bc = bends.bends(); bc.ok(); bc.next()) {
238 Bend b = bc.bend();
239 NodeList connectors = EdgeConnectorManager.getConnectorNodes(b.getEdge());
240 result.splice(connectors);
241 }
242 return result;
243 }
244 }
245
246
249 static class CreateEdgeConnectorMode extends CreateEdgeMode {
250 private Node startNode;
251
252 public void mouseShiftPressedLeft(double x, double y) {
253 if(isEditing()) {
254 super.mouseShiftPressedLeft(x,y);
255 }
256 else {
257 Graph2D graph = getGraph2D();
258 Edge edge = getHitInfo(x,y).getHitEdge();
259 if (edge != null) {
260 EdgeConnectorRealizer ecNR = new EdgeConnectorRealizer();
261 Point2D p = new Point2D.Double(x, y);
262 double[] result = PointPathProjector.calculateClosestPathPoint(graph.getRealizer(edge).getPath(), p);
263 ecNR.setCenter(result[0], result[1]);
264 startNode = getGraph2D().createNode(ecNR);
266 view.updateView();
267 super.mouseShiftPressedLeft(result[0], result[1]);
268 EdgeConnectorManager.addEdgeConnection(startNode, edge, result[5]);
269 }
270 else {
271 startNode = null;
272 super.mouseShiftPressedLeft(x, y);
273 }
274 }
275 }
276
277 public void mouseShiftReleasedLeft(double x, double y) {
278 Graph2D graph = getGraph2D();
279 Edge edge = getHitInfo(x, y).getHitEdge();
280 if (edge != null) {
281 EdgeConnectorRealizer ecNR = new EdgeConnectorRealizer();
282 Point2D p = new Point2D.Double(x, y);
283 double[] result = PointPathProjector.calculateClosestPathPoint(graph.getRealizer(edge).getPath(), p);
284 ecNR.setCenter(result[0], result[1]);
285 Node endNode = getGraph2D().createNode(ecNR);
286 view.updateView();
287 super.mouseShiftReleasedLeft(result[0], result[1]);
288 EdgeConnectorManager.addEdgeConnection(endNode, edge, result[5]);
289 } else {
290 super.mouseShiftReleasedLeft(x, y);
291 }
292 }
293
294 public HitInfo getHitInfo(double x, double y) {
295 return getGraph2D().getHitInfo(x,y, false);
296 }
297
298 protected void cancelEdgeCreation() {
299 if(startNode != null) {
300 getGraph2D().removeNode(startNode);
301 }
302 super.cancelEdgeCreation();
303 }
304
305 public void setEditing(boolean active) {
306 if(!active) startNode = null;
307 super.setEditing(active);
308 }
309 }
310
311 static class EdgeConnectorEditMode extends EditMode {
312 public void mouseDraggedLeft(double x, double y) {
313 if(isModifierPressed(lastPressEvent)) {
314 double px = translateX(lastPressEvent.getX());
315 double py = translateY(lastPressEvent.getY());
316 Edge edge = getHitInfo(px,py).getHitEdge();
317 if(edge != null) {
318 setChild(getCreateEdgeMode(), lastPressEvent, lastDragEvent);
319 return;
320 }
321 }
322 super.mouseDraggedLeft(x, y);
323 }
324 }
325
326
330 static class EdgeConnectorMovePortMode extends MovePortMode {
331
332 protected YList getPortCandidates(Node v, Edge e, double gridSpacing) {
333 Edge connectedEdge = EdgeConnectorManager.getEdgeConnection(v);
334 if(connectedEdge != null) {
335 Graph2D graph = getGraph2D();
336 YList result = new YList();
338 YPoint yport = e.source() == v ? graph.getSourcePointAbs(e) : graph.getTargetPointAbs(e);
339 Point2D p = new Point2D.Double(yport.x, yport.y);
340 double[] pppResult = PointPathProjector.calculateClosestPathPoint(getGraph2D().getRealizer(connectedEdge).getPath(), p);
341 result.add(new YPoint(pppResult[0], pppResult[1]));
342 return result;
343 }
344 return super.getPortCandidates(v,e,gridSpacing);
345 }
346
347 public void mouseReleasedLeft(double x, double y) {
348 Port p = this.port;
349 if(p != null) {
350 Edge e = p.getOwner().getEdge();
351 Node v = null;
352 if(p == p.getOwner().getTargetPort()) {
353 v = e.target();
354 }
355 else {
356 v = e.source();
357 }
358 Edge connectedEdge = EdgeConnectorManager.getEdgeConnection(v);
359 if(connectedEdge == null) {
360 super.mouseReleasedLeft(x,y);
361 return;
362 }
363 else {
364 double[] result = PointPathProjector.calculateClosestPathPoint(getGraph2D().getRealizer(connectedEdge).getPath(), x, y);
365 double ratio = result[5];
366 EdgeConnectorManager.addEdgeConnection(v, connectedEdge, ratio);
367 super.mouseReleasedLeft(x,y);
368 getGraph2D().setCenter(v, result[0], result[1]);
369 p.setOffsets(0,0);
370 }
371 getGraph2D().updateViews();
372 }
373 }
374 }
375
376
379 static class PointPathProjector {
380 private PointPathProjector() {
381 }
382
383 static double[] calculateClosestPathPoint(GeneralPath path, double px, double py) {
384 return calculateClosestPathPoint(path, new Point2D.Double(px,py));
385 }
386
387
404 static double[] calculateClosestPathPoint(GeneralPath path, Point2D p) {
405 double[] result = new double[6];
406 double px = p.getX();
407 double py = p.getY();
408 YPoint point = new YPoint(px, py);
409 double pathLength = 0;
410
411 CustomPathIterator pi = new CustomPathIterator(path, 1.0);
412 double[] curSeg = new double[4];
413 double minDist;
414 if (pi.ok()) {
415 curSeg = pi.segment();
416 minDist = YPoint.distance(px, py, curSeg[0], curSeg[1]);
417 result[0] = curSeg[0];
418 result[1] = curSeg[1];
419 result[2] = minDist;
420 result[3] = 0.0;
421 result[4] = 0.0;
422 result[5] = 0.0;
423 } else {
424 throw new IllegalStateException("path without any coordinates");
426 }
427
428 int segmentIndex = 0;
429 double lastPathLength = 0.0;
430 do {
431 YPoint segmentStart = new YPoint(curSeg[0], curSeg[1]);
432 YPoint segmentEnd = new YPoint(curSeg[2], curSeg[3]);
433 YVector segmentDirection = new YVector(segmentEnd, segmentStart);
434 double segmentLength = segmentDirection.length();
435 pathLength += segmentLength;
436 segmentDirection.norm();
437
438 AffineLine currentSegment = new AffineLine(segmentStart, segmentDirection);
439 AffineLine throughPoint = new AffineLine(point, YVector.orthoNormal(segmentDirection));
440 YPoint crossing = AffineLine.getCrossing(currentSegment, throughPoint);
441 YVector crossingVector = new YVector(crossing, segmentStart);
442
443 YVector segmentVector = new YVector(segmentEnd, segmentStart);
444 double indexEnd = YVector.scalarProduct(segmentVector, segmentDirection);
445 double indexCrossing = YVector.scalarProduct(crossingVector, segmentDirection);
446
447 double dist;
448 double segmentRatio;
449 YPoint nearestOnSegment;
450 if (indexCrossing <= 0.0) {
451 dist = YPoint.distance(point, segmentStart);
452 nearestOnSegment = segmentStart;
453 segmentRatio = 0.0;
454 } else if (indexCrossing >= indexEnd) {
455 dist = YPoint.distance(point, segmentEnd);
456 nearestOnSegment = segmentEnd;
457 segmentRatio = 1.0;
458 } else {
459 dist = YPoint.distance(point, crossing);
460 nearestOnSegment = crossing;
461 segmentRatio = indexCrossing / indexEnd;
462 }
463
464 if (dist < minDist) {
465 minDist = dist;
466 result[0] = nearestOnSegment.getX();
467 result[1] = nearestOnSegment.getY();
468 result[2] = minDist;
469 result[3] = segmentIndex;
470 result[4] = segmentRatio;
471 result[5] = segmentLength * segmentRatio + lastPathLength;
472 }
473
474 segmentIndex++;
475 lastPathLength = pathLength;
476 pi.next();
477 } while (pi.ok());
478
479 if(pathLength > 0) {
480 result[5] = result[5] / pathLength;
481 } else {
482 result[5] = 0.0;
483 }
484 return result;
485 }
486
487 static Point2D getPointForGlobalRatio(GeneralPath path, double globalRatio) {
488 if(globalRatio > 1.0 || globalRatio < 0.0) {
489 throw new IllegalArgumentException("globalRatio outside of [0,1]");
490 }
491 double totalPathLength = getPathLength(path);
492 double targetPathLength = totalPathLength * globalRatio;
493 CustomPathIterator pi = new CustomPathIterator(path, 1.0);
494 YPoint segmentStart = null, segmentEnd = null;
495 if (pi.isDone()) {
496 throw new IllegalStateException("path without any coordinates");
498 } else {
499 segmentStart = pi.segmentStart();
500 segmentEnd = pi.segmentEnd();
501 }
502
503 double currentPathLength = 0.0;
504 double lastPathLength = 0.0;
505 while (pi.ok()) {
506 YVector segmentDirection = new YVector(segmentEnd, segmentStart);
507 double segmentLength = segmentDirection.length();
508 currentPathLength += segmentLength;
509 if(currentPathLength / totalPathLength >= globalRatio) {
510 double remainingLength = targetPathLength - lastPathLength;
511 double localRatio = remainingLength / segmentLength;
512 segmentDirection.scale(localRatio);
513 YPoint targetPoint = YVector.add(segmentStart, segmentDirection);
514 return new Point2D.Double(targetPoint.getX(),targetPoint.getY());
515 }
516
517 lastPathLength = currentPathLength;
518 pi.next();
519 segmentStart = pi.segmentStart();
520 segmentEnd = pi.segmentEnd();
521 }
522
523 return new Point2D.Double(segmentStart.getX(), segmentStart.getY());
525 }
526
527 static Point2D getPointForLocalRatio(GeneralPath path, int segmentIndex, double segmentRatio) {
528 if (segmentRatio > 1.0 || segmentRatio < 0.0) {
529 throw new IllegalArgumentException("segmentRatio outside of [0,1]");
530 }
531 CustomPathIterator pi = new CustomPathIterator(path, 1.0);
532 if (pi.isDone()) {
533 throw new IllegalStateException("path without any coordinates");
535 }
536 int currentIndex = 0;
537 while (pi.ok() && currentIndex < segmentIndex) {
538 pi.next();
539 currentIndex++;
540 }
541 if(currentIndex < segmentIndex)
542 {
543 throw new IllegalArgumentException("found no segment for given segmentIndex");
544 }
545
546 YPoint segmentStart = pi.segmentStart();
547 YPoint segmentEnd = pi.segmentEnd();
548 YVector segmentDirection = new YVector(segmentEnd, segmentStart);
549 segmentDirection.scale(segmentRatio);
550 YPoint targetPoint = YVector.add(segmentStart, segmentDirection);
551 return new Point2D.Double(targetPoint.getX(), targetPoint.getY());
552 }
553
554 private static double getPathLength(GeneralPath path) {
555 double length = 0.0;
556 for(CustomPathIterator pi = new CustomPathIterator(path, 1.0); pi.ok(); pi.next()) {
557 length += pi.segmentDirection().length();
558 }
559 return length;
560 }
561 }
562
563
566 static class CustomPathIterator {
567 private double[] cachedSegment;
568 private boolean moreToGet;
569 private PathIterator pathIterator;
570
571 public CustomPathIterator(GeneralPath path, double flatness) {
572 pathIterator = (new GeneralPath(path)).getPathIterator(Util.TRANSFORM, flatness);
574 cachedSegment = new double[4];
575 getFirstSegment();
576 }
577
578 public boolean ok()
579 {
580 return moreToGet;
581 }
582
583 public boolean isDone() {
584 return !moreToGet;
585 }
586
587 public final double[] segment() {
588 if (moreToGet) {
589 return cachedSegment;
590 } else {
591 return null;
592 }
593 }
594
595 public YPoint segmentStart() {
596 if(moreToGet) {
597 return new YPoint(cachedSegment[0], cachedSegment[1]);
598 } else {
599 return null;
600 }
601 }
602
603 public YPoint segmentEnd() {
604 if(moreToGet) {
605 return new YPoint(cachedSegment[2], cachedSegment[3]);
606 } else {
607 return null;
608 }
609 }
610
611 public YVector segmentDirection() {
612 if(moreToGet) {
613 return new YVector(segmentEnd(), segmentStart());
614 } else {
615 return null;
616 }
617 }
618
619 public void next() {
620 if (!pathIterator.isDone()) {
621 float[] curSeg = new float[2];
622 cachedSegment[0] = cachedSegment[2];
623 cachedSegment[1] = cachedSegment[3];
624 pathIterator.currentSegment(curSeg);
625 cachedSegment[2] = curSeg[0];
626 cachedSegment[3] = curSeg[1];
627 pathIterator.next();
628 } else {
629 moreToGet = false;
630 }
631 }
632
633 private void getFirstSegment() {
634 float[] curSeg = new float[2];
635 if (!pathIterator.isDone()) {
636 pathIterator.currentSegment(curSeg);
637 cachedSegment[0] = curSeg[0];
638 cachedSegment[1] = curSeg[1];
639 pathIterator.next();
640 moreToGet = true;
641 } else {
642 moreToGet = false;
643 }
644 if (!pathIterator.isDone()) {
645 pathIterator.currentSegment(curSeg);
646 cachedSegment[2] = curSeg[0];
647 cachedSegment[3] = curSeg[1];
648 pathIterator.next();
649 moreToGet = true;
650 } else {
651 moreToGet = false;
652 }
653 }
654 }
655
656 public static void main(String[] args) {
657 initLnF();
658 EdgeConnectorDemo demo = new EdgeConnectorDemo();
659 demo.start(demo.getClass().getName());
660 }
661
662 }
663