1. /*
  2. * @(#)PopupFactory.java 1.24 03/12/19
  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.applet.Applet;
  9. import java.awt.*;
  10. import java.awt.event.WindowAdapter;
  11. import java.awt.event.WindowEvent;
  12. import java.util.ArrayList;
  13. import java.util.HashMap;
  14. import java.util.List;
  15. import java.util.Map;
  16. /**
  17. * <code>PopupFactory</code>, as the name implies, is used to obtain
  18. * instances of <code>Popup</code>s. <code>Popup</code>s are used to
  19. * display a <code>Component</code> above all other <code>Component</code>s
  20. * in a particular containment hierarchy. The general contract is that
  21. * once you have obtained a <code>Popup</code> from a
  22. * <code>PopupFactory</code>, you must invoke <code>hide</code> on the
  23. * <code>Popup</code>. The typical usage is:
  24. * <pre>
  25. * PopupFactory factory = PopupFactory.getSharedInstance();
  26. * Popup popup = factory.getPopup(owner, contents, x, y);
  27. * popup.show();
  28. * ...
  29. * popup.hide();
  30. * </pre>
  31. *
  32. * @see Popup
  33. *
  34. * @version 1.24 12/19/03
  35. * @since 1.4
  36. */
  37. public class PopupFactory {
  38. /**
  39. * The shared instanceof <code>PopupFactory</code> is per
  40. * <code>AppContext</code>. This is the key used in the
  41. * <code>AppContext</code> to locate the <code>PopupFactory</code>.
  42. */
  43. private static final Object SharedInstanceKey =
  44. new StringBuffer("PopupFactory.SharedInstanceKey");
  45. /**
  46. * Max number of items to store in any one particular cache.
  47. */
  48. private static final int MAX_CACHE_SIZE = 5;
  49. /**
  50. * Key used to indicate a light weight popup should be used.
  51. */
  52. static final int LIGHT_WEIGHT_POPUP = 0;
  53. /**
  54. * Key used to indicate a medium weight Popup should be used.
  55. */
  56. static final int MEDIUM_WEIGHT_POPUP = 1;
  57. /*
  58. * Key used to indicate a heavy weight Popup should be used.
  59. */
  60. static final int HEAVY_WEIGHT_POPUP = 2;
  61. /**
  62. * Default type of Popup to create.
  63. */
  64. private int popupType = LIGHT_WEIGHT_POPUP;
  65. /**
  66. * Key used for client property to force heavy weight popups for a
  67. * component.
  68. */
  69. static final StringBuffer forceHeavyWeightPopupKey =
  70. new StringBuffer("__force_heavy_weight_popup__");
  71. /**
  72. * Sets the <code>PopupFactory</code> that will be used to obtain
  73. * <code>Popup</code>s.
  74. * This will throw an <code>IllegalArgumentException</code> if
  75. * <code>factory</code> is null.
  76. *
  77. * @param factory Shared PopupFactory
  78. * @exception IllegalArgumentException if <code>factory</code> is null
  79. * @see #getPopup
  80. */
  81. public static void setSharedInstance(PopupFactory factory) {
  82. if (factory == null) {
  83. throw new IllegalArgumentException("PopupFactory can not be null");
  84. }
  85. SwingUtilities.appContextPut(SharedInstanceKey, factory);
  86. }
  87. /**
  88. * Returns the shared <code>PopupFactory</code> which can be used
  89. * to obtain <code>Popup</code>s.
  90. *
  91. * @return Shared PopupFactory
  92. */
  93. public static PopupFactory getSharedInstance() {
  94. PopupFactory factory = (PopupFactory)SwingUtilities.appContextGet(
  95. SharedInstanceKey);
  96. if (factory == null) {
  97. factory = new PopupFactory();
  98. setSharedInstance(factory);
  99. }
  100. return factory;
  101. }
  102. /**
  103. * Provides a hint as to the type of <code>Popup</code> that should
  104. * be created.
  105. */
  106. void setPopupType(int type) {
  107. popupType = type;
  108. }
  109. /**
  110. * Returns the preferred type of Popup to create.
  111. */
  112. int getPopupType() {
  113. return popupType;
  114. }
  115. /**
  116. * Creates a <code>Popup</code> for the Component <code>owner</code>
  117. * containing the Component <code>contents</code>. <code>owner</code>
  118. * is used to determine which <code>Window</code> the new
  119. * <code>Popup</code> will parent the <code>Component</code> the
  120. * <code>Popup</code> creates to. A null <code>owner</code> implies there
  121. * is no valid parent. <code>x</code> and
  122. * <code>y</code> specify the preferred initial location to place
  123. * the <code>Popup</code> at. Based on screen size, or other paramaters,
  124. * the <code>Popup</code> may not display at <code>x</code> and
  125. * <code>y</code>.
  126. *
  127. * @param owner Component mouse coordinates are relative to, may be null
  128. * @param contents Contents of the Popup
  129. * @param x Initial x screen coordinate
  130. * @param y Initial y screen coordinate
  131. * @exception IllegalArgumentException if contents is null
  132. * @return Popup containing Contents
  133. */
  134. public Popup getPopup(Component owner, Component contents,
  135. int x, int y) throws IllegalArgumentException {
  136. if (contents == null) {
  137. throw new IllegalArgumentException(
  138. "Popup.getPopup must be passed non-null contents");
  139. }
  140. int popupType = getPopupType(owner, contents, x, y);
  141. Popup popup = getPopup(owner, contents, x, y, popupType);
  142. if (popup == null) {
  143. // Didn't fit, force to heavy.
  144. popup = getPopup(owner, contents, x, y, HEAVY_WEIGHT_POPUP);
  145. }
  146. return popup;
  147. }
  148. /**
  149. * Returns the popup type to use for the specified parameters.
  150. */
  151. private int getPopupType(Component owner, Component contents,
  152. int ownerX, int ownerY) {
  153. int popupType = getPopupType();
  154. if (owner == null || invokerInHeavyWeightPopup(owner)) {
  155. popupType = HEAVY_WEIGHT_POPUP;
  156. }
  157. else if (popupType == LIGHT_WEIGHT_POPUP &&
  158. !(contents instanceof JToolTip) &&
  159. !(contents instanceof JPopupMenu)) {
  160. popupType = MEDIUM_WEIGHT_POPUP;
  161. }
  162. // Check if the parent component is an option pane. If so we need to
  163. // force a heavy weight popup in order to have event dispatching work
  164. // correctly.
  165. Component c = owner;
  166. while (c != null) {
  167. if (c instanceof JComponent) {
  168. if (((JComponent)c).getClientProperty(
  169. forceHeavyWeightPopupKey) == Boolean.TRUE) {
  170. popupType = HEAVY_WEIGHT_POPUP;
  171. break;
  172. }
  173. }
  174. c = c.getParent();
  175. }
  176. return popupType;
  177. }
  178. /**
  179. * Obtains the appropriate <code>Popup</code> based on
  180. * <code>popupType</code>.
  181. */
  182. private Popup getPopup(Component owner, Component contents,
  183. int ownerX, int ownerY, int popupType) {
  184. if (GraphicsEnvironment.isHeadless()) {
  185. return getHeadlessPopup(owner, contents, ownerX, ownerY);
  186. }
  187. switch(popupType) {
  188. case LIGHT_WEIGHT_POPUP:
  189. return getLightWeightPopup(owner, contents, ownerX, ownerY);
  190. case MEDIUM_WEIGHT_POPUP:
  191. return getMediumWeightPopup(owner, contents, ownerX, ownerY);
  192. case HEAVY_WEIGHT_POPUP:
  193. return getHeavyWeightPopup(owner, contents, ownerX, ownerY);
  194. }
  195. return null;
  196. }
  197. /**
  198. * Creates a headless popup
  199. */
  200. private Popup getHeadlessPopup(Component owner, Component contents,
  201. int ownerX, int ownerY) {
  202. return HeadlessPopup.getHeadlessPopup(owner, contents, ownerX, ownerY);
  203. }
  204. /**
  205. * Creates a light weight popup.
  206. */
  207. private Popup getLightWeightPopup(Component owner, Component contents,
  208. int ownerX, int ownerY) {
  209. return LightWeightPopup.getLightWeightPopup(owner, contents, ownerX,
  210. ownerY);
  211. }
  212. /**
  213. * Creates a medium weight popup.
  214. */
  215. private Popup getMediumWeightPopup(Component owner, Component contents,
  216. int ownerX, int ownerY) {
  217. return MediumWeightPopup.getMediumWeightPopup(owner, contents,
  218. ownerX, ownerY);
  219. }
  220. /**
  221. * Creates a heavy weight popup.
  222. */
  223. private Popup getHeavyWeightPopup(Component owner, Component contents,
  224. int ownerX, int ownerY) {
  225. if (GraphicsEnvironment.isHeadless()) {
  226. return getMediumWeightPopup(owner, contents, ownerX, ownerY);
  227. }
  228. return HeavyWeightPopup.getHeavyWeightPopup(owner, contents, ownerX,
  229. ownerY);
  230. }
  231. /**
  232. * Returns true if the Component <code>i</code> inside a heavy weight
  233. * <code>Popup</code>.
  234. */
  235. private boolean invokerInHeavyWeightPopup(Component i) {
  236. if (i != null) {
  237. Container parent;
  238. for(parent = i.getParent() ; parent != null ; parent =
  239. parent.getParent()) {
  240. if (parent instanceof Popup.HeavyWeightWindow) {
  241. return true;
  242. }
  243. }
  244. }
  245. return false;
  246. }
  247. /**
  248. * Popup implementation that uses a Window as the popup.
  249. */
  250. private static class HeavyWeightPopup extends Popup {
  251. private static final Object heavyWeightPopupCacheKey =
  252. new StringBuffer("PopupFactory.heavyWeightPopupCache");
  253. /**
  254. * Returns either a new or recycled <code>Popup</code> containing
  255. * the specified children.
  256. */
  257. static Popup getHeavyWeightPopup(Component owner, Component contents,
  258. int ownerX, int ownerY) {
  259. Window window = (owner != null) ? SwingUtilities.
  260. getWindowAncestor(owner) : null;
  261. HeavyWeightPopup popup = null;
  262. if (window != null) {
  263. popup = getRecycledHeavyWeightPopup(window);
  264. }
  265. boolean focusPopup = false;
  266. if(contents != null && contents.isFocusable()) {
  267. if(contents instanceof JPopupMenu) {
  268. JPopupMenu jpm = (JPopupMenu) contents;
  269. Component popComps[] = jpm.getComponents();
  270. for(int i=0;i<popComps.length;i++) {
  271. if(!(popComps[i] instanceof MenuElement) &&
  272. !(popComps[i] instanceof JSeparator)) {
  273. focusPopup = true;
  274. break;
  275. }
  276. }
  277. }
  278. }
  279. if (popup == null ||
  280. ((JWindow) popup.getComponent())
  281. .getFocusableWindowState() != focusPopup) {
  282. if(popup != null) {
  283. // The recycled popup can't serve us well
  284. // dispose it and create new one
  285. popup._dispose();
  286. }
  287. popup = new HeavyWeightPopup();
  288. }
  289. popup.reset(owner, contents, ownerX, ownerY);
  290. if(focusPopup) {
  291. JWindow wnd = (JWindow) popup.getComponent();
  292. wnd.setFocusableWindowState(true);
  293. // Set window name. We need this in BasicPopupMenuUI
  294. // to identify focusable popup window.
  295. wnd.setName("###focusableSwingPopup###");
  296. }
  297. return popup;
  298. }
  299. /**
  300. * Returns a previously disposed heavy weight <code>Popup</code>
  301. * associated with <code>window</code>. This will return null if
  302. * there is no <code>HeavyWeightPopup</code> associated with
  303. * <code>window</code>.
  304. */
  305. private static HeavyWeightPopup getRecycledHeavyWeightPopup(Window w) {
  306. synchronized (HeavyWeightPopup.class) {
  307. List cache;
  308. Map heavyPopupCache = getHeavyWeightPopupCache();
  309. if (heavyPopupCache.containsKey(w)) {
  310. cache = (List)heavyPopupCache.get(w);
  311. } else {
  312. return null;
  313. }
  314. int c;
  315. if ((c = cache.size()) > 0) {
  316. HeavyWeightPopup r = (HeavyWeightPopup)cache.get(0);
  317. cache.remove(0);
  318. return r;
  319. }
  320. return null;
  321. }
  322. }
  323. /**
  324. * Returns the cache to use for heavy weight popups. Maps from
  325. * <code>Window</code> to a <code>List</code> of
  326. * <code>HeavyWeightPopup</code>s.
  327. */
  328. private static Map getHeavyWeightPopupCache() {
  329. synchronized (HeavyWeightPopup.class) {
  330. Map cache = (Map)SwingUtilities.appContextGet(
  331. heavyWeightPopupCacheKey);
  332. if (cache == null) {
  333. cache = new HashMap(2);
  334. SwingUtilities.appContextPut(heavyWeightPopupCacheKey,
  335. cache);
  336. }
  337. return cache;
  338. }
  339. }
  340. /**
  341. * Recycles the passed in <code>HeavyWeightPopup</code>.
  342. */
  343. private static void recycleHeavyWeightPopup(HeavyWeightPopup popup) {
  344. synchronized (HeavyWeightPopup.class) {
  345. List cache;
  346. Object window = SwingUtilities.getWindowAncestor(
  347. popup.getComponent());
  348. Map heavyPopupCache = getHeavyWeightPopupCache();
  349. if (window instanceof Popup.DefaultFrame ||
  350. !((Window)window).isVisible()) {
  351. // If the Window isn't visible, we don't cache it as we
  352. // likely won't ever get a windowClosed event to clean up.
  353. // We also don't cache DefaultFrames as this indicates
  354. // there wasn't a valid Window parent, and thus we don't
  355. // know when to clean up.
  356. popup._dispose();
  357. return;
  358. } else if (heavyPopupCache.containsKey(window)) {
  359. cache = (List)heavyPopupCache.get(window);
  360. } else {
  361. cache = new ArrayList();
  362. heavyPopupCache.put(window, cache);
  363. // Clean up if the Window is closed
  364. final Window w = (Window)window;
  365. w.addWindowListener(new WindowAdapter() {
  366. public void windowClosed(WindowEvent e) {
  367. List popups;
  368. synchronized(HeavyWeightPopup.class) {
  369. Map heavyPopupCache2 =
  370. getHeavyWeightPopupCache();
  371. popups = (List)heavyPopupCache2.remove(w);
  372. }
  373. if (popups != null) {
  374. for (int counter = popups.size() - 1;
  375. counter >= 0; counter--) {
  376. ((HeavyWeightPopup)popups.get(counter)).
  377. _dispose();
  378. }
  379. }
  380. }
  381. });
  382. }
  383. if(cache.size() < MAX_CACHE_SIZE) {
  384. cache.add(popup);
  385. } else {
  386. popup._dispose();
  387. }
  388. }
  389. }
  390. //
  391. // Popup methods
  392. //
  393. public void hide() {
  394. super.hide();
  395. recycleHeavyWeightPopup(this);
  396. }
  397. /**
  398. * As we recycle the <code>Window</code>, we don't want to dispose it,
  399. * thus this method does nothing, instead use <code>_dipose</code>
  400. * which will handle the disposing.
  401. */
  402. void dispose() {
  403. }
  404. void _dispose() {
  405. super.dispose();
  406. }
  407. }
  408. /**
  409. * ContainerPopup consolidates the common code used in the light/medium
  410. * weight implementations of <code>Popup</code>.
  411. */
  412. private static class ContainerPopup extends Popup {
  413. /** Component we are to be added to. */
  414. Component owner;
  415. /** Desired x location. */
  416. int x;
  417. /** Desired y location. */
  418. int y;
  419. public void hide() {
  420. Component component = getComponent();
  421. if (component != null) {
  422. Container parent = component.getParent();
  423. if (parent != null) {
  424. Rectangle bounds = component.getBounds();
  425. parent.remove(component);
  426. parent.repaint(bounds.x, bounds.y, bounds.width,
  427. bounds.height);
  428. }
  429. }
  430. owner = null;
  431. }
  432. public void pack() {
  433. Component component = getComponent();
  434. if (component != null) {
  435. component.setSize(component.getPreferredSize());
  436. }
  437. }
  438. void reset(Component owner, Component contents, int ownerX,
  439. int ownerY) {
  440. if ((owner instanceof JFrame) || (owner instanceof JDialog) ||
  441. (owner instanceof JWindow)) {
  442. // Force the content to be added to the layered pane, otherwise
  443. // we'll get an exception when adding to the RootPaneContainer.
  444. owner = ((RootPaneContainer)owner).getLayeredPane();
  445. }
  446. super.reset(owner, contents, ownerX, ownerY);
  447. x = ownerX;
  448. y = ownerY;
  449. this.owner = owner;
  450. }
  451. boolean overlappedByOwnedWindow() {
  452. Component component = getComponent();
  453. if(owner != null && component != null) {
  454. Window w = SwingUtilities.getWindowAncestor(owner);
  455. if (w == null) {
  456. return false;
  457. }
  458. Window[] ownedWindows = w.getOwnedWindows();
  459. if(ownedWindows != null) {
  460. Rectangle bnd = component.getBounds();
  461. for(int i=0; i<ownedWindows.length;i++) {
  462. Window owned = ownedWindows[i];
  463. if (owned.isVisible() &&
  464. bnd.intersects(owned.getBounds())) {
  465. return true;
  466. }
  467. }
  468. }
  469. }
  470. return false;
  471. }
  472. /**
  473. * Returns true if the Popup can fit on the screen.
  474. */
  475. boolean fitsOnScreen() {
  476. Component component = getComponent();
  477. if (owner != null && component != null) {
  478. Container parent;
  479. int width = component.getWidth();
  480. int height = component.getHeight();
  481. for(parent = owner.getParent(); parent != null ;
  482. parent = parent.getParent()) {
  483. if (parent instanceof JFrame ||
  484. parent instanceof JDialog ||
  485. parent instanceof JWindow) {
  486. Rectangle r = parent.getBounds();
  487. Insets i = parent.getInsets();
  488. r.x += i.left;
  489. r.y += i.top;
  490. r.width -= (i.left + i.right);
  491. r.height -= (i.top + i.bottom);
  492. return SwingUtilities.isRectangleContainingRectangle(
  493. r, new Rectangle(x, y, width, height));
  494. } else if (parent instanceof JApplet) {
  495. Rectangle r = parent.getBounds();
  496. Point p = parent.getLocationOnScreen();
  497. r.x = p.x;
  498. r.y = p.y;
  499. return SwingUtilities.isRectangleContainingRectangle(
  500. r, new Rectangle(x, y, width, height));
  501. } else if (parent instanceof Window ||
  502. parent instanceof Applet) {
  503. // No suitable swing component found
  504. break;
  505. }
  506. }
  507. }
  508. return false;
  509. }
  510. }
  511. /**
  512. * Popup implementation that is used in headless environment.
  513. */
  514. private static class HeadlessPopup extends ContainerPopup {
  515. static Popup getHeadlessPopup(Component owner, Component contents,
  516. int ownerX, int ownerY) {
  517. HeadlessPopup popup = new HeadlessPopup();
  518. popup.reset(owner, contents, ownerX, ownerY);
  519. return popup;
  520. }
  521. Component createComponent(Component owner) {
  522. return new Panel(new BorderLayout());
  523. }
  524. public void show() {
  525. }
  526. public void hide() {
  527. }
  528. }
  529. /**
  530. * Popup implementation that uses a JPanel as the popup.
  531. */
  532. private static class LightWeightPopup extends ContainerPopup {
  533. private static final Object lightWeightPopupCacheKey =
  534. new StringBuffer("PopupFactory.lightPopupCache");
  535. /**
  536. * Returns a light weight <code>Popup</code> implementation. If
  537. * the <code>Popup</code> needs more space that in available in
  538. * <code>owner</code>, this will return null.
  539. */
  540. static Popup getLightWeightPopup(Component owner, Component contents,
  541. int ownerX, int ownerY) {
  542. LightWeightPopup popup = getRecycledLightWeightPopup();
  543. if (popup == null) {
  544. popup = new LightWeightPopup();
  545. }
  546. popup.reset(owner, contents, ownerX, ownerY);
  547. if (!popup.fitsOnScreen() ||
  548. popup.overlappedByOwnedWindow()) {
  549. popup.hide();
  550. return null;
  551. }
  552. return popup;
  553. }
  554. /**
  555. * Returns the cache to use for heavy weight popups.
  556. */
  557. private static List getLightWeightPopupCache() {
  558. List cache = (List)SwingUtilities.appContextGet(
  559. lightWeightPopupCacheKey);
  560. if (cache == null) {
  561. cache = new ArrayList();
  562. SwingUtilities.appContextPut(lightWeightPopupCacheKey, cache);
  563. }
  564. return cache;
  565. }
  566. /**
  567. * Recycles the LightWeightPopup <code>popup</code>.
  568. */
  569. private static void recycleLightWeightPopup(LightWeightPopup popup) {
  570. synchronized (LightWeightPopup.class) {
  571. List lightPopupCache = getLightWeightPopupCache();
  572. if (lightPopupCache.size() < MAX_CACHE_SIZE) {
  573. lightPopupCache.add(popup);
  574. }
  575. }
  576. }
  577. /**
  578. * Returns a previously used <code>LightWeightPopup</code>, or null
  579. * if none of the popups have been recycled.
  580. */
  581. private static LightWeightPopup getRecycledLightWeightPopup() {
  582. synchronized (LightWeightPopup.class) {
  583. List lightPopupCache = getLightWeightPopupCache();
  584. int c;
  585. if((c = lightPopupCache.size()) > 0) {
  586. LightWeightPopup r = (LightWeightPopup)lightPopupCache.
  587. get(0);
  588. lightPopupCache.remove(0);
  589. return r;
  590. }
  591. return null;
  592. }
  593. }
  594. //
  595. // Popup methods
  596. //
  597. public void hide() {
  598. super.hide();
  599. Container component = (Container)getComponent();
  600. component.removeAll();
  601. recycleLightWeightPopup(this);
  602. }
  603. public void show() {
  604. Container parent = null;
  605. if (owner != null) {
  606. parent = (owner instanceof Container? (Container)owner : owner.getParent());
  607. }
  608. // Try to find a JLayeredPane and Window to add
  609. for (Container p = parent; p != null; p = p.getParent()) {
  610. if (p instanceof JRootPane) {
  611. if (p.getParent() instanceof JInternalFrame) {
  612. continue;
  613. }
  614. parent = ((JRootPane)p).getLayeredPane();
  615. // Continue, so that if there is a higher JRootPane, we'll
  616. // pick it up.
  617. } else if(p instanceof Window) {
  618. if (parent == null) {
  619. parent = p;
  620. }
  621. break;
  622. } else if (p instanceof JApplet) {
  623. // Painting code stops at Applets, we don't want
  624. // to add to a Component above an Applet otherwise
  625. // you'll never see it painted.
  626. break;
  627. }
  628. }
  629. Point p = SwingUtilities.convertScreenLocationToParent(parent, x,
  630. y);
  631. Component component = getComponent();
  632. component.setLocation(p.x, p.y);
  633. if (parent instanceof JLayeredPane) {
  634. ((JLayeredPane)parent).add(component,
  635. JLayeredPane.POPUP_LAYER, 0);
  636. } else {
  637. parent.add(component);
  638. }
  639. }
  640. Component createComponent(Component owner) {
  641. JComponent component = new JPanel(new BorderLayout(), true);
  642. component.setOpaque(true);
  643. return component;
  644. }
  645. //
  646. // Local methods
  647. //
  648. /**
  649. * Resets the <code>Popup</code> to an initial state.
  650. */
  651. void reset(Component owner, Component contents, int ownerX,
  652. int ownerY) {
  653. super.reset(owner, contents, ownerX, ownerY);
  654. JComponent component = (JComponent)getComponent();
  655. component.setLocation(ownerX, ownerY);
  656. component.add(contents, BorderLayout.CENTER);
  657. contents.invalidate();
  658. pack();
  659. }
  660. }
  661. /**
  662. * Popup implementation that uses a Panel as the popup.
  663. */
  664. private static class MediumWeightPopup extends ContainerPopup {
  665. private static final Object mediumWeightPopupCacheKey =
  666. new StringBuffer("PopupFactory.mediumPopupCache");
  667. /** Child of the panel. The contents are added to this. */
  668. private JRootPane rootPane;
  669. /**
  670. * Returns a medium weight <code>Popup</code> implementation. If
  671. * the <code>Popup</code> needs more space that in available in
  672. * <code>owner</code>, this will return null.
  673. */
  674. static Popup getMediumWeightPopup(Component owner, Component contents,
  675. int ownerX, int ownerY) {
  676. MediumWeightPopup popup = getRecycledMediumWeightPopup();
  677. if (popup == null) {
  678. popup = new MediumWeightPopup();
  679. }
  680. popup.reset(owner, contents, ownerX, ownerY);
  681. if (!popup.fitsOnScreen() ||
  682. popup.overlappedByOwnedWindow()) {
  683. popup.hide();
  684. return null;
  685. }
  686. return popup;
  687. }
  688. /**
  689. * Returns the cache to use for medium weight popups.
  690. */
  691. private static List getMediumWeightPopupCache() {
  692. List cache = (List)SwingUtilities.appContextGet(
  693. mediumWeightPopupCacheKey);
  694. if (cache == null) {
  695. cache = new ArrayList();
  696. SwingUtilities.appContextPut(mediumWeightPopupCacheKey, cache);
  697. }
  698. return cache;
  699. }
  700. /**
  701. * Recycles the MediumWeightPopup <code>popup</code>.
  702. */
  703. private static void recycleMediumWeightPopup(MediumWeightPopup popup) {
  704. synchronized (MediumWeightPopup.class) {
  705. List mediumPopupCache = getMediumWeightPopupCache();
  706. if (mediumPopupCache.size() < MAX_CACHE_SIZE) {
  707. mediumPopupCache.add(popup);
  708. }
  709. }
  710. }
  711. /**
  712. * Returns a previously used <code>MediumWeightPopup</code>, or null
  713. * if none of the popups have been recycled.
  714. */
  715. private static MediumWeightPopup getRecycledMediumWeightPopup() {
  716. synchronized (MediumWeightPopup.class) {
  717. java.util.List mediumPopupCache =
  718. getMediumWeightPopupCache();
  719. int c;
  720. if ((c=mediumPopupCache.size()) > 0) {
  721. MediumWeightPopup r = (MediumWeightPopup)mediumPopupCache.
  722. get(0);
  723. mediumPopupCache.remove(0);
  724. return r;
  725. }
  726. return null;
  727. }
  728. }
  729. //
  730. // Popup
  731. //
  732. public void hide() {
  733. super.hide();
  734. rootPane.getContentPane().removeAll();
  735. recycleMediumWeightPopup(this);
  736. }
  737. public void show() {
  738. Component component = getComponent();
  739. Container parent = null;
  740. if (owner != null) {
  741. parent = owner.getParent();
  742. }
  743. /*
  744. Find the top level window,
  745. if it has a layered pane,
  746. add to that, otherwise
  747. add to the window. */
  748. while (!(parent instanceof Window || parent instanceof Applet) &&
  749. (parent!=null)) {
  750. parent = parent.getParent();
  751. }
  752. // Set the visibility to false before adding to workaround a
  753. // bug in Solaris in which the Popup gets added at the wrong
  754. // location, which will result in a mouseExit, which will then
  755. // result in the ToolTip being removed.
  756. if (parent instanceof RootPaneContainer) {
  757. parent = ((RootPaneContainer)parent).getLayeredPane();
  758. Point p = SwingUtilities.convertScreenLocationToParent(parent,
  759. x, y);
  760. component.setVisible(false);
  761. component.setLocation(p.x, p.y);
  762. ((JLayeredPane)parent).add(component, JLayeredPane.POPUP_LAYER,
  763. 0);
  764. } else {
  765. Point p = SwingUtilities.convertScreenLocationToParent(parent,
  766. x, y);
  767. component.setLocation(p.x, p.y);
  768. component.setVisible(false);
  769. parent.add(component);
  770. }
  771. component.setVisible(true);
  772. }
  773. Component createComponent(Component owner) {
  774. Panel component = new Panel(new BorderLayout());
  775. rootPane = new JRootPane();
  776. // NOTE: this uses setOpaque vs LookAndFeel.installProperty as
  777. // there is NO reason for the RootPane not to be opaque. For
  778. // painting to work the contentPane must be opaque, therefor the
  779. // RootPane can also be opaque.
  780. rootPane.setOpaque(true);
  781. component.add(rootPane, BorderLayout.CENTER);
  782. return component;
  783. }
  784. /**
  785. * Resets the <code>Popup</code> to an initial state.
  786. */
  787. void reset(Component owner, Component contents, int ownerX,
  788. int ownerY) {
  789. super.reset(owner, contents, ownerX, ownerY);
  790. Component component = getComponent();
  791. component.setLocation(ownerX, ownerY);
  792. rootPane.getContentPane().add(contents, BorderLayout.CENTER);
  793. contents.invalidate();
  794. component.validate();
  795. pack();
  796. }
  797. }
  798. }