1
28 package demo.view.mindmap;
29
30 import demo.view.DemoBase;
31 import y.base.Command;
32 import y.base.Edge;
33 import y.base.EdgeCursor;
34 import y.base.EdgeList;
35 import y.base.Node;
36 import y.layout.FreeNodeLabelModel;
37 import y.util.Cursors;
38 import y.view.GenericEdgeRealizer;
39 import y.view.GenericNodeRealizer;
40 import y.view.Graph2D;
41 import y.view.Graph2DUndoManager;
42 import y.view.Graph2DView;
43 import y.view.LineType;
44 import y.view.NodeLabel;
45 import y.view.NodeRealizer;
46 import y.view.ShapeNodeRealizer;
47
48 import javax.swing.Icon;
49 import java.awt.Color;
50 import java.awt.Font;
51 import java.util.Collection;
52 import java.util.Iterator;
53 import java.util.LinkedHashSet;
54
55
58 class MindMapUtil {
59 static final Color CROSS_EDGE_COLOR = new Color(126, 192, 200);
60 static final Color BLACK = new Color(50, 50, 50);
61 static final Color RED = new Color(216, 38, 34);
62 static final Color GREEN = new Color(128, 255, 128);
63 static final Color DARK_GREEN = new Color(87, 173, 87);
64 static final Color BLUE = new Color(80, 80, 255);
65 static final Color LIGHT_BLUE = new Color(44, 174, 212);
66 static final Color MAGENTA = new Color(255, 145, 255);
67 static final Color ORANGE = new Color(255, 101, 2);
68 static final Color BROWN = new Color(139,69,19);
69
70
71 private static final int MINIMUM_NODE_WIDTH = 20;
72
73
74
77 private MindMapUtil() {
78 }
79
80
81
86 static void addNode( final Graph2DView view, final Node parent ) {
87 addNodeImpl(view, parent, ViewModel.instance.isLeft(parent));
88 }
89
90
99 static void addNode( final Graph2DView view, final Node parent, final boolean placeLeft ) {
100 addNodeImpl(view, parent, placeLeft);
101 }
102
103 private static void addNodeImpl(
104 final Graph2DView view,
105 final Node parent,
106 final boolean placeLeft
107 ) {
108 final Graph2D graph = view.getGraph2D();
109 final Node child = addNodeImpl(graph, parent, "", placeLeft, false);
110 KeyboardHandling.editLabel(view, graph.getRealizer(child).getLabel());
111 }
112
113
124 static Node addNode(
125 final Graph2D graph,
126 final Node parent,
127 final String name,
128 final boolean placeLeft
129 ) {
130 return addNodeImpl(graph, parent, name, placeLeft, true);
131 }
132
133 private static Node addNodeImpl(
134 final Graph2D graph,
135 final Node parent,
136 final String name,
137 final boolean placeLeft,
138 final boolean loadFromFile
139 ) {
140 final ViewModel model = ViewModel.instance;
141
142 graph.firePreEvent(parent);
143 final Node node = graph.createNode(0, 0, name);
144 final Edge edge = graph.createEdge(parent, node);
145 graph.setRealizer(edge, new GenericEdgeRealizer("BezierGradientEdge"));
146
147 if (!loadFromFile && model.isCollapsed(parent)) {
149 expandNode(graph, parent);
150 }
151
152 final NodeRealizer realizer = graph.getRealizer(node);
153 realizer.setFillColor(
154 ViewModel.instance.isRoot(parent)
155 ? MindMapUtil.BLUE
156 : graph.getRealizer(parent).getFillColor());
157 final NodeLabel label = realizer.getLabel();
158 label.setFontSize(16);
159 realizer.setWidth(Math.max(20, label.getWidth()));
160
161 boolean isLeftSide = placeLeft;
162 if (model.isRoot(parent)) {
163 int left = 0;
164 int right = 0;
165 for (EdgeCursor ec = parent.outEdges(); ec.ok(); ec.next()) {
166 if (model.isLeft(ec.edge().target())) {
167 ++left;
168 } else {
169 ++right;
170 }
171 }
172 isLeftSide = left < right;
173 }
174
175 updateVisuals(graph, node, isLeftSide);
176 final EdgeList edgeList = outEdges(parent);
178 if (edgeList.size() > 1 && !loadFromFile) {
179 final NodeRealizer firstRealizer = graph.getRealizer(edgeList.firstEdge().target());
181 final NodeRealizer lastRealizer = graph.getRealizer(((Edge)edgeList.get(edgeList.size()-2)).target());
183 final double max = Math.max(firstRealizer.getY(), lastRealizer.getY());
184 graph.getRealizer(node).setY(max+1);
185 }
186 if (!loadFromFile) {
187 LayoutUtil.layout(graph);
188 }
189 graph.firePostEvent();
190 return node;
191 }
192
193
198 static void removeSubtree( final Graph2D graph, final Node node ) {
199 final ViewModel model = ViewModel.instance;
200 for (EdgeCursor ec = node.outEdges(); ec.ok(); ec.next()) {
201 final Edge edge = ec.edge();
202 if (!model.isCrossReference(edge)) {
204 removeSubtree(graph, edge.target());
205 } else if (graph.contains(edge)) {
208 graph.removeEdge(edge);
209 }
210 }
211 graph.removeNode(node);
212 }
213
214
220 static void setRootRealizer(
221 final Graph2D graph, final Node node, final String nodeText
222 ) {
223 final ShapeNodeRealizer nr = new ShapeNodeRealizer(ShapeNodeRealizer.ELLIPSE);
224 nr.setLocation(0, 0);
225 nr.setLineType(LineType.LINE_4);
226 nr.setFillColor(Color.WHITE);
227 nr.setLineColor(MindMapUtil.BLACK);
228 final NodeLabel nl = nr.getLabel();
229 nl.setFontSize(30);
230 nl.setText(nodeText);
231 nr.setWidth(Math.max(20, nl.getWidth() * 1.3));
232 nr.setHeight(nl.getHeight() * 2.5);
233
234 graph.setRealizer(node, nr);
235 }
236
237
242 static void toggleCollapseState( final Graph2D graph, final Node node ) {
243 graph.firePreEvent();
244
245 if (ViewModel.instance.isCollapsed(node)) {
246 MindMapUtil.expandNode(graph, node);
247 } else {
248 MindMapUtil.collapseNode(graph, node);
249 }
250 LayoutUtil.layout(graph);
251
252 graph.firePostEvent();
253 }
254
255
262 static void collapseNode( final Graph2D graph, final Node root ) {
263 final ViewModel model = ViewModel.instance;
264
265 final LinkedHashSet nodesToHide = new LinkedHashSet();
267 final EdgeList edgesToHide = new EdgeList();
268 final LinkedHashSet crossReferences = new LinkedHashSet();
269 for (EdgeCursor ec = root.outEdges(); ec.ok(); ec.next()) {
270 final Edge edge = ec.edge();
271 if (!model.isCrossReference(edge)) {
272 final Node node = edge.target();
273 edgesToHide.add(edge);
274 nodesToHide.add(node);
275 collectSubgraph(node, nodesToHide, edgesToHide, crossReferences);
276 }
277 }
278
279 graph.firePreEvent();
281 graph.backupRealizers(Cursors.createEdgeCursor(crossReferences));
282 graph.backupRealizers(edgesToHide.edges());
283 graph.backupRealizers(Cursors.createNodeCursor(nodesToHide));
284
285 for (Iterator it = crossReferences.iterator(); it.hasNext(); ) {
286 graph.removeEdge((Edge) it.next());
287 }
288 for (EdgeCursor ec = edgesToHide.edges(); ec.ok(); ec.next()) {
289 graph.removeEdge(ec.edge());
290 }
291
292 model.addHiddenCrossReferences(crossReferences);
294 model.setHiddenEdges(root, edgesToHide);
295
296 getUndoManager(graph).push(new Collapse(root, edgesToHide, crossReferences));
299
300 for (Iterator it = nodesToHide.iterator(); it.hasNext(); ) {
301 final Node node = (Node) it.next();
302
303 final double dx = graph.getCenterX(node) - graph.getCenterX(root);
305 final double dy = graph.getCenterY(node) - graph.getCenterY(root);
306 graph.getRealizer(node).setLocation(dx, dy);
307
308 graph.removeNode(node);
309 }
310 graph.firePostEvent();
311 }
312
313
324 private static void collectSubgraph(
325 final Node root,
326 final Collection nodesToHide,
327 final Collection edgesToHide,
328 final Collection cfsToHide
329 ) {
330 final ViewModel model = ViewModel.instance;
331 for (EdgeCursor ec = root.inEdges(); ec.ok(); ec.next()) {
332 final Edge edge = ec.edge();
333 if (model.isCrossReference(edge)) {
334 cfsToHide.add(edge);
336 }
337 }
338 for (EdgeCursor ec = root.outEdges(); ec.ok(); ec.next()) {
339 final Edge edge = ec.edge();
340 if (model.isCrossReference(edge)) {
341 cfsToHide.add(edge);
343 } else {
344 final Node node = edge.target();
345 nodesToHide.add(node);
346 edgesToHide.add(edge);
347 collectSubgraph(node, nodesToHide, edgesToHide, cfsToHide);
348 }
349 }
350 }
351
352
360 static void expandNode( final Graph2D graph, final Node root ) {
361 final ViewModel model = ViewModel.instance;
362 final EdgeList hiddenEdges = model.popHiddenEdges(root);
363 if (hiddenEdges != null) {
364 graph.firePreEvent();
365
366 for (EdgeCursor ec = hiddenEdges.edges(); ec.ok(); ec.next()) {
367 final Edge edge = ec.edge();
368
369 if (!graph.contains(edge.source())) {
370 graph.reInsertNode(edge.source());
371 graph.setLocation(edge.source(),
372 graph.getX(root) + graph.getX(edge.source()),
373 graph.getY(root) + graph.getY(edge.source()));
374 }
375
376 if (!graph.contains(edge.target())) {
377 graph.reInsertNode(edge.target());
378 graph.setLocation(edge.target(),
379 graph.getX(root) + graph.getX(edge.target()),
380 graph.getY(root) + graph.getY(edge.target()));
381 }
382
383 graph.reInsertEdge(edge);
384
385 graph.getRealizer(edge).clearBends();
387 }
388
389 final LinkedHashSet crossReferences = new LinkedHashSet();
391 for (Iterator it = model.hiddenCrossReferences(); it.hasNext();) {
392 final Edge edge = (Edge) it.next();
393 if (graph.contains(edge)) {
394 it.remove();
395 continue;
396 }
397
398 if (graph.contains(edge.source()) && graph.contains(edge.target())) {
399 it.remove();
400 crossReferences.add(edge);
401 graph.reInsertEdge(edge);
402 graph.getRealizer(edge).clearBends();
404 }
405 }
406
407 getUndoManager(graph).push(new Expand(root, hiddenEdges, crossReferences));
410
411 if (!model.isRoot(root)) {
413 updateVisualsRecursive(graph, root, model.isLeft(root));
414 }
415
416 graph.firePostEvent();
417 }
418 }
419
420
424 static Icon getIcon( final String iconName ) {
425 return DemoBase.getIconResource("resource/" + iconName);
426 }
427
428
436 static void updateVisualsRecursive(
437 final Graph2D graph, final Node node, final boolean left
438 ) {
439 updateVisuals(graph, node, left);
440 for (EdgeCursor ec = outEdges(node).edges(); ec.ok(); ec.next()) {
441 updateVisualsRecursive(graph, ec.edge().target(), left);
443 }
444 }
445
446
454 static void updateVisuals(
455 final Graph2D graph, final Node node, final boolean left
456 ) {
457 final NodeRealizer nr = graph.getRealizer(node);
458
459 final ViewModel model = ViewModel.instance;
460 final boolean notRoot = !model.isRoot(node);
461 final boolean updateVisuals = notRoot && isMindMapRealizer(nr);
462
463 final Edge inEdge = inEdge(node);
465 if (inEdge != null && updateVisuals) {
466 if (model.isRoot(inEdge.source())) {
467 nr.setLineType(LineType.LINE_6);
468 } else {
469 nr.setLineType(LineType.LINE_3);
470 }
471
472 final NodeLabel label = nr.getLabel();
474 label.setFontStyle(Font.BOLD);
475 if (model.isRoot(inEdge.source())) {
476 label.setFontSize(16);
477 } else {
478 label.setFontSize(14);
479 }
480 }
481
482 if (notRoot) {
484 model.setLeft(node, left);
485 }
486
487 if (updateVisuals) {
489 updateWidth(graph, node);
490 }
491 }
492
493
500 private static boolean isMindMapRealizer( final NodeRealizer nr ) {
501 return nr instanceof GenericNodeRealizer &&
502 "MindMapUnderline".equals(((GenericNodeRealizer) nr).getConfiguration());
503 }
504
505
511 static void updateWidth( final Graph2D graph, final Node node ) {
512 if (!ViewModel.instance.isRoot(node)) {
513 NodeRealizer nr = graph.getRealizer(node);
514 NodeLabel nl = nr.getLabel();
515
516 int xoffset = 0;
517 final Icon icon = MindMapNodePainter.getStateIcon(nr);
518 if (icon == null) {
519 nr.setWidth(Math.max(MINIMUM_NODE_WIDTH, nl.getWidth()));
520 } else {
521 final int reserved = icon.getIconWidth() + 4;
522 if (!ViewModel.instance.isLeft(node)) {
523 xoffset = reserved;
524 }
525 nr.setWidth(Math.max(MINIMUM_NODE_WIDTH, nl.getWidth() + reserved));
526 }
527
528 final FreeNodeLabelModel m = new FreeNodeLabelModel();
529 nl.setLabelModel(m, new FreeNodeLabelModel.ModelParameter(xoffset, 0));
530 } else {
531 final NodeRealizer realizer = graph.getRealizer(node);
532 realizer.setWidth(Math.max(MINIMUM_NODE_WIDTH, realizer.getLabel().getWidth() * 1.3));
533 }
534 }
535
536
541 static EdgeList outEdges( final Node node ) {
542 final ViewModel model = ViewModel.instance;
543 final EdgeList edges = new EdgeList();
544 for (EdgeCursor ec = node.outEdges();ec.ok();ec.next()) {
545 if (!model.isCrossReference(ec.edge())) {
546 edges.add(ec.edge());
547 }
548 }
549 return edges;
550 }
551
552
558 static Edge inEdge( final Node node ) {
559 final ViewModel model = ViewModel.instance;
560 for (EdgeCursor ec = node.inEdges(); ec.ok(); ec.next()) {
561 if (!model.isCrossReference(ec.edge())) {
562 return ec.edge();
563 }
564 }
565 return null;
566 }
567
568
571 private static Graph2DUndoManager getUndoManager( final Graph2D graph ) {
572 return (Graph2DUndoManager) graph.getBackupRealizersHandler();
573 }
574
575
579 private static class ChangeState {
580 private final Node root;
581 private final EdgeList edges;
582 private final Collection refs;
583
584
591 ChangeState( final Node root, final EdgeList edges, final Collection refs ) {
592 this.root = root;
593 this.edges = edges;
594 this.refs = refs;
595 }
596
597 public void execute() {
598 }
599
600
604 void collapse() {
605 final ViewModel model = ViewModel.instance;
606 model.addHiddenCrossReferences(refs);
607 model.setHiddenEdges(root, edges);
608 }
609
610
614 void expand() {
615 final ViewModel model = ViewModel.instance;
616 model.popHiddenEdges(root);
617 for (Iterator it = model.hiddenCrossReferences(); it.hasNext();) {
618 if (refs.contains(it.next())) {
619 it.remove();
620 }
621 }
622 }
623 }
624
625
629 private static class Collapse extends ChangeState implements Command {
630 Collapse( final Node root, final EdgeList edges, final Collection refs ) {
631 super(root, edges, refs);
632 }
633
634 public void undo() {
635 expand();
636 }
637
638 public void redo() {
639 collapse();
640 }
641 }
642
643
647 private static class Expand extends ChangeState implements Command {
648 Expand( final Node root, final EdgeList edges, final Collection refs ) {
649 super(root, edges, refs);
650 }
651
652 public void undo() {
653 collapse();
654 }
655
656 public void redo() {
657 expand();
658 }
659 }
660 }
661