Source code: com/imagero/gui/swing/TLToolTipManager.java
1 /*
2 * Copyright (c) imagero Andrey Kuznetsov. All Rights Reserved.
3 * http://jgui.imagero.com
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
8 * any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19
20 package com.imagero.gui.swing;
21
22 import javax.swing.*;
23 import javax.swing.border.*;
24 import javax.swing.event.*;
25 import javax.swing.tree.*;
26 import java.awt.*;
27 import java.awt.event.*;
28 import java.awt.image.*;
29
30 /**
31 * <pre>
32 * TLToolTipManager.java
33 * ToolTipManager for both J<b>T</b>ree and J<b>L</b>ist.
34 * Usage:
35 * new TLToolTipManager(JTree) or new TLToolTipManager(JList);
36 * Note - there is no need to keep reference to TLToolTipManager objects.
37 * </pre>
38 * @author Andrei Kouznetsov
39 * @version 1.3
40 */
41 public class TLToolTipManager {
42
43 public static final int UNDEFINED_ROW = -1;
44
45 int lastRow = -1;
46 TipWindow window;
47 JComponent owner;
48 boolean showFullTip;
49
50 JToolTip tip = new JToolTip();
51
52 Rectangle visibleRect = new Rectangle();
53
54 Rectangle rowBounds = new Rectangle();
55 Border lb = new LineBorder(Color.gray);
56
57 /**
58 * create new TLToolTipManager for supplied JTree
59 * @param tree JTree
60 */
61 public TLToolTipManager(JTree tree) {
62 this(tree, false);
63 }
64
65 /**
66 * create new TLToolTipManager for supplied JTree
67 * @param tree JTree
68 * @param showFullTip if true then full tooltip shown,
69 * otherwise only missed part shown (this is preferred way)
70 */
71 public TLToolTipManager(JTree tree, boolean showFullTip) {
72 this.owner = tree;
73 this.showFullTip = showFullTip;
74
75 MouseHandler tmh = new MouseHandler();
76 this.owner.addMouseListener(tmh);
77 this.owner.addMouseMotionListener(tmh);
78
79 TipMouseHandler tmh2 = new TipMouseHandler();
80 this.tip.addMouseListener(tmh2);
81 this.tip.addMouseMotionListener(tmh2);
82 owner.addComponentListener(new OwnerListener());
83 }
84
85 /**
86 * create new TLToolTipManager for supplied JList
87 * @param list JList
88 */
89 public TLToolTipManager(JList list) {
90 this(list, false);
91 }
92
93 /**
94 * create new TLToolTipManager for supplied JList
95 * @param list JList
96 * @param showFullTip if true then full tooltip shown,
97 * otherwise only missed part shown (this is preferred way)
98 */
99 public TLToolTipManager(JList list, boolean showFullTip) {
100 this.owner = list;
101 this.showFullTip = showFullTip;
102
103 MouseHandler tmh = new MouseHandler();
104 this.owner.addMouseListener(tmh);
105 this.owner.addMouseMotionListener(tmh);
106
107 TipMouseHandler tmh2 = new TipMouseHandler();
108 this.tip.addMouseListener(tmh2);
109 this.tip.addMouseMotionListener(tmh2);
110 owner.addComponentListener(new OwnerListener());
111 }
112
113 /**
114 * set lastRow to UNDEFINED_ROW
115 * @see #UNDEFINED_ROW
116 *
117 */
118 private void resetRow() {
119 lastRow = UNDEFINED_ROW;
120 }
121
122 /**
123 * Get TipWindow. If TipWindow is null then new one is created.
124 * @return JWindow
125 */
126 protected JWindow getTipWindow() {
127 if (this.window == null) {
128 Window w = SwingUtilities.getWindowAncestor(owner);
129 this.window = new TipWindow(w);
130 }
131 return this.window;
132 }
133
134 /**
135 * TipWindow. Used for showing tooltip.
136 */
137 static class TipWindow extends JWindow {
138 BufferedImage bi;
139 CellRendererPane pane;
140
141 int xOffset;
142
143 public TipWindow(Window owner) {
144 super(owner);
145 pane = new CellRendererPane();
146 // getContentPane().add(pane);
147 }
148
149 public void setXOffset(int offset) {
150 this.xOffset = offset;
151 if (bi != null) {
152 setSize(bi.getWidth(), bi.getHeight());
153 }
154 }
155
156 void setImage(BufferedImage bi) {
157 this.bi = bi;
158 if (bi != null) {
159 setSize(bi.getWidth(), bi.getHeight());
160 }
161 repaint();
162 }
163
164 public void paint(Graphics g) {
165 g.setColor(Color.white);
166 g.fillRect(0, 0, getWidth(), getHeight());
167 if (bi != null) {
168 g.drawImage(bi, 0 - (bi.getWidth() - xOffset), 0, null);
169 }
170 }
171 }
172
173 /**
174 * Get row for given location.
175 * For JTree it calls JTree#getRowForLocation, for JList - JList#locationToIndex
176 * @param comp JTree or JList
177 * @param p Point
178 * @return row for given point
179 * @see JTree#getRowForLocation
180 * @see JList#locationToIndex
181 */
182 private int getRow(JComponent comp, Point p) {
183 if (comp instanceof JTree) {
184 return ((JTree) comp).getRowForLocation(p.x, p.y);
185 }
186 else if (comp instanceof JList) {
187 return ((JList) comp).locationToIndex(p);
188 }
189 throw new RuntimeException("JComponent shouls be JList or JTree");
190 }
191
192 /**
193 * Get bounds for given row.<br>
194 * If comp is instance of JTree then bounds are determined by call to JTree#getRowBounds.<br>
195 * If comp is instance of JList then bounds are determined by call to JList#getCellBounds<br>
196 * @param comp JTree or JList
197 * @param row row number
198 * @return bounds of given row (Rectangle)
199 * @see JTree#getRowBounds
200 * @see JList#getCellBounds
201 */
202 private Rectangle getRowBounds(JComponent comp, int row) {
203 if (comp instanceof JTree) {
204 return ((JTree) comp).getRowBounds(row);
205 }
206 else if (comp instanceof JList) {
207 return ((JList) comp).getCellBounds(row, row);
208 }
209 throw new RuntimeException("JComponent shouls be JList or JTree");
210 }
211
212 /**
213 * get start of label's text. <br>
214 * Should the ComponentOrientation be checked here?
215 */
216 static int getLabelStart(JLabel label) {
217 Icon currentI = label.getIcon();
218 if (currentI != null && label.getText() != null) {
219 return currentI.getIconWidth() + Math.max(0, label.getIconTextGap() - 4);
220 }
221 return 0;
222 }
223
224 /**
225 * MouseHandler.<br>
226 * MouseListener for TLToolTipManager.
227 */
228 class MouseHandler extends MouseInputAdapter {
229
230 public void mousePressed(MouseEvent e) {
231 resetRow();
232 mouseMoved(e);
233 }
234
235 public void mouseMoved(MouseEvent e) {
236 JComponent component = (JComponent) e.getSource();
237
238 Point p = e.getPoint();
239
240 int row = getRow(component, p);
241
242 if (row == -1) {
243 resetRow();
244 hideTipWindow();
245 return;
246 }
247
248 Rectangle rowBounds = getRowBounds(component, row);
249
250 if (!rowBounds.contains(p)) {
251 hideTipWindow();
252 resetRow();
253 return;
254 }
255
256 Rectangle vr = computeVisibleRect();
257
258 if (vr.contains(rowBounds)) {
259 lastRow = row;
260 hideTipWindow();
261 return;
262 }
263
264 // row does not changed
265 if (row == lastRow) {
266 return;
267 }
268
269 lastRow = row;
270 hideTipWindow();
271
272 Component label = getRenderer(component, row, rowBounds);
273
274 if (vr.contains(rowBounds)) {
275 hideTipWindow();
276 return;
277 }
278
279 createImage(rowBounds, label, component);
280
281 Point screenLocation = new Point(rowBounds.x, rowBounds.y);
282 SwingUtilities.convertPointToScreen(screenLocation, component);
283
284 getTipWindow().getContentPane().add(tip);
285
286 if (showFullTip) {
287 window.setXOffset(rowBounds.width);
288 window.setLocation(screenLocation.x, screenLocation.y);
289 }
290 else {
291 final int offset = (rowBounds.width + rowBounds.x) - (vr.width + vr.x);
292 window.setXOffset(offset);
293 window.setBounds(screenLocation.x + rowBounds.width - offset, screenLocation.y, offset, rowBounds.height);
294 }
295 window.show();
296 }
297
298 private Component getRenderer(JComponent comp, int row, Rectangle rowBounds) {
299 if (comp instanceof JTree) {
300 JTree tree = (JTree) comp;
301 TreePath tp = tree.getPathForRow(row);
302 DefaultMutableTreeNode value = (DefaultMutableTreeNode) tp.getLastPathComponent();
303
304 boolean isSelected = tree.isRowSelected(row);
305 TreeCellRenderer renderer = tree.getCellRenderer();
306
307 Component label = renderer.getTreeCellRendererComponent(
308 tree, value, isSelected, !tree.isCollapsed(row), value.isLeaf(), row, false);
309 return label;
310 }
311 else if (comp instanceof JList) {
312 JList list = (JList) comp;
313 boolean isSelected = list.isSelectedIndex(row);
314 ListCellRenderer renderer = list.getCellRenderer();
315
316 Object value = list.getModel().getElementAt(row);
317 String tipText = String.valueOf(value);
318
319 tip.setTipText(tipText);
320
321 // FontMetrics fm = tip.getFontMetrics(tip.getFont());
322 // int strlen = fm.stringWidth(tipText);
323
324 Component label = renderer.getListCellRendererComponent(
325 list, value, row, isSelected, false);
326
327 // int labelStart = getLabelStart(label);
328
329 // rowBounds.x += labelStart;
330 rowBounds.width = label.getPreferredSize().width;//strlen + 20;
331 return label;
332 }
333 throw new RuntimeException("JComponent shouls be JList or JTree");
334 }
335
336 /**
337 * Create buffer image.<br>
338 *
339 * @param rowBounds row bounds
340 * @param label renderer component
341 * @param cont Container (JTree or JList)
342 * @see #getRowBounds
343 * @see #getRenderer
344 */
345 private void createImage(Rectangle rowBounds, Component label, Container cont) {
346 int w = rowBounds.width;
347 int h = rowBounds.height;
348 BufferedImage bi = window.bi;
349 if (w > 0 && h > 0) {
350 window.getContentPane().add(label);
351 window.validate();
352 if (bi == null || bi.getWidth() < w || bi.getHeight() != h) {
353 /*BufferedImage */bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
354 }
355 }
356 Graphics2D g = bi.createGraphics();
357 g.setColor(label.getBackground());
358 g.fillRect(0, 0, bi.getWidth(), bi.getHeight());
359 lb.paintBorder(label, g, bi.getWidth() - w, 0, w, h);
360 g.setClip(1, 1, bi.getWidth() - 2, bi.getHeight() - 2);
361 window.pane.paintComponent(g, label, cont, bi.getWidth() - w, 0, w, h, true);
362 g.dispose();
363
364 window.setImage(bi);
365 }
366
367 public void mouseExited(MouseEvent e) {
368 Rectangle vr = computeVisibleRect();
369 if (vr.contains(e.getPoint())) {
370 return;
371 }
372 hideTipWindow();
373 }
374 }
375
376 /**
377 * Optimized computing of visible rectangle (without creating new Rectangle Object every time)
378 * @return visible rectangle
379 */
380 protected Rectangle computeVisibleRect() {
381 owner.computeVisibleRect(visibleRect);
382 return visibleRect;
383 }
384
385 /**
386 * hide TipWindow
387 */
388 void hideTipWindow() {
389 getTipWindow().setVisible(false);
390 }
391
392 /**
393 * MouseListener for TLToolTipManager
394 */
395 protected class TipMouseHandler extends MouseInputAdapter {
396
397 public void mouseExited(MouseEvent e) {
398 hideTipWindow();
399 resetRow();
400 }
401
402 public void mousePressed(MouseEvent e) {
403 resetRow();
404 Point p = e.getPoint();
405 Point nps = new Point(p);
406
407 SwingUtilities.convertPointToScreen(nps, tip);
408 Point np = new Point(nps);
409
410 SwingUtilities.convertPointFromScreen(np, owner);
411
412 hideTipWindow();
413
414 MouseEvent ne = new MouseEvent(owner, e.getID(), e.getWhen(), e.getModifiers(),
415 np.x, np.y, e.getClickCount(), e.isPopupTrigger()
416 );
417 computeVisibleRect();
418 if (visibleRect.contains(np)) {
419 owner.dispatchEvent(ne);
420 }
421 }
422
423 public void mouseMoved(MouseEvent e) {
424 Point p = e.getPoint();
425 Rectangle vr = computeVisibleRect();
426 Point rp = new Point(vr.x, vr.y);
427 SwingUtilities.convertPointToScreen(rp, owner);
428 SwingUtilities.convertPointToScreen(p, tip);
429 vr = new Rectangle(rp.x, rp.y, vr.width, vr.height);
430 if (!vr.contains(p)) {
431 hideTipWindow();
432 resetRow();
433 }
434 }
435 }
436
437 /**
438 * OwnerListener hides TipWindow if Component is resized, moved or made invisible.
439 */
440 private class OwnerListener extends ComponentAdapter {
441 public void componentResized(ComponentEvent e) {
442 hideTipWindow();
443 }
444
445 public void componentMoved(ComponentEvent e) {
446 hideTipWindow();
447 }
448
449 public void componentHidden(ComponentEvent e) {
450 hideTipWindow();
451 }
452 }
453 }