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.Component;
41 import java.awt.Container;
42 import java.awt.Dimension;
43 import java.awt.FlowLayout;
44 import java.awt.LayoutManager;
45 import java.awt.Panel;
46 import java.awt.Point;
47 import java.awt.Rectangle;
48 import java.awt.event.ActionEvent;
49 import java.awt.event.ActionListener;
50 import java.awt.event.MouseAdapter;
51 import java.awt.event.MouseEvent;
52 import java.awt.event.MouseMotionListener;
55 * This class is responsible for the registration of JToolTips to Components
56 * and for displaying them when appropriate.
58 public class ToolTipManager extends MouseAdapter implements MouseMotionListener
61 * This ActionListener is associated with the Timer that listens to whether
62 * the JToolTip can be hidden after four seconds.
64 protected class stillInsideTimerAction implements ActionListener
67 * This method creates a new stillInsideTimerAction object.
69 protected stillInsideTimerAction()
74 * This method hides the JToolTip when the Timer has finished.
76 * @param event The ActionEvent.
78 public void actionPerformed(ActionEvent event)
85 * This Actionlistener is associated with the Timer that listens to whether
86 * the mouse cursor has re-entered the JComponent in time for an immediate
87 * redisplay of the JToolTip.
89 protected class outsideTimerAction implements ActionListener
92 * This method creates a new outsideTimerAction object.
94 protected outsideTimerAction()
99 * This method is called when the Timer that listens to whether the mouse
100 * cursor has re-entered the JComponent has run out.
102 * @param event The ActionEvent.
104 public void actionPerformed(ActionEvent event)
110 * This ActionListener is associated with the Timer that listens to whether
111 * it is time for the JToolTip to be displayed after the mouse has entered
114 protected class insideTimerAction implements ActionListener
117 * This method creates a new insideTimerAction object.
119 protected insideTimerAction()
124 * This method displays the JToolTip when the Mouse has been still for the
127 * @param event The ActionEvent.
129 public void actionPerformed(ActionEvent event)
132 if (insideTimer != null)
138 * The Timer that determines whether the Mouse has been still long enough
139 * for the JToolTip to be displayed.
144 * The Timer that determines whether the Mouse has re-entered the JComponent
145 * quickly enough for the JToolTip to be displayed immediately.
150 * The Timer that determines whether the JToolTip has been displayed long
151 * enough for it to be hidden.
155 /** A global enabled setting for the ToolTipManager. */
156 private transient boolean enabled = true;
158 /** lightWeightPopupEnabled */
159 protected boolean lightWeightPopupEnabled = true;
161 /** heavyWeightPopupEnabled */
162 protected boolean heavyWeightPopupEnabled = false;
164 /** The shared instance of the ToolTipManager. */
165 private static ToolTipManager shared;
167 /** The current component the tooltip is being displayed for. */
168 private static Component currentComponent;
170 /** The current tooltip. */
171 private static JToolTip currentTip;
173 /** The last known position of the mouse cursor. */
174 private static Point currentPoint;
177 * The panel that holds the tooltip when the tooltip is displayed fully
178 * inside the current container.
180 private static Container containerPanel;
183 * The window used when the tooltip doesn't fit inside the current
186 private static JWindow tooltipWindow;
189 * Creates a new ToolTipManager and sets up the timers.
193 enterTimer = new Timer(750, new insideTimerAction());
194 enterTimer.setRepeats(false);
196 insideTimer = new Timer(4000, new stillInsideTimerAction());
197 insideTimer.setRepeats(false);
199 exitTimer = new Timer(500, new outsideTimerAction());
200 exitTimer.setRepeats(false);
204 * This method returns the shared instance of ToolTipManager used by all
207 * @return The shared instance of ToolTipManager.
209 public static ToolTipManager sharedInstance()
212 shared = new ToolTipManager();
218 * This method sets whether ToolTips are enabled or disabled for all
221 * @param enabled Whether ToolTips are enabled or disabled for all
224 public void setEnabled(boolean enabled)
233 this.enabled = enabled;
237 * This method returns whether ToolTips are enabled.
239 * @return Whether ToolTips are enabled.
241 public boolean isEnabled()
247 * This method returns whether LightweightToolTips are enabled.
249 * @return Whether LighweightToolTips are enabled.
251 public boolean isLightWeightPopupEnabled()
253 return lightWeightPopupEnabled;
257 * This method sets whether LightweightToolTips are enabled. If you mix
258 * Lightweight and Heavyweight components, you must set this to false to
259 * ensure that the ToolTips popup above all other components.
261 * @param enabled Whether LightweightToolTips will be enabled.
263 public void setLightWeightPopupEnabled(boolean enabled)
265 lightWeightPopupEnabled = enabled;
266 heavyWeightPopupEnabled = ! enabled;
270 * This method returns the initial delay before the ToolTip is shown when
271 * the mouse enters a Component.
273 * @return The initial delay before the ToolTip is shown.
275 public int getInitialDelay()
277 return enterTimer.getDelay();
281 * This method sets the initial delay before the ToolTip is shown when the
282 * mouse enters a Component.
284 * @param delay The initial delay before the ToolTip is shown.
286 public void setInitialDelay(int delay)
288 enterTimer.setDelay(delay);
292 * This method returns the time the ToolTip will be shown before being
295 * @return The time the ToolTip will be shown before being hidden.
297 public int getDismissDelay()
299 return insideTimer.getDelay();
303 * This method sets the time the ToolTip will be shown before being hidden.
305 * @param delay The time the ToolTip will be shown before being hidden.
307 public void setDismissDelay(int delay)
309 insideTimer.setDelay(delay);
313 * This method returns the amount of delay where if the mouse re-enters a
314 * Component, the tooltip will be shown immediately.
316 * @return The reshow delay.
318 public int getReshowDelay()
320 return exitTimer.getDelay();
324 * This method sets the amount of delay where if the mouse re-enters a
325 * Component, the tooltip will be shown immediately.
327 * @param delay The reshow delay.
329 public void setReshowDelay(int delay)
331 exitTimer.setDelay(delay);
335 * This method registers a JComponent with the ToolTipManager.
337 * @param component The JComponent to register with the ToolTipManager.
339 public void registerComponent(JComponent component)
341 component.addMouseListener(this);
342 component.addMouseMotionListener(this);
346 * This method unregisters a JComponent with the ToolTipManager.
348 * @param component The JComponent to unregister with the ToolTipManager.
350 public void unregisterComponent(JComponent component)
352 component.removeMouseMotionListener(this);
353 component.removeMouseListener(this);
357 * This method is called whenever the mouse enters a JComponent registered
358 * with the ToolTipManager. When the mouse enters within the period of time
359 * specified by the reshow delay, the tooltip will be displayed
360 * immediately. Otherwise, it must wait for the initial delay before
361 * displaying the tooltip.
363 * @param event The MouseEvent.
365 public void mouseEntered(MouseEvent event)
367 if (currentComponent != null
368 && getContentPaneDeepestComponent(event) == currentComponent)
370 currentPoint = event.getPoint();
371 currentComponent = (Component) event.getSource();
373 if (exitTimer.isRunning())
381 // This should always be stopped unless we have just fake-exited.
382 if (! enterTimer.isRunning())
387 * This method is called when the mouse exits a JComponent registered with
388 * the ToolTipManager. When the mouse exits, the tooltip should be hidden
391 * @param event The MouseEvent.
393 public void mouseExited(MouseEvent event)
395 if (getContentPaneDeepestComponent(event) == currentComponent)
398 currentPoint = event.getPoint();
399 currentComponent = null;
402 if (! enterTimer.isRunning() && insideTimer.isRunning())
404 if (enterTimer.isRunning())
406 if (insideTimer.isRunning())
411 * This method is called when the mouse is pressed on a JComponent
412 * registered with the ToolTipManager. When the mouse is pressed, the
413 * tooltip (if it is shown) must be hidden immediately.
415 * @param event The MouseEvent.
417 public void mousePressed(MouseEvent event)
419 currentPoint = event.getPoint();
420 if (enterTimer.isRunning())
421 enterTimer.restart();
422 else if (insideTimer.isRunning())
427 currentComponent.invalidate();
428 currentComponent.validate();
429 currentComponent.repaint();
433 * This method is called when the mouse is dragged in a JComponent
434 * registered with the ToolTipManager.
436 * @param event The MouseEvent.
438 public void mouseDragged(MouseEvent event)
440 currentPoint = event.getPoint();
441 if (enterTimer.isRunning())
442 enterTimer.restart();
446 * This method is called when the mouse is moved in a JComponent registered
447 * with the ToolTipManager.
449 * @param event The MouseEvent.
451 public void mouseMoved(MouseEvent event)
453 currentPoint = event.getPoint();
454 if (currentTip != null)
456 if (currentComponent == null)
457 currentComponent = (Component) event.getSource();
459 String text = ((JComponent) currentComponent).getToolTipText(event);
460 currentTip.setTipText(text);
462 if (enterTimer.isRunning())
463 enterTimer.restart();
467 * This method displays the ToolTip. It can figure out the method needed to
468 * show it as well (whether to display it in heavyweight/lightweight panel
471 private void showTip()
473 if (! enabled || currentComponent == null)
476 if (currentTip == null
477 || currentTip.getComponent() != currentComponent
478 && currentComponent instanceof JComponent)
479 currentTip = ((JComponent) currentComponent).createToolTip();
480 Point p = currentPoint;
481 Dimension dims = currentTip.getPreferredSize();
482 if (canToolTipFit(currentTip))
484 JLayeredPane pane = ((JRootPane) SwingUtilities.getAncestorOfClass(JRootPane.class,
488 // This should never happen, but just in case.
492 if (containerPanel != null)
494 if (isLightWeightPopupEnabled())
496 containerPanel = new Panel();
497 JRootPane root = new JRootPane();
498 root.getContentPane().add(currentTip);
499 containerPanel.add(root);
503 containerPanel = new JPanel();
504 containerPanel.add(currentTip);
506 LayoutManager lm = containerPanel.getLayout();
507 if (lm instanceof FlowLayout)
509 FlowLayout fm = (FlowLayout) lm;
514 p = getGoodPoint(p, pane, currentTip, dims);
516 pane.add(containerPanel);
517 containerPanel.setBounds(p.x, p.y, dims.width, dims.height);
518 currentTip.setBounds(0, 0, dims.width, dims.height);
525 SwingUtilities.convertPointToScreen(p, currentComponent);
526 tooltipWindow = new JWindow();
527 tooltipWindow.getContentPane().add(currentTip);
528 tooltipWindow.setFocusable(false);
529 tooltipWindow.pack();
530 tooltipWindow.setBounds(p.x, p.y, dims.width, dims.height);
531 tooltipWindow.show();
533 currentTip.setVisible(true);
537 * This method hides the ToolTip.
539 private void hideTip()
541 if (currentTip == null || ! currentTip.isVisible() || ! enabled)
543 currentTip.setVisible(false);
544 if (containerPanel != null)
546 Container parent = containerPanel.getParent();
549 parent.remove(containerPanel);
554 parent = currentTip.getParent();
557 parent.remove(currentTip);
559 containerPanel = null;
561 if (tooltipWindow != null)
563 tooltipWindow.hide();
564 tooltipWindow.dispose();
565 tooltipWindow = null;
570 * This method returns a point in the LayeredPane where the ToolTip can be
571 * shown. The point returned (if the ToolTip is to be displayed at the
572 * preferred dimensions) will always place the ToolTip inside the
573 * currentComponent if possible.
575 * @param p The last known good point for the mouse.
576 * @param c The JLayeredPane in the first RootPaneContainer up from the
578 * @param tip The ToolTip to display.
579 * @param dims The ToolTip preferred dimensions (can be null).
581 * @return A good point to place the ToolTip.
583 private Point getGoodPoint(Point p, JLayeredPane c, JToolTip tip,
587 dims = tip.getPreferredSize();
588 Rectangle bounds = currentComponent.getBounds();
589 if (p.x + dims.width > bounds.width)
590 p.x = bounds.width - dims.width;
591 if (p.y + dims.height > bounds.height)
592 p.y = bounds.height - dims.height;
594 p = SwingUtilities.convertPoint(currentComponent, p, c);
599 * This method returns the deepest component in the content pane for the
600 * first RootPaneContainer up from the currentComponent. This method is
601 * used in conjunction with one of the mouseXXX methods.
603 * @param e The MouseEvent.
605 * @return The deepest component in the content pane.
607 private Component getContentPaneDeepestComponent(MouseEvent e)
609 Component source = (Component) e.getSource();
610 Container parent = (Container) SwingUtilities.getAncestorOfClass(JRootPane.class,
614 parent = ((JRootPane) parent).getContentPane();
615 Point p = e.getPoint();
616 p = SwingUtilities.convertPoint(source, p, parent);
617 Component target = SwingUtilities.getDeepestComponentAt(parent, p.x, p.y);
622 * This method returns whether the ToolTip can fit in the first
623 * RootPaneContainer up from the currentComponent.
625 * @param tip The ToolTip.
627 * @return Whether the ToolTip can fit.
629 private boolean canToolTipFit(JToolTip tip)
631 JRootPane root = (JRootPane) SwingUtilities.getAncestorOfClass(JRootPane.class,
635 Dimension pref = tip.getPreferredSize();
636 Dimension rootSize = root.getSize();
637 if (rootSize.width > pref.width && rootSize.height > pref.height)