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 */ stillInsideTimerAction()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 */ actionPerformed(ActionEvent event)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 */ outsideTimerAction()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 */ actionPerformed(ActionEvent event)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 */ insideTimerAction()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 */ actionPerformed(ActionEvent event)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 */ ToolTipManager()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 */ sharedInstance()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 */ setEnabled(boolean enabled)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 */ isEnabled()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 */ isLightWeightPopupEnabled()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 */ setLightWeightPopupEnabled(boolean enabled)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 */ getInitialDelay()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 */ setInitialDelay(int delay)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 */ getDismissDelay()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 */ setDismissDelay(int delay)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 */ getReshowDelay()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 */ setReshowDelay(int delay)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 */ registerComponent(JComponent component)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 */ unregisterComponent(JComponent component)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 */ mouseEntered(MouseEvent event)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 */ mouseExited(MouseEvent event)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 */ mousePressed(MouseEvent event)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 */ mouseDragged(MouseEvent event)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 */ mouseMoved(MouseEvent event)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 */ checkTipUpdate(MouseEvent ev)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 */ showTip()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 */ adjustLocation(Point p, Component c, Dimension d)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 */ hideTip()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 */ getContentPaneDeepestComponent(MouseEvent e)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 } 594