1 package net.sourceforge.jpotpourri.gui.flexi;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 import java.awt.Color;
18
19 import java.awt.Component;
20
21 import java.awt.Dimension;
22
23 import java.awt.Graphics;
24
25 import java.awt.Graphics2D;
26
27 import java.awt.Insets;
28
29 import java.awt.KeyboardFocusManager;
30
31 import java.awt.Point;
32
33 import java.awt.Rectangle;
34
35 import java.awt.event.ComponentEvent;
36
37 import java.awt.event.ComponentListener;
38
39 import java.awt.event.HierarchyBoundsListener;
40
41 import java.awt.event.HierarchyEvent;
42
43 import java.awt.event.HierarchyListener;
44
45 import java.awt.event.MouseAdapter;
46
47 import java.awt.event.MouseEvent;
48
49 import java.awt.event.MouseMotionListener;
50
51 import java.awt.geom.AffineTransform;
52
53 import java.awt.image.BufferedImage;
54
55 import java.beans.PropertyChangeEvent;
56
57 import java.beans.PropertyChangeListener;
58
59 import javax.swing.JComponent;
60
61 import javax.swing.JList;
62
63 import javax.swing.JScrollPane;
64
65 import javax.swing.JTree;
66
67 import javax.swing.ListCellRenderer;
68
69 import javax.swing.Popup;
70
71 import javax.swing.PopupFactory;
72
73 import javax.swing.SwingUtilities;
74
75 import javax.swing.border.Border;
76
77 import javax.swing.event.ChangeEvent;
78
79 import javax.swing.event.ChangeListener;
80
81 import javax.swing.event.ListDataEvent;
82
83 import javax.swing.event.ListDataListener;
84
85 import javax.swing.event.ListSelectionEvent;
86
87 import javax.swing.event.ListSelectionListener;
88
89 import javax.swing.event.TreeModelEvent;
90
91 import javax.swing.event.TreeModelListener;
92
93 import javax.swing.event.TreeSelectionEvent;
94
95 import javax.swing.event.TreeSelectionListener;
96
97 import javax.swing.tree.TreePath;
98
99
100
101
102
103
104
105
106
107
108 public final class ViewTooltips extends MouseAdapter implements MouseMotionListener {
109
110
111
112
113
114
115
116
117 private static ViewTooltips instance = null;
118
119
120 private int refcount = 0;
121
122
123 private JComponent inner = null;
124
125
126 private int row = -1;
127
128
129 private Popup[] popups = new Popup[2];
130
131
132 private ImgComp painter = new ImgComp();
133
134
135 private ViewTooltips() {
136
137 }
138
139
140
141
142
143
144 public static void register(final JList list) {
145 registerSafeType(list);
146 }
147
148 public static void register(final JTree tree) {
149 registerSafeType(tree);
150 }
151
152 private static void registerSafeType(final JComponent comp) {
153 if (instance == null) {
154 instance = new ViewTooltips();
155 }
156 instance.attachTo(comp);
157 }
158
159
160
161
162
163
164 public static void unregister(final JComponent comp) {
165 assert instance != null : "Unregister asymmetrically called";
166 if (null != instance && instance.detachFrom(comp) == 0) {
167 instance.hide();
168 instance = null;
169 }
170 }
171
172
173 private void attachTo(final JComponent comp) {
174 assert comp instanceof JTree || comp instanceof JList;
175 comp.addMouseListener(this);
176 comp.addMouseMotionListener(this);
177 refcount++;
178 }
179
180
181 private int detachFrom(final JComponent comp) {
182 assert comp instanceof JTree || comp instanceof JList;
183 comp.removeMouseMotionListener(this);
184 comp.removeMouseListener(this);
185 return refcount--;
186 }
187
188 public void mouseMoved(final MouseEvent e) {
189 Point p = e.getPoint();
190 JComponent comp = (JComponent) e.getSource();
191 JScrollPane jsp = (JScrollPane)
192 SwingUtilities.getAncestorOfClass(JScrollPane.class, comp);
193
194 if (jsp != null) {
195 p = SwingUtilities.convertPoint(comp, p, jsp);
196 show(jsp, p);
197 }
198 }
199
200 public void mouseDragged(final MouseEvent e) {
201 hide();
202 }
203
204 public void mouseEntered(final MouseEvent e) {
205 hide();
206 }
207
208 public void mouseExited(final MouseEvent e) {
209 hide();
210 }
211
212
213
214
215
216
217
218 void show(final JScrollPane view, final Point pt) {
219 if (view.getViewport().getView() instanceof JTree) {
220 showJTree(view, pt);
221 } else if (view.getViewport().getView() instanceof JList) {
222 showJList(view, pt);
223 } else {
224 assert false : "Bad component type registered: "
225 + view.getViewport().getView();
226 }
227 }
228
229 private void showJList(final JScrollPane view, final Point pt) {
230 JList list = (JList) view.getViewport().getView();
231 Point p = SwingUtilities.convertPoint(view, pt.x, pt.y, list);
232 int listRow = list.locationToIndex(p);
233
234 if (listRow == -1) {
235 hide();
236 return;
237 }
238
239 Rectangle bds = list.getCellBounds(listRow, listRow);
240
241
242
243
244
245 ListCellRenderer ren = list.getCellRenderer();
246 Dimension rendererSize =
247 ren.getListCellRendererComponent(list,
248 list.getModel().getElementAt(listRow),
249 listRow, false, false).getPreferredSize();
250 bds.width = rendererSize.width;
251
252 if (bds == null || !bds.contains(p)) {
253 hide();
254 return;
255 }
256
257 if (setCompAndRow(list, listRow)) {
258 Rectangle visible = getShowingRect(view);
259 Rectangle[] rects = getRects(bds, visible);
260
261 if (rects.length > 0) {
262 ensureOldPopupsHidden();
263 painter.configure(list.getModel().getElementAt(listRow),
264 view, list, listRow);
265 showPopups(rects, bds, visible, list, view);
266
267 } else {
268 hide();
269 }
270 }
271 }
272
273 private void showJTree(final JScrollPane view, final Point pt) {
274 JTree tree = (JTree) view.getViewport().getView();
275 Point p = SwingUtilities.convertPoint(view, pt.x, pt.y, tree);
276 final int closestRow = tree.getClosestRowForLocation(p.x, p.y);
277 TreePath path = tree.getClosestPathForLocation(p.x, p.y);
278 Rectangle bds = tree.getPathBounds(path);
279
280 if (bds == null || !bds.contains(p)) {
281 hide();
282 return;
283 }
284
285 if (setCompAndRow(tree, closestRow)) {
286 Rectangle visible = getShowingRect(view);
287 Rectangle[] rects = getRects(bds, visible);
288
289 if (rects.length > 0) {
290 ensureOldPopupsHidden();
291 painter.configure(path.getLastPathComponent(),
292 view, tree, path, closestRow);
293 showPopups(rects, bds, visible, tree, view);
294 } else {
295 hide();
296 }
297 }
298 }
299
300
301
302
303
304 private boolean setCompAndRow(final JComponent newInner, final int newRow) {
305 boolean rowChanged = newRow != this.row;
306 boolean compChanged = newInner != this.inner;
307 this.inner = newInner;
308 this.row = newRow;
309 return (rowChanged || compChanged);
310 }
311
312
313
314
315
316 void hide() {
317 ensureOldPopupsHidden();
318 if (painter != null) {
319 painter.clear();
320 }
321
322 setHideComponent(null, null);
323 inner = null;
324 row = -1;
325 }
326
327 private void ensureOldPopupsHidden() {
328 for (int i = 0; i < popups.length; i++) {
329 if (popups[i] != null) {
330 popups[i].hide();
331 popups[i] = null;
332 }
333 }
334 }
335
336
337
338
339
340 private Rectangle getShowingRect(final JScrollPane pane) {
341 final Insets ins1 = pane.getViewport().getInsets();
342 final Border border = pane.getViewportBorder();
343
344 final Insets ins2;
345 if(border != null) {
346 ins2 = border.getBorderInsets(pane);
347 } else {
348 ins2 = new Insets(0, 0, 0, 0);
349 }
350
351 Insets ins3 = new Insets(0, 0, 0, 0);
352 if(pane.getBorder() != null) {
353 ins3 = pane.getBorder().getBorderInsets(pane);
354 }
355
356 final Rectangle r = pane.getViewportBorderBounds();
357 r.translate(-r.x, -r.y);
358 r.width -= ins1.left + ins1.right;
359 r.width -= ins2.left + ins2.right;
360 r.height -= ins1.top + ins1.bottom;
361 r.height -= ins2.top + ins2.bottom;
362 r.x -= ins2.left;
363 r.x -= ins3.left;
364
365 final Point p = pane.getViewport().getViewPosition();
366 r.translate(p.x, p.y);
367
368 return SwingUtilities.convertRectangle(pane.getViewport(), r, pane);
369 }
370
371
372
373
374
375
376
377 private static Rectangle[] getRects(final Rectangle bds, final Rectangle vis) {
378 Rectangle[] result;
379
380 if (vis.contains(bds)) {
381 result = new Rectangle[0];
382 } else {
383
384 if (bds.x < vis.x && bds.x + bds.width > vis.x + vis.width) {
385 Rectangle a = new Rectangle(bds.x, bds.y, vis.x - bds.x, bds.height);
386 Rectangle b = new Rectangle(vis.x + vis.width, bds.y,
387 (bds.x + bds.width) - (vis.x + vis.width), bds.height);
388 result = new Rectangle[] { a, b };
389
390 } else if (bds.x < vis.x) {
391 result = new Rectangle[] {
392 new Rectangle(bds.x, bds.y, vis.x - bds.x, bds.height)
393 };
394
395 } else if (bds.x + bds.width > vis.x + vis.width) {
396 result = new Rectangle[] {
397 new Rectangle(vis.x + vis.width, bds.y, (bds.x + bds.width) - (vis.x + vis.width), bds.height)
398 };
399 } else {
400 result = new Rectangle[0];
401 }
402 }
403
404
405
406
407 return result;
408 }
409
410
411
412
413 private void showPopups(
414 final Rectangle[] rects,
415 final Rectangle bds,
416 final Rectangle visible,
417 final JComponent comp,
418 final JScrollPane view
419 ) {
420 boolean shown = false;
421
422 for (int i = 0; i < rects.length; i++) {
423 Rectangle sect = rects[i];
424 sect.translate(-bds.x, -bds.y);
425 ImgComp part = painter.getPartial(sect, bds.x + rects[i].x < visible.x);
426 Point pos = new Point(bds.x + rects[i].x, bds.y + rects[i].y);
427 SwingUtilities.convertPointToScreen(pos, comp);
428
429 if (comp instanceof JList) {
430
431 pos.y--;
432 }
433
434 if (pos.x > 0) {
435
436 popups[i] = getPopupFactory().getPopup(view,
437 part, pos.x, pos.y);
438 popups[i].show();
439 shown = true;
440 }
441 }
442
443 if (shown) {
444 setHideComponent(comp, view);
445 } else {
446 setHideComponent(null, null);
447 }
448 }
449
450 private static PopupFactory getPopupFactory() {
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475 return PopupFactory.getSharedInstance();
476
477 }
478
479 private Hider hider = null;
480
481
482
483
484
485
486 private void setHideComponent(final JComponent comp, final JScrollPane parent) {
487 if (hider != null) {
488 if (hider.isListeningTo(comp)) {
489 return;
490 }
491 }
492
493 if (hider != null) {
494 hider.detach();
495 }
496
497 if (comp != null) {
498 hider = new Hider(comp, parent);
499 } else {
500 hider = null;
501 }
502 }
503
504
505
506
507
508
509 private static final class ImgComp extends JComponent {
510
511 private static final long serialVersionUID = -3812447265015213647L;
512
513 private BufferedImage img;
514
515 private Dimension d = null;
516
517 private Color bg = Color.WHITE;
518
519
520
521
522
523 private AffineTransform at = AffineTransform.getTranslateInstance(0d, 0d);
524
525 private boolean isRight = false;
526
527 ImgComp() {
528
529 }
530
531
532
533
534 ImgComp(final BufferedImage img, final Rectangle off, final boolean right) {
535 this.img = img;
536 at = AffineTransform.getTranslateInstance(-off.x, 0);
537 d = new Dimension(off.width, off.height);
538 isRight = right;
539 }
540
541 public ImgComp getPartial(final Rectangle bds, final boolean right) {
542 assert img != null;
543 return new ImgComp(img, bds, right);
544 }
545
546
547
548 public boolean configure(
549 final Object nd,
550 @SuppressWarnings("unused") final JScrollPane tv,
551 final JTree tree,
552 final TreePath path,
553 final int row
554 ) {
555
556
557 Component renderer = null;
558
559 bg = tree.getBackground();
560 boolean sel = tree.isSelectionEmpty() ? false :
561 tree.getSelectionModel().isPathSelected(path);
562
563 boolean exp = tree.isExpanded(path);
564 boolean leaf = !exp && tree.getModel().isLeaf(nd);
565 boolean lead = path.equals(tree.getSelectionModel().getLeadSelectionPath());
566
567 renderer = tree.getCellRenderer().getTreeCellRendererComponent(tree, nd, sel, exp, leaf, row, lead);
568 if (renderer != null) {
569 setComponent(renderer);
570 }
571 return true;
572 }
573
574
575
576 public boolean configure(
577 final Object nd,
578 @SuppressWarnings("unused") final JScrollPane tv,
579 final JList list,
580 final int row
581 ) {
582
583
584 Component renderer = null;
585 bg = list.getBackground();
586
587 boolean sel = list.isSelectionEmpty() ? false :
588 list.getSelectionModel().isSelectedIndex(row);
589 renderer = list.getCellRenderer().getListCellRendererComponent(list, nd, row, sel, false);
590 if (renderer != null) {
591 setComponent(renderer);
592 }
593 return true;
594 }
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610 void clear() {
611
612
613 }
614
615
616
617
618 public void setComponent(final Component jc) {
619 final Dimension prefDim = jc.getPreferredSize();
620 BufferedImage nue = new BufferedImage(prefDim.width, prefDim.height + 2,
621 BufferedImage.TYPE_INT_ARGB_PRE);
622 SwingUtilities.paintComponent(nue.getGraphics(), jc, this, 0, 0, prefDim.width, prefDim.height + 2);
623 setImg(nue);
624 }
625
626 public Rectangle getBounds() {
627 Dimension dd = getPreferredSize();
628 return new Rectangle(0, 0, dd.width, dd.height);
629 }
630
631 private void setImg(final BufferedImage img) {
632 this.img = img;
633 d = null;
634 }
635
636 public Dimension getPreferredSize() {
637 if (d == null) {
638 d = new Dimension(img.getWidth(), img.getHeight());
639 }
640 return d;
641 }
642
643 public Dimension getSize() {
644 return getPreferredSize();
645 }
646
647 public void paint(final Graphics g) {
648 g.setColor(bg);
649 g.fillRect(0, 0, d.width, d.height);
650 Graphics2D g2d = (Graphics2D) g;
651 g2d.drawRenderedImage(img, at);
652 g.setColor(Color.GRAY);
653 g.drawLine(0, 0, d.width, 0);
654 g.drawLine(0, d.height - 1, d.width, d.height - 1);
655
656 if (isRight) {
657 g.drawLine(0, 0, 0, d.height - 1);
658 } else {
659 g.drawLine(d.width - 1, 0, d.width - 1, d.height - 1);
660 }
661 }
662
663 public void firePropertyChange(final String s, final Object a, final Object b) {
664
665 }
666
667 public void invalidate() {
668
669 }
670
671 public void validate() {
672
673 }
674
675 public void revalidate() {
676
677 }
678 }
679
680
681
682
683
684 private static final class Hider implements ChangeListener,
685 PropertyChangeListener, TreeModelListener, TreeSelectionListener,
686 HierarchyListener, HierarchyBoundsListener, ListSelectionListener,
687 ListDataListener, ComponentListener {
688
689 private final JTree tree;
690
691 private JScrollPane pane;
692
693 private final JList list;
694
695 private boolean detached = false;
696
697 public Hider(final JComponent comp, final JScrollPane pane) {
698 if (comp instanceof JTree) {
699 this.tree = (JTree) comp;
700 this.list = null;
701 } else {
702 this.list = (JList) comp;
703 this.tree = null;
704 }
705
706 assert tree != null || list != null;
707 this.pane = pane;
708 attach();
709 }
710
711 private boolean isListeningTo(final JComponent comp) {
712 return !detached && (comp == list || comp == tree);
713 }
714
715 private void attach() {
716 if (tree != null) {
717 tree.getModel().addTreeModelListener(this);
718 tree.getSelectionModel().addTreeSelectionListener(this);
719 tree.addHierarchyBoundsListener(this);
720 tree.addHierarchyListener(this);
721 tree.addComponentListener(this);
722 } else {
723 list.getSelectionModel().addListSelectionListener(this);
724 list.getModel().addListDataListener(this);
725 list.addHierarchyBoundsListener(this);
726 list.addHierarchyListener(this);
727 list.addComponentListener(this);
728 }
729
730 if (null != pane.getHorizontalScrollBar()) {
731 pane.getHorizontalScrollBar().getModel().addChangeListener(this);
732 }
733
734 if (null != pane.getVerticalScrollBar()) {
735 pane.getVerticalScrollBar().getModel().addChangeListener(this);
736 }
737 KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener(this);
738 }
739
740
741
742 private void detach() {
743 KeyboardFocusManager.getCurrentKeyboardFocusManager().removePropertyChangeListener(this);
744
745 if (tree != null) {
746 tree.getSelectionModel().removeTreeSelectionListener(this);
747 tree.getModel().removeTreeModelListener(this);
748 tree.removeHierarchyBoundsListener(this);
749 tree.removeHierarchyListener(this);
750 tree.removeComponentListener(this);
751 } else {
752 list.getSelectionModel().removeListSelectionListener(this);
753 list.getModel().removeListDataListener(this);
754 list.removeHierarchyBoundsListener(this);
755 list.removeHierarchyListener(this);
756 list.removeComponentListener(this);
757 }
758
759 if (null != pane.getHorizontalScrollBar()) {
760 pane.getHorizontalScrollBar().getModel().removeChangeListener(this);
761 }
762
763 if (null != pane.getVerticalScrollBar()) {
764 pane.getVerticalScrollBar().getModel().removeChangeListener(this);
765 }
766 detached = true;
767 }
768
769 private void change() {
770 if (ViewTooltips.instance != null) {
771 ViewTooltips.instance.hide();
772 }
773 detach();
774 }
775
776 public void propertyChange(final PropertyChangeEvent evt) {
777 change();
778 }
779
780 public void treeNodesChanged(final TreeModelEvent e) {
781 change();
782 }
783
784 public void treeNodesInserted(final TreeModelEvent e) {
785 change();
786 }
787
788 public void treeNodesRemoved(final TreeModelEvent e) {
789 change();
790 }
791
792 public void treeStructureChanged(final TreeModelEvent e) {
793 change();
794 }
795
796 public void hierarchyChanged(final HierarchyEvent e) {
797 change();
798 }
799
800 public void valueChanged(final TreeSelectionEvent e) {
801 change();
802 }
803
804 public void ancestorMoved(final HierarchyEvent e) {
805 change();
806 }
807
808 public void ancestorResized(final HierarchyEvent e) {
809 change();
810 }
811
812 public void stateChanged(final ChangeEvent e) {
813 change();
814 }
815
816 public void valueChanged(final ListSelectionEvent e) {
817 change();
818 }
819
820 public void intervalAdded(final ListDataEvent e) {
821 change();
822 }
823
824 public void intervalRemoved(final ListDataEvent e) {
825 change();
826 }
827
828 public void contentsChanged(final ListDataEvent e) {
829 change();
830 }
831
832 public void componentResized(final ComponentEvent e) {
833 change();
834 }
835
836 public void componentMoved(final ComponentEvent e) {
837 change();
838 }
839
840 public void componentShown(final ComponentEvent e) {
841 change();
842 }
843
844 public void componentHidden(final ComponentEvent e) {
845 change();
846 }
847 }
848 }