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