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