ToolTipManager.java 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  1. /* ToolTipManager.java --
  2. Copyright (C) 2002, 2004, 2006, Free Software Foundation, Inc.
  3. This file is part of GNU Classpath.
  4. GNU Classpath is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation; either version 2, or (at your option)
  7. any later version.
  8. GNU Classpath is distributed in the hope that it will be useful, but
  9. WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  11. General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with GNU Classpath; see the file COPYING. If not, write to the
  14. Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  15. 02110-1301 USA.
  16. Linking this library statically or dynamically with other modules is
  17. making a combined work based on this library. Thus, the terms and
  18. conditions of the GNU General Public License cover the whole
  19. combination.
  20. As a special exception, the copyright holders of this library give you
  21. permission to link this library with independent modules to produce an
  22. executable, regardless of the license terms of these independent
  23. modules, and to copy and distribute the resulting executable under
  24. terms of your choice, provided that you also meet, for each linked
  25. independent module, the terms and conditions of the license of that
  26. module. An independent module is a module which is not derived from
  27. or based on this library. If you modify this library, you may extend
  28. this exception to your version of the library, but you are not
  29. obligated to do so. If you do not wish to do so, delete this
  30. exception statement from your version. */
  31. package javax.swing;
  32. import java.awt.Component;
  33. import java.awt.Container;
  34. import java.awt.Dimension;
  35. import java.awt.Point;
  36. import java.awt.event.ActionEvent;
  37. import java.awt.event.ActionListener;
  38. import java.awt.event.MouseAdapter;
  39. import java.awt.event.MouseEvent;
  40. import java.awt.event.MouseMotionListener;
  41. /**
  42. * This class is responsible for the registration of JToolTips to Components
  43. * and for displaying them when appropriate.
  44. */
  45. public class ToolTipManager extends MouseAdapter implements MouseMotionListener
  46. {
  47. /**
  48. * This ActionListener is associated with the Timer that listens to whether
  49. * the JToolTip can be hidden after four seconds.
  50. */
  51. protected class stillInsideTimerAction implements ActionListener
  52. {
  53. /**
  54. * This method creates a new stillInsideTimerAction object.
  55. */
  56. protected stillInsideTimerAction()
  57. {
  58. // Nothing to do here.
  59. }
  60. /**
  61. * This method hides the JToolTip when the Timer has finished.
  62. *
  63. * @param event The ActionEvent.
  64. */
  65. public void actionPerformed(ActionEvent event)
  66. {
  67. hideTip();
  68. }
  69. }
  70. /**
  71. * This Actionlistener is associated with the Timer that listens to whether
  72. * the mouse cursor has re-entered the JComponent in time for an immediate
  73. * redisplay of the JToolTip.
  74. */
  75. protected class outsideTimerAction implements ActionListener
  76. {
  77. /**
  78. * This method creates a new outsideTimerAction object.
  79. */
  80. protected outsideTimerAction()
  81. {
  82. // Nothing to do here.
  83. }
  84. /**
  85. * This method is called when the Timer that listens to whether the mouse
  86. * cursor has re-entered the JComponent has run out.
  87. *
  88. * @param event The ActionEvent.
  89. */
  90. public void actionPerformed(ActionEvent event)
  91. {
  92. // TODO: What should be done here, if anything?
  93. }
  94. }
  95. /**
  96. * This ActionListener is associated with the Timer that listens to whether
  97. * it is time for the JToolTip to be displayed after the mouse has entered
  98. * the JComponent.
  99. */
  100. protected class insideTimerAction implements ActionListener
  101. {
  102. /**
  103. * This method creates a new insideTimerAction object.
  104. */
  105. protected insideTimerAction()
  106. {
  107. // Nothing to do here.
  108. }
  109. /**
  110. * This method displays the JToolTip when the Mouse has been still for the
  111. * delay.
  112. *
  113. * @param event The ActionEvent.
  114. */
  115. public void actionPerformed(ActionEvent event)
  116. {
  117. showTip();
  118. }
  119. }
  120. /**
  121. * The Timer that determines whether the Mouse has been still long enough
  122. * for the JToolTip to be displayed.
  123. */
  124. Timer enterTimer;
  125. /**
  126. * The Timer that determines whether the Mouse has re-entered the JComponent
  127. * quickly enough for the JToolTip to be displayed immediately.
  128. */
  129. Timer exitTimer;
  130. /**
  131. * The Timer that determines whether the JToolTip has been displayed long
  132. * enough for it to be hidden.
  133. */
  134. Timer insideTimer;
  135. /** A global enabled setting for the ToolTipManager. */
  136. private transient boolean enabled = true;
  137. /** lightWeightPopupEnabled */
  138. protected boolean lightWeightPopupEnabled = true;
  139. /** heavyWeightPopupEnabled */
  140. protected boolean heavyWeightPopupEnabled = false;
  141. /** The shared instance of the ToolTipManager. */
  142. private static ToolTipManager shared;
  143. /** The current component the tooltip is being displayed for. */
  144. private JComponent currentComponent;
  145. /** The current tooltip. */
  146. private JToolTip currentTip;
  147. /**
  148. * The tooltip text.
  149. */
  150. private String toolTipText;
  151. /** The last known position of the mouse cursor. */
  152. private Point currentPoint;
  153. /** */
  154. private Popup popup;
  155. /**
  156. * Creates a new ToolTipManager and sets up the timers.
  157. */
  158. ToolTipManager()
  159. {
  160. enterTimer = new Timer(750, new insideTimerAction());
  161. enterTimer.setRepeats(false);
  162. insideTimer = new Timer(4000, new stillInsideTimerAction());
  163. insideTimer.setRepeats(false);
  164. exitTimer = new Timer(500, new outsideTimerAction());
  165. exitTimer.setRepeats(false);
  166. }
  167. /**
  168. * This method returns the shared instance of ToolTipManager used by all
  169. * JComponents.
  170. *
  171. * @return The shared instance of ToolTipManager.
  172. */
  173. public static ToolTipManager sharedInstance()
  174. {
  175. if (shared == null)
  176. shared = new ToolTipManager();
  177. return shared;
  178. }
  179. /**
  180. * This method sets whether ToolTips are enabled or disabled for all
  181. * JComponents.
  182. *
  183. * @param enabled Whether ToolTips are enabled or disabled for all
  184. * JComponents.
  185. */
  186. public void setEnabled(boolean enabled)
  187. {
  188. if (! enabled)
  189. {
  190. enterTimer.stop();
  191. exitTimer.stop();
  192. insideTimer.stop();
  193. }
  194. this.enabled = enabled;
  195. }
  196. /**
  197. * This method returns whether ToolTips are enabled.
  198. *
  199. * @return Whether ToolTips are enabled.
  200. */
  201. public boolean isEnabled()
  202. {
  203. return enabled;
  204. }
  205. /**
  206. * This method returns whether LightweightToolTips are enabled.
  207. *
  208. * @return Whether LighweightToolTips are enabled.
  209. */
  210. public boolean isLightWeightPopupEnabled()
  211. {
  212. return lightWeightPopupEnabled;
  213. }
  214. /**
  215. * This method sets whether LightweightToolTips are enabled. If you mix
  216. * Lightweight and Heavyweight components, you must set this to false to
  217. * ensure that the ToolTips popup above all other components.
  218. *
  219. * @param enabled Whether LightweightToolTips will be enabled.
  220. */
  221. public void setLightWeightPopupEnabled(boolean enabled)
  222. {
  223. lightWeightPopupEnabled = enabled;
  224. heavyWeightPopupEnabled = ! enabled;
  225. }
  226. /**
  227. * This method returns the initial delay before the ToolTip is shown when
  228. * the mouse enters a Component.
  229. *
  230. * @return The initial delay before the ToolTip is shown.
  231. */
  232. public int getInitialDelay()
  233. {
  234. return enterTimer.getDelay();
  235. }
  236. /**
  237. * Sets the initial delay before the ToolTip is shown when the
  238. * mouse enters a Component.
  239. *
  240. * @param delay The initial delay before the ToolTip is shown.
  241. *
  242. * @throws IllegalArgumentException if <code>delay</code> is less than zero.
  243. */
  244. public void setInitialDelay(int delay)
  245. {
  246. enterTimer.setDelay(delay);
  247. }
  248. /**
  249. * This method returns the time the ToolTip will be shown before being
  250. * hidden.
  251. *
  252. * @return The time the ToolTip will be shown before being hidden.
  253. */
  254. public int getDismissDelay()
  255. {
  256. return insideTimer.getDelay();
  257. }
  258. /**
  259. * Sets the time the ToolTip will be shown before being hidden.
  260. *
  261. * @param delay the delay (in milliseconds) before tool tips are hidden.
  262. *
  263. * @throws IllegalArgumentException if <code>delay</code> is less than zero.
  264. */
  265. public void setDismissDelay(int delay)
  266. {
  267. insideTimer.setDelay(delay);
  268. }
  269. /**
  270. * This method returns the amount of delay where if the mouse re-enters a
  271. * Component, the tooltip will be shown immediately.
  272. *
  273. * @return The reshow delay.
  274. */
  275. public int getReshowDelay()
  276. {
  277. return exitTimer.getDelay();
  278. }
  279. /**
  280. * Sets the amount of delay where if the mouse re-enters a
  281. * Component, the tooltip will be shown immediately.
  282. *
  283. * @param delay The reshow delay (in milliseconds).
  284. *
  285. * @throws IllegalArgumentException if <code>delay</code> is less than zero.
  286. */
  287. public void setReshowDelay(int delay)
  288. {
  289. exitTimer.setDelay(delay);
  290. }
  291. /**
  292. * This method registers a JComponent with the ToolTipManager.
  293. *
  294. * @param component The JComponent to register with the ToolTipManager.
  295. */
  296. public void registerComponent(JComponent component)
  297. {
  298. component.addMouseListener(this);
  299. component.addMouseMotionListener(this);
  300. }
  301. /**
  302. * This method unregisters a JComponent with the ToolTipManager.
  303. *
  304. * @param component The JComponent to unregister with the ToolTipManager.
  305. */
  306. public void unregisterComponent(JComponent component)
  307. {
  308. component.removeMouseMotionListener(this);
  309. component.removeMouseListener(this);
  310. }
  311. /**
  312. * This method is called whenever the mouse enters a JComponent registered
  313. * with the ToolTipManager. When the mouse enters within the period of time
  314. * specified by the reshow delay, the tooltip will be displayed
  315. * immediately. Otherwise, it must wait for the initial delay before
  316. * displaying the tooltip.
  317. *
  318. * @param event The MouseEvent.
  319. */
  320. public void mouseEntered(MouseEvent event)
  321. {
  322. if (currentComponent != null
  323. && getContentPaneDeepestComponent(event) == currentComponent)
  324. return;
  325. currentPoint = event.getPoint();
  326. currentComponent = (JComponent) event.getSource();
  327. toolTipText = currentComponent.getToolTipText(event);
  328. if (exitTimer.isRunning())
  329. {
  330. exitTimer.stop();
  331. showTip();
  332. return;
  333. }
  334. // This should always be stopped unless we have just fake-exited.
  335. if (!enterTimer.isRunning())
  336. enterTimer.start();
  337. }
  338. /**
  339. * This method is called when the mouse exits a JComponent registered with the
  340. * ToolTipManager. When the mouse exits, the tooltip should be hidden
  341. * immediately.
  342. *
  343. * @param event
  344. * The MouseEvent.
  345. */
  346. public void mouseExited(MouseEvent event)
  347. {
  348. if (getContentPaneDeepestComponent(event) == currentComponent)
  349. return;
  350. currentPoint = event.getPoint();
  351. currentComponent = null;
  352. hideTip();
  353. if (! enterTimer.isRunning())
  354. exitTimer.start();
  355. if (enterTimer.isRunning())
  356. enterTimer.stop();
  357. if (insideTimer.isRunning())
  358. insideTimer.stop();
  359. }
  360. /**
  361. * This method is called when the mouse is pressed on a JComponent
  362. * registered with the ToolTipManager. When the mouse is pressed, the
  363. * tooltip (if it is shown) must be hidden immediately.
  364. *
  365. * @param event The MouseEvent.
  366. */
  367. public void mousePressed(MouseEvent event)
  368. {
  369. currentPoint = event.getPoint();
  370. if (enterTimer.isRunning())
  371. enterTimer.restart();
  372. else if (insideTimer.isRunning())
  373. {
  374. insideTimer.stop();
  375. hideTip();
  376. }
  377. }
  378. /**
  379. * This method is called when the mouse is dragged in a JComponent
  380. * registered with the ToolTipManager.
  381. *
  382. * @param event The MouseEvent.
  383. */
  384. public void mouseDragged(MouseEvent event)
  385. {
  386. currentPoint = event.getPoint();
  387. if (enterTimer.isRunning())
  388. enterTimer.restart();
  389. }
  390. /**
  391. * This method is called when the mouse is moved in a JComponent registered
  392. * with the ToolTipManager.
  393. *
  394. * @param event The MouseEvent.
  395. */
  396. public void mouseMoved(MouseEvent event)
  397. {
  398. currentPoint = event.getPoint();
  399. if (currentTip != null && currentTip.isShowing())
  400. checkTipUpdate(event);
  401. else
  402. {
  403. if (enterTimer.isRunning())
  404. enterTimer.restart();
  405. }
  406. }
  407. /**
  408. * Checks if the tooltip's text or location changes when the mouse is moved
  409. * over the component.
  410. */
  411. private void checkTipUpdate(MouseEvent ev)
  412. {
  413. JComponent comp = (JComponent) ev.getSource();
  414. String newText = comp.getToolTipText(ev);
  415. String oldText = toolTipText;
  416. if (newText != null)
  417. {
  418. if (((newText != null && newText.equals(oldText)) || newText == null))
  419. {
  420. // No change at all. Restart timers.
  421. if (popup == null)
  422. enterTimer.restart();
  423. else
  424. insideTimer.restart();
  425. }
  426. else
  427. {
  428. // Update the tooltip.
  429. toolTipText = newText;
  430. hideTip();
  431. showTip();
  432. exitTimer.stop();
  433. }
  434. }
  435. else
  436. {
  437. // Hide tooltip.
  438. currentTip = null;
  439. currentPoint = null;
  440. hideTip();
  441. enterTimer.stop();
  442. exitTimer.stop();
  443. }
  444. }
  445. /**
  446. * This method displays the ToolTip. It can figure out the method needed to
  447. * show it as well (whether to display it in heavyweight/lightweight panel
  448. * or a window.) This is package-private to avoid an accessor method.
  449. */
  450. void showTip()
  451. {
  452. if (!enabled || currentComponent == null || !currentComponent.isEnabled()
  453. || !currentComponent.isShowing())
  454. {
  455. popup = null;
  456. return;
  457. }
  458. if (currentTip == null || currentTip.getComponent() != currentComponent)
  459. currentTip = currentComponent.createToolTip();
  460. currentTip.setTipText(toolTipText);
  461. Point p = currentPoint;
  462. Point cP = currentComponent.getLocationOnScreen();
  463. Dimension dims = currentTip.getPreferredSize();
  464. JLayeredPane pane = null;
  465. JRootPane r = ((JRootPane) SwingUtilities.getAncestorOfClass(JRootPane.class,
  466. currentComponent));
  467. if (r != null)
  468. pane = r.getLayeredPane();
  469. if (pane == null)
  470. return;
  471. p.translate(cP.x, cP.y);
  472. adjustLocation(p, pane, dims);
  473. currentTip.setBounds(0, 0, dims.width, dims.height);
  474. PopupFactory factory = PopupFactory.getSharedInstance();
  475. popup = factory.getPopup(currentComponent, currentTip, p.x, p.y);
  476. popup.show();
  477. }
  478. /**
  479. * Adjusts the point to a new location on the component,
  480. * using the currentTip's dimensions.
  481. *
  482. * @param p - the point to convert.
  483. * @param c - the component the point is on.
  484. * @param d - the dimensions of the currentTip.
  485. */
  486. private Point adjustLocation(Point p, Component c, Dimension d)
  487. {
  488. if (p.x + d.width > c.getWidth())
  489. p.x -= d.width;
  490. if (p.x < 0)
  491. p.x = 0;
  492. if (p.y + d.height < c.getHeight())
  493. p.y += d.height;
  494. if (p.y + d.height > c.getHeight())
  495. p.y -= d.height;
  496. return p;
  497. }
  498. /**
  499. * This method hides the ToolTip.
  500. * This is package-private to avoid an accessor method.
  501. */
  502. void hideTip()
  503. {
  504. if (popup != null)
  505. popup.hide();
  506. }
  507. /**
  508. * This method returns the deepest component in the content pane for the
  509. * first RootPaneContainer up from the currentComponent. This method is
  510. * used in conjunction with one of the mouseXXX methods.
  511. *
  512. * @param e The MouseEvent.
  513. *
  514. * @return The deepest component in the content pane.
  515. */
  516. private Component getContentPaneDeepestComponent(MouseEvent e)
  517. {
  518. Component source = (Component) e.getSource();
  519. Container parent = SwingUtilities.getAncestorOfClass(JRootPane.class,
  520. currentComponent);
  521. if (parent == null)
  522. return null;
  523. parent = ((JRootPane) parent).getContentPane();
  524. Point p = e.getPoint();
  525. p = SwingUtilities.convertPoint(source, p, parent);
  526. Component target = SwingUtilities.getDeepestComponentAt(parent, p.x, p.y);
  527. return target;
  528. }
  529. }