Source for javax.swing.ToolTipManager

   1: /* ToolTipManager.java --
   2:    Copyright (C) 2002, 2004, 2006, 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., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 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.Point;
  44: import java.awt.event.ActionEvent;
  45: import java.awt.event.ActionListener;
  46: import java.awt.event.MouseAdapter;
  47: import java.awt.event.MouseEvent;
  48: import java.awt.event.MouseMotionListener;
  49: 
  50: /**
  51:  * This class is responsible for the registration of JToolTips to Components
  52:  * and for displaying them when appropriate.
  53:  */
  54: public class ToolTipManager extends MouseAdapter implements MouseMotionListener
  55: {
  56:   /**
  57:    * This ActionListener is associated with the Timer that listens to whether
  58:    * the JToolTip can be hidden after four seconds.
  59:    */
  60:   protected class stillInsideTimerAction implements ActionListener
  61:   {
  62:     /**
  63:      * This method creates a new stillInsideTimerAction object.
  64:      */
  65:     protected stillInsideTimerAction()
  66:     {
  67:       // Nothing to do here.
  68:     }
  69: 
  70:     /**
  71:      * This method hides the JToolTip when the Timer has finished.
  72:      *
  73:      * @param event The ActionEvent.
  74:      */
  75:     public void actionPerformed(ActionEvent event)
  76:     {
  77:       hideTip();
  78:     }
  79:   }
  80: 
  81:   /**
  82:    * This Actionlistener is associated with the Timer that listens to whether
  83:    * the mouse cursor has re-entered the JComponent in time for an immediate
  84:    * redisplay of the JToolTip.
  85:    */
  86:   protected class outsideTimerAction implements ActionListener
  87:   {
  88:     /**
  89:      * This method creates a new outsideTimerAction object.
  90:      */
  91:     protected outsideTimerAction()
  92:     {
  93:       // Nothing to do here.
  94:     }
  95: 
  96:     /**
  97:      * This method is called when the Timer that listens to whether the mouse
  98:      * cursor has re-entered the JComponent has run out.
  99:      *
 100:      * @param event The ActionEvent.
 101:      */
 102:     public void actionPerformed(ActionEvent event)
 103:     {
 104:       // TODO: What should be done here, if anything?
 105:     }
 106:   }
 107: 
 108:   /**
 109:    * This ActionListener is associated with the Timer that listens to whether
 110:    * it is time for the JToolTip to be displayed after the mouse has entered
 111:    * the JComponent.
 112:    */
 113:   protected class insideTimerAction implements ActionListener
 114:   {
 115:     /**
 116:      * This method creates a new insideTimerAction object.
 117:      */
 118:     protected insideTimerAction()
 119:     {
 120:       // Nothing to do here.
 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:     }
 133:   }
 134: 
 135:   /**
 136:    * The Timer that determines whether the Mouse has been still long enough
 137:    * for the JToolTip to be displayed.
 138:    */
 139:   Timer enterTimer;
 140: 
 141:   /**
 142:    * The Timer that determines whether the Mouse has re-entered the JComponent
 143:    * quickly enough for the JToolTip to be displayed immediately.
 144:    */
 145:   Timer exitTimer;
 146: 
 147:   /**
 148:    * The Timer that determines whether the JToolTip has been displayed long
 149:    * enough for it to be hidden.
 150:    */
 151:   Timer insideTimer;
 152: 
 153:   /** A global enabled setting for the ToolTipManager. */
 154:   private transient boolean enabled = true;
 155: 
 156:   /** lightWeightPopupEnabled */
 157:   protected boolean lightWeightPopupEnabled = true;
 158: 
 159:   /** heavyWeightPopupEnabled */
 160:   protected boolean heavyWeightPopupEnabled = false;
 161: 
 162:   /** The shared instance of the ToolTipManager. */
 163:   private static ToolTipManager shared;
 164: 
 165:   /** The current component the tooltip is being displayed for. */
 166:   private JComponent currentComponent;
 167: 
 168:   /** The current tooltip. */
 169:   private JToolTip currentTip;
 170: 
 171:   /**
 172:    * The tooltip text.
 173:    */
 174:   private String toolTipText;
 175: 
 176:   /** The last known position of the mouse cursor. */
 177:   private Point currentPoint;
 178: 
 179:   /**  */
 180:   private Popup popup;
 181: 
 182:   /**
 183:    * Creates a new ToolTipManager and sets up the timers.
 184:    */
 185:   ToolTipManager()
 186:   {
 187:     enterTimer = new Timer(750, new insideTimerAction());
 188:     enterTimer.setRepeats(false);
 189: 
 190:     insideTimer = new Timer(4000, new stillInsideTimerAction());
 191:     insideTimer.setRepeats(false);
 192: 
 193:     exitTimer = new Timer(500, new outsideTimerAction());
 194:     exitTimer.setRepeats(false);
 195:   }
 196: 
 197:   /**
 198:    * This method returns the shared instance of ToolTipManager used by all
 199:    * JComponents.
 200:    *
 201:    * @return The shared instance of ToolTipManager.
 202:    */
 203:   public static ToolTipManager sharedInstance()
 204:   {
 205:     if (shared == null)
 206:       shared = new ToolTipManager();
 207: 
 208:     return shared;
 209:   }
 210: 
 211:   /**
 212:    * This method sets whether ToolTips are enabled or disabled for all
 213:    * JComponents.
 214:    *
 215:    * @param enabled Whether ToolTips are enabled or disabled for all
 216:    *        JComponents.
 217:    */
 218:   public void setEnabled(boolean enabled)
 219:   {
 220:     if (! enabled)
 221:       {
 222:         enterTimer.stop();
 223:         exitTimer.stop();
 224:         insideTimer.stop();
 225:       }
 226: 
 227:     this.enabled = enabled;
 228:   }
 229: 
 230:   /**
 231:    * This method returns whether ToolTips are enabled.
 232:    *
 233:    * @return Whether ToolTips are enabled.
 234:    */
 235:   public boolean isEnabled()
 236:   {
 237:     return enabled;
 238:   }
 239: 
 240:   /**
 241:    * This method returns whether LightweightToolTips are enabled.
 242:    *
 243:    * @return Whether LighweightToolTips are enabled.
 244:    */
 245:   public boolean isLightWeightPopupEnabled()
 246:   {
 247:     return lightWeightPopupEnabled;
 248:   }
 249: 
 250:   /**
 251:    * This method sets whether LightweightToolTips are enabled. If you mix
 252:    * Lightweight and Heavyweight components, you must set this to false to
 253:    * ensure that the ToolTips popup above all other components.
 254:    *
 255:    * @param enabled Whether LightweightToolTips will be enabled.
 256:    */
 257:   public void setLightWeightPopupEnabled(boolean enabled)
 258:   {
 259:     lightWeightPopupEnabled = enabled;
 260:     heavyWeightPopupEnabled = ! enabled;
 261:   }
 262: 
 263:   /**
 264:    * This method returns the initial delay before the ToolTip is shown when
 265:    * the mouse enters a Component.
 266:    *
 267:    * @return The initial delay before the ToolTip is shown.
 268:    */
 269:   public int getInitialDelay()
 270:   {
 271:     return enterTimer.getDelay();
 272:   }
 273: 
 274:   /**
 275:    * Sets the initial delay before the ToolTip is shown when the
 276:    * mouse enters a Component.
 277:    *
 278:    * @param delay The initial delay before the ToolTip is shown.
 279:    *
 280:    * @throws IllegalArgumentException if <code>delay</code> is less than zero.
 281:    */
 282:   public void setInitialDelay(int delay)
 283:   {
 284:     enterTimer.setDelay(delay);
 285:   }
 286: 
 287:   /**
 288:    * This method returns the time the ToolTip will be shown before being
 289:    * hidden.
 290:    *
 291:    * @return The time the ToolTip will be shown before being hidden.
 292:    */
 293:   public int getDismissDelay()
 294:   {
 295:     return insideTimer.getDelay();
 296:   }
 297: 
 298:   /**
 299:    * Sets the time the ToolTip will be shown before being hidden.
 300:    *
 301:    * @param delay  the delay (in milliseconds) before tool tips are hidden.
 302:    *
 303:    * @throws IllegalArgumentException if <code>delay</code> is less than zero.
 304:    */
 305:   public void setDismissDelay(int delay)
 306:   {
 307:     insideTimer.setDelay(delay);
 308:   }
 309: 
 310:   /**
 311:    * This method returns the amount of delay where if the mouse re-enters a
 312:    * Component, the tooltip will be shown immediately.
 313:    *
 314:    * @return The reshow delay.
 315:    */
 316:   public int getReshowDelay()
 317:   {
 318:     return exitTimer.getDelay();
 319:   }
 320: 
 321:   /**
 322:    * Sets the amount of delay where if the mouse re-enters a
 323:    * Component, the tooltip will be shown immediately.
 324:    *
 325:    * @param delay The reshow delay (in milliseconds).
 326:    *
 327:    * @throws IllegalArgumentException if <code>delay</code> is less than zero.
 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: 
 372:     currentComponent = (JComponent) event.getSource();
 373:     toolTipText = currentComponent.getToolTipText(event);
 374:     if (exitTimer.isRunning())
 375:       {
 376:         exitTimer.stop();
 377:         showTip();
 378:         return;
 379:       }
 380:     // This should always be stopped unless we have just fake-exited.
 381:     if (!enterTimer.isRunning())
 382:       enterTimer.start();
 383:   }
 384: 
 385:   /**
 386:    * This method is called when the mouse exits a JComponent registered with the
 387:    * ToolTipManager. When the mouse exits, the tooltip should be hidden
 388:    * immediately.
 389:    *
 390:    * @param event
 391:    *          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())
 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:   }
 428: 
 429:   /**
 430:    * This method is called when the mouse is dragged in a JComponent
 431:    * registered with the ToolTipManager.
 432:    *
 433:    * @param event The MouseEvent.
 434:    */
 435:   public void mouseDragged(MouseEvent event)
 436:   {
 437:     currentPoint = event.getPoint();
 438:     if (enterTimer.isRunning())
 439:       enterTimer.restart();
 440:   }
 441: 
 442:   /**
 443:    * This method is called when the mouse is moved in a JComponent registered
 444:    * with the ToolTipManager.
 445:    *
 446:    * @param event The MouseEvent.
 447:    */
 448:   public void mouseMoved(MouseEvent event)
 449:   {
 450:     currentPoint = event.getPoint();
 451:     if (currentTip != null && currentTip.isShowing())
 452:       checkTipUpdate(event);
 453:     else
 454:       {
 455:         if (enterTimer.isRunning())
 456:           enterTimer.restart();
 457:       }
 458:   }
 459: 
 460:   /**
 461:    * Checks if the tooltip's text or location changes when the mouse is moved
 462:    * over the component.
 463:    */
 464:   private void checkTipUpdate(MouseEvent ev)
 465:   {
 466:     JComponent comp = (JComponent) ev.getSource();
 467:     String newText = comp.getToolTipText(ev);
 468:     String oldText = toolTipText;
 469:     if (newText != null)
 470:       {
 471:         if (((newText != null && newText.equals(oldText)) || newText == null))
 472:           {
 473:             // No change at all. Restart timers.
 474:             if (popup == null)
 475:               enterTimer.restart();
 476:             else
 477:               insideTimer.restart();
 478:           }
 479:         else
 480:           {
 481:             // Update the tooltip.
 482:             toolTipText = newText;
 483:             hideTip();
 484:             showTip();
 485:             exitTimer.stop();
 486:           }
 487:       }
 488:     else
 489:       {
 490:         // Hide tooltip.
 491:         currentTip = null;
 492:         currentPoint = null;
 493:         hideTip();
 494:         enterTimer.stop();
 495:         exitTimer.stop();
 496:       }
 497:   }
 498: 
 499:   /**
 500:    * This method displays the ToolTip. It can figure out the method needed to
 501:    * show it as well (whether to display it in heavyweight/lightweight panel
 502:    * or a window.)  This is package-private to avoid an accessor method.
 503:    */
 504:   void showTip()
 505:   {
 506:     if (!enabled || currentComponent == null || !currentComponent.isEnabled()
 507:         || !currentComponent.isShowing())
 508:       {
 509:         popup = null;
 510:         return;
 511:       }
 512: 
 513:     if (currentTip == null || currentTip.getComponent() != currentComponent)
 514:       currentTip = currentComponent.createToolTip();
 515:     currentTip.setTipText(toolTipText);
 516: 
 517:     Point p = currentPoint;
 518:     Point cP = currentComponent.getLocationOnScreen();
 519:     Dimension dims = currentTip.getPreferredSize();
 520: 
 521:     JLayeredPane pane = null;
 522:     JRootPane r = ((JRootPane) SwingUtilities.getAncestorOfClass(JRootPane.class,
 523:                                                                  currentComponent));
 524:     if (r != null)
 525:       pane = r.getLayeredPane();
 526:     if (pane == null)
 527:       return;
 528: 
 529:     p.translate(cP.x, cP.y);
 530:     adjustLocation(p, pane, dims);
 531: 
 532:     currentTip.setBounds(0, 0, dims.width, dims.height);
 533: 
 534:     PopupFactory factory = PopupFactory.getSharedInstance();
 535:     popup = factory.getPopup(currentComponent, currentTip, p.x, p.y);
 536:     popup.show();
 537:   }
 538: 
 539:   /**
 540:    * Adjusts the point to a new location on the component,
 541:    * using the currentTip's dimensions.
 542:    *
 543:    * @param p - the point to convert.
 544:    * @param c - the component the point is on.
 545:    * @param d - the dimensions of the currentTip.
 546:    */
 547:   private Point adjustLocation(Point p, Component c, Dimension d)
 548:   {
 549:     if (p.x + d.width > c.getWidth())
 550:       p.x -= d.width;
 551:     if (p.x < 0)
 552:       p.x = 0;
 553:     if (p.y + d.height < c.getHeight())
 554:       p.y += d.height;
 555:     if (p.y + d.height > c.getHeight())
 556:       p.y -= d.height;
 557: 
 558:     return p;
 559:   }
 560: 
 561:   /**
 562:    * This method hides the ToolTip.
 563:    * This is package-private to avoid an accessor method.
 564:    */
 565:   void hideTip()
 566:   {
 567:     if (popup != null)
 568:       popup.hide();
 569:   }
 570: 
 571:   /**
 572:    * This method returns the deepest component in the content pane for the
 573:    * first RootPaneContainer up from the currentComponent. This method is
 574:    * used in conjunction with one of the mouseXXX methods.
 575:    *
 576:    * @param e The MouseEvent.
 577:    *
 578:    * @return The deepest component in the content pane.
 579:    */
 580:   private Component getContentPaneDeepestComponent(MouseEvent e)
 581:   {
 582:     Component source = (Component) e.getSource();
 583:     Container parent = SwingUtilities.getAncestorOfClass(JRootPane.class,
 584:                                                          currentComponent);
 585:     if (parent == null)
 586:       return null;
 587:     parent = ((JRootPane) parent).getContentPane();
 588:     Point p = e.getPoint();
 589:     p = SwingUtilities.convertPoint(source, p, parent);
 590:     Component target = SwingUtilities.getDeepestComponentAt(parent, p.x, p.y);
 591:     return target;
 592:   }
 593: }