GNU Classpath (0.17) | ||
Frames | No Frames |
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., 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.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: 428: if (currentComponent == null) 429: currentComponent = (Component) event.getSource(); 430: 431: currentComponent.invalidate(); 432: currentComponent.validate(); 433: currentComponent.repaint(); 434: } 435: 436: /** 437: * This method is called when the mouse is dragged in a JComponent 438: * registered with the ToolTipManager. 439: * 440: * @param event The MouseEvent. 441: */ 442: public void mouseDragged(MouseEvent event) 443: { 444: currentPoint = event.getPoint(); 445: if (enterTimer.isRunning()) 446: enterTimer.restart(); 447: } 448: 449: /** 450: * This method is called when the mouse is moved in a JComponent registered 451: * with the ToolTipManager. 452: * 453: * @param event The MouseEvent. 454: */ 455: public void mouseMoved(MouseEvent event) 456: { 457: currentPoint = event.getPoint(); 458: if (currentTip != null) 459: { 460: if (currentComponent == null) 461: currentComponent = (Component) event.getSource(); 462: 463: String text = ((JComponent) currentComponent).getToolTipText(event); 464: currentTip.setTipText(text); 465: } 466: if (enterTimer.isRunning()) 467: enterTimer.restart(); 468: } 469: 470: /** 471: * This method displays the ToolTip. It can figure out the method needed to 472: * show it as well (whether to display it in heavyweight/lightweight panel 473: * or a window.) This is package-private to avoid an accessor method. 474: */ 475: void showTip() 476: { 477: if (! enabled || currentComponent == null) 478: return; 479: 480: if (currentTip == null 481: || currentTip.getComponent() != currentComponent 482: && currentComponent instanceof JComponent) 483: currentTip = ((JComponent) currentComponent).createToolTip(); 484: Point p = currentPoint; 485: Dimension dims = currentTip.getPreferredSize(); 486: if (canToolTipFit(currentTip)) 487: { 488: JLayeredPane pane = ((JRootPane) SwingUtilities.getAncestorOfClass(JRootPane.class, 489: currentComponent)) 490: .getLayeredPane(); 491: 492: // This should never happen, but just in case. 493: if (pane == null) 494: return; 495: 496: if (containerPanel != null) 497: hideTip(); 498: if (isLightWeightPopupEnabled()) 499: { 500: containerPanel = new Panel(); 501: JRootPane root = new JRootPane(); 502: root.getContentPane().add(currentTip); 503: containerPanel.add(root); 504: } 505: else 506: { 507: containerPanel = new JPanel(); 508: containerPanel.add(currentTip); 509: } 510: LayoutManager lm = containerPanel.getLayout(); 511: if (lm instanceof FlowLayout) 512: { 513: FlowLayout fm = (FlowLayout) lm; 514: fm.setVgap(0); 515: fm.setHgap(0); 516: } 517: 518: p = getGoodPoint(p, pane, currentTip, dims); 519: 520: pane.add(containerPanel); 521: containerPanel.setBounds(p.x, p.y, dims.width, dims.height); 522: currentTip.setBounds(0, 0, dims.width, dims.height); 523: 524: pane.revalidate(); 525: pane.repaint(); 526: } 527: else 528: { 529: SwingUtilities.convertPointToScreen(p, currentComponent); 530: tooltipWindow = new JWindow(); 531: tooltipWindow.getContentPane().add(currentTip); 532: tooltipWindow.setFocusable(false); 533: tooltipWindow.pack(); 534: tooltipWindow.setBounds(p.x, p.y, dims.width, dims.height); 535: tooltipWindow.show(); 536: } 537: currentTip.setVisible(true); 538: } 539: 540: /** 541: * This method hides the ToolTip. 542: * This is package-private to avoid an accessor method. 543: */ 544: void hideTip() 545: { 546: if (currentTip == null || ! currentTip.isVisible() || ! enabled) 547: return; 548: currentTip.setVisible(false); 549: if (containerPanel != null) 550: { 551: Container parent = containerPanel.getParent(); 552: if (parent == null) 553: return; 554: parent.remove(containerPanel); 555: parent.invalidate(); 556: parent.validate(); 557: parent.repaint(); 558: 559: parent = currentTip.getParent(); 560: if (parent == null) 561: return; 562: parent.remove(currentTip); 563: 564: containerPanel = null; 565: } 566: if (tooltipWindow != null) 567: { 568: tooltipWindow.hide(); 569: tooltipWindow.dispose(); 570: tooltipWindow = null; 571: } 572: } 573: 574: /** 575: * This method returns a point in the LayeredPane where the ToolTip can be 576: * shown. The point returned (if the ToolTip is to be displayed at the 577: * preferred dimensions) will always place the ToolTip inside the 578: * currentComponent if possible. 579: * 580: * @param p The last known good point for the mouse. 581: * @param c The JLayeredPane in the first RootPaneContainer up from the 582: * currentComponent. 583: * @param tip The ToolTip to display. 584: * @param dims The ToolTip preferred dimensions (can be null). 585: * 586: * @return A good point to place the ToolTip. 587: */ 588: private Point getGoodPoint(Point p, JLayeredPane c, JToolTip tip, 589: Dimension dims) 590: { 591: if (dims == null) 592: dims = tip.getPreferredSize(); 593: Rectangle bounds = currentComponent.getBounds(); 594: if (p.x + dims.width > bounds.width) 595: p.x = bounds.width - dims.width; 596: if (p.y + dims.height > bounds.height) 597: p.y = bounds.height - dims.height; 598: 599: p = SwingUtilities.convertPoint(currentComponent, p, c); 600: return p; 601: } 602: 603: /** 604: * This method returns the deepest component in the content pane for the 605: * first RootPaneContainer up from the currentComponent. This method is 606: * used in conjunction with one of the mouseXXX methods. 607: * 608: * @param e The MouseEvent. 609: * 610: * @return The deepest component in the content pane. 611: */ 612: private Component getContentPaneDeepestComponent(MouseEvent e) 613: { 614: Component source = (Component) e.getSource(); 615: Container parent = (Container) SwingUtilities.getAncestorOfClass(JRootPane.class, 616: currentComponent); 617: if (parent == null) 618: return null; 619: parent = ((JRootPane) parent).getContentPane(); 620: Point p = e.getPoint(); 621: p = SwingUtilities.convertPoint(source, p, parent); 622: Component target = SwingUtilities.getDeepestComponentAt(parent, p.x, p.y); 623: return target; 624: } 625: 626: /** 627: * This method returns whether the ToolTip can fit in the first 628: * RootPaneContainer up from the currentComponent. 629: * 630: * @param tip The ToolTip. 631: * 632: * @return Whether the ToolTip can fit. 633: */ 634: private boolean canToolTipFit(JToolTip tip) 635: { 636: JRootPane root = (JRootPane) SwingUtilities.getAncestorOfClass(JRootPane.class, 637: currentComponent); 638: if (root == null) 639: return false; 640: Dimension pref = tip.getPreferredSize(); 641: Dimension rootSize = root.getSize(); 642: if (rootSize.width > pref.width && rootSize.height > pref.height) 643: return true; 644: return false; 645: } 646: }
GNU Classpath (0.17) |