Frames | No Frames |
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: }