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