1
28 package demo.view.mindmap;
29
30 import demo.view.mindmap.StateIconProvider.StateIcon;
31
32 import y.anim.AnimationObject;
33 import y.anim.AnimationPlayer;
34 import y.base.Node;
35 import y.base.NodeList;
36 import y.geom.Geom;
37 import y.view.AbstractMouseInputEditor;
38 import y.view.Drawable;
39 import y.view.Graph2D;
40 import y.view.Graph2DView;
41 import y.view.Graph2DViewRepaintManager;
42 import y.view.HitInfo;
43 import y.view.Mouse2DEvent;
44 import y.view.MouseInputEditor;
45 import y.view.MouseInputEditorProvider;
46 import y.view.NodeRealizer;
47 import y.view.ViewMode;
48
49 import java.awt.Color;
50 import java.awt.Graphics2D;
51 import java.awt.Rectangle;
52 import java.awt.geom.GeneralPath;
53 import java.util.Iterator;
54 import java.util.NoSuchElementException;
55 import javax.swing.Icon;
56
57
61 class HoverButton implements Drawable, MouseInputEditorProvider, AnimationObject {
62 private static final Color PANEL_BACKGROUND = new Color(28, 181, 255, 85);
63 private static final Color GREEN = new Color(21, 186, 7);
64 private static final Color YELLOW = new Color(255, 237, 0);
65
66
67
70 private Node currentNode;
71
72
75 private boolean visible;
76
77
80 private boolean showsChooser;
81
82
85 private int mouseX;
86
87
90 private double animationYOffset;
91 private final AnimationPlayer player;
92
93 private final Graph2DView view;
94
95 private final DeleteButton delete;
96 private final ColorButton color;
97 private final IconButton icon;
98 private final AddButton add;
99 private final CrossReferenceButton reference;
100
101
104 HoverButton( final Graph2DView view ) {
105 this.view = view;
106 delete = new DeleteButton();
107 color = new ColorButton(this);
108 icon = new IconButton(this);
109 add = new AddButton();
110 reference = new CrossReferenceButton();
111 visible = false;
112 player = new AnimationPlayer();
113 Graph2DViewRepaintManager manager = new Graph2DViewRepaintManager(view);
114 manager.add(this);
115 player.addAnimationListener(manager);
116 view.addViewMode(new HoverViewMode());
117 }
118
119
123 private Iterator activeButtons() {
124 return new ControlsIterator(ViewModel.instance.isRoot(currentNode));
125 }
126
127
131 public void setNode( final Node node, final double mouseOffset ) {
132 if (!color.isVisible() && !icon.isVisible()) {
134 if (node == null) {
136 hideButtons();
137 currentNode = null;
138 view.updateView();
139 } else if (currentNode != node) {
140 if (!visible) {
141 view.addDrawable(this);
142 visible = true;
143 final double zoom = 1 / view.getZoom();
144 mouseX = (int) (mouseOffset - view.getGraph2D().getRealizer(node).getX() - (80*zoom));
145 if (ViewModel.instance.isRoot(node)) {
146 mouseX = 0;
147 }
148 }
149 for (Iterator it = new ControlsIterator(false); it.hasNext();) {
150 ((InlineControl) it.next()).setNode(node);
151 }
152 currentNode = node;
153 player.animate(this);
154 }
155 }
156 }
157
158
161 private void hideButtons() {
162 player.stop();
163 view.removeDrawable(this);
164 visible = false;
165 }
166
167
172 public void paint(final Graphics2D g) {
173 if (isValid()) {
174 Color oldColor = g.getColor();
175 g.setColor(PANEL_BACKGROUND);
176 g.fill(getBounds());
177 g.setColor(oldColor);
178
179 for (Iterator it = activeButtons(); it.hasNext();) {
180 ((InlineControl) it.next()).paint(g);
181 }
182 }
183 }
184
185
189 public Rectangle getBounds() {
190 Rectangle r = new Rectangle(0,0,-1,-1);
191 if (isValid()) {
192 for (final Iterator it = activeButtons(); it.hasNext(); ) {
193 Geom.calcUnion(r, ((InlineControl) it.next()).getBounds(), r);
194 }
195 }
196 return r;
197 }
198
199
204 private boolean isValid() {
205 if (currentNode != null && currentNode.getGraph() != null) {
207 return true;
208 }
209 currentNode = null;
210 return false;
211 }
212
213
224 public MouseInputEditor findMouseInputEditor(Graph2DView view, double x, double y, HitInfo hitInfo) {
225 if (isValid()) {
226 for (Iterator it = activeButtons(); it.hasNext();) {
227 final InlineControl button = (InlineControl) it.next();
228 if (button.getBounds().contains(x, y)) {
229 return button;
230 }
231 }
232 }
233 return null;
234 }
235
236
239 public void closeAll() {
240 if (color.isVisible()) {
241 color.toggleVisibility();
242 }
243 if (icon.isVisible()) {
244 icon.toggleVisibility();
245 }
246 }
247
248
252 public void initAnimation() {
253 animationYOffset = 10;
254 }
255
256
260 public void calcFrame(final double time) {
261 animationYOffset = (1 - time) * 10;
262 }
263
264
268 public void disposeAnimation() {
269 animationYOffset = 0;
270 }
271
272
278 public long preferredDuration() {
279 return 150;
280 }
281
282 String getToolTipText( final double x, final double y ) {
283 if (isValid()) {
284 for (Iterator it = activeButtons(); it.hasNext();) {
285 final InlineControl button = (InlineControl) it.next();
286 if (button.getBounds().contains(x, y)) {
287 return button.getToolTipText();
288 }
289 }
290 }
291 return null;
292 }
293
294
295
299 private class CrossReferenceButton extends InlineControl {
300 CrossReferenceButton() {
301 super(90);
302 }
303
304
308 String getToolTipText() {
309 return "Add a cross reference.";
310 }
311
312
316 void action() {
317 for(final Iterator viewModes = view.getViewModes();viewModes.hasNext();) {
318 final ViewMode viewMode = (ViewMode) viewModes.next();
319 if (viewMode instanceof MoveNodeMode) {
320 MoveNodeMode moveMode = (MoveNodeMode) viewMode;
321 moveMode.startCrossEdgeCreation(node);
322 view.updateView();
323 }
324 }
325 }
326
327
330 void paint(Graphics2D g) {
331 g = newZoomInvariant(g);
332
333 g.scale(1.5, 1.5);
334
335 g.setColor(MindMapUtil.CROSS_EDGE_COLOR);
336 g.fillOval(0, 0, 16, 16);
337
338 g.setColor(Color.WHITE);
339 GeneralPath gp = new GeneralPath();
340 gp.moveTo(8, 3);
341 gp.lineTo(12, 8);
342 gp.lineTo(10, 8);
343 gp.lineTo(10, 12);
344 gp.lineTo(6, 12);
345 gp.lineTo(6, 8);
346 gp.lineTo(4, 8);
347 g.fill(gp);
348
349 g.dispose();
350 }
351 }
352
353
356 private class AddButton extends InlineControl {
357 AddButton() {
358 super(120);
359 }
360
361
365 String getToolTipText() {
366 return "Add a new child item.";
367 }
368
369
372 void action() {
373 MindMapUtil.addNode(view, node);
374 }
375
376
379 void paint(Graphics2D g) {
380 g = newZoomInvariant(g);
381
382 g.scale(1.5,1.5);
383 g.setColor(GREEN);
384 g.fillOval(0, 0, 16, 16);
385 g.setColor(Color.WHITE);
386 g.fillRect(7, 4, 2, 8);
387 g.fillRect(4, 7, 8, 2);
388
389 g.dispose();
390 }
391 }
392
393
396 private class DeleteButton extends InlineControl {
397 DeleteButton() {
398 super(150);
399 }
400
401
405 String getToolTipText() {
406 return "Remove this item and all of its children.";
407 }
408
409
412 void action() {
413 hideButtons();
414
415 final Graph2D graph2D = view.getGraph2D();
416 graph2D.firePreEvent();
417 MindMapUtil.removeSubtree(graph2D, node);
418 LayoutUtil.layout(graph2D);
419 graph2D.firePostEvent();
420 }
421
422
425 void paint( Graphics2D g ) {
426 g = newZoomInvariant(g);
427
428 g.scale(1.5, 1.5);
429 g.setColor(Color.RED);
430 g.fillOval(0, 0, 16, 16);
431 g.setColor(Color.WHITE);
432 g.fillRect(4, 7, 8, 2);
433
434 g.dispose();
435 }
436 }
437
438
441 private class IconButton extends InlineControl {
442 private final Icon icon;
443 private final IconChooser iconChooser;
444 private final HoverButton parent;
445 private boolean visible;
446
447 IconButton( final HoverButton parent ) {
448 super(30);
449 this.icon = MindMapUtil.getIcon("smiley-happy-24.png");
450 this.parent = parent;
451 iconChooser = new IconChooser(this);
452 visible = false;
453 }
454
455
459 String getToolTipText() {
460 return "Choose a state icon.";
461 }
462
463
466 void paint( Graphics2D g ) {
467 if (icon != null) {
468 g = newZoomInvariant(g);
469
470 icon.paintIcon(view.getRootPane(), g, 0, 0);
471
472 g.dispose();
473 }
474 }
475
476
480 void setNode( final Node node ) {
481 super.setNode(node);
482 iconChooser.setNode(node);
483 }
484
485
488 void action() {
489 toggleVisibility();
490 }
491
492
495 void toggleVisibility() {
496 if (visible) {
497 view.removeDrawable(iconChooser);
498 } else if (!parent.showsChooser) {
499 view.addDrawable(iconChooser);
500 } else {
501 return;
502 }
503 parent.showsChooser = !parent.showsChooser;
504 visible = !visible;
505 view.updateView();
506 }
507
508
514 boolean isVisible() {
515 return visible;
516 }
517 }
518
519
522 private class IconChooser extends InlineControl implements Drawable, MouseInputEditorProvider {
523 private static final int V_OFFSET = 2;
524 private static final int H_OFFSET = 2;
525
526 private final IconButton parent;
527 private final StateIcon[] icons;
528
529 IconChooser( final IconButton parent ) {
530 super(36, 0);
531 this.parent = parent;
532 final StateIconProvider provider = StateIconProvider.instance;
533 icons = new StateIcon[] {
534 StateIconProvider.NULL_ICON,
535 provider.getIcon("smiley-happy"),
536 provider.getIcon("smiley-not-amused"),
537 provider.getIcon("smiley-grumpy"),
538 provider.getIcon("abstract-green"),
539 provider.getIcon("abstract-red"),
540 provider.getIcon("abstract-blue"),
541 provider.getIcon("questionmark"),
542 provider.getIcon("exclamationmark"),
543 provider.getIcon("delete"),
544 provider.getIcon("checkmark"),
545 provider.getIcon("star"),
546 };
547
548 int maxW = 0;
549 int maxH = 0;
550 int w = 0;
551 int h = 0;
552 final int l = icons.length;
553 for (int i = 0; i < l; ++i) {
554 final Icon icon = icons[i];
555 w += icon.getIconWidth();
556 final int ih = icon.getIconHeight();
557 if (maxH < ih) {
558 maxH = ih;
559 }
560
561 if (i == (l - 1)/2) {
562 maxW = w;
563 w = 0;
564 h = maxH;
565 maxH = 0;
566 }
567 }
568 w = Math.max(w, maxW) + (l > 0 ? ((l - 1) / 2) * H_OFFSET : 0);
569 h += maxH + V_OFFSET;
570 this.yOffset = -h - parent.height;
571 this.width = w;
572 this.height = h;
573 }
574
575
579 String getToolTipText() {
580 return null;
581 }
582
583
586 void action() {
587 throw new UnsupportedOperationException();
588 }
589
590 public Rectangle getBounds() {
591 return super.getBounds();
592 }
593
594
597 public void paint( Graphics2D g ) {
598 if (parent.isVisible()) {
599 g = newZoomInvariant(g);
600
601 int x = 0;
602 int y = 0;
603 int maxH = 0;
604 final Graph2DView view = HoverButton.this.view;
605 for (int i = 0, l = icons.length; i < l; ++i) {
606 final Icon icon = icons[i];
607 icon.paintIcon(view, g, x, y);
608
609 x += icon.getIconWidth() + H_OFFSET;
610 final int h = icon.getIconHeight();
611 if (maxH < h) {
612 maxH = h;
613 }
614
615 if (i == (l - 1)/2) {
616 x = 0;
617 y = maxH + V_OFFSET;
618 }
619 }
620 g.dispose();
621 }
622 }
623
624
634 public MouseInputEditor findMouseInputEditor(
635 final Graph2DView view, final double x, final double y, final HitInfo hitInfo
636 ) {
637 return getBounds().contains(x, y) ? this : null;
638 }
639
640
647 void action( final double relativeX, final double relativeY ) {
648 final StateIcon icon = findIcon(relativeX, relativeY);
649
650 final Node node = this.node;
651 final Graph2D graph = view.getGraph2D();
652 graph.firePreEvent();
653 graph.backupRealizers();
654 MindMapNodePainter.setStateIcon(graph.getRealizer(node), icon);
655 MindMapUtil.updateWidth(graph, node);
656 LayoutUtil.layout(graph);
657 graph.firePostEvent();
658
659 closeAll();
660 view.updateView();
661 }
662
663
673 private StateIcon findIcon( final double relativeX, final double relativeY ) {
674 final double z = 1 / view.getZoom();
675 double x = 0;
676 double y = 0;
677 int maxH = 0;
678 for (int i = 0, l = icons.length; i < l; ++i) {
679 final StateIcon icon = icons[i];
680 final int w = icon.getIconWidth();
681 final int h = icon.getIconHeight();
682 if (x <= relativeX && relativeX <= x + w * z &&
683 y <= relativeY && relativeY <= y + h * z) {
684 return icon;
685 }
686
687 if (maxH < h) {
688 maxH = h;
689 }
690
691 if (i == (l - 1)/2) {
692 x = 0;
693 y = (maxH + V_OFFSET) * z;
694 } else {
695 x += (w + H_OFFSET) * z;
696 }
697 }
698 return StateIconProvider.NULL_ICON;
699 }
700 }
701
702
705 private class ColorButton extends InlineControl {
706 private final ColorChooser colorChooser;
707 private boolean visible;
708 private HoverButton parent;
709
710 ColorButton( final HoverButton parent ) {
711 super(60);
712 this.parent = parent;
713 this.colorChooser = new ColorChooser(this);
714 this.visible = false;
715 }
716
717
721 String getToolTipText() {
722 return "Choose a color.";
723 }
724
725
728 void paint(Graphics2D g) {
729 g = newZoomInvariant(g);
730
731 g.scale(1.5, 1.5);
732 g.setColor(Color.RED);
733 g.fillArc(0, 0, 16, 16, 180, -90);
734 g.setColor(GREEN);
735 g.fillArc(0, 0, 16, 16, 270, -90);
736 g.setColor(Color.BLUE);
737 g.fillArc(0, 0, 16, 16, 0, -90);
738 g.setColor(YELLOW);
739 g.fillArc(0,0,16,16,90,-90);
740
741 g.dispose();
742 }
743
744
748 void setNode(final Node node) {
749 super.setNode(node);
750 colorChooser.setNode(node);
751 }
752
753
756 void action() {
757 toggleVisibility();
758 }
759
760
763 void toggleVisibility() {
764 if (visible) {
765 view.getGraph2D().removeDrawable(colorChooser);
766 } else if (!parent.showsChooser) {
767 view.getGraph2D().addDrawable(colorChooser);
768 } else {
769 return;
770 }
771 parent.showsChooser = !parent.showsChooser;
772 visible = !visible;
773 view.updateView();
774 }
775
776
782 boolean isVisible() {
783 return visible;
784 }
785 }
786
787
790 private class ColorChooser extends InlineControl implements Drawable, MouseInputEditorProvider {
791 private static final int WIDTH = 60;
792 private static final int HEIGHT = 15;
793 private static final int V_OFFSET = 2;
794
795 private final ColorButton parent;
796 private final Color[] colors;
797
798 ColorChooser( final ColorButton parent ) {
799 super(36, 0);
800 this.parent = parent;
801 colors = new Color[] {
802 MindMapUtil.ORANGE,
803 MindMapUtil.RED,
804 MindMapUtil.MAGENTA,
805 MindMapUtil.GREEN,
806 MindMapUtil.DARK_GREEN,
807 MindMapUtil.LIGHT_BLUE,
808 MindMapUtil.BLUE,
809 MindMapUtil.BROWN,
810 MindMapUtil.BLACK,
811 };
812 final int l = colors.length;
813 final int w = WIDTH;
814 final int h = l > 0 ? l * HEIGHT + (l - 1) * V_OFFSET : 0;
815 this.yOffset = -h - parent.height;
816 this.width = w;
817 this.height = h;
818 }
819
820
824 String getToolTipText() {
825 return null;
826 }
827
828
831 void action() {
832 throw new UnsupportedOperationException();
833 }
834
835 public Rectangle getBounds() {
836 return super.getBounds();
837 }
838
839
842 public void paint( Graphics2D g ) {
843 if (parent.isVisible()) {
844 g = newZoomInvariant(g);
845
846 int y = 0;
847 for (int i = 0, l = colors.length; i < l; ++i) {
848 g.setColor(colors[i]);
849 g.fillRect(0, y, WIDTH, HEIGHT);
850 y += HEIGHT + V_OFFSET;
851 }
852
853 g.dispose();
854 }
855 }
856
857
867 public MouseInputEditor findMouseInputEditor(
868 final Graph2DView view, final double x, final double y, final HitInfo hitInfo
869 ) {
870 return getBounds().contains(x, y) ? this : null;
871 }
872
873
881 void action( final double relativeX, final double relativeY ) {
882 final Color color = findColor(relativeY);
883 if (color != null) {
884 final Graph2D graph = view.getGraph2D();
885 final Node node = this.node;
886 graph.backupRealizers(new NodeList(node).nodes());
887 final NodeRealizer nr = graph.getRealizer(node);
888 nr.setFillColor(color);
889 }
890
891 closeAll();
892 view.updateView();
893 }
894
895
903 private Color findColor( final double relativeY ) {
904 final double z = 1 / view.getZoom();
905 double y = 0;
906 for (int i = 0, l = colors.length; i < l; ++i) {
907 if (y <= relativeY && relativeY <= y + HEIGHT * z) {
908 return colors[i];
909 }
910 y += (HEIGHT + V_OFFSET) * z;
911 }
912 return null;
913 }
914 }
915
916
919 private abstract class InlineControl extends AbstractMouseInputEditor {
920 Node node;
921
922 final int xOffset;
923 int yOffset;
924 int width;
925 int height;
926
927 InlineControl( final int xOffset ) {
928 this(xOffset, -22);
929 }
930
931 InlineControl( final int xOffset, final int yOffset ) {
932 this.xOffset = xOffset;
933 this.yOffset = yOffset;
934 this.width = 24;
935 this.height = 24;
936 }
937
938
942 abstract String getToolTipText();
943
944
947 abstract void paint( Graphics2D g );
948
949
952 abstract void action();
953
954
962 void action( final double relativeX, final double relativeY ) {
963 action();
964 }
965
966
974 Graphics2D newZoomInvariant( final Graphics2D g ) {
975 final Graphics2D gfx = (Graphics2D) g.create();
976
977 final Rectangle bnds = getBounds();
978 gfx.translate(bnds.x, bnds.y);
979
980 final double invZoom = 1 / view.getZoom();
981 gfx.scale(invZoom, invZoom);
982
983 return gfx;
984 }
985
986
990 void setNode(final Node node) {
991 this.node = node;
992 }
993
994
1000 Rectangle getBounds() {
1001 final Rectangle r = new Rectangle(width, height);
1002 final NodeRealizer realizer = view.getGraph2D().getRealizer(node);
1003 final double z2 = 1 / view.getZoom();
1004 r.x = (int) (realizer.getX() + (xOffset * z2 + mouseX) );
1005 r.width *= z2;
1006 r.height *= z2;
1007 r.y = (int) (realizer.getY() + (yOffset * z2) + animationYOffset);
1008 return r;
1009 }
1010
1011
1017 public boolean startsEditing( final Mouse2DEvent event ) {
1018 return getBounds().contains(event.getX(), event.getY());
1019 }
1020
1021
1027 public void mouse2DEventHappened( final Mouse2DEvent event ) {
1028 final double evtX = event.getX();
1029 final double evtY = event.getY();
1030 final Rectangle bnds = getBounds();
1031 if (bnds.contains(evtX, evtY)) {
1032 if (event.getId() == Mouse2DEvent.MOUSE_CLICKED) {
1033 action(evtX - bnds.x, evtY- bnds.y);
1034 }
1035 } else {
1036 stopEditing();
1037 }
1038 }
1039 }
1040
1041
1046 class HoverViewMode extends ViewMode {
1047 public void mouseClicked( final double x, final double y ) {
1048 final HitInfo hitInfo = getHitInfo(x, y);
1049 if (!hitInfo.hasHits()) {
1050 setNode(null, 0);
1051 closeAll();
1052 }
1053 super.mouseClicked(x, y);
1054 }
1055
1056 public void mouseMoved( final double x, final double y ) {
1057 view.setToolTipText(null);
1058
1059 final HoverButton controls = HoverButton.this;
1060 final Node lastNode = controls.currentNode;
1061 if (lastNode != null) {
1062 if (controls.getBounds().contains(x, y)) {
1063 view.setToolTipText(controls.getToolTipText(x, y));
1064 return;
1065 }
1066 if (contains(getGraph2D().getRealizer(lastNode), x, y)) {
1067 return;
1068 }
1069 }
1070
1071 final HitInfo hitInfo = getHitInfo(x, y);
1072 if (hitInfo.hasHitNodes()) {
1073 setNode(hitInfo.getHitNode(), x);
1074 } else if (lastNode != null) {
1075 setNode(null, 0);
1076 }
1077 }
1078
1079 private boolean contains( final NodeRealizer nr, final double x, final double y ) {
1080 return nr.getX() <= x && x <= nr.getX() + nr.getWidth() &&
1081 nr.getY() <= y && y <= nr.getY() + nr.getHeight();
1082 }
1083 }
1084
1085
1089 private final class ControlsIterator implements Iterator {
1090 private int index;
1091
1092 ControlsIterator( final boolean root ) {
1093 index = root ? 3 : 0;
1094 }
1095
1096 public void remove() {
1097 throw new UnsupportedOperationException();
1098 }
1099
1100 public boolean hasNext() {
1101 return index < 5;
1102 }
1103
1104 public Object next() {
1105 if (hasNext()) {
1106 switch (index++) {
1107 case 0:
1108 return delete;
1109 case 1:
1110 return color;
1111 case 2:
1112 return icon;
1113 case 3:
1114 return add;
1115 case 4:
1116 return reference;
1117 default:
1118 throw new IllegalStateException();
1119 }
1120 } else {
1121 throw new NoSuchElementException();
1122 }
1123 }
1124 }
1125}
1126