< prev index next >

src/java.desktop/share/classes/javax/swing/PopupFactory.java

Print this page




  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 {
< prev index next >