1 /*
   2  * Copyright (c) 1999, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  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.
 189             popup = getPopup(owner, contents, x, y, HEAVY_WEIGHT_POPUP);
 190         }
 191         return popup;
 192     }
 193 
 194     /**
 195      * Returns the popup type to use for the specified parameters.
 196      */
 197     private int getPopupType(Component owner, Component contents,
 198                              int ownerX, int ownerY) {
 199         int popupType = getPopupType();
 200 
 201         if (owner == null || invokerInHeavyWeightPopup(owner)) {
 202             popupType = HEAVY_WEIGHT_POPUP;
 203         }
 204         else if (popupType == LIGHT_WEIGHT_POPUP &&
 205                  !(contents instanceof JToolTip) &&
 206                  !(contents instanceof JPopupMenu)) {
 207             popupType = MEDIUM_WEIGHT_POPUP;
 208         }
 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) && (EmbeddedFrame.getAppletIfAncestorOf(owner) != null)) {
 247                 ((HeavyWeightPopup)popup).setCacheEnabled(false);
 248             }
 249             return popup;
 250         }
 251         return null;
 252     }
 253 
 254     /**
 255      * Creates a headless popup
 256      */
 257     private Popup getHeadlessPopup(Component owner, Component contents,
 258                                    int ownerX, int ownerY) {
 259         return HeadlessPopup.getHeadlessPopup(owner, contents, ownerX, ownerY);
 260     }
 261 
 262     /**
 263      * Creates a light weight popup.
 264      */
 265     private Popup getLightWeightPopup(Component owner, Component contents,
 266                                          int ownerX, int ownerY) {
 267         return LightWeightPopup.getLightWeightPopup(owner, contents, ownerX,
 268                                                     ownerY);
 269     }
 270 
 271     /**
 272      * Creates a medium weight popup.
 273      */
 274     private Popup getMediumWeightPopup(Component owner, Component contents,
 275                                           int ownerX, int ownerY) {
 276         return MediumWeightPopup.getMediumWeightPopup(owner, contents,
 277                                                       ownerX, ownerY);
 278     }
 279 
 280     /**
 281      * Creates a heavy weight popup.
 282      */
 283     private Popup getHeavyWeightPopup(Component owner, Component contents,
 284                                          int ownerX, int ownerY) {
 285         if (GraphicsEnvironment.isHeadless()) {
 286             return getMediumWeightPopup(owner, contents, ownerX, ownerY);
 287         }
 288         return HeavyWeightPopup.getHeavyWeightPopup(owner, contents, ownerX,
 289                                                     ownerY);
 290     }
 291 
 292     /**
 293      * Returns true if the Component <code>i</code> inside a heavy weight
 294      * <code>Popup</code>.
 295      */
 296     private boolean invokerInHeavyWeightPopup(Component i) {
 297         if (i != null) {
 298             Container parent;
 299             for(parent = i.getParent() ; parent != null ; parent =
 300                     parent.getParent()) {
 301                 if (parent instanceof Popup.HeavyWeightWindow) {
 302                     return true;
 303                 }
 304             }
 305         }
 306         return false;
 307     }
 308 
 309 
 310     /**
 311      * Popup implementation that uses a Window as the popup.
 312      */
 313     private static class HeavyWeightPopup extends Popup {
 314         private static final Object heavyWeightPopupCacheKey =
 315                  new StringBuffer("PopupFactory.heavyWeightPopupCache");
 316 
 317         private volatile boolean isCacheEnabled = true;
 318 
 319         /**
 320          * Returns either a new or recycled <code>Popup</code> containing
 321          * the specified children.
 322          */
 323         static Popup getHeavyWeightPopup(Component owner, Component contents,
 324                                          int ownerX, int ownerY) {
 325             Window window = (owner != null) ? SwingUtilities.
 326                               getWindowAncestor(owner) : null;
 327             HeavyWeightPopup popup = null;
 328 
 329             if (window != null) {
 330                 popup = getRecycledHeavyWeightPopup(window);
 331             }
 332 
 333             boolean focusPopup = false;
 334             if(contents != null && contents.isFocusable()) {
 335                 if(contents instanceof JPopupMenu) {
 336                     JPopupMenu jpm = (JPopupMenu) contents;
 337                     Component popComps[] = jpm.getComponents();
 338                     for (Component popComp : popComps) {
 339                         if (!(popComp instanceof MenuElement) &&
 340                                 !(popComp instanceof JSeparator)) {
 341                             focusPopup = true;
 342                             break;
 343                         }
 344                     }
 345                 }
 346             }
 347 
 348             if (popup == null ||
 349                 ((JWindow) popup.getComponent())
 350                  .getFocusableWindowState() != focusPopup) {
 351 
 352                 if(popup != null) {
 353                     // The recycled popup can't serve us well
 354                     // dispose it and create new one
 355                     popup._dispose();
 356                 }
 357 
 358                 popup = new HeavyWeightPopup();
 359             }
 360 
 361             popup.reset(owner, contents, ownerX, ownerY);
 362 
 363             if(focusPopup) {
 364                 JWindow wnd = (JWindow) popup.getComponent();
 365                 wnd.setFocusableWindowState(true);
 366                 // Set window name. We need this in BasicPopupMenuUI
 367                 // to identify focusable popup window.
 368                 wnd.setName("###focusableSwingPopup###");
 369             }
 370 
 371             return popup;
 372         }
 373 
 374         /**
 375          * Returns a previously disposed heavy weight <code>Popup</code>
 376          * associated with <code>window</code>. This will return null if
 377          * there is no <code>HeavyWeightPopup</code> associated with
 378          * <code>window</code>.
 379          */
 380         private static HeavyWeightPopup getRecycledHeavyWeightPopup(Window w) {
 381             synchronized (HeavyWeightPopup.class) {
 382                 List<HeavyWeightPopup> cache;
 383                 Map<Window, List<HeavyWeightPopup>> heavyPopupCache = getHeavyWeightPopupCache();
 384 
 385                 if (heavyPopupCache.containsKey(w)) {
 386                     cache = heavyPopupCache.get(w);
 387                 } else {
 388                     return null;
 389                 }
 390                 if (cache.size() > 0) {
 391                     HeavyWeightPopup r = cache.get(0);
 392                     cache.remove(0);
 393                     return r;
 394                 }
 395                 return null;
 396             }
 397         }
 398 
 399         /**
 400          * Returns the cache to use for heavy weight popups. Maps from
 401          * <code>Window</code> to a <code>List</code> of
 402          * <code>HeavyWeightPopup</code>s.
 403          */
 404         @SuppressWarnings("unchecked")
 405         private static Map<Window, List<HeavyWeightPopup>> getHeavyWeightPopupCache() {
 406             synchronized (HeavyWeightPopup.class) {
 407                 Map<Window, List<HeavyWeightPopup>> cache = (Map<Window, List<HeavyWeightPopup>>)SwingUtilities.appContextGet(
 408                                   heavyWeightPopupCacheKey);
 409 
 410                 if (cache == null) {
 411                     cache = new HashMap<>(2);
 412                     SwingUtilities.appContextPut(heavyWeightPopupCacheKey,
 413                                                  cache);
 414                 }
 415                 return cache;
 416             }
 417         }
 418 
 419         /**
 420          * Recycles the passed in <code>HeavyWeightPopup</code>.
 421          */
 422         private static void recycleHeavyWeightPopup(HeavyWeightPopup popup) {
 423             synchronized (HeavyWeightPopup.class) {
 424                 List<HeavyWeightPopup> cache;
 425                 Window window = SwingUtilities.getWindowAncestor(
 426                                      popup.getComponent());
 427                 Map<Window, List<HeavyWeightPopup>> heavyPopupCache = getHeavyWeightPopupCache();
 428 
 429                 if (window instanceof Popup.DefaultFrame ||
 430                                       !window.isVisible()) {
 431                     // If the Window isn't visible, we don't cache it as we
 432                     // likely won't ever get a windowClosed event to clean up.
 433                     // We also don't cache DefaultFrames as this indicates
 434                     // there wasn't a valid Window parent, and thus we don't
 435                     // know when to clean up.
 436                     popup._dispose();
 437                     return;
 438                 } else if (heavyPopupCache.containsKey(window)) {
 439                     cache = heavyPopupCache.get(window);
 440                 } else {
 441                     cache = new ArrayList<HeavyWeightPopup>();
 442                     heavyPopupCache.put(window, cache);
 443                     // Clean up if the Window is closed
 444                     final Window w = window;
 445 
 446                     w.addWindowListener(new WindowAdapter() {
 447                         public void windowClosed(WindowEvent e) {
 448                             List<HeavyWeightPopup> popups;
 449 
 450                             synchronized(HeavyWeightPopup.class) {
 451                                 Map<Window, List<HeavyWeightPopup>> heavyPopupCache2 =
 452                                               getHeavyWeightPopupCache();
 453 
 454                                 popups = heavyPopupCache2.remove(w);
 455                             }
 456                             if (popups != null) {
 457                                 for (int counter = popups.size() - 1;
 458                                                    counter >= 0; counter--) {
 459                                     popups.get(counter)._dispose();
 460                                 }
 461                             }
 462                         }
 463                     });
 464                 }
 465 
 466                 if(cache.size() < MAX_CACHE_SIZE) {
 467                     cache.add(popup);
 468                 } else {
 469                     popup._dispose();
 470                 }
 471             }
 472         }
 473 
 474         /**
 475          * Enables or disables cache for current object.
 476          */
 477         void setCacheEnabled(boolean enable) {
 478             isCacheEnabled = enable;
 479         }
 480 
 481         //
 482         // Popup methods
 483         //
 484         public void hide() {
 485             super.hide();
 486             if (isCacheEnabled) {
 487                 recycleHeavyWeightPopup(this);
 488             } else {
 489                 this._dispose();
 490             }
 491         }
 492 
 493         /**
 494          * As we recycle the <code>Window</code>, we don't want to dispose it,
 495          * thus this method does nothing, instead use <code>_dipose</code>
 496          * which will handle the disposing.
 497          */
 498         void dispose() {
 499         }
 500 
 501         void _dispose() {
 502             super.dispose();
 503         }
 504     }
 505 
 506 
 507 
 508     /**
 509      * ContainerPopup consolidates the common code used in the light/medium
 510      * weight implementations of <code>Popup</code>.
 511      */
 512     private static class ContainerPopup extends Popup {
 513         /** Component we are to be added to. */
 514         Component owner;
 515         /** Desired x location. */
 516         int x;
 517         /** Desired y location. */
 518         int y;
 519 
 520         public void hide() {
 521             Component component = getComponent();
 522 
 523             if (component != null) {
 524                 Container parent = component.getParent();
 525 
 526                 if (parent != null) {
 527                     Rectangle bounds = component.getBounds();
 528 
 529                     parent.remove(component);
 530                     parent.repaint(bounds.x, bounds.y, bounds.width,
 531                                    bounds.height);
 532                 }
 533             }
 534             owner = null;
 535         }
 536         public void pack() {
 537             Component component = getComponent();
 538 
 539             if (component != null) {
 540                 component.setSize(component.getPreferredSize());
 541             }
 542         }
 543 
 544         void reset(Component owner, Component contents, int ownerX,
 545                    int ownerY) {
 546             if ((owner instanceof JFrame) || (owner instanceof JDialog) ||
 547                                                  (owner instanceof JWindow)) {
 548                 // Force the content to be added to the layered pane, otherwise
 549                 // we'll get an exception when adding to the RootPaneContainer.
 550                 owner = ((RootPaneContainer)owner).getLayeredPane();
 551             }
 552             super.reset(owner, contents, ownerX, ownerY);
 553 
 554             x = ownerX;
 555             y = ownerY;
 556             this.owner = owner;
 557         }
 558 
 559         boolean overlappedByOwnedWindow() {
 560             Component component = getComponent();
 561             if(owner != null && component != null) {
 562                 Window w = SwingUtilities.getWindowAncestor(owner);
 563                 if (w == null) {
 564                     return false;
 565                 }
 566                 Window[] ownedWindows = w.getOwnedWindows();
 567                 if(ownedWindows != null) {
 568                     Rectangle bnd = component.getBounds();
 569                     for (Window window : ownedWindows) {
 570                         if (window.isVisible() &&
 571                                 bnd.intersects(window.getBounds())) {
 572 
 573                             return true;
 574                         }
 575                     }
 576                 }
 577             }
 578             return false;
 579         }
 580 
 581         /**
 582          * Returns true if popup can fit the screen and the owner's top parent.
 583          * It determines can popup be lightweight or mediumweight.
 584          */
 585         boolean fitsOnScreen() {
 586             boolean result = false;
 587             Component component = getComponent();
 588             if (owner != null && component != null) {
 589                 int popupWidth = component.getWidth();
 590                 int popupHeight = component.getHeight();
 591 
 592                 Container parent = (Container) SwingUtilities.getRoot(owner);
 593                 if (parent instanceof JFrame ||
 594                     parent instanceof JDialog ||
 595                     parent instanceof JWindow) {
 596 
 597                     Rectangle parentBounds = parent.getBounds();
 598                     Insets i = parent.getInsets();
 599                     parentBounds.x += i.left;
 600                     parentBounds.y += i.top;
 601                     parentBounds.width -= i.left + i.right;
 602                     parentBounds.height -= i.top + i.bottom;
 603 
 604                     if (JPopupMenu.canPopupOverlapTaskBar()) {
 605                         GraphicsConfiguration gc =
 606                                 parent.getGraphicsConfiguration();
 607                         Rectangle popupArea = getContainerPopupArea(gc);
 608                         result = parentBounds.intersection(popupArea)
 609                                 .contains(x, y, popupWidth, popupHeight);
 610                     } else {
 611                         result = parentBounds
 612                                 .contains(x, y, popupWidth, popupHeight);
 613                     }
 614                 } else if (parent instanceof JApplet) {
 615                     Rectangle parentBounds = parent.getBounds();
 616                     Point p = parent.getLocationOnScreen();
 617                     parentBounds.x = p.x;
 618                     parentBounds.y = p.y;
 619                     result = parentBounds.contains(x, y, popupWidth, popupHeight);
 620                 }
 621             }
 622             return result;
 623         }
 624 
 625         Rectangle getContainerPopupArea(GraphicsConfiguration gc) {
 626             Rectangle screenBounds;
 627             Toolkit toolkit = Toolkit.getDefaultToolkit();
 628             Insets insets;
 629             if(gc != null) {
 630                 // If we have GraphicsConfiguration use it
 631                 // to get screen bounds
 632                 screenBounds = gc.getBounds();
 633                 insets = toolkit.getScreenInsets(gc);
 634             } else {
 635                 // If we don't have GraphicsConfiguration use primary screen
 636                 screenBounds = new Rectangle(toolkit.getScreenSize());
 637                 insets = new Insets(0, 0, 0, 0);
 638             }
 639             // Take insets into account
 640             screenBounds.x += insets.left;
 641             screenBounds.y += insets.top;
 642             screenBounds.width -= (insets.left + insets.right);
 643             screenBounds.height -= (insets.top + insets.bottom);
 644             return screenBounds;
 645         }
 646     }
 647 
 648 
 649     /**
 650      * Popup implementation that is used in headless environment.
 651      */
 652     private static class HeadlessPopup extends ContainerPopup {
 653         static Popup getHeadlessPopup(Component owner, Component contents,
 654                                       int ownerX, int ownerY) {
 655             HeadlessPopup popup = new HeadlessPopup();
 656             popup.reset(owner, contents, ownerX, ownerY);
 657             return popup;
 658         }
 659 
 660         Component createComponent(Component owner) {
 661             return new Panel(new BorderLayout());
 662         }
 663 
 664         public void show() {
 665         }
 666         public void hide() {
 667         }
 668     }
 669 
 670 
 671     /**
 672      * Popup implementation that uses a JPanel as the popup.
 673      */
 674     private static class LightWeightPopup extends ContainerPopup {
 675         private static final Object lightWeightPopupCacheKey =
 676                          new StringBuffer("PopupFactory.lightPopupCache");
 677 
 678         /**
 679          * Returns a light weight <code>Popup</code> implementation. If
 680          * the <code>Popup</code> needs more space that in available in
 681          * <code>owner</code>, this will return null.
 682          */
 683         static Popup getLightWeightPopup(Component owner, Component contents,
 684                                          int ownerX, int ownerY) {
 685             LightWeightPopup popup = getRecycledLightWeightPopup();
 686 
 687             if (popup == null) {
 688                 popup = new LightWeightPopup();
 689             }
 690             popup.reset(owner, contents, ownerX, ownerY);
 691             if (!popup.fitsOnScreen() ||
 692                  popup.overlappedByOwnedWindow()) {
 693                 popup.hide();
 694                 return null;
 695             }
 696             return popup;
 697         }
 698 
 699         /**
 700          * Returns the cache to use for heavy weight popups.
 701          */
 702         @SuppressWarnings("unchecked")
 703         private static List<LightWeightPopup> getLightWeightPopupCache() {
 704             List<LightWeightPopup> cache = (List<LightWeightPopup>)SwingUtilities.appContextGet(
 705                                    lightWeightPopupCacheKey);
 706             if (cache == null) {
 707                 cache = new ArrayList<>();
 708                 SwingUtilities.appContextPut(lightWeightPopupCacheKey, cache);
 709             }
 710             return cache;
 711         }
 712 
 713         /**
 714          * Recycles the LightWeightPopup <code>popup</code>.
 715          */
 716         private static void recycleLightWeightPopup(LightWeightPopup popup) {
 717             synchronized (LightWeightPopup.class) {
 718                 List<LightWeightPopup> lightPopupCache = getLightWeightPopupCache();
 719                 if (lightPopupCache.size() < MAX_CACHE_SIZE) {
 720                     lightPopupCache.add(popup);
 721                 }
 722             }
 723         }
 724 
 725         /**
 726          * Returns a previously used <code>LightWeightPopup</code>, or null
 727          * if none of the popups have been recycled.
 728          */
 729         private static LightWeightPopup getRecycledLightWeightPopup() {
 730             synchronized (LightWeightPopup.class) {
 731                 List<LightWeightPopup> lightPopupCache = getLightWeightPopupCache();
 732                 if (lightPopupCache.size() > 0) {
 733                     LightWeightPopup r = lightPopupCache.get(0);
 734                     lightPopupCache.remove(0);
 735                     return r;
 736                 }
 737                 return null;
 738             }
 739         }
 740 
 741 
 742 
 743         //
 744         // Popup methods
 745         //
 746         public void hide() {
 747             super.hide();
 748 
 749             Container component = (Container)getComponent();
 750 
 751             component.removeAll();
 752             recycleLightWeightPopup(this);
 753         }
 754         public void show() {
 755             Container parent = null;
 756 
 757             if (owner != null) {
 758                 parent = (owner instanceof Container? (Container)owner : owner.getParent());
 759             }
 760 
 761             // Try to find a JLayeredPane and Window to add
 762             for (Container p = parent; p != null; p = p.getParent()) {
 763                 if (p instanceof JRootPane) {
 764                     if (p.getParent() instanceof JInternalFrame) {
 765                         continue;
 766                     }
 767                     parent = ((JRootPane)p).getLayeredPane();
 768                     // Continue, so that if there is a higher JRootPane, we'll
 769                     // pick it up.
 770                 } else if(p instanceof Window) {
 771                     if (parent == null) {
 772                         parent = p;
 773                     }
 774                     break;
 775                 } else if (p instanceof JApplet) {
 776                     // Painting code stops at Applets, we don't want
 777                     // to add to a Component above an Applet otherwise
 778                     // you'll never see it painted.
 779                     break;
 780                 }
 781             }
 782 
 783             Point p = SwingUtilities.convertScreenLocationToParent(parent, x,
 784                                                                    y);
 785             Component component = getComponent();
 786 
 787             component.setLocation(p.x, p.y);
 788             if (parent instanceof JLayeredPane) {
 789                 parent.add(component, JLayeredPane.POPUP_LAYER, 0);
 790             } else {
 791                 parent.add(component);
 792             }
 793         }
 794 
 795         Component createComponent(Component owner) {
 796             JComponent component = new JPanel(new BorderLayout(), true);
 797 
 798             component.setOpaque(true);
 799             return component;
 800         }
 801 
 802         //
 803         // Local methods
 804         //
 805 
 806         /**
 807          * Resets the <code>Popup</code> to an initial state.
 808          */
 809         void reset(Component owner, Component contents, int ownerX,
 810                    int ownerY) {
 811             super.reset(owner, contents, ownerX, ownerY);
 812 
 813             JComponent component = (JComponent)getComponent();
 814 
 815             component.setOpaque(contents.isOpaque());
 816             component.setLocation(ownerX, ownerY);
 817             component.add(contents, BorderLayout.CENTER);
 818             contents.invalidate();
 819             pack();
 820         }
 821     }
 822 
 823 
 824     /**
 825      * Popup implementation that uses a Panel as the popup.
 826      */
 827     private static class MediumWeightPopup extends ContainerPopup {
 828         private static final Object mediumWeightPopupCacheKey =
 829                              new StringBuffer("PopupFactory.mediumPopupCache");
 830 
 831         /** Child of the panel. The contents are added to this. */
 832         private JRootPane rootPane;
 833 
 834 
 835         /**
 836          * Returns a medium weight <code>Popup</code> implementation. If
 837          * the <code>Popup</code> needs more space that in available in
 838          * <code>owner</code>, this will return null.
 839          */
 840         static Popup getMediumWeightPopup(Component owner, Component contents,
 841                                           int ownerX, int ownerY) {
 842             MediumWeightPopup popup = getRecycledMediumWeightPopup();
 843 
 844             if (popup == null) {
 845                 popup = new MediumWeightPopup();
 846             }
 847             popup.reset(owner, contents, ownerX, ownerY);
 848             if (!popup.fitsOnScreen() ||
 849                  popup.overlappedByOwnedWindow()) {
 850                 popup.hide();
 851                 return null;
 852             }
 853             return popup;
 854         }
 855 
 856         /**
 857          * Returns the cache to use for medium weight popups.
 858          */
 859         @SuppressWarnings("unchecked")
 860         private static List<MediumWeightPopup> getMediumWeightPopupCache() {
 861             List<MediumWeightPopup> cache = (List<MediumWeightPopup>)SwingUtilities.appContextGet(
 862                                     mediumWeightPopupCacheKey);
 863 
 864             if (cache == null) {
 865                 cache = new ArrayList<>();
 866                 SwingUtilities.appContextPut(mediumWeightPopupCacheKey, cache);
 867             }
 868             return cache;
 869         }
 870 
 871         /**
 872          * Recycles the MediumWeightPopup <code>popup</code>.
 873          */
 874         private static void recycleMediumWeightPopup(MediumWeightPopup popup) {
 875             synchronized (MediumWeightPopup.class) {
 876                 List<MediumWeightPopup> mediumPopupCache = getMediumWeightPopupCache();
 877                 if (mediumPopupCache.size() < MAX_CACHE_SIZE) {
 878                     mediumPopupCache.add(popup);
 879                 }
 880             }
 881         }
 882 
 883         /**
 884          * Returns a previously used <code>MediumWeightPopup</code>, or null
 885          * if none of the popups have been recycled.
 886          */
 887         private static MediumWeightPopup getRecycledMediumWeightPopup() {
 888             synchronized (MediumWeightPopup.class) {
 889                 List<MediumWeightPopup> mediumPopupCache = getMediumWeightPopupCache();
 890                 if (mediumPopupCache.size() > 0) {
 891                     MediumWeightPopup r = mediumPopupCache.get(0);
 892                     mediumPopupCache.remove(0);
 893                     return r;
 894                 }
 895                 return null;
 896             }
 897         }
 898 
 899 
 900         //
 901         // Popup
 902         //
 903 
 904         public void hide() {
 905             super.hide();
 906             rootPane.getContentPane().removeAll();
 907             recycleMediumWeightPopup(this);
 908         }
 909         public void show() {
 910             Component component = getComponent();
 911             Container parent = null;
 912 
 913             if (owner != null) {
 914                 parent = owner.getParent();
 915             }
 916             /*
 917               Find the top level window,
 918               if it has a layered pane,
 919               add to that, otherwise
 920               add to the window. */
 921             while (!(parent instanceof Window || parent instanceof Applet) &&
 922                    (parent!=null)) {
 923                 parent = parent.getParent();
 924             }
 925             // Set the visibility to false before adding to workaround a
 926             // bug in Solaris in which the Popup gets added at the wrong
 927             // location, which will result in a mouseExit, which will then
 928             // result in the ToolTip being removed.
 929             if (parent instanceof RootPaneContainer) {
 930                 parent = ((RootPaneContainer)parent).getLayeredPane();
 931                 Point p = SwingUtilities.convertScreenLocationToParent(parent,
 932                                                                        x, y);
 933                 component.setVisible(false);
 934                 component.setLocation(p.x, p.y);
 935                 parent.add(component, JLayeredPane.POPUP_LAYER,
 936                                            0);
 937             } else {
 938                 Point p = SwingUtilities.convertScreenLocationToParent(parent,
 939                                                                        x, y);
 940 
 941                 component.setLocation(p.x, p.y);
 942                 component.setVisible(false);
 943                 parent.add(component);
 944             }
 945             component.setVisible(true);
 946         }
 947 
 948         Component createComponent(Component owner) {
 949             Panel component = new MediumWeightComponent();
 950 
 951             rootPane = new JRootPane();
 952             // NOTE: this uses setOpaque vs LookAndFeel.installProperty as
 953             // there is NO reason for the RootPane not to be opaque. For
 954             // painting to work the contentPane must be opaque, therefor the
 955             // RootPane can also be opaque.
 956             rootPane.setOpaque(true);
 957             component.add(rootPane, BorderLayout.CENTER);
 958             return component;
 959         }
 960 
 961         /**
 962          * Resets the <code>Popup</code> to an initial state.
 963          */
 964         void reset(Component owner, Component contents, int ownerX,
 965                    int ownerY) {
 966             super.reset(owner, contents, ownerX, ownerY);
 967 
 968             Component component = getComponent();
 969 
 970             component.setLocation(ownerX, ownerY);
 971             rootPane.getContentPane().add(contents, BorderLayout.CENTER);
 972             contents.invalidate();
 973             component.validate();
 974             pack();
 975         }
 976 
 977 
 978         // This implements SwingHeavyWeight so that repaints on it
 979         // are processed by the RepaintManager and SwingPaintEventDispatcher.
 980         @SuppressWarnings("serial") // JDK-implementation class
 981         private static class MediumWeightComponent extends Panel implements
 982                                                            SwingHeavyWeight {
 983             MediumWeightComponent() {
 984                 super(new BorderLayout());
 985             }
 986         }
 987     }
 988 }