24 */
25
26 package javax.swing;
27
28 import sun.awt.EmbeddedFrame;
29 import sun.awt.OSInfo;
30 import sun.swing.SwingAccessor;
31
32 import java.applet.Applet;
33 import java.awt.*;
34 import java.awt.event.WindowAdapter;
35 import java.awt.event.WindowEvent;
36 import java.security.AccessController;
37 import java.util.ArrayList;
38 import java.util.HashMap;
39 import java.util.List;
40 import java.util.Map;
41 import static javax.swing.ClientPropertyKey.PopupFactory_FORCE_HEAVYWEIGHT_POPUP;
42
43 /**
44 * <code>PopupFactory</code>, as the name implies, is used to obtain
45 * instances of <code>Popup</code>s. <code>Popup</code>s are used to
46 * display a <code>Component</code> above all other <code>Component</code>s
47 * in a particular containment hierarchy. The general contract is that
48 * once you have obtained a <code>Popup</code> from a
49 * <code>PopupFactory</code>, you must invoke <code>hide</code> on the
50 * <code>Popup</code>. The typical usage is:
51 * <pre>
52 * PopupFactory factory = PopupFactory.getSharedInstance();
53 * Popup popup = factory.getPopup(owner, contents, x, y);
54 * popup.show();
55 * ...
56 * popup.hide();
57 * </pre>
58 *
59 * @see Popup
60 *
61 * @since 1.4
62 */
63 public class PopupFactory {
64
65 static {
66 SwingAccessor.setPopupFactoryAccessor(new SwingAccessor.PopupFactoryAccessor() {
67 @Override
68 public Popup getHeavyWeightPopup(PopupFactory factory, Component owner,
69 Component contents, int ownerX, int ownerY) {
70 return factory.getPopup(owner, contents, ownerX, ownerY, HEAVY_WEIGHT_POPUP);
71 }
72 });
73 }
74 /**
75 * The shared instanceof <code>PopupFactory</code> is per
76 * <code>AppContext</code>. This is the key used in the
77 * <code>AppContext</code> to locate the <code>PopupFactory</code>.
78 */
79 private static final Object SharedInstanceKey =
80 new StringBuffer("PopupFactory.SharedInstanceKey");
81
82 /**
83 * Max number of items to store in any one particular cache.
84 */
85 private static final int MAX_CACHE_SIZE = 5;
86
87 /**
88 * Key used to indicate a light weight popup should be used.
89 */
90 static final int LIGHT_WEIGHT_POPUP = 0;
91
92 /**
93 * Key used to indicate a medium weight Popup should be used.
94 */
95 static final int MEDIUM_WEIGHT_POPUP = 1;
96
97 /*
98 * Key used to indicate a heavy weight Popup should be used.
99 */
100 static final int HEAVY_WEIGHT_POPUP = 2;
101
102 /**
103 * Default type of Popup to create.
104 */
105 private int popupType = LIGHT_WEIGHT_POPUP;
106
107
108 /**
109 * Sets the <code>PopupFactory</code> that will be used to obtain
110 * <code>Popup</code>s.
111 * This will throw an <code>IllegalArgumentException</code> if
112 * <code>factory</code> is null.
113 *
114 * @param factory Shared PopupFactory
115 * @exception IllegalArgumentException if <code>factory</code> is null
116 * @see #getPopup
117 */
118 public static void setSharedInstance(PopupFactory factory) {
119 if (factory == null) {
120 throw new IllegalArgumentException("PopupFactory can not be null");
121 }
122 SwingUtilities.appContextPut(SharedInstanceKey, factory);
123 }
124
125 /**
126 * Returns the shared <code>PopupFactory</code> which can be used
127 * to obtain <code>Popup</code>s.
128 *
129 * @return Shared PopupFactory
130 */
131 public static PopupFactory getSharedInstance() {
132 PopupFactory factory = (PopupFactory)SwingUtilities.appContextGet(
133 SharedInstanceKey);
134
135 if (factory == null) {
136 factory = new PopupFactory();
137 setSharedInstance(factory);
138 }
139 return factory;
140 }
141
142
143 /**
144 * Provides a hint as to the type of <code>Popup</code> that should
145 * be created.
146 */
147 void setPopupType(int type) {
148 popupType = type;
149 }
150
151 /**
152 * Returns the preferred type of Popup to create.
153 */
154 int getPopupType() {
155 return popupType;
156 }
157
158 /**
159 * Creates a <code>Popup</code> for the Component <code>owner</code>
160 * containing the Component <code>contents</code>. <code>owner</code>
161 * is used to determine which <code>Window</code> the new
162 * <code>Popup</code> will parent the <code>Component</code> the
163 * <code>Popup</code> creates to. A null <code>owner</code> implies there
164 * is no valid parent. <code>x</code> and
165 * <code>y</code> specify the preferred initial location to place
166 * the <code>Popup</code> at. Based on screen size, or other paramaters,
167 * the <code>Popup</code> may not display at <code>x</code> and
168 * <code>y</code>.
169 *
170 * @param owner Component mouse coordinates are relative to, may be null
171 * @param contents Contents of the Popup
172 * @param x Initial x screen coordinate
173 * @param y Initial y screen coordinate
174 * @exception IllegalArgumentException if contents is null
175 * @return Popup containing Contents
176 */
177 public Popup getPopup(Component owner, Component contents,
178 int x, int y) throws IllegalArgumentException {
179 if (contents == null) {
180 throw new IllegalArgumentException(
181 "Popup.getPopup must be passed non-null contents");
182 }
183
184 int popupType = getPopupType(owner, contents, x, y);
185 Popup popup = getPopup(owner, contents, x, y, popupType);
186
187 if (popup == null) {
188 // Didn't fit, force to heavy.
209
210 // Check if the parent component is an option pane. If so we need to
211 // force a heavy weight popup in order to have event dispatching work
212 // correctly.
213 Component c = owner;
214 while (c != null) {
215 if (c instanceof JComponent) {
216 if (((JComponent)c).getClientProperty(
217 PopupFactory_FORCE_HEAVYWEIGHT_POPUP) == Boolean.TRUE) {
218 popupType = HEAVY_WEIGHT_POPUP;
219 break;
220 }
221 }
222 c = c.getParent();
223 }
224
225 return popupType;
226 }
227
228 /**
229 * Obtains the appropriate <code>Popup</code> based on
230 * <code>popupType</code>.
231 */
232 private Popup getPopup(Component owner, Component contents,
233 int ownerX, int ownerY, int popupType) {
234 if (GraphicsEnvironment.isHeadless()) {
235 return getHeadlessPopup(owner, contents, ownerX, ownerY);
236 }
237
238 switch(popupType) {
239 case LIGHT_WEIGHT_POPUP:
240 return getLightWeightPopup(owner, contents, ownerX, ownerY);
241 case MEDIUM_WEIGHT_POPUP:
242 return getMediumWeightPopup(owner, contents, ownerX, ownerY);
243 case HEAVY_WEIGHT_POPUP:
244 Popup popup = getHeavyWeightPopup(owner, contents, ownerX, ownerY);
245 if ((AccessController.doPrivileged(OSInfo.getOSTypeAction()) ==
246 OSInfo.OSType.MACOSX) && (owner != null) &&
247 (EmbeddedFrame.getAppletIfAncestorOf(owner) != null)) {
248 ((HeavyWeightPopup)popup).setCacheEnabled(false);
249 }
250 return popup;
274 */
275 private Popup getMediumWeightPopup(Component owner, Component contents,
276 int ownerX, int ownerY) {
277 return MediumWeightPopup.getMediumWeightPopup(owner, contents,
278 ownerX, ownerY);
279 }
280
281 /**
282 * Creates a heavy weight popup.
283 */
284 private Popup getHeavyWeightPopup(Component owner, Component contents,
285 int ownerX, int ownerY) {
286 if (GraphicsEnvironment.isHeadless()) {
287 return getMediumWeightPopup(owner, contents, ownerX, ownerY);
288 }
289 return HeavyWeightPopup.getHeavyWeightPopup(owner, contents, ownerX,
290 ownerY);
291 }
292
293 /**
294 * Returns true if the Component <code>i</code> inside a heavy weight
295 * <code>Popup</code>.
296 */
297 private boolean invokerInHeavyWeightPopup(Component i) {
298 if (i != null) {
299 Container parent;
300 for(parent = i.getParent() ; parent != null ; parent =
301 parent.getParent()) {
302 if (parent instanceof Popup.HeavyWeightWindow) {
303 return true;
304 }
305 }
306 }
307 return false;
308 }
309
310
311 /**
312 * Popup implementation that uses a Window as the popup.
313 */
314 private static class HeavyWeightPopup extends Popup {
315 private static final Object heavyWeightPopupCacheKey =
316 new StringBuffer("PopupFactory.heavyWeightPopupCache");
317
318 private volatile boolean isCacheEnabled = true;
319
320 /**
321 * Returns either a new or recycled <code>Popup</code> containing
322 * the specified children.
323 */
324 static Popup getHeavyWeightPopup(Component owner, Component contents,
325 int ownerX, int ownerY) {
326 Window window = (owner != null) ? SwingUtilities.
327 getWindowAncestor(owner) : null;
328 HeavyWeightPopup popup = null;
329
330 if (window != null) {
331 popup = getRecycledHeavyWeightPopup(window);
332 }
333
334 boolean focusPopup = false;
335 if(contents != null && contents.isFocusable()) {
336 if(contents instanceof JPopupMenu) {
337 JPopupMenu jpm = (JPopupMenu) contents;
338 Component popComps[] = jpm.getComponents();
339 for (Component popComp : popComps) {
340 if (!(popComp instanceof MenuElement) &&
341 !(popComp instanceof JSeparator)) {
356 popup._dispose();
357 }
358
359 popup = new HeavyWeightPopup();
360 }
361
362 popup.reset(owner, contents, ownerX, ownerY);
363
364 if(focusPopup) {
365 JWindow wnd = (JWindow) popup.getComponent();
366 wnd.setFocusableWindowState(true);
367 // Set window name. We need this in BasicPopupMenuUI
368 // to identify focusable popup window.
369 wnd.setName("###focusableSwingPopup###");
370 }
371
372 return popup;
373 }
374
375 /**
376 * Returns a previously disposed heavy weight <code>Popup</code>
377 * associated with <code>window</code>. This will return null if
378 * there is no <code>HeavyWeightPopup</code> associated with
379 * <code>window</code>.
380 */
381 private static HeavyWeightPopup getRecycledHeavyWeightPopup(Window w) {
382 synchronized (HeavyWeightPopup.class) {
383 List<HeavyWeightPopup> cache;
384 Map<Window, List<HeavyWeightPopup>> heavyPopupCache = getHeavyWeightPopupCache();
385
386 if (heavyPopupCache.containsKey(w)) {
387 cache = heavyPopupCache.get(w);
388 } else {
389 return null;
390 }
391 if (cache.size() > 0) {
392 HeavyWeightPopup r = cache.get(0);
393 cache.remove(0);
394 return r;
395 }
396 return null;
397 }
398 }
399
400 /**
401 * Returns the cache to use for heavy weight popups. Maps from
402 * <code>Window</code> to a <code>List</code> of
403 * <code>HeavyWeightPopup</code>s.
404 */
405 @SuppressWarnings("unchecked")
406 private static Map<Window, List<HeavyWeightPopup>> getHeavyWeightPopupCache() {
407 synchronized (HeavyWeightPopup.class) {
408 Map<Window, List<HeavyWeightPopup>> cache = (Map<Window, List<HeavyWeightPopup>>)SwingUtilities.appContextGet(
409 heavyWeightPopupCacheKey);
410
411 if (cache == null) {
412 cache = new HashMap<>(2);
413 SwingUtilities.appContextPut(heavyWeightPopupCacheKey,
414 cache);
415 }
416 return cache;
417 }
418 }
419
420 /**
421 * Recycles the passed in <code>HeavyWeightPopup</code>.
422 */
423 private static void recycleHeavyWeightPopup(HeavyWeightPopup popup) {
424 synchronized (HeavyWeightPopup.class) {
425 List<HeavyWeightPopup> cache;
426 Window window = SwingUtilities.getWindowAncestor(
427 popup.getComponent());
428 Map<Window, List<HeavyWeightPopup>> heavyPopupCache = getHeavyWeightPopupCache();
429
430 if (window instanceof Popup.DefaultFrame ||
431 !window.isVisible()) {
432 // If the Window isn't visible, we don't cache it as we
433 // likely won't ever get a windowClosed event to clean up.
434 // We also don't cache DefaultFrames as this indicates
435 // there wasn't a valid Window parent, and thus we don't
436 // know when to clean up.
437 popup._dispose();
438 return;
439 } else if (heavyPopupCache.containsKey(window)) {
440 cache = heavyPopupCache.get(window);
441 } else {
475 /**
476 * Enables or disables cache for current object.
477 */
478 void setCacheEnabled(boolean enable) {
479 isCacheEnabled = enable;
480 }
481
482 //
483 // Popup methods
484 //
485 public void hide() {
486 super.hide();
487 if (isCacheEnabled) {
488 recycleHeavyWeightPopup(this);
489 } else {
490 this._dispose();
491 }
492 }
493
494 /**
495 * As we recycle the <code>Window</code>, we don't want to dispose it,
496 * thus this method does nothing, instead use <code>_dipose</code>
497 * which will handle the disposing.
498 */
499 void dispose() {
500 }
501
502 void _dispose() {
503 super.dispose();
504 }
505 }
506
507
508
509 /**
510 * ContainerPopup consolidates the common code used in the light/medium
511 * weight implementations of <code>Popup</code>.
512 */
513 private static class ContainerPopup extends Popup {
514 /** Component we are to be added to. */
515 Component owner;
516 /** Desired x location. */
517 int x;
518 /** Desired y location. */
519 int y;
520
521 public void hide() {
522 Component component = getComponent();
523
524 if (component != null) {
525 Container parent = component.getParent();
526
527 if (parent != null) {
528 Rectangle bounds = component.getBounds();
529
530 parent.remove(component);
531 parent.repaint(bounds.x, bounds.y, bounds.width,
660
661 Component createComponent(Component owner) {
662 return new Panel(new BorderLayout());
663 }
664
665 public void show() {
666 }
667 public void hide() {
668 }
669 }
670
671
672 /**
673 * Popup implementation that uses a JPanel as the popup.
674 */
675 private static class LightWeightPopup extends ContainerPopup {
676 private static final Object lightWeightPopupCacheKey =
677 new StringBuffer("PopupFactory.lightPopupCache");
678
679 /**
680 * Returns a light weight <code>Popup</code> implementation. If
681 * the <code>Popup</code> needs more space that in available in
682 * <code>owner</code>, this will return null.
683 */
684 static Popup getLightWeightPopup(Component owner, Component contents,
685 int ownerX, int ownerY) {
686 LightWeightPopup popup = getRecycledLightWeightPopup();
687
688 if (popup == null) {
689 popup = new LightWeightPopup();
690 }
691 popup.reset(owner, contents, ownerX, ownerY);
692 if (!popup.fitsOnScreen() ||
693 popup.overlappedByOwnedWindow()) {
694 popup.hide();
695 return null;
696 }
697 return popup;
698 }
699
700 /**
701 * Returns the cache to use for heavy weight popups.
702 */
703 @SuppressWarnings("unchecked")
704 private static List<LightWeightPopup> getLightWeightPopupCache() {
705 List<LightWeightPopup> cache = (List<LightWeightPopup>)SwingUtilities.appContextGet(
706 lightWeightPopupCacheKey);
707 if (cache == null) {
708 cache = new ArrayList<>();
709 SwingUtilities.appContextPut(lightWeightPopupCacheKey, cache);
710 }
711 return cache;
712 }
713
714 /**
715 * Recycles the LightWeightPopup <code>popup</code>.
716 */
717 private static void recycleLightWeightPopup(LightWeightPopup popup) {
718 synchronized (LightWeightPopup.class) {
719 List<LightWeightPopup> lightPopupCache = getLightWeightPopupCache();
720 if (lightPopupCache.size() < MAX_CACHE_SIZE) {
721 lightPopupCache.add(popup);
722 }
723 }
724 }
725
726 /**
727 * Returns a previously used <code>LightWeightPopup</code>, or null
728 * if none of the popups have been recycled.
729 */
730 private static LightWeightPopup getRecycledLightWeightPopup() {
731 synchronized (LightWeightPopup.class) {
732 List<LightWeightPopup> lightPopupCache = getLightWeightPopupCache();
733 if (lightPopupCache.size() > 0) {
734 LightWeightPopup r = lightPopupCache.get(0);
735 lightPopupCache.remove(0);
736 return r;
737 }
738 return null;
739 }
740 }
741
742
743
744 //
745 // Popup methods
746 //
747 public void hide() {
788 component.setLocation(p.x, p.y);
789 if (parent instanceof JLayeredPane) {
790 parent.add(component, JLayeredPane.POPUP_LAYER, 0);
791 } else {
792 parent.add(component);
793 }
794 }
795
796 Component createComponent(Component owner) {
797 JComponent component = new JPanel(new BorderLayout(), true);
798
799 component.setOpaque(true);
800 return component;
801 }
802
803 //
804 // Local methods
805 //
806
807 /**
808 * Resets the <code>Popup</code> to an initial state.
809 */
810 void reset(Component owner, Component contents, int ownerX,
811 int ownerY) {
812 super.reset(owner, contents, ownerX, ownerY);
813
814 JComponent component = (JComponent)getComponent();
815
816 component.setOpaque(contents.isOpaque());
817 component.setLocation(ownerX, ownerY);
818 component.add(contents, BorderLayout.CENTER);
819 contents.invalidate();
820 pack();
821 }
822 }
823
824
825 /**
826 * Popup implementation that uses a Panel as the popup.
827 */
828 private static class MediumWeightPopup extends ContainerPopup {
829 private static final Object mediumWeightPopupCacheKey =
830 new StringBuffer("PopupFactory.mediumPopupCache");
831
832 /** Child of the panel. The contents are added to this. */
833 private JRootPane rootPane;
834
835
836 /**
837 * Returns a medium weight <code>Popup</code> implementation. If
838 * the <code>Popup</code> needs more space that in available in
839 * <code>owner</code>, this will return null.
840 */
841 static Popup getMediumWeightPopup(Component owner, Component contents,
842 int ownerX, int ownerY) {
843 MediumWeightPopup popup = getRecycledMediumWeightPopup();
844
845 if (popup == null) {
846 popup = new MediumWeightPopup();
847 }
848 popup.reset(owner, contents, ownerX, ownerY);
849 if (!popup.fitsOnScreen() ||
850 popup.overlappedByOwnedWindow()) {
851 popup.hide();
852 return null;
853 }
854 return popup;
855 }
856
857 /**
858 * Returns the cache to use for medium weight popups.
859 */
860 @SuppressWarnings("unchecked")
861 private static List<MediumWeightPopup> getMediumWeightPopupCache() {
862 List<MediumWeightPopup> cache = (List<MediumWeightPopup>)SwingUtilities.appContextGet(
863 mediumWeightPopupCacheKey);
864
865 if (cache == null) {
866 cache = new ArrayList<>();
867 SwingUtilities.appContextPut(mediumWeightPopupCacheKey, cache);
868 }
869 return cache;
870 }
871
872 /**
873 * Recycles the MediumWeightPopup <code>popup</code>.
874 */
875 private static void recycleMediumWeightPopup(MediumWeightPopup popup) {
876 synchronized (MediumWeightPopup.class) {
877 List<MediumWeightPopup> mediumPopupCache = getMediumWeightPopupCache();
878 if (mediumPopupCache.size() < MAX_CACHE_SIZE) {
879 mediumPopupCache.add(popup);
880 }
881 }
882 }
883
884 /**
885 * Returns a previously used <code>MediumWeightPopup</code>, or null
886 * if none of the popups have been recycled.
887 */
888 private static MediumWeightPopup getRecycledMediumWeightPopup() {
889 synchronized (MediumWeightPopup.class) {
890 List<MediumWeightPopup> mediumPopupCache = getMediumWeightPopupCache();
891 if (mediumPopupCache.size() > 0) {
892 MediumWeightPopup r = mediumPopupCache.get(0);
893 mediumPopupCache.remove(0);
894 return r;
895 }
896 return null;
897 }
898 }
899
900
901 //
902 // Popup
903 //
904
905 public void hide() {
943 component.setVisible(false);
944 parent.add(component);
945 }
946 component.setVisible(true);
947 }
948
949 Component createComponent(Component owner) {
950 Panel component = new MediumWeightComponent();
951
952 rootPane = new JRootPane();
953 // NOTE: this uses setOpaque vs LookAndFeel.installProperty as
954 // there is NO reason for the RootPane not to be opaque. For
955 // painting to work the contentPane must be opaque, therefor the
956 // RootPane can also be opaque.
957 rootPane.setOpaque(true);
958 component.add(rootPane, BorderLayout.CENTER);
959 return component;
960 }
961
962 /**
963 * Resets the <code>Popup</code> to an initial state.
964 */
965 void reset(Component owner, Component contents, int ownerX,
966 int ownerY) {
967 super.reset(owner, contents, ownerX, ownerY);
968
969 Component component = getComponent();
970
971 component.setLocation(ownerX, ownerY);
972 rootPane.getContentPane().add(contents, BorderLayout.CENTER);
973 contents.invalidate();
974 component.validate();
975 pack();
976 }
977
978
979 // This implements SwingHeavyWeight so that repaints on it
980 // are processed by the RepaintManager and SwingPaintEventDispatcher.
981 @SuppressWarnings("serial") // JDK-implementation class
982 private static class MediumWeightComponent extends Panel implements
983 SwingHeavyWeight {
|
24 */
25
26 package javax.swing;
27
28 import sun.awt.EmbeddedFrame;
29 import sun.awt.OSInfo;
30 import sun.swing.SwingAccessor;
31
32 import java.applet.Applet;
33 import java.awt.*;
34 import java.awt.event.WindowAdapter;
35 import java.awt.event.WindowEvent;
36 import java.security.AccessController;
37 import java.util.ArrayList;
38 import java.util.HashMap;
39 import java.util.List;
40 import java.util.Map;
41 import static javax.swing.ClientPropertyKey.PopupFactory_FORCE_HEAVYWEIGHT_POPUP;
42
43 /**
44 * {@code PopupFactory}, as the name implies, is used to obtain
45 * instances of {@code Popup}s. {@code Popup}s are used to
46 * display a {@code Component} above all other {@code Component}s
47 * in a particular containment hierarchy. The general contract is that
48 * once you have obtained a {@code Popup} from a
49 * {@code PopupFactory}, you must invoke {@code hide} on the
50 * {@code Popup}. The typical usage is:
51 * <pre>
52 * PopupFactory factory = PopupFactory.getSharedInstance();
53 * Popup popup = factory.getPopup(owner, contents, x, y);
54 * popup.show();
55 * ...
56 * popup.hide();
57 * </pre>
58 *
59 * @see Popup
60 *
61 * @since 1.4
62 */
63 public class PopupFactory {
64
65 static {
66 SwingAccessor.setPopupFactoryAccessor(new SwingAccessor.PopupFactoryAccessor() {
67 @Override
68 public Popup getHeavyWeightPopup(PopupFactory factory, Component owner,
69 Component contents, int ownerX, int ownerY) {
70 return factory.getPopup(owner, contents, ownerX, ownerY, HEAVY_WEIGHT_POPUP);
71 }
72 });
73 }
74 /**
75 * The shared instanceof {@code PopupFactory} is per
76 * {@code AppContext}. This is the key used in the
77 * {@code AppContext} to locate the {@code PopupFactory}.
78 */
79 private static final Object SharedInstanceKey =
80 new StringBuffer("PopupFactory.SharedInstanceKey");
81
82 /**
83 * Max number of items to store in any one particular cache.
84 */
85 private static final int MAX_CACHE_SIZE = 5;
86
87 /**
88 * Key used to indicate a light weight popup should be used.
89 */
90 static final int LIGHT_WEIGHT_POPUP = 0;
91
92 /**
93 * Key used to indicate a medium weight Popup should be used.
94 */
95 static final int MEDIUM_WEIGHT_POPUP = 1;
96
97 /*
98 * Key used to indicate a heavy weight Popup should be used.
99 */
100 static final int HEAVY_WEIGHT_POPUP = 2;
101
102 /**
103 * Default type of Popup to create.
104 */
105 private int popupType = LIGHT_WEIGHT_POPUP;
106
107
108 /**
109 * Sets the {@code PopupFactory} that will be used to obtain
110 * {@code Popup}s.
111 * This will throw an {@code IllegalArgumentException} if
112 * {@code factory} is null.
113 *
114 * @param factory Shared PopupFactory
115 * @exception IllegalArgumentException if {@code factory} is null
116 * @see #getPopup
117 */
118 public static void setSharedInstance(PopupFactory factory) {
119 if (factory == null) {
120 throw new IllegalArgumentException("PopupFactory can not be null");
121 }
122 SwingUtilities.appContextPut(SharedInstanceKey, factory);
123 }
124
125 /**
126 * Returns the shared {@code PopupFactory} which can be used
127 * to obtain {@code Popup}s.
128 *
129 * @return Shared PopupFactory
130 */
131 public static PopupFactory getSharedInstance() {
132 PopupFactory factory = (PopupFactory)SwingUtilities.appContextGet(
133 SharedInstanceKey);
134
135 if (factory == null) {
136 factory = new PopupFactory();
137 setSharedInstance(factory);
138 }
139 return factory;
140 }
141
142
143 /**
144 * Provides a hint as to the type of {@code Popup} that should
145 * be created.
146 */
147 void setPopupType(int type) {
148 popupType = type;
149 }
150
151 /**
152 * Returns the preferred type of Popup to create.
153 */
154 int getPopupType() {
155 return popupType;
156 }
157
158 /**
159 * Creates a {@code Popup} for the Component {@code owner}
160 * containing the Component {@code contents}. {@code owner}
161 * is used to determine which {@code Window} the new
162 * {@code Popup} will parent the {@code Component} the
163 * {@code Popup} creates to. A null {@code owner} implies there
164 * is no valid parent. {@code x} and
165 * {@code y} specify the preferred initial location to place
166 * the {@code Popup} at. Based on screen size, or other paramaters,
167 * the {@code Popup} may not display at {@code x} and
168 * {@code y}.
169 *
170 * @param owner Component mouse coordinates are relative to, may be null
171 * @param contents Contents of the Popup
172 * @param x Initial x screen coordinate
173 * @param y Initial y screen coordinate
174 * @exception IllegalArgumentException if contents is null
175 * @return Popup containing Contents
176 */
177 public Popup getPopup(Component owner, Component contents,
178 int x, int y) throws IllegalArgumentException {
179 if (contents == null) {
180 throw new IllegalArgumentException(
181 "Popup.getPopup must be passed non-null contents");
182 }
183
184 int popupType = getPopupType(owner, contents, x, y);
185 Popup popup = getPopup(owner, contents, x, y, popupType);
186
187 if (popup == null) {
188 // Didn't fit, force to heavy.
209
210 // Check if the parent component is an option pane. If so we need to
211 // force a heavy weight popup in order to have event dispatching work
212 // correctly.
213 Component c = owner;
214 while (c != null) {
215 if (c instanceof JComponent) {
216 if (((JComponent)c).getClientProperty(
217 PopupFactory_FORCE_HEAVYWEIGHT_POPUP) == Boolean.TRUE) {
218 popupType = HEAVY_WEIGHT_POPUP;
219 break;
220 }
221 }
222 c = c.getParent();
223 }
224
225 return popupType;
226 }
227
228 /**
229 * Obtains the appropriate {@code Popup} based on
230 * {@code popupType}.
231 */
232 private Popup getPopup(Component owner, Component contents,
233 int ownerX, int ownerY, int popupType) {
234 if (GraphicsEnvironment.isHeadless()) {
235 return getHeadlessPopup(owner, contents, ownerX, ownerY);
236 }
237
238 switch(popupType) {
239 case LIGHT_WEIGHT_POPUP:
240 return getLightWeightPopup(owner, contents, ownerX, ownerY);
241 case MEDIUM_WEIGHT_POPUP:
242 return getMediumWeightPopup(owner, contents, ownerX, ownerY);
243 case HEAVY_WEIGHT_POPUP:
244 Popup popup = getHeavyWeightPopup(owner, contents, ownerX, ownerY);
245 if ((AccessController.doPrivileged(OSInfo.getOSTypeAction()) ==
246 OSInfo.OSType.MACOSX) && (owner != null) &&
247 (EmbeddedFrame.getAppletIfAncestorOf(owner) != null)) {
248 ((HeavyWeightPopup)popup).setCacheEnabled(false);
249 }
250 return popup;
274 */
275 private Popup getMediumWeightPopup(Component owner, Component contents,
276 int ownerX, int ownerY) {
277 return MediumWeightPopup.getMediumWeightPopup(owner, contents,
278 ownerX, ownerY);
279 }
280
281 /**
282 * Creates a heavy weight popup.
283 */
284 private Popup getHeavyWeightPopup(Component owner, Component contents,
285 int ownerX, int ownerY) {
286 if (GraphicsEnvironment.isHeadless()) {
287 return getMediumWeightPopup(owner, contents, ownerX, ownerY);
288 }
289 return HeavyWeightPopup.getHeavyWeightPopup(owner, contents, ownerX,
290 ownerY);
291 }
292
293 /**
294 * Returns true if the Component {@code i} inside a heavy weight
295 * {@code Popup}.
296 */
297 private boolean invokerInHeavyWeightPopup(Component i) {
298 if (i != null) {
299 Container parent;
300 for(parent = i.getParent() ; parent != null ; parent =
301 parent.getParent()) {
302 if (parent instanceof Popup.HeavyWeightWindow) {
303 return true;
304 }
305 }
306 }
307 return false;
308 }
309
310
311 /**
312 * Popup implementation that uses a Window as the popup.
313 */
314 private static class HeavyWeightPopup extends Popup {
315 private static final Object heavyWeightPopupCacheKey =
316 new StringBuffer("PopupFactory.heavyWeightPopupCache");
317
318 private volatile boolean isCacheEnabled = true;
319
320 /**
321 * Returns either a new or recycled {@code Popup} containing
322 * the specified children.
323 */
324 static Popup getHeavyWeightPopup(Component owner, Component contents,
325 int ownerX, int ownerY) {
326 Window window = (owner != null) ? SwingUtilities.
327 getWindowAncestor(owner) : null;
328 HeavyWeightPopup popup = null;
329
330 if (window != null) {
331 popup = getRecycledHeavyWeightPopup(window);
332 }
333
334 boolean focusPopup = false;
335 if(contents != null && contents.isFocusable()) {
336 if(contents instanceof JPopupMenu) {
337 JPopupMenu jpm = (JPopupMenu) contents;
338 Component popComps[] = jpm.getComponents();
339 for (Component popComp : popComps) {
340 if (!(popComp instanceof MenuElement) &&
341 !(popComp instanceof JSeparator)) {
356 popup._dispose();
357 }
358
359 popup = new HeavyWeightPopup();
360 }
361
362 popup.reset(owner, contents, ownerX, ownerY);
363
364 if(focusPopup) {
365 JWindow wnd = (JWindow) popup.getComponent();
366 wnd.setFocusableWindowState(true);
367 // Set window name. We need this in BasicPopupMenuUI
368 // to identify focusable popup window.
369 wnd.setName("###focusableSwingPopup###");
370 }
371
372 return popup;
373 }
374
375 /**
376 * Returns a previously disposed heavy weight {@code Popup}
377 * associated with {@code window}. This will return null if
378 * there is no {@code HeavyWeightPopup} associated with
379 * {@code window}.
380 */
381 private static HeavyWeightPopup getRecycledHeavyWeightPopup(Window w) {
382 synchronized (HeavyWeightPopup.class) {
383 List<HeavyWeightPopup> cache;
384 Map<Window, List<HeavyWeightPopup>> heavyPopupCache = getHeavyWeightPopupCache();
385
386 if (heavyPopupCache.containsKey(w)) {
387 cache = heavyPopupCache.get(w);
388 } else {
389 return null;
390 }
391 if (cache.size() > 0) {
392 HeavyWeightPopup r = cache.get(0);
393 cache.remove(0);
394 return r;
395 }
396 return null;
397 }
398 }
399
400 /**
401 * Returns the cache to use for heavy weight popups. Maps from
402 * {@code Window} to a {@code List} of
403 * {@code HeavyWeightPopup}s.
404 */
405 @SuppressWarnings("unchecked")
406 private static Map<Window, List<HeavyWeightPopup>> getHeavyWeightPopupCache() {
407 synchronized (HeavyWeightPopup.class) {
408 Map<Window, List<HeavyWeightPopup>> cache = (Map<Window, List<HeavyWeightPopup>>)SwingUtilities.appContextGet(
409 heavyWeightPopupCacheKey);
410
411 if (cache == null) {
412 cache = new HashMap<>(2);
413 SwingUtilities.appContextPut(heavyWeightPopupCacheKey,
414 cache);
415 }
416 return cache;
417 }
418 }
419
420 /**
421 * Recycles the passed in {@code HeavyWeightPopup}.
422 */
423 private static void recycleHeavyWeightPopup(HeavyWeightPopup popup) {
424 synchronized (HeavyWeightPopup.class) {
425 List<HeavyWeightPopup> cache;
426 Window window = SwingUtilities.getWindowAncestor(
427 popup.getComponent());
428 Map<Window, List<HeavyWeightPopup>> heavyPopupCache = getHeavyWeightPopupCache();
429
430 if (window instanceof Popup.DefaultFrame ||
431 !window.isVisible()) {
432 // If the Window isn't visible, we don't cache it as we
433 // likely won't ever get a windowClosed event to clean up.
434 // We also don't cache DefaultFrames as this indicates
435 // there wasn't a valid Window parent, and thus we don't
436 // know when to clean up.
437 popup._dispose();
438 return;
439 } else if (heavyPopupCache.containsKey(window)) {
440 cache = heavyPopupCache.get(window);
441 } else {
475 /**
476 * Enables or disables cache for current object.
477 */
478 void setCacheEnabled(boolean enable) {
479 isCacheEnabled = enable;
480 }
481
482 //
483 // Popup methods
484 //
485 public void hide() {
486 super.hide();
487 if (isCacheEnabled) {
488 recycleHeavyWeightPopup(this);
489 } else {
490 this._dispose();
491 }
492 }
493
494 /**
495 * As we recycle the {@code Window}, we don't want to dispose it,
496 * thus this method does nothing, instead use {@code _dipose}
497 * which will handle the disposing.
498 */
499 void dispose() {
500 }
501
502 void _dispose() {
503 super.dispose();
504 }
505 }
506
507
508
509 /**
510 * ContainerPopup consolidates the common code used in the light/medium
511 * weight implementations of {@code Popup}.
512 */
513 private static class ContainerPopup extends Popup {
514 /** Component we are to be added to. */
515 Component owner;
516 /** Desired x location. */
517 int x;
518 /** Desired y location. */
519 int y;
520
521 public void hide() {
522 Component component = getComponent();
523
524 if (component != null) {
525 Container parent = component.getParent();
526
527 if (parent != null) {
528 Rectangle bounds = component.getBounds();
529
530 parent.remove(component);
531 parent.repaint(bounds.x, bounds.y, bounds.width,
660
661 Component createComponent(Component owner) {
662 return new Panel(new BorderLayout());
663 }
664
665 public void show() {
666 }
667 public void hide() {
668 }
669 }
670
671
672 /**
673 * Popup implementation that uses a JPanel as the popup.
674 */
675 private static class LightWeightPopup extends ContainerPopup {
676 private static final Object lightWeightPopupCacheKey =
677 new StringBuffer("PopupFactory.lightPopupCache");
678
679 /**
680 * Returns a light weight {@code Popup} implementation. If
681 * the {@code Popup} needs more space that in available in
682 * {@code owner}, this will return null.
683 */
684 static Popup getLightWeightPopup(Component owner, Component contents,
685 int ownerX, int ownerY) {
686 LightWeightPopup popup = getRecycledLightWeightPopup();
687
688 if (popup == null) {
689 popup = new LightWeightPopup();
690 }
691 popup.reset(owner, contents, ownerX, ownerY);
692 if (!popup.fitsOnScreen() ||
693 popup.overlappedByOwnedWindow()) {
694 popup.hide();
695 return null;
696 }
697 return popup;
698 }
699
700 /**
701 * Returns the cache to use for heavy weight popups.
702 */
703 @SuppressWarnings("unchecked")
704 private static List<LightWeightPopup> getLightWeightPopupCache() {
705 List<LightWeightPopup> cache = (List<LightWeightPopup>)SwingUtilities.appContextGet(
706 lightWeightPopupCacheKey);
707 if (cache == null) {
708 cache = new ArrayList<>();
709 SwingUtilities.appContextPut(lightWeightPopupCacheKey, cache);
710 }
711 return cache;
712 }
713
714 /**
715 * Recycles the LightWeightPopup {@code popup}.
716 */
717 private static void recycleLightWeightPopup(LightWeightPopup popup) {
718 synchronized (LightWeightPopup.class) {
719 List<LightWeightPopup> lightPopupCache = getLightWeightPopupCache();
720 if (lightPopupCache.size() < MAX_CACHE_SIZE) {
721 lightPopupCache.add(popup);
722 }
723 }
724 }
725
726 /**
727 * Returns a previously used {@code LightWeightPopup}, or null
728 * if none of the popups have been recycled.
729 */
730 private static LightWeightPopup getRecycledLightWeightPopup() {
731 synchronized (LightWeightPopup.class) {
732 List<LightWeightPopup> lightPopupCache = getLightWeightPopupCache();
733 if (lightPopupCache.size() > 0) {
734 LightWeightPopup r = lightPopupCache.get(0);
735 lightPopupCache.remove(0);
736 return r;
737 }
738 return null;
739 }
740 }
741
742
743
744 //
745 // Popup methods
746 //
747 public void hide() {
788 component.setLocation(p.x, p.y);
789 if (parent instanceof JLayeredPane) {
790 parent.add(component, JLayeredPane.POPUP_LAYER, 0);
791 } else {
792 parent.add(component);
793 }
794 }
795
796 Component createComponent(Component owner) {
797 JComponent component = new JPanel(new BorderLayout(), true);
798
799 component.setOpaque(true);
800 return component;
801 }
802
803 //
804 // Local methods
805 //
806
807 /**
808 * Resets the {@code Popup} to an initial state.
809 */
810 void reset(Component owner, Component contents, int ownerX,
811 int ownerY) {
812 super.reset(owner, contents, ownerX, ownerY);
813
814 JComponent component = (JComponent)getComponent();
815
816 component.setOpaque(contents.isOpaque());
817 component.setLocation(ownerX, ownerY);
818 component.add(contents, BorderLayout.CENTER);
819 contents.invalidate();
820 pack();
821 }
822 }
823
824
825 /**
826 * Popup implementation that uses a Panel as the popup.
827 */
828 private static class MediumWeightPopup extends ContainerPopup {
829 private static final Object mediumWeightPopupCacheKey =
830 new StringBuffer("PopupFactory.mediumPopupCache");
831
832 /** Child of the panel. The contents are added to this. */
833 private JRootPane rootPane;
834
835
836 /**
837 * Returns a medium weight {@code Popup} implementation. If
838 * the {@code Popup} needs more space that in available in
839 * {@code owner}, this will return null.
840 */
841 static Popup getMediumWeightPopup(Component owner, Component contents,
842 int ownerX, int ownerY) {
843 MediumWeightPopup popup = getRecycledMediumWeightPopup();
844
845 if (popup == null) {
846 popup = new MediumWeightPopup();
847 }
848 popup.reset(owner, contents, ownerX, ownerY);
849 if (!popup.fitsOnScreen() ||
850 popup.overlappedByOwnedWindow()) {
851 popup.hide();
852 return null;
853 }
854 return popup;
855 }
856
857 /**
858 * Returns the cache to use for medium weight popups.
859 */
860 @SuppressWarnings("unchecked")
861 private static List<MediumWeightPopup> getMediumWeightPopupCache() {
862 List<MediumWeightPopup> cache = (List<MediumWeightPopup>)SwingUtilities.appContextGet(
863 mediumWeightPopupCacheKey);
864
865 if (cache == null) {
866 cache = new ArrayList<>();
867 SwingUtilities.appContextPut(mediumWeightPopupCacheKey, cache);
868 }
869 return cache;
870 }
871
872 /**
873 * Recycles the MediumWeightPopup {@code popup}.
874 */
875 private static void recycleMediumWeightPopup(MediumWeightPopup popup) {
876 synchronized (MediumWeightPopup.class) {
877 List<MediumWeightPopup> mediumPopupCache = getMediumWeightPopupCache();
878 if (mediumPopupCache.size() < MAX_CACHE_SIZE) {
879 mediumPopupCache.add(popup);
880 }
881 }
882 }
883
884 /**
885 * Returns a previously used {@code MediumWeightPopup}, or null
886 * if none of the popups have been recycled.
887 */
888 private static MediumWeightPopup getRecycledMediumWeightPopup() {
889 synchronized (MediumWeightPopup.class) {
890 List<MediumWeightPopup> mediumPopupCache = getMediumWeightPopupCache();
891 if (mediumPopupCache.size() > 0) {
892 MediumWeightPopup r = mediumPopupCache.get(0);
893 mediumPopupCache.remove(0);
894 return r;
895 }
896 return null;
897 }
898 }
899
900
901 //
902 // Popup
903 //
904
905 public void hide() {
943 component.setVisible(false);
944 parent.add(component);
945 }
946 component.setVisible(true);
947 }
948
949 Component createComponent(Component owner) {
950 Panel component = new MediumWeightComponent();
951
952 rootPane = new JRootPane();
953 // NOTE: this uses setOpaque vs LookAndFeel.installProperty as
954 // there is NO reason for the RootPane not to be opaque. For
955 // painting to work the contentPane must be opaque, therefor the
956 // RootPane can also be opaque.
957 rootPane.setOpaque(true);
958 component.add(rootPane, BorderLayout.CENTER);
959 return component;
960 }
961
962 /**
963 * Resets the {@code Popup} to an initial state.
964 */
965 void reset(Component owner, Component contents, int ownerX,
966 int ownerY) {
967 super.reset(owner, contents, ownerX, ownerY);
968
969 Component component = getComponent();
970
971 component.setLocation(ownerX, ownerY);
972 rootPane.getContentPane().add(contents, BorderLayout.CENTER);
973 contents.invalidate();
974 component.validate();
975 pack();
976 }
977
978
979 // This implements SwingHeavyWeight so that repaints on it
980 // are processed by the RepaintManager and SwingPaintEventDispatcher.
981 @SuppressWarnings("serial") // JDK-implementation class
982 private static class MediumWeightComponent extends Panel implements
983 SwingHeavyWeight {
|