1 /* ToolTipManager.java --
2 Copyright (C) 2002, 2004 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
40 import java.awt.AWTEvent;
41 import java.awt.Component;
42 import java.awt.Container;
43 import java.awt.Dimension;
44 import java.awt.FlowLayout;
45 import java.awt.Insets;
46 import java.awt.LayoutManager;
47 import java.awt.Panel;
48 import java.awt.Point;
49 import java.awt.Rectangle;
50 import java.awt.event.*;
51 import java.awt.event.ActionEvent;
52 import java.awt.event.ActionListener;
53 import java.awt.event.MouseAdapter;
54 import java.awt.event.MouseEvent;
55 import java.awt.event.MouseMotionListener;
56 import javax.swing.JComponent;
57 import javax.swing.Popup;
58 import javax.swing.PopupFactory;
59 import javax.swing.SwingUtilities;
60 import javax.swing.Timer;
64 * This class is responsible for the registration of JToolTips to Components
65 * and for displaying them when appropriate.
67 public class ToolTipManager extends MouseAdapter implements MouseMotionListener
70 * This ActionListener is associated with the Timer that listens to whether
71 * the JToolTip can be hidden after four seconds.
73 protected class stillInsideTimerAction implements ActionListener
76 * This method creates a new stillInsideTimerAction object.
78 protected stillInsideTimerAction()
83 * This method hides the JToolTip when the Timer has finished.
85 * @param event The ActionEvent.
87 public void actionPerformed(ActionEvent event)
94 * This Actionlistener is associated with the Timer that listens to whether
95 * the mouse cursor has re-entered the JComponent in time for an immediate
96 * redisplay of the JToolTip.
98 protected class outsideTimerAction implements ActionListener
101 * This method creates a new outsideTimerAction object.
103 protected outsideTimerAction()
108 * This method is called when the Timer that listens to whether the mouse
109 * cursor has re-entered the JComponent has run out.
111 * @param event The ActionEvent.
113 public void actionPerformed(ActionEvent event)
119 * This ActionListener is associated with the Timer that listens to whether
120 * it is time for the JToolTip to be displayed after the mouse has entered
123 protected class insideTimerAction implements ActionListener
126 * This method creates a new insideTimerAction object.
128 protected insideTimerAction()
133 * This method displays the JToolTip when the Mouse has been still for the
136 * @param event The ActionEvent.
138 public void actionPerformed(ActionEvent event)
141 if (insideTimer != null)
147 * The Timer that determines whether the Mouse has been still long enough
148 * for the JToolTip to be displayed.
153 * The Timer that determines whether the Mouse has re-entered the JComponent
154 * quickly enough for the JToolTip to be displayed immediately.
159 * The Timer that determines whether the JToolTip has been displayed long
160 * enough for it to be hidden.
164 /** A global enabled setting for the ToolTipManager. */
165 private transient boolean enabled = true;
167 /** lightWeightPopupEnabled */
168 protected boolean lightWeightPopupEnabled = true;
170 /** heavyWeightPopupEnabled */
171 protected boolean heavyWeightPopupEnabled = false;
173 /** The shared instance of the ToolTipManager. */
174 private static ToolTipManager shared;
176 /** The current component the tooltip is being displayed for. */
177 private static Component currentComponent;
179 /** The current tooltip. */
180 private static JToolTip currentTip;
182 /** The last known position of the mouse cursor. */
183 private static Point currentPoint;
186 * The panel that holds the tooltip when the tooltip is displayed fully
187 * inside the current container.
189 private static Container containerPanel;
192 * The window used when the tooltip doesn't fit inside the current
195 private static JWindow tooltipWindow;
198 * Creates a new ToolTipManager and sets up the timers.
202 enterTimer = new Timer(750, new insideTimerAction());
203 enterTimer.setRepeats(false);
205 insideTimer = new Timer(4000, new stillInsideTimerAction());
206 insideTimer.setRepeats(false);
208 exitTimer = new Timer(500, new outsideTimerAction());
209 exitTimer.setRepeats(false);
213 * This method returns the shared instance of ToolTipManager used by all
216 * @return The shared instance of ToolTipManager.
218 public static ToolTipManager sharedInstance()
221 shared = new ToolTipManager();
227 * This method sets whether ToolTips are enabled or disabled for all
230 * @param enabled Whether ToolTips are enabled or disabled for all
233 public void setEnabled(boolean enabled)
242 this.enabled = enabled;
246 * This method returns whether ToolTips are enabled.
248 * @return Whether ToolTips are enabled.
250 public boolean isEnabled()
256 * This method returns whether LightweightToolTips are enabled.
258 * @return Whether LighweightToolTips are enabled.
260 public boolean isLightWeightPopupEnabled()
262 return lightWeightPopupEnabled;
266 * This method sets whether LightweightToolTips are enabled. If you mix
267 * Lightweight and Heavyweight components, you must set this to false to
268 * ensure that the ToolTips popup above all other components.
270 * @param enabled Whether LightweightToolTips will be enabled.
272 public void setLightWeightPopupEnabled(boolean enabled)
274 lightWeightPopupEnabled = enabled;
275 heavyWeightPopupEnabled = ! enabled;
279 * This method returns the initial delay before the ToolTip is shown when
280 * the mouse enters a Component.
282 * @return The initial delay before the ToolTip is shown.
284 public int getInitialDelay()
286 return enterTimer.getDelay();
290 * This method sets the initial delay before the ToolTip is shown when the
291 * mouse enters a Component.
293 * @param delay The initial delay before the ToolTip is shown.
295 public void setInitialDelay(int delay)
297 enterTimer.setDelay(delay);
301 * This method returns the time the ToolTip will be shown before being
304 * @return The time the ToolTip will be shown before being hidden.
306 public int getDismissDelay()
308 return insideTimer.getDelay();
312 * This method sets the time the ToolTip will be shown before being hidden.
314 * @param delay The time the ToolTip will be shown before being hidden.
316 public void setDismissDelay(int delay)
318 insideTimer.setDelay(delay);
322 * This method returns the amount of delay where if the mouse re-enters a
323 * Component, the tooltip will be shown immediately.
325 * @return The reshow delay.
327 public int getReshowDelay()
329 return exitTimer.getDelay();
333 * This method sets the amount of delay where if the mouse re-enters a
334 * Component, the tooltip will be shown immediately.
336 * @param delay The reshow delay.
338 public void setReshowDelay(int delay)
340 exitTimer.setDelay(delay);
344 * This method registers a JComponent with the ToolTipManager.
346 * @param component The JComponent to register with the ToolTipManager.
348 public void registerComponent(JComponent component)
350 component.addMouseListener(this);
351 component.addMouseMotionListener(this);
355 * This method unregisters a JComponent with the ToolTipManager.
357 * @param component The JComponent to unregister with the ToolTipManager.
359 public void unregisterComponent(JComponent component)
361 component.removeMouseMotionListener(this);
362 component.removeMouseListener(this);
366 * This method is called whenever the mouse enters a JComponent registered
367 * with the ToolTipManager. When the mouse enters within the period of time
368 * specified by the reshow delay, the tooltip will be displayed
369 * immediately. Otherwise, it must wait for the initial delay before
370 * displaying the tooltip.
372 * @param event The MouseEvent.
374 public void mouseEntered(MouseEvent event)
376 if (currentComponent != null
377 && getContentPaneDeepestComponent(event) == currentComponent)
379 currentPoint = event.getPoint();
380 currentComponent = (Component) event.getSource();
382 if (exitTimer.isRunning())
390 // This should always be stopped unless we have just fake-exited.
391 if (! enterTimer.isRunning())
396 * This method is called when the mouse exits a JComponent registered with
397 * the ToolTipManager. When the mouse exits, the tooltip should be hidden
400 * @param event The MouseEvent.
402 public void mouseExited(MouseEvent event)
404 if (getContentPaneDeepestComponent(event) == currentComponent)
407 currentPoint = event.getPoint();
408 currentComponent = null;
411 if (! enterTimer.isRunning() && insideTimer.isRunning())
413 if (enterTimer.isRunning())
415 if (insideTimer.isRunning())
420 * This method is called when the mouse is pressed on a JComponent
421 * registered with the ToolTipManager. When the mouse is pressed, the
422 * tooltip (if it is shown) must be hidden immediately.
424 * @param event The MouseEvent.
426 public void mousePressed(MouseEvent event)
428 currentPoint = event.getPoint();
429 if (enterTimer.isRunning())
430 enterTimer.restart();
431 else if (insideTimer.isRunning())
436 currentComponent.invalidate();
437 currentComponent.validate();
438 currentComponent.repaint();
442 * This method is called when the mouse is dragged in a JComponent
443 * registered with the ToolTipManager.
445 * @param event The MouseEvent.
447 public void mouseDragged(MouseEvent event)
449 currentPoint = event.getPoint();
450 if (enterTimer.isRunning())
451 enterTimer.restart();
455 * This method is called when the mouse is moved in a JComponent registered
456 * with the ToolTipManager.
458 * @param event The MouseEvent.
460 public void mouseMoved(MouseEvent event)
462 currentPoint = event.getPoint();
463 if (currentTip != null)
464 currentTip.setTipText(((JComponent) currentComponent).getToolTipText(event));
465 if (enterTimer.isRunning())
466 enterTimer.restart();
470 * This method displays the ToolTip. It can figure out the method needed to
471 * show it as well (whether to display it in heavyweight/lightweight panel
474 private void showTip()
479 if (currentTip == null
480 || currentTip.getComponent() != currentComponent
481 && currentComponent instanceof JComponent)
482 currentTip = ((JComponent) currentComponent).createToolTip();
483 Point p = currentPoint;
484 Dimension dims = currentTip.getPreferredSize();
485 if (canToolTipFit(currentTip))
487 JLayeredPane pane = ((JRootPane) SwingUtilities.getAncestorOfClass(JRootPane.class,
491 // This should never happen, but just in case.
495 if (containerPanel != null)
497 if (isLightWeightPopupEnabled())
499 containerPanel = new Panel();
500 JRootPane root = new JRootPane();
501 root.getContentPane().add(currentTip);
502 containerPanel.add(root);
506 containerPanel = new JPanel();
507 containerPanel.add(currentTip);
509 LayoutManager lm = containerPanel.getLayout();
510 if (lm instanceof FlowLayout)
512 FlowLayout fm = (FlowLayout) lm;
517 p = getGoodPoint(p, pane, currentTip, dims);
519 pane.add(containerPanel);
520 containerPanel.setBounds(p.x, p.y, dims.width, dims.height);
521 currentTip.setBounds(0, 0, dims.width, dims.height);
528 SwingUtilities.convertPointToScreen(p, currentComponent);
529 tooltipWindow = new JWindow();
530 tooltipWindow.getContentPane().add(currentTip);
531 tooltipWindow.setFocusable(false);
532 tooltipWindow.pack();
533 tooltipWindow.setBounds(p.x, p.y, dims.width, dims.height);
534 tooltipWindow.show();
536 currentTip.setVisible(true);
540 * This method hides the ToolTip.
542 private void hideTip()
544 if (currentTip == null || ! currentTip.isVisible() || ! enabled)
546 currentTip.setVisible(false);
547 if (containerPanel != null)
549 Container parent = containerPanel.getParent();
552 parent.remove(containerPanel);
557 parent = currentTip.getParent();
560 parent.remove(currentTip);
562 containerPanel = null;
564 if (tooltipWindow != null)
566 tooltipWindow.hide();
567 tooltipWindow.dispose();
568 tooltipWindow = null;
573 * This method returns a point in the LayeredPane where the ToolTip can be
574 * shown. The point returned (if the ToolTip is to be displayed at the
575 * preferred dimensions) will always place the ToolTip inside the
576 * currentComponent if possible.
578 * @param p The last known good point for the mouse.
579 * @param c The JLayeredPane in the first RootPaneContainer up from the
581 * @param tip The ToolTip to display.
582 * @param dims The ToolTip preferred dimensions (can be null).
584 * @return A good point to place the ToolTip.
586 private Point getGoodPoint(Point p, JLayeredPane c, JToolTip tip,
590 dims = tip.getPreferredSize();
591 Rectangle bounds = currentComponent.getBounds();
592 if (p.x + dims.width > bounds.width)
593 p.x = bounds.width - dims.width;
594 if (p.y + dims.height > bounds.height)
595 p.y = bounds.height - dims.height;
597 p = SwingUtilities.convertPoint(currentComponent, p, c);
602 * This method returns the deepest component in the content pane for the
603 * first RootPaneContainer up from the currentComponent. This method is
604 * used in conjunction with one of the mouseXXX methods.
606 * @param e The MouseEvent.
608 * @return The deepest component in the content pane.
610 private Component getContentPaneDeepestComponent(MouseEvent e)
612 Component source = (Component) e.getSource();
613 Container parent = (Container) SwingUtilities.getAncestorOfClass(JRootPane.class,
617 parent = ((JRootPane) parent).getContentPane();
618 Point p = e.getPoint();
619 p = SwingUtilities.convertPoint(source, p, parent);
620 Component target = SwingUtilities.getDeepestComponentAt(parent, p.x, p.y);
625 * This method returns whether the ToolTip can fit in the first
626 * RootPaneContainer up from the currentComponent.
628 * @param tip The ToolTip.
630 * @return Whether the ToolTip can fit.
632 private boolean canToolTipFit(JToolTip tip)
634 JRootPane root = (JRootPane) SwingUtilities.getAncestorOfClass(JRootPane.class,
638 Dimension pref = tip.getPreferredSize();
639 Dimension rootSize = root.getSize();
640 if (rootSize.width > pref.width && rootSize.height > pref.height)