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