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