J. Duke | 319a3b9 | 2007-12-01 00:00:00 +0000 | [diff] [blame^] | 1 | /* |
| 2 | * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved. |
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| 4 | * |
| 5 | * This code is free software; you can redistribute it and/or modify it |
| 6 | * under the terms of the GNU General Public License version 2 only, as |
| 7 | * published by the Free Software Foundation. Sun designates this |
| 8 | * particular file as subject to the "Classpath" exception as provided |
| 9 | * by Sun in the LICENSE file that accompanied this code. |
| 10 | * |
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT |
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| 14 | * version 2 for more details (a copy is included in the LICENSE file that |
| 15 | * accompanied this code). |
| 16 | * |
| 17 | * You should have received a copy of the GNU General Public License version |
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, |
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| 20 | * |
| 21 | * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
| 22 | * CA 95054 USA or visit www.sun.com if you need additional information or |
| 23 | * have any questions. |
| 24 | */ |
| 25 | |
| 26 | |
| 27 | package javax.swing; |
| 28 | |
| 29 | import java.awt.event.*; |
| 30 | import java.applet.*; |
| 31 | import java.awt.*; |
| 32 | import java.io.Serializable; |
| 33 | import sun.swing.UIAction; |
| 34 | |
| 35 | /** |
| 36 | * Manages all the <code>ToolTips</code> in the system. |
| 37 | * <p> |
| 38 | * ToolTipManager contains numerous properties for configuring how long it |
| 39 | * will take for the tooltips to become visible, and how long till they |
| 40 | * hide. Consider a component that has a different tooltip based on where |
| 41 | * the mouse is, such as JTree. When the mouse moves into the JTree and |
| 42 | * over a region that has a valid tooltip, the tooltip will become |
| 43 | * visibile after <code>initialDelay</code> milliseconds. After |
| 44 | * <code>dismissDelay</code> milliseconds the tooltip will be hidden. If |
| 45 | * the mouse is over a region that has a valid tooltip, and the tooltip |
| 46 | * is currently visible, when the mouse moves to a region that doesn't have |
| 47 | * a valid tooltip the tooltip will be hidden. If the mouse then moves back |
| 48 | * into a region that has a valid tooltip within <code>reshowDelay</code> |
| 49 | * milliseconds, the tooltip will immediately be shown, otherwise the |
| 50 | * tooltip will be shown again after <code>initialDelay</code> milliseconds. |
| 51 | * |
| 52 | * @see JComponent#createToolTip |
| 53 | * @author Dave Moore |
| 54 | * @author Rich Schiavi |
| 55 | */ |
| 56 | public class ToolTipManager extends MouseAdapter implements MouseMotionListener { |
| 57 | Timer enterTimer, exitTimer, insideTimer; |
| 58 | String toolTipText; |
| 59 | Point preferredLocation; |
| 60 | JComponent insideComponent; |
| 61 | MouseEvent mouseEvent; |
| 62 | boolean showImmediately; |
| 63 | final static ToolTipManager sharedInstance = new ToolTipManager(); |
| 64 | transient Popup tipWindow; |
| 65 | /** The Window tip is being displayed in. This will be non-null if |
| 66 | * the Window tip is in differs from that of insideComponent's Window. |
| 67 | */ |
| 68 | private Window window; |
| 69 | JToolTip tip; |
| 70 | |
| 71 | private Rectangle popupRect = null; |
| 72 | private Rectangle popupFrameRect = null; |
| 73 | |
| 74 | boolean enabled = true; |
| 75 | private boolean tipShowing = false; |
| 76 | |
| 77 | private FocusListener focusChangeListener = null; |
| 78 | private MouseMotionListener moveBeforeEnterListener = null; |
| 79 | private KeyListener accessibilityKeyListener = null; |
| 80 | |
| 81 | // PENDING(ges) |
| 82 | protected boolean lightWeightPopupEnabled = true; |
| 83 | protected boolean heavyWeightPopupEnabled = false; |
| 84 | |
| 85 | ToolTipManager() { |
| 86 | enterTimer = new Timer(750, new insideTimerAction()); |
| 87 | enterTimer.setRepeats(false); |
| 88 | exitTimer = new Timer(500, new outsideTimerAction()); |
| 89 | exitTimer.setRepeats(false); |
| 90 | insideTimer = new Timer(4000, new stillInsideTimerAction()); |
| 91 | insideTimer.setRepeats(false); |
| 92 | |
| 93 | moveBeforeEnterListener = new MoveBeforeEnterListener(); |
| 94 | accessibilityKeyListener = new AccessibilityKeyListener(); |
| 95 | } |
| 96 | |
| 97 | /** |
| 98 | * Enables or disables the tooltip. |
| 99 | * |
| 100 | * @param flag true to enable the tip, false otherwise |
| 101 | */ |
| 102 | public void setEnabled(boolean flag) { |
| 103 | enabled = flag; |
| 104 | if (!flag) { |
| 105 | hideTipWindow(); |
| 106 | } |
| 107 | } |
| 108 | |
| 109 | /** |
| 110 | * Returns true if this object is enabled. |
| 111 | * |
| 112 | * @return true if this object is enabled, false otherwise |
| 113 | */ |
| 114 | public boolean isEnabled() { |
| 115 | return enabled; |
| 116 | } |
| 117 | |
| 118 | /** |
| 119 | * When displaying the <code>JToolTip</code>, the |
| 120 | * <code>ToolTipManager</code> chooses to use a lightweight |
| 121 | * <code>JPanel</code> if it fits. This method allows you to |
| 122 | * disable this feature. You have to do disable it if your |
| 123 | * application mixes light weight and heavy weights components. |
| 124 | * |
| 125 | * @param aFlag true if a lightweight panel is desired, false otherwise |
| 126 | * |
| 127 | */ |
| 128 | public void setLightWeightPopupEnabled(boolean aFlag){ |
| 129 | lightWeightPopupEnabled = aFlag; |
| 130 | } |
| 131 | |
| 132 | /** |
| 133 | * Returns true if lightweight (all-Java) <code>Tooltips</code> |
| 134 | * are in use, or false if heavyweight (native peer) |
| 135 | * <code>Tooltips</code> are being used. |
| 136 | * |
| 137 | * @return true if lightweight <code>ToolTips</code> are in use |
| 138 | */ |
| 139 | public boolean isLightWeightPopupEnabled() { |
| 140 | return lightWeightPopupEnabled; |
| 141 | } |
| 142 | |
| 143 | |
| 144 | /** |
| 145 | * Specifies the initial delay value. |
| 146 | * |
| 147 | * @param milliseconds the number of milliseconds to delay |
| 148 | * (after the cursor has paused) before displaying the |
| 149 | * tooltip |
| 150 | * @see #getInitialDelay |
| 151 | */ |
| 152 | public void setInitialDelay(int milliseconds) { |
| 153 | enterTimer.setInitialDelay(milliseconds); |
| 154 | } |
| 155 | |
| 156 | /** |
| 157 | * Returns the initial delay value. |
| 158 | * |
| 159 | * @return an integer representing the initial delay value, |
| 160 | * in milliseconds |
| 161 | * @see #setInitialDelay |
| 162 | */ |
| 163 | public int getInitialDelay() { |
| 164 | return enterTimer.getInitialDelay(); |
| 165 | } |
| 166 | |
| 167 | /** |
| 168 | * Specifies the dismissal delay value. |
| 169 | * |
| 170 | * @param milliseconds the number of milliseconds to delay |
| 171 | * before taking away the tooltip |
| 172 | * @see #getDismissDelay |
| 173 | */ |
| 174 | public void setDismissDelay(int milliseconds) { |
| 175 | insideTimer.setInitialDelay(milliseconds); |
| 176 | } |
| 177 | |
| 178 | /** |
| 179 | * Returns the dismissal delay value. |
| 180 | * |
| 181 | * @return an integer representing the dismissal delay value, |
| 182 | * in milliseconds |
| 183 | * @see #setDismissDelay |
| 184 | */ |
| 185 | public int getDismissDelay() { |
| 186 | return insideTimer.getInitialDelay(); |
| 187 | } |
| 188 | |
| 189 | /** |
| 190 | * Used to specify the amount of time before the user has to wait |
| 191 | * <code>initialDelay</code> milliseconds before a tooltip will be |
| 192 | * shown. That is, if the tooltip is hidden, and the user moves into |
| 193 | * a region of the same Component that has a valid tooltip within |
| 194 | * <code>milliseconds</code> milliseconds the tooltip will immediately |
| 195 | * be shown. Otherwise, if the user moves into a region with a valid |
| 196 | * tooltip after <code>milliseconds</code> milliseconds, the user |
| 197 | * will have to wait an additional <code>initialDelay</code> |
| 198 | * milliseconds before the tooltip is shown again. |
| 199 | * |
| 200 | * @param milliseconds time in milliseconds |
| 201 | * @see #getReshowDelay |
| 202 | */ |
| 203 | public void setReshowDelay(int milliseconds) { |
| 204 | exitTimer.setInitialDelay(milliseconds); |
| 205 | } |
| 206 | |
| 207 | /** |
| 208 | * Returns the reshow delay property. |
| 209 | * |
| 210 | * @return reshown delay property |
| 211 | * @see #setReshowDelay |
| 212 | */ |
| 213 | public int getReshowDelay() { |
| 214 | return exitTimer.getInitialDelay(); |
| 215 | } |
| 216 | |
| 217 | void showTipWindow() { |
| 218 | if(insideComponent == null || !insideComponent.isShowing()) |
| 219 | return; |
| 220 | String mode = UIManager.getString("ToolTipManager.enableToolTipMode"); |
| 221 | if ("activeApplication".equals(mode)) { |
| 222 | KeyboardFocusManager kfm = |
| 223 | KeyboardFocusManager.getCurrentKeyboardFocusManager(); |
| 224 | if (kfm.getFocusedWindow() == null) { |
| 225 | return; |
| 226 | } |
| 227 | } |
| 228 | if (enabled) { |
| 229 | Dimension size; |
| 230 | Point screenLocation = insideComponent.getLocationOnScreen(); |
| 231 | Point location = new Point(); |
| 232 | GraphicsConfiguration gc; |
| 233 | gc = insideComponent.getGraphicsConfiguration(); |
| 234 | Rectangle sBounds = gc.getBounds(); |
| 235 | Insets screenInsets = Toolkit.getDefaultToolkit() |
| 236 | .getScreenInsets(gc); |
| 237 | // Take into account screen insets, decrease viewport |
| 238 | sBounds.x += screenInsets.left; |
| 239 | sBounds.y += screenInsets.top; |
| 240 | sBounds.width -= (screenInsets.left + screenInsets.right); |
| 241 | sBounds.height -= (screenInsets.top + screenInsets.bottom); |
| 242 | boolean leftToRight |
| 243 | = SwingUtilities.isLeftToRight(insideComponent); |
| 244 | |
| 245 | // Just to be paranoid |
| 246 | hideTipWindow(); |
| 247 | |
| 248 | tip = insideComponent.createToolTip(); |
| 249 | tip.setTipText(toolTipText); |
| 250 | size = tip.getPreferredSize(); |
| 251 | |
| 252 | if(preferredLocation != null) { |
| 253 | location.x = screenLocation.x + preferredLocation.x; |
| 254 | location.y = screenLocation.y + preferredLocation.y; |
| 255 | if (!leftToRight) { |
| 256 | location.x -= size.width; |
| 257 | } |
| 258 | } else { |
| 259 | location.x = screenLocation.x + mouseEvent.getX(); |
| 260 | location.y = screenLocation.y + mouseEvent.getY() + 20; |
| 261 | if (!leftToRight) { |
| 262 | if(location.x - size.width>=0) { |
| 263 | location.x -= size.width; |
| 264 | } |
| 265 | } |
| 266 | |
| 267 | } |
| 268 | |
| 269 | // we do not adjust x/y when using awt.Window tips |
| 270 | if (popupRect == null){ |
| 271 | popupRect = new Rectangle(); |
| 272 | } |
| 273 | popupRect.setBounds(location.x,location.y, |
| 274 | size.width,size.height); |
| 275 | |
| 276 | // Fit as much of the tooltip on screen as possible |
| 277 | if (location.x < sBounds.x) { |
| 278 | location.x = sBounds.x; |
| 279 | } |
| 280 | else if (location.x - sBounds.x + size.width > sBounds.width) { |
| 281 | location.x = sBounds.x + Math.max(0, sBounds.width - size.width) |
| 282 | ; |
| 283 | } |
| 284 | if (location.y < sBounds.y) { |
| 285 | location.y = sBounds.y; |
| 286 | } |
| 287 | else if (location.y - sBounds.y + size.height > sBounds.height) { |
| 288 | location.y = sBounds.y + Math.max(0, sBounds.height - size.height); |
| 289 | } |
| 290 | |
| 291 | PopupFactory popupFactory = PopupFactory.getSharedInstance(); |
| 292 | |
| 293 | if (lightWeightPopupEnabled) { |
| 294 | int y = getPopupFitHeight(popupRect, insideComponent); |
| 295 | int x = getPopupFitWidth(popupRect,insideComponent); |
| 296 | if (x>0 || y>0) { |
| 297 | popupFactory.setPopupType(PopupFactory.MEDIUM_WEIGHT_POPUP); |
| 298 | } else { |
| 299 | popupFactory.setPopupType(PopupFactory.LIGHT_WEIGHT_POPUP); |
| 300 | } |
| 301 | } |
| 302 | else { |
| 303 | popupFactory.setPopupType(PopupFactory.MEDIUM_WEIGHT_POPUP); |
| 304 | } |
| 305 | tipWindow = popupFactory.getPopup(insideComponent, tip, |
| 306 | location.x, |
| 307 | location.y); |
| 308 | popupFactory.setPopupType(PopupFactory.LIGHT_WEIGHT_POPUP); |
| 309 | |
| 310 | tipWindow.show(); |
| 311 | |
| 312 | Window componentWindow = SwingUtilities.windowForComponent( |
| 313 | insideComponent); |
| 314 | |
| 315 | window = SwingUtilities.windowForComponent(tip); |
| 316 | if (window != null && window != componentWindow) { |
| 317 | window.addMouseListener(this); |
| 318 | } |
| 319 | else { |
| 320 | window = null; |
| 321 | } |
| 322 | |
| 323 | insideTimer.start(); |
| 324 | tipShowing = true; |
| 325 | } |
| 326 | } |
| 327 | |
| 328 | void hideTipWindow() { |
| 329 | if (tipWindow != null) { |
| 330 | if (window != null) { |
| 331 | window.removeMouseListener(this); |
| 332 | window = null; |
| 333 | } |
| 334 | tipWindow.hide(); |
| 335 | tipWindow = null; |
| 336 | tipShowing = false; |
| 337 | tip = null; |
| 338 | insideTimer.stop(); |
| 339 | } |
| 340 | } |
| 341 | |
| 342 | /** |
| 343 | * Returns a shared <code>ToolTipManager</code> instance. |
| 344 | * |
| 345 | * @return a shared <code>ToolTipManager</code> object |
| 346 | */ |
| 347 | public static ToolTipManager sharedInstance() { |
| 348 | return sharedInstance; |
| 349 | } |
| 350 | |
| 351 | // add keylistener here to trigger tip for access |
| 352 | /** |
| 353 | * Registers a component for tooltip management. |
| 354 | * <p> |
| 355 | * This will register key bindings to show and hide the tooltip text |
| 356 | * only if <code>component</code> has focus bindings. This is done |
| 357 | * so that components that are not normally focus traversable, such |
| 358 | * as <code>JLabel</code>, are not made focus traversable as a result |
| 359 | * of invoking this method. |
| 360 | * |
| 361 | * @param component a <code>JComponent</code> object to add |
| 362 | * @see JComponent#isFocusTraversable |
| 363 | */ |
| 364 | public void registerComponent(JComponent component) { |
| 365 | component.removeMouseListener(this); |
| 366 | component.addMouseListener(this); |
| 367 | component.removeMouseMotionListener(moveBeforeEnterListener); |
| 368 | component.addMouseMotionListener(moveBeforeEnterListener); |
| 369 | component.removeKeyListener(accessibilityKeyListener); |
| 370 | component.addKeyListener(accessibilityKeyListener); |
| 371 | } |
| 372 | |
| 373 | /** |
| 374 | * Removes a component from tooltip control. |
| 375 | * |
| 376 | * @param component a <code>JComponent</code> object to remove |
| 377 | */ |
| 378 | public void unregisterComponent(JComponent component) { |
| 379 | component.removeMouseListener(this); |
| 380 | component.removeMouseMotionListener(moveBeforeEnterListener); |
| 381 | component.removeKeyListener(accessibilityKeyListener); |
| 382 | } |
| 383 | |
| 384 | // implements java.awt.event.MouseListener |
| 385 | /** |
| 386 | * Called when the mouse enters the region of a component. |
| 387 | * This determines whether the tool tip should be shown. |
| 388 | * |
| 389 | * @param event the event in question |
| 390 | */ |
| 391 | public void mouseEntered(MouseEvent event) { |
| 392 | initiateToolTip(event); |
| 393 | } |
| 394 | |
| 395 | private void initiateToolTip(MouseEvent event) { |
| 396 | if (event.getSource() == window) { |
| 397 | return; |
| 398 | } |
| 399 | JComponent component = (JComponent)event.getSource(); |
| 400 | component.removeMouseMotionListener(moveBeforeEnterListener); |
| 401 | |
| 402 | exitTimer.stop(); |
| 403 | |
| 404 | Point location = event.getPoint(); |
| 405 | // ensure tooltip shows only in proper place |
| 406 | if (location.x < 0 || |
| 407 | location.x >=component.getWidth() || |
| 408 | location.y < 0 || |
| 409 | location.y >= component.getHeight()) { |
| 410 | return; |
| 411 | } |
| 412 | |
| 413 | if (insideComponent != null) { |
| 414 | enterTimer.stop(); |
| 415 | } |
| 416 | // A component in an unactive internal frame is sent two |
| 417 | // mouseEntered events, make sure we don't end up adding |
| 418 | // ourselves an extra time. |
| 419 | component.removeMouseMotionListener(this); |
| 420 | component.addMouseMotionListener(this); |
| 421 | |
| 422 | boolean sameComponent = (insideComponent == component); |
| 423 | |
| 424 | insideComponent = component; |
| 425 | if (tipWindow != null){ |
| 426 | mouseEvent = event; |
| 427 | if (showImmediately) { |
| 428 | String newToolTipText = component.getToolTipText(event); |
| 429 | Point newPreferredLocation = component.getToolTipLocation( |
| 430 | event); |
| 431 | boolean sameLoc = (preferredLocation != null) ? |
| 432 | preferredLocation.equals(newPreferredLocation) : |
| 433 | (newPreferredLocation == null); |
| 434 | |
| 435 | if (!sameComponent || !toolTipText.equals(newToolTipText) || |
| 436 | !sameLoc) { |
| 437 | toolTipText = newToolTipText; |
| 438 | preferredLocation = newPreferredLocation; |
| 439 | showTipWindow(); |
| 440 | } |
| 441 | } else { |
| 442 | enterTimer.start(); |
| 443 | } |
| 444 | } |
| 445 | } |
| 446 | |
| 447 | // implements java.awt.event.MouseListener |
| 448 | /** |
| 449 | * Called when the mouse exits the region of a component. |
| 450 | * Any tool tip showing should be hidden. |
| 451 | * |
| 452 | * @param event the event in question |
| 453 | */ |
| 454 | public void mouseExited(MouseEvent event) { |
| 455 | boolean shouldHide = true; |
| 456 | if (insideComponent == null) { |
| 457 | // Drag exit |
| 458 | } |
| 459 | if (window != null && event.getSource() == window) { |
| 460 | // if we get an exit and have a heavy window |
| 461 | // we need to check if it if overlapping the inside component |
| 462 | Container insideComponentWindow = insideComponent.getTopLevelAncestor(); |
| 463 | // insideComponent may be removed after tooltip is made visible |
| 464 | if (insideComponentWindow != null) { |
| 465 | Point location = event.getPoint(); |
| 466 | SwingUtilities.convertPointToScreen(location, window); |
| 467 | |
| 468 | location.x -= insideComponentWindow.getX(); |
| 469 | location.y -= insideComponentWindow.getY(); |
| 470 | |
| 471 | location = SwingUtilities.convertPoint(null, location, insideComponent); |
| 472 | if (location.x >= 0 && location.x < insideComponent.getWidth() && |
| 473 | location.y >= 0 && location.y < insideComponent.getHeight()) { |
| 474 | shouldHide = false; |
| 475 | } else { |
| 476 | shouldHide = true; |
| 477 | } |
| 478 | } |
| 479 | } else if(event.getSource() == insideComponent && tipWindow != null) { |
| 480 | Window win = SwingUtilities.getWindowAncestor(insideComponent); |
| 481 | if (win != null) { // insideComponent may have been hidden (e.g. in a menu) |
| 482 | Point location = SwingUtilities.convertPoint(insideComponent, |
| 483 | event.getPoint(), |
| 484 | win); |
| 485 | Rectangle bounds = insideComponent.getTopLevelAncestor().getBounds(); |
| 486 | location.x += bounds.x; |
| 487 | location.y += bounds.y; |
| 488 | |
| 489 | Point loc = new Point(0, 0); |
| 490 | SwingUtilities.convertPointToScreen(loc, tip); |
| 491 | bounds.x = loc.x; |
| 492 | bounds.y = loc.y; |
| 493 | bounds.width = tip.getWidth(); |
| 494 | bounds.height = tip.getHeight(); |
| 495 | |
| 496 | if (location.x >= bounds.x && location.x < (bounds.x + bounds.width) && |
| 497 | location.y >= bounds.y && location.y < (bounds.y + bounds.height)) { |
| 498 | shouldHide = false; |
| 499 | } else { |
| 500 | shouldHide = true; |
| 501 | } |
| 502 | } |
| 503 | } |
| 504 | |
| 505 | if (shouldHide) { |
| 506 | enterTimer.stop(); |
| 507 | if (insideComponent != null) { |
| 508 | insideComponent.removeMouseMotionListener(this); |
| 509 | } |
| 510 | insideComponent = null; |
| 511 | toolTipText = null; |
| 512 | mouseEvent = null; |
| 513 | hideTipWindow(); |
| 514 | exitTimer.restart(); |
| 515 | } |
| 516 | } |
| 517 | |
| 518 | // implements java.awt.event.MouseListener |
| 519 | /** |
| 520 | * Called when the mouse is pressed. |
| 521 | * Any tool tip showing should be hidden. |
| 522 | * |
| 523 | * @param event the event in question |
| 524 | */ |
| 525 | public void mousePressed(MouseEvent event) { |
| 526 | hideTipWindow(); |
| 527 | enterTimer.stop(); |
| 528 | showImmediately = false; |
| 529 | insideComponent = null; |
| 530 | mouseEvent = null; |
| 531 | } |
| 532 | |
| 533 | // implements java.awt.event.MouseMotionListener |
| 534 | /** |
| 535 | * Called when the mouse is pressed and dragged. |
| 536 | * Does nothing. |
| 537 | * |
| 538 | * @param event the event in question |
| 539 | */ |
| 540 | public void mouseDragged(MouseEvent event) { |
| 541 | } |
| 542 | |
| 543 | // implements java.awt.event.MouseMotionListener |
| 544 | /** |
| 545 | * Called when the mouse is moved. |
| 546 | * Determines whether the tool tip should be displayed. |
| 547 | * |
| 548 | * @param event the event in question |
| 549 | */ |
| 550 | public void mouseMoved(MouseEvent event) { |
| 551 | if (tipShowing) { |
| 552 | checkForTipChange(event); |
| 553 | } |
| 554 | else if (showImmediately) { |
| 555 | JComponent component = (JComponent)event.getSource(); |
| 556 | toolTipText = component.getToolTipText(event); |
| 557 | if (toolTipText != null) { |
| 558 | preferredLocation = component.getToolTipLocation(event); |
| 559 | mouseEvent = event; |
| 560 | insideComponent = component; |
| 561 | exitTimer.stop(); |
| 562 | showTipWindow(); |
| 563 | } |
| 564 | } |
| 565 | else { |
| 566 | // Lazily lookup the values from within insideTimerAction |
| 567 | insideComponent = (JComponent)event.getSource(); |
| 568 | mouseEvent = event; |
| 569 | toolTipText = null; |
| 570 | enterTimer.restart(); |
| 571 | } |
| 572 | } |
| 573 | |
| 574 | /** |
| 575 | * Checks to see if the tooltip needs to be changed in response to |
| 576 | * the MouseMoved event <code>event</code>. |
| 577 | */ |
| 578 | private void checkForTipChange(MouseEvent event) { |
| 579 | JComponent component = (JComponent)event.getSource(); |
| 580 | String newText = component.getToolTipText(event); |
| 581 | Point newPreferredLocation = component.getToolTipLocation(event); |
| 582 | |
| 583 | if (newText != null || newPreferredLocation != null) { |
| 584 | mouseEvent = event; |
| 585 | if (((newText != null && newText.equals(toolTipText)) || newText == null) && |
| 586 | ((newPreferredLocation != null && newPreferredLocation.equals(preferredLocation)) |
| 587 | || newPreferredLocation == null)) { |
| 588 | if (tipWindow != null) { |
| 589 | insideTimer.restart(); |
| 590 | } else { |
| 591 | enterTimer.restart(); |
| 592 | } |
| 593 | } else { |
| 594 | toolTipText = newText; |
| 595 | preferredLocation = newPreferredLocation; |
| 596 | if (showImmediately) { |
| 597 | hideTipWindow(); |
| 598 | showTipWindow(); |
| 599 | exitTimer.stop(); |
| 600 | } else { |
| 601 | enterTimer.restart(); |
| 602 | } |
| 603 | } |
| 604 | } else { |
| 605 | toolTipText = null; |
| 606 | preferredLocation = null; |
| 607 | mouseEvent = null; |
| 608 | insideComponent = null; |
| 609 | hideTipWindow(); |
| 610 | enterTimer.stop(); |
| 611 | exitTimer.restart(); |
| 612 | } |
| 613 | } |
| 614 | |
| 615 | protected class insideTimerAction implements ActionListener { |
| 616 | public void actionPerformed(ActionEvent e) { |
| 617 | if(insideComponent != null && insideComponent.isShowing()) { |
| 618 | // Lazy lookup |
| 619 | if (toolTipText == null && mouseEvent != null) { |
| 620 | toolTipText = insideComponent.getToolTipText(mouseEvent); |
| 621 | preferredLocation = insideComponent.getToolTipLocation( |
| 622 | mouseEvent); |
| 623 | } |
| 624 | if(toolTipText != null) { |
| 625 | showImmediately = true; |
| 626 | showTipWindow(); |
| 627 | } |
| 628 | else { |
| 629 | insideComponent = null; |
| 630 | toolTipText = null; |
| 631 | preferredLocation = null; |
| 632 | mouseEvent = null; |
| 633 | hideTipWindow(); |
| 634 | } |
| 635 | } |
| 636 | } |
| 637 | } |
| 638 | |
| 639 | protected class outsideTimerAction implements ActionListener { |
| 640 | public void actionPerformed(ActionEvent e) { |
| 641 | showImmediately = false; |
| 642 | } |
| 643 | } |
| 644 | |
| 645 | protected class stillInsideTimerAction implements ActionListener { |
| 646 | public void actionPerformed(ActionEvent e) { |
| 647 | hideTipWindow(); |
| 648 | enterTimer.stop(); |
| 649 | showImmediately = false; |
| 650 | insideComponent = null; |
| 651 | mouseEvent = null; |
| 652 | } |
| 653 | } |
| 654 | |
| 655 | /* This listener is registered when the tooltip is first registered |
| 656 | * on a component in order to catch the situation where the tooltip |
| 657 | * was turned on while the mouse was already within the bounds of |
| 658 | * the component. This way, the tooltip will be initiated on a |
| 659 | * mouse-entered or mouse-moved, whichever occurs first. Once the |
| 660 | * tooltip has been initiated, we can remove this listener and rely |
| 661 | * solely on mouse-entered to initiate the tooltip. |
| 662 | */ |
| 663 | private class MoveBeforeEnterListener extends MouseMotionAdapter { |
| 664 | public void mouseMoved(MouseEvent e) { |
| 665 | initiateToolTip(e); |
| 666 | } |
| 667 | } |
| 668 | |
| 669 | static Frame frameForComponent(Component component) { |
| 670 | while (!(component instanceof Frame)) { |
| 671 | component = component.getParent(); |
| 672 | } |
| 673 | return (Frame)component; |
| 674 | } |
| 675 | |
| 676 | private FocusListener createFocusChangeListener(){ |
| 677 | return new FocusAdapter(){ |
| 678 | public void focusLost(FocusEvent evt){ |
| 679 | hideTipWindow(); |
| 680 | insideComponent = null; |
| 681 | JComponent c = (JComponent)evt.getSource(); |
| 682 | c.removeFocusListener(focusChangeListener); |
| 683 | } |
| 684 | }; |
| 685 | } |
| 686 | |
| 687 | // Returns: 0 no adjust |
| 688 | // -1 can't fit |
| 689 | // >0 adjust value by amount returned |
| 690 | private int getPopupFitWidth(Rectangle popupRectInScreen, Component invoker){ |
| 691 | if (invoker != null){ |
| 692 | Container parent; |
| 693 | for (parent = invoker.getParent(); parent != null; parent = parent.getParent()){ |
| 694 | // fix internal frame size bug: 4139087 - 4159012 |
| 695 | if(parent instanceof JFrame || parent instanceof JDialog || |
| 696 | parent instanceof JWindow) { // no check for awt.Frame since we use Heavy tips |
| 697 | return getWidthAdjust(parent.getBounds(),popupRectInScreen); |
| 698 | } else if (parent instanceof JApplet || parent instanceof JInternalFrame) { |
| 699 | if (popupFrameRect == null){ |
| 700 | popupFrameRect = new Rectangle(); |
| 701 | } |
| 702 | Point p = parent.getLocationOnScreen(); |
| 703 | popupFrameRect.setBounds(p.x,p.y, |
| 704 | parent.getBounds().width, |
| 705 | parent.getBounds().height); |
| 706 | return getWidthAdjust(popupFrameRect,popupRectInScreen); |
| 707 | } |
| 708 | } |
| 709 | } |
| 710 | return 0; |
| 711 | } |
| 712 | |
| 713 | // Returns: 0 no adjust |
| 714 | // >0 adjust by value return |
| 715 | private int getPopupFitHeight(Rectangle popupRectInScreen, Component invoker){ |
| 716 | if (invoker != null){ |
| 717 | Container parent; |
| 718 | for (parent = invoker.getParent(); parent != null; parent = parent.getParent()){ |
| 719 | if(parent instanceof JFrame || parent instanceof JDialog || |
| 720 | parent instanceof JWindow) { |
| 721 | return getHeightAdjust(parent.getBounds(),popupRectInScreen); |
| 722 | } else if (parent instanceof JApplet || parent instanceof JInternalFrame) { |
| 723 | if (popupFrameRect == null){ |
| 724 | popupFrameRect = new Rectangle(); |
| 725 | } |
| 726 | Point p = parent.getLocationOnScreen(); |
| 727 | popupFrameRect.setBounds(p.x,p.y, |
| 728 | parent.getBounds().width, |
| 729 | parent.getBounds().height); |
| 730 | return getHeightAdjust(popupFrameRect,popupRectInScreen); |
| 731 | } |
| 732 | } |
| 733 | } |
| 734 | return 0; |
| 735 | } |
| 736 | |
| 737 | private int getHeightAdjust(Rectangle a, Rectangle b){ |
| 738 | if (b.y >= a.y && (b.y + b.height) <= (a.y + a.height)) |
| 739 | return 0; |
| 740 | else |
| 741 | return (((b.y + b.height) - (a.y + a.height)) + 5); |
| 742 | } |
| 743 | |
| 744 | // Return the number of pixels over the edge we are extending. |
| 745 | // If we are over the edge the ToolTipManager can adjust. |
| 746 | // REMIND: what if the Tooltip is just too big to fit at all - we currently will just clip |
| 747 | private int getWidthAdjust(Rectangle a, Rectangle b){ |
| 748 | // System.out.println("width b.x/b.width: " + b.x + "/" + b.width + |
| 749 | // "a.x/a.width: " + a.x + "/" + a.width); |
| 750 | if (b.x >= a.x && (b.x + b.width) <= (a.x + a.width)){ |
| 751 | return 0; |
| 752 | } |
| 753 | else { |
| 754 | return (((b.x + b.width) - (a.x +a.width)) + 5); |
| 755 | } |
| 756 | } |
| 757 | |
| 758 | |
| 759 | // |
| 760 | // Actions |
| 761 | // |
| 762 | private void show(JComponent source) { |
| 763 | if (tipWindow != null) { // showing we unshow |
| 764 | hideTipWindow(); |
| 765 | insideComponent = null; |
| 766 | } |
| 767 | else { |
| 768 | hideTipWindow(); // be safe |
| 769 | enterTimer.stop(); |
| 770 | exitTimer.stop(); |
| 771 | insideTimer.stop(); |
| 772 | insideComponent = source; |
| 773 | if (insideComponent != null){ |
| 774 | toolTipText = insideComponent.getToolTipText(); |
| 775 | preferredLocation = new Point(10,insideComponent.getHeight()+ |
| 776 | 10); // manual set |
| 777 | showTipWindow(); |
| 778 | // put a focuschange listener on to bring the tip down |
| 779 | if (focusChangeListener == null){ |
| 780 | focusChangeListener = createFocusChangeListener(); |
| 781 | } |
| 782 | insideComponent.addFocusListener(focusChangeListener); |
| 783 | } |
| 784 | } |
| 785 | } |
| 786 | |
| 787 | private void hide(JComponent source) { |
| 788 | hideTipWindow(); |
| 789 | source.removeFocusListener(focusChangeListener); |
| 790 | preferredLocation = null; |
| 791 | insideComponent = null; |
| 792 | } |
| 793 | |
| 794 | /* This listener is registered when the tooltip is first registered |
| 795 | * on a component in order to process accessibility keybindings. |
| 796 | * This will apply globally across L&F |
| 797 | * |
| 798 | * Post Tip: Ctrl+F1 |
| 799 | * Unpost Tip: Esc and Ctrl+F1 |
| 800 | */ |
| 801 | private class AccessibilityKeyListener extends KeyAdapter { |
| 802 | public void keyPressed(KeyEvent e) { |
| 803 | if (!e.isConsumed()) { |
| 804 | JComponent source = (JComponent) e.getComponent(); |
| 805 | if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { |
| 806 | if (tipWindow != null) { |
| 807 | hide(source); |
| 808 | e.consume(); |
| 809 | } |
| 810 | } else if (e.getKeyCode() == KeyEvent.VK_F1 |
| 811 | && e.getModifiers() == Event.CTRL_MASK) { |
| 812 | // Shown tooltip will be hidden |
| 813 | ToolTipManager.this.show(source); |
| 814 | e.consume(); |
| 815 | } |
| 816 | } |
| 817 | } |
| 818 | } |
| 819 | } |