OSDN Git Service

6245786afa3c7f26b0896899b48daca3f16a2f68
[pf3gnuchains/gcc-fork.git] / libjava / javax / swing / ToolTipManager.java
1 /* ToolTipManager.java --
2    Copyright (C) 2002, 2004 Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
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)
9 any later version.
10
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.
15
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
19 02111-1307 USA.
20
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
24 combination.
25
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. */
37
38 package javax.swing;
39
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;
53
54 /**
55  * This class is responsible for the registration of JToolTips to Components
56  * and for displaying them when appropriate.
57  */
58 public class ToolTipManager extends MouseAdapter implements MouseMotionListener
59 {
60   /**
61    * This ActionListener is associated with the Timer that listens to whether
62    * the JToolTip can be hidden after four seconds.
63    */
64   protected class stillInsideTimerAction implements ActionListener
65   {
66     /**
67      * This method creates a new stillInsideTimerAction object.
68      */
69     protected stillInsideTimerAction()
70     {
71     }
72
73     /**
74      * This method hides the JToolTip when the Timer has finished.
75      *
76      * @param event The ActionEvent.
77      */
78     public void actionPerformed(ActionEvent event)
79     {
80       hideTip();
81     }
82   }
83
84   /**
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.
88    */
89   protected class outsideTimerAction implements ActionListener
90   {
91     /**
92      * This method creates a new outsideTimerAction object.
93      */
94     protected outsideTimerAction()
95     {
96     }
97
98     /**
99      * This method is called when the Timer that listens to whether the mouse
100      * cursor has re-entered the JComponent has run out.
101      *
102      * @param event The ActionEvent.
103      */
104     public void actionPerformed(ActionEvent event)
105     {
106     }
107   }
108
109   /**
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
112    * the JComponent.
113    */
114   protected class insideTimerAction implements ActionListener
115   {
116     /**
117      * This method creates a new insideTimerAction object.
118      */
119     protected insideTimerAction()
120     {
121     }
122
123     /**
124      * This method displays the JToolTip when the Mouse has been still for the
125      * delay.
126      *
127      * @param event The ActionEvent.
128      */
129     public void actionPerformed(ActionEvent event)
130     {
131       showTip();
132       if (insideTimer != null)
133         insideTimer.start();
134     }
135   }
136
137   /**
138    * The Timer that determines whether the Mouse has been still long enough
139    * for the JToolTip to be displayed.
140    */
141   Timer enterTimer;
142
143   /**
144    * The Timer that determines whether the Mouse has re-entered the JComponent
145    * quickly enough for the JToolTip to be displayed immediately.
146    */
147   Timer exitTimer;
148
149   /**
150    * The Timer that determines whether the JToolTip has been displayed long
151    * enough for it to be hidden.
152    */
153   Timer insideTimer;
154
155   /** A global enabled setting for the ToolTipManager. */
156   private transient boolean enabled = true;
157
158   /** lightWeightPopupEnabled */
159   protected boolean lightWeightPopupEnabled = true;
160
161   /** heavyWeightPopupEnabled */
162   protected boolean heavyWeightPopupEnabled = false;
163
164   /** The shared instance of the ToolTipManager. */
165   private static ToolTipManager shared;
166
167   /** The current component the tooltip is being displayed for. */
168   private static Component currentComponent;
169
170   /** The current tooltip. */
171   private static JToolTip currentTip;
172
173   /** The last known position of the mouse cursor. */
174   private static Point currentPoint;
175
176   /**
177    * The panel that holds the tooltip when the tooltip is displayed fully
178    * inside the current container.
179    */
180   private static Container containerPanel;
181
182   /**
183    * The window used when the tooltip doesn't fit inside the current
184    * container.
185    */
186   private static JWindow tooltipWindow;
187
188   /**
189    * Creates a new ToolTipManager and sets up the timers.
190    */
191   ToolTipManager()
192   {
193     enterTimer = new Timer(750, new insideTimerAction());
194     enterTimer.setRepeats(false);
195
196     insideTimer = new Timer(4000, new stillInsideTimerAction());
197     insideTimer.setRepeats(false);
198
199     exitTimer = new Timer(500, new outsideTimerAction());
200     exitTimer.setRepeats(false);
201   }
202
203   /**
204    * This method returns the shared instance of ToolTipManager used by all
205    * JComponents.
206    *
207    * @return The shared instance of ToolTipManager.
208    */
209   public static ToolTipManager sharedInstance()
210   {
211     if (shared == null)
212       shared = new ToolTipManager();
213
214     return shared;
215   }
216
217   /**
218    * This method sets whether ToolTips are enabled or disabled for all
219    * JComponents.
220    *
221    * @param enabled Whether ToolTips are enabled or disabled for all
222    *        JComponents.
223    */
224   public void setEnabled(boolean enabled)
225   {
226     if (! enabled)
227       {
228         enterTimer.stop();
229         exitTimer.stop();
230         insideTimer.stop();
231       }
232
233     this.enabled = enabled;
234   }
235
236   /**
237    * This method returns whether ToolTips are enabled.
238    *
239    * @return Whether ToolTips are enabled.
240    */
241   public boolean isEnabled()
242   {
243     return enabled;
244   }
245
246   /**
247    * This method returns whether LightweightToolTips are enabled.
248    *
249    * @return Whether LighweightToolTips are enabled.
250    */
251   public boolean isLightWeightPopupEnabled()
252   {
253     return lightWeightPopupEnabled;
254   }
255
256   /**
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.
260    *
261    * @param enabled Whether LightweightToolTips will be enabled.
262    */
263   public void setLightWeightPopupEnabled(boolean enabled)
264   {
265     lightWeightPopupEnabled = enabled;
266     heavyWeightPopupEnabled = ! enabled;
267   }
268
269   /**
270    * This method returns the initial delay before the ToolTip is shown when
271    * the mouse enters a Component.
272    *
273    * @return The initial delay before the ToolTip is shown.
274    */
275   public int getInitialDelay()
276   {
277     return enterTimer.getDelay();
278   }
279
280   /**
281    * This method sets the initial delay before the ToolTip is shown when the
282    * mouse enters a Component.
283    *
284    * @param delay The initial delay before the ToolTip is shown.
285    */
286   public void setInitialDelay(int delay)
287   {
288     enterTimer.setDelay(delay);
289   }
290
291   /**
292    * This method returns the time the ToolTip will be shown before being
293    * hidden.
294    *
295    * @return The time the ToolTip will be shown before being hidden.
296    */
297   public int getDismissDelay()
298   {
299     return insideTimer.getDelay();
300   }
301
302   /**
303    * This method sets the time the ToolTip will be shown before being hidden.
304    *
305    * @param delay The time the ToolTip will be shown before being hidden.
306    */
307   public void setDismissDelay(int delay)
308   {
309     insideTimer.setDelay(delay);
310   }
311
312   /**
313    * This method returns the amount of delay where if the mouse re-enters a
314    * Component, the tooltip will be shown immediately.
315    *
316    * @return The reshow delay.
317    */
318   public int getReshowDelay()
319   {
320     return exitTimer.getDelay();
321   }
322
323   /**
324    * This method sets the amount of delay where if the mouse re-enters a
325    * Component, the tooltip will be shown immediately.
326    *
327    * @param delay The reshow delay.
328    */
329   public void setReshowDelay(int delay)
330   {
331     exitTimer.setDelay(delay);
332   }
333
334   /**
335    * This method registers a JComponent with the ToolTipManager.
336    *
337    * @param component The JComponent to register with the ToolTipManager.
338    */
339   public void registerComponent(JComponent component)
340   {
341     component.addMouseListener(this);
342     component.addMouseMotionListener(this);
343   }
344
345   /**
346    * This method unregisters a JComponent with the ToolTipManager.
347    *
348    * @param component The JComponent to unregister with the ToolTipManager.
349    */
350   public void unregisterComponent(JComponent component)
351   {
352     component.removeMouseMotionListener(this);
353     component.removeMouseListener(this);
354   }
355
356   /**
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.
362    *
363    * @param event The MouseEvent.
364    */
365   public void mouseEntered(MouseEvent event)
366   {
367     if (currentComponent != null
368         && getContentPaneDeepestComponent(event) == currentComponent)
369       return;
370     currentPoint = event.getPoint();
371     currentComponent = (Component) event.getSource();
372
373     if (exitTimer.isRunning())
374       {
375         exitTimer.stop();
376         showTip();
377         insideTimer.start();
378         return;
379       }
380
381     // This should always be stopped unless we have just fake-exited.
382     if (! enterTimer.isRunning())
383       enterTimer.start();
384   }
385
386   /**
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
389    * immediately.
390    *
391    * @param event The MouseEvent.
392    */
393   public void mouseExited(MouseEvent event)
394   {
395     if (getContentPaneDeepestComponent(event) == currentComponent)
396       return;
397
398     currentPoint = event.getPoint();
399     currentComponent = null;
400     hideTip();
401
402     if (! enterTimer.isRunning() && insideTimer.isRunning())
403       exitTimer.start();
404     if (enterTimer.isRunning())
405       enterTimer.stop();
406     if (insideTimer.isRunning())
407       insideTimer.stop();
408   }
409
410   /**
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.
414    *
415    * @param event The MouseEvent.
416    */
417   public void mousePressed(MouseEvent event)
418   {
419     currentPoint = event.getPoint();
420     if (enterTimer.isRunning())
421       enterTimer.restart();
422     else if (insideTimer.isRunning())
423       {
424         insideTimer.stop();
425         hideTip();
426       }
427     currentComponent.invalidate();
428     currentComponent.validate();
429     currentComponent.repaint();
430   }
431
432   /**
433    * This method is called when the mouse is dragged in a JComponent
434    * registered with the ToolTipManager.
435    *
436    * @param event The MouseEvent.
437    */
438   public void mouseDragged(MouseEvent event)
439   {
440     currentPoint = event.getPoint();
441     if (enterTimer.isRunning())
442       enterTimer.restart();
443   }
444
445   /**
446    * This method is called when the mouse is moved in a JComponent registered
447    * with the ToolTipManager.
448    *
449    * @param event The MouseEvent.
450    */
451   public void mouseMoved(MouseEvent event)
452   {
453     currentPoint = event.getPoint();
454     if (currentTip != null)
455       {
456         if (currentComponent == null)
457           currentComponent = (Component) event.getSource();
458         
459         String text = ((JComponent) currentComponent).getToolTipText(event);
460         currentTip.setTipText(text);
461       }
462     if (enterTimer.isRunning())
463       enterTimer.restart();
464   }
465
466   /**
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
469    * or a window.)
470    */
471   private void showTip()
472   {
473     if (! enabled || currentComponent == null)
474       return;
475
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))
483       {
484         JLayeredPane pane = ((JRootPane) SwingUtilities.getAncestorOfClass(JRootPane.class,
485                                                                            currentComponent))
486                             .getLayeredPane();
487
488         // This should never happen, but just in case.
489         if (pane == null)
490           return;
491
492         if (containerPanel != null)
493           hideTip();
494         if (isLightWeightPopupEnabled())
495           {
496             containerPanel = new Panel();
497             JRootPane root = new JRootPane();
498             root.getContentPane().add(currentTip);
499             containerPanel.add(root);
500           }
501         else
502           {
503             containerPanel = new JPanel();
504             containerPanel.add(currentTip);
505           }
506         LayoutManager lm = containerPanel.getLayout();
507         if (lm instanceof FlowLayout)
508           {
509             FlowLayout fm = (FlowLayout) lm;
510             fm.setVgap(0);
511             fm.setHgap(0);
512           }
513
514         p = getGoodPoint(p, pane, currentTip, dims);
515
516         pane.add(containerPanel);
517         containerPanel.setBounds(p.x, p.y, dims.width, dims.height);
518         currentTip.setBounds(0, 0, dims.width, dims.height);
519
520         pane.revalidate();
521         pane.repaint();
522       }
523     else
524       {
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();
532       }
533     currentTip.setVisible(true);
534   }
535
536   /**
537    * This method hides the ToolTip.
538    */
539   private void hideTip()
540   {
541     if (currentTip == null || ! currentTip.isVisible() || ! enabled)
542       return;
543     currentTip.setVisible(false);
544     if (containerPanel != null)
545       {
546         Container parent = containerPanel.getParent();
547         if (parent == null)
548           return;
549         parent.remove(containerPanel);
550         parent.invalidate();
551         parent.validate();
552         parent.repaint();
553
554         parent = currentTip.getParent();
555         if (parent == null)
556           return;
557         parent.remove(currentTip);
558
559         containerPanel = null;
560       }
561     if (tooltipWindow != null)
562       {
563         tooltipWindow.hide();
564         tooltipWindow.dispose();
565         tooltipWindow = null;
566       }
567   }
568
569   /**
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.
574    *
575    * @param p The last known good point for the mouse.
576    * @param c The JLayeredPane in the first RootPaneContainer up from the
577    *        currentComponent.
578    * @param tip The ToolTip to display.
579    * @param dims The ToolTip preferred dimensions (can be null).
580    *
581    * @return A good point to place the ToolTip.
582    */
583   private Point getGoodPoint(Point p, JLayeredPane c, JToolTip tip,
584                              Dimension dims)
585   {
586     if (dims == null)
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;
593
594     p = SwingUtilities.convertPoint(currentComponent, p, c);
595     return p;
596   }
597
598   /**
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.
602    *
603    * @param e The MouseEvent.
604    *
605    * @return The deepest component in the content pane.
606    */
607   private Component getContentPaneDeepestComponent(MouseEvent e)
608   {
609     Component source = (Component) e.getSource();
610     Container parent = (Container) SwingUtilities.getAncestorOfClass(JRootPane.class,
611                                                                      currentComponent);
612     if (parent == null)
613       return null;
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);
618     return target;
619   }
620
621   /**
622    * This method returns whether the ToolTip can fit in the first
623    * RootPaneContainer up from the currentComponent.
624    *
625    * @param tip The ToolTip.
626    *
627    * @return Whether the ToolTip can fit.
628    */
629   private boolean canToolTipFit(JToolTip tip)
630   {
631     JRootPane root = (JRootPane) SwingUtilities.getAncestorOfClass(JRootPane.class,
632                                                                    currentComponent);
633     if (root == null)
634       return false;
635     Dimension pref = tip.getPreferredSize();
636     Dimension rootSize = root.getSize();
637     if (rootSize.width > pref.width && rootSize.height > pref.height)
638       return true;
639     return false;
640   }
641 }