1 /*
   2  * Copyright (c) 1997, 2015, 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 package javax.swing;
  26 
  27 import java.awt.*;
  28 import java.awt.event.*;
  29 import java.io.IOException;
  30 import java.io.ObjectInputStream;
  31 import java.io.ObjectOutputStream;
  32 import java.io.Serializable;
  33 import java.beans.JavaBean;
  34 import java.beans.BeanProperty;
  35 import java.beans.PropertyChangeEvent;
  36 import java.beans.PropertyChangeListener;
  37 
  38 import java.util.Vector;
  39 import javax.accessibility.*;
  40 import javax.swing.plaf.PopupMenuUI;
  41 import javax.swing.plaf.basic.BasicComboPopup;
  42 import javax.swing.event.*;
  43 
  44 import sun.awt.SunToolkit;
  45 
  46 /**
  47  * An implementation of a popup menu -- a small window that pops up
  48  * and displays a series of choices. A <code>JPopupMenu</code> is used for the
  49  * menu that appears when the user selects an item on the menu bar.
  50  * It is also used for "pull-right" menu that appears when the
  51  * selects a menu item that activates it. Finally, a <code>JPopupMenu</code>
  52  * can also be used anywhere else you want a menu to appear.  For
  53  * example, when the user right-clicks in a specified area.
  54  * <p>
  55  * For information and examples of using popup menus, see
  56  * <a
  57  href="http://docs.oracle.com/javase/tutorial/uiswing/components/menu.html">How to Use Menus</a>
  58  * in <em>The Java Tutorial.</em>
  59  * <p>
  60  * <strong>Warning:</strong> Swing is not thread safe. For more
  61  * information see <a
  62  * href="package-summary.html#threading">Swing's Threading
  63  * Policy</a>.
  64  * <p>
  65  * <strong>Warning:</strong>
  66  * Serialized objects of this class will not be compatible with
  67  * future Swing releases. The current serialization support is
  68  * appropriate for short term storage or RMI between applications running
  69  * the same version of Swing.  As of 1.4, support for long term storage
  70  * of all JavaBeans&trade;
  71  * has been added to the <code>java.beans</code> package.
  72  * Please see {@link java.beans.XMLEncoder}.
  73  *
  74  * @author Georges Saab
  75  * @author David Karlton
  76  * @author Arnaud Weber
  77  * @since 1.2
  78  */
  79 @JavaBean(defaultProperty = "UI", description = "A small window that pops up and displays a series of choices.")
  80 @SwingContainer(false)
  81 @SuppressWarnings("serial")
  82 public class JPopupMenu extends JComponent implements Accessible,MenuElement {
  83 
  84     /**
  85      * @see #getUIClassID
  86      * @see #readObject
  87      */
  88     private static final String uiClassID = "PopupMenuUI";
  89 
  90     /**
  91      * Key used in AppContext to determine if light way popups are the default.
  92      */
  93     private static final Object defaultLWPopupEnabledKey =
  94         new StringBuffer("JPopupMenu.defaultLWPopupEnabledKey");
  95 
  96     /** Bug#4425878-Property javax.swing.adjustPopupLocationToFit introduced */
  97     static boolean popupPostionFixDisabled = false;
  98 
  99     static {
 100         popupPostionFixDisabled = java.security.AccessController.doPrivileged(
 101                 new sun.security.action.GetPropertyAction(
 102                 "javax.swing.adjustPopupLocationToFit","")).equals("false");
 103 
 104     }
 105 
 106     transient  Component invoker;
 107     transient  Popup popup;
 108     transient  Frame frame;
 109     private    int desiredLocationX,desiredLocationY;
 110 
 111     private    String     label                   = null;
 112     private    boolean   paintBorder              = true;
 113     private    Insets    margin                   = null;
 114 
 115     /**
 116      * Used to indicate if lightweight popups should be used.
 117      */
 118     private    boolean   lightWeightPopup         = true;
 119 
 120     /*
 121      * Model for the selected subcontrol.
 122      */
 123     private SingleSelectionModel selectionModel;
 124 
 125     /* Lock object used in place of class object for synchronization.
 126      * (4187686)
 127      */
 128     private static final Object classLock = new Object();
 129 
 130     /* diagnostic aids -- should be false for production builds. */
 131     private static final boolean TRACE =   false; // trace creates and disposes
 132     private static final boolean VERBOSE = false; // show reuse hits/misses
 133     private static final boolean DEBUG =   false;  // show bad params, misc.
 134 
 135     /**
 136      *  Sets the default value of the <code>lightWeightPopupEnabled</code>
 137      *  property.
 138      *
 139      *  @param aFlag <code>true</code> if popups can be lightweight,
 140      *               otherwise <code>false</code>
 141      *  @see #getDefaultLightWeightPopupEnabled
 142      *  @see #setLightWeightPopupEnabled
 143      */
 144     public static void setDefaultLightWeightPopupEnabled(boolean aFlag) {
 145         SwingUtilities.appContextPut(defaultLWPopupEnabledKey,
 146                                      Boolean.valueOf(aFlag));
 147     }
 148 
 149     /**
 150      *  Gets the <code>defaultLightWeightPopupEnabled</code> property,
 151      *  which by default is <code>true</code>.
 152      *
 153      *  @return the value of the <code>defaultLightWeightPopupEnabled</code>
 154      *          property
 155      *
 156      *  @see #setDefaultLightWeightPopupEnabled
 157      */
 158     public static boolean getDefaultLightWeightPopupEnabled() {
 159         Boolean b = (Boolean)
 160             SwingUtilities.appContextGet(defaultLWPopupEnabledKey);
 161         if (b == null) {
 162             SwingUtilities.appContextPut(defaultLWPopupEnabledKey,
 163                                          Boolean.TRUE);
 164             return true;
 165         }
 166         return b.booleanValue();
 167     }
 168 
 169     /**
 170      * Constructs a <code>JPopupMenu</code> without an "invoker".
 171      */
 172     public JPopupMenu() {
 173         this(null);
 174     }
 175 
 176     /**
 177      * Constructs a <code>JPopupMenu</code> with the specified title.
 178      *
 179      * @param label  the string that a UI may use to display as a title
 180      * for the popup menu.
 181      */
 182     public JPopupMenu(String label) {
 183         this.label = label;
 184         lightWeightPopup = getDefaultLightWeightPopupEnabled();
 185         setSelectionModel(new DefaultSingleSelectionModel());
 186         enableEvents(AWTEvent.MOUSE_EVENT_MASK);
 187         setFocusTraversalKeysEnabled(false);
 188         updateUI();
 189     }
 190 
 191 
 192 
 193     /**
 194      * Returns the look and feel (L&amp;F) object that renders this component.
 195      *
 196      * @return the <code>PopupMenuUI</code> object that renders this component
 197      */
 198     public PopupMenuUI getUI() {
 199         return (PopupMenuUI)ui;
 200     }
 201 
 202     /**
 203      * Sets the L&amp;F object that renders this component.
 204      *
 205      * @param ui the new <code>PopupMenuUI</code> L&amp;F object
 206      * @see UIDefaults#getUI
 207      */
 208     @BeanProperty(hidden = true, visualUpdate = true, description
 209             = "The UI object that implements the Component's LookAndFeel.")
 210     public void setUI(PopupMenuUI ui) {
 211         super.setUI(ui);
 212     }
 213 
 214     /**
 215      * Resets the UI property to a value from the current look and feel.
 216      *
 217      * @see JComponent#updateUI
 218      */
 219     public void updateUI() {
 220         setUI((PopupMenuUI)UIManager.getUI(this));
 221     }
 222 
 223 
 224     /**
 225      * Returns the name of the L&amp;F class that renders this component.
 226      *
 227      * @return the string "PopupMenuUI"
 228      * @see JComponent#getUIClassID
 229      * @see UIDefaults#getUI
 230      */
 231     @BeanProperty(bound = false)
 232     public String getUIClassID() {
 233         return uiClassID;
 234     }
 235 
 236     protected void processFocusEvent(FocusEvent evt) {
 237         super.processFocusEvent(evt);
 238     }
 239 
 240     /**
 241      * Processes key stroke events such as mnemonics and accelerators.
 242      *
 243      * @param evt  the key event to be processed
 244      */
 245     protected void processKeyEvent(KeyEvent evt) {
 246         MenuSelectionManager.defaultManager().processKeyEvent(evt);
 247         if (evt.isConsumed()) {
 248             return;
 249         }
 250         super.processKeyEvent(evt);
 251     }
 252 
 253 
 254     /**
 255      * Returns the model object that handles single selections.
 256      *
 257      * @return the <code>selectionModel</code> property
 258      * @see SingleSelectionModel
 259      */
 260     public SingleSelectionModel getSelectionModel() {
 261         return selectionModel;
 262     }
 263 
 264     /**
 265      * Sets the model object to handle single selections.
 266      *
 267      * @param model the new <code>SingleSelectionModel</code>
 268      * @see SingleSelectionModel
 269      */
 270     @BeanProperty(bound = false, expert = true, description
 271             = "The selection model for the popup menu")
 272     public void setSelectionModel(SingleSelectionModel model) {
 273         selectionModel = model;
 274     }
 275 
 276     /**
 277      * Appends the specified menu item to the end of this menu.
 278      *
 279      * @param menuItem the <code>JMenuItem</code> to add
 280      * @return the <code>JMenuItem</code> added
 281      */
 282     public JMenuItem add(JMenuItem menuItem) {
 283         super.add(menuItem);
 284         return menuItem;
 285     }
 286 
 287     /**
 288      * Creates a new menu item with the specified text and appends
 289      * it to the end of this menu.
 290      *
 291      * @param s the string for the menu item to be added
 292      * @return a new {@code JMenuItem} created using {@code s}
 293      */
 294     public JMenuItem add(String s) {
 295         return add(new JMenuItem(s));
 296     }
 297 
 298     /**
 299      * Appends a new menu item to the end of the menu which
 300      * dispatches the specified <code>Action</code> object.
 301      *
 302      * @param a the <code>Action</code> to add to the menu
 303      * @return the new menu item
 304      * @see Action
 305      */
 306     public JMenuItem add(Action a) {
 307         JMenuItem mi = createActionComponent(a);
 308         mi.setAction(a);
 309         add(mi);
 310         return mi;
 311     }
 312 
 313     /**
 314      * Returns an point which has been adjusted to take into account of the
 315      * desktop bounds, taskbar and multi-monitor configuration.
 316      * <p>
 317      * This adustment may be cancelled by invoking the application with
 318      * -Djavax.swing.adjustPopupLocationToFit=false
 319      */
 320     Point adjustPopupLocationToFitScreen(int xPosition, int yPosition) {
 321         Point popupLocation = new Point(xPosition, yPosition);
 322 
 323         if(popupPostionFixDisabled == true || GraphicsEnvironment.isHeadless()) {
 324             return popupLocation;
 325         }
 326 
 327         // Get screen bounds
 328         Rectangle scrBounds;
 329         GraphicsConfiguration gc = getCurrentGraphicsConfiguration(popupLocation);
 330         Toolkit toolkit = Toolkit.getDefaultToolkit();
 331         if(gc != null) {
 332             // If we have GraphicsConfiguration use it to get screen bounds
 333             scrBounds = gc.getBounds();
 334         } else {
 335             // If we don't have GraphicsConfiguration use primary screen
 336             scrBounds = new Rectangle(toolkit.getScreenSize());
 337         }
 338 
 339         // Calculate the screen size that popup should fit
 340         Dimension popupSize = JPopupMenu.this.getPreferredSize();
 341         long popupRightX = (long)popupLocation.x + (long)popupSize.width;
 342         long popupBottomY = (long)popupLocation.y + (long)popupSize.height;
 343         int scrWidth = scrBounds.width;
 344         int scrHeight = scrBounds.height;
 345 
 346         if (!canPopupOverlapTaskBar()) {
 347             // Insets include the task bar. Take them into account.
 348             Insets scrInsets = toolkit.getScreenInsets(gc);
 349             scrBounds.x += scrInsets.left;
 350             scrBounds.y += scrInsets.top;
 351             scrWidth -= scrInsets.left + scrInsets.right;
 352             scrHeight -= scrInsets.top + scrInsets.bottom;
 353         }
 354         int scrRightX = scrBounds.x + scrWidth;
 355         int scrBottomY = scrBounds.y + scrHeight;
 356 
 357         // Ensure that popup menu fits the screen
 358         if (popupRightX > (long) scrRightX) {
 359             popupLocation.x = scrRightX - popupSize.width;
 360         }
 361 
 362         if (popupBottomY > (long) scrBottomY) {
 363             popupLocation.y = scrBottomY - popupSize.height;
 364         }
 365 
 366         if (popupLocation.x < scrBounds.x) {
 367             popupLocation.x = scrBounds.x;
 368         }
 369 
 370         if (popupLocation.y < scrBounds.y) {
 371             popupLocation.y = scrBounds.y;
 372         }
 373 
 374         return popupLocation;
 375     }
 376 
 377     /**
 378      * Tries to find GraphicsConfiguration
 379      * that contains the mouse cursor position.
 380      * Can return null.
 381      */
 382     private GraphicsConfiguration getCurrentGraphicsConfiguration(
 383             Point popupLocation) {
 384         GraphicsConfiguration gc = null;
 385         GraphicsEnvironment ge =
 386             GraphicsEnvironment.getLocalGraphicsEnvironment();
 387         GraphicsDevice[] gd = ge.getScreenDevices();
 388         for(int i = 0; i < gd.length; i++) {
 389             if(gd[i].getType() == GraphicsDevice.TYPE_RASTER_SCREEN) {
 390                 GraphicsConfiguration dgc =
 391                     gd[i].getDefaultConfiguration();
 392                 if(dgc.getBounds().contains(popupLocation)) {
 393                     gc = dgc;
 394                     break;
 395                 }
 396             }
 397         }
 398         // If not found and we have invoker, ask invoker about his gc
 399         if(gc == null && getInvoker() != null) {
 400             gc = getInvoker().getGraphicsConfiguration();
 401         }
 402         return gc;
 403     }
 404 
 405     /**
 406      * Returns whether popup is allowed to be shown above the task bar.
 407      */
 408     static boolean canPopupOverlapTaskBar() {
 409         boolean result = true;
 410 
 411         Toolkit tk = Toolkit.getDefaultToolkit();
 412         if (tk instanceof SunToolkit) {
 413             result = ((SunToolkit)tk).canPopupOverlapTaskBar();
 414         }
 415 
 416         return result;
 417     }
 418 
 419     /**
 420      * Factory method which creates the <code>JMenuItem</code> for
 421      * <code>Actions</code> added to the <code>JPopupMenu</code>.
 422      *
 423      * @param a the <code>Action</code> for the menu item to be added
 424      * @return the new menu item
 425      * @see Action
 426      *
 427      * @since 1.3
 428      */
 429     protected JMenuItem createActionComponent(Action a) {
 430         JMenuItem mi = new JMenuItem() {
 431             protected PropertyChangeListener createActionPropertyChangeListener(Action a) {
 432                 PropertyChangeListener pcl = createActionChangeListener(this);
 433                 if (pcl == null) {
 434                     pcl = super.createActionPropertyChangeListener(a);
 435                 }
 436                 return pcl;
 437             }
 438         };
 439         mi.setHorizontalTextPosition(JButton.TRAILING);
 440         mi.setVerticalTextPosition(JButton.CENTER);
 441         return mi;
 442     }
 443 
 444     /**
 445      * Returns a properly configured <code>PropertyChangeListener</code>
 446      * which updates the control as changes to the <code>Action</code> occur.
 447      *
 448      * @param b the menu item for which to create a listener
 449      * @return a properly configured {@code PropertyChangeListener}
 450      */
 451     protected PropertyChangeListener createActionChangeListener(JMenuItem b) {
 452         return b.createActionPropertyChangeListener0(b.getAction());
 453     }
 454 
 455     /**
 456      * Removes the component at the specified index from this popup menu.
 457      *
 458      * @param       pos the position of the item to be removed
 459      * @exception   IllegalArgumentException if the value of
 460      *                          <code>pos</code> &lt; 0, or if the value of
 461      *                          <code>pos</code> is greater than the
 462      *                          number of items
 463      */
 464     public void remove(int pos) {
 465         if (pos < 0) {
 466             throw new IllegalArgumentException("index less than zero.");
 467         }
 468         if (pos > getComponentCount() -1) {
 469             throw new IllegalArgumentException("index greater than the number of items.");
 470         }
 471         super.remove(pos);
 472     }
 473 
 474     /**
 475      * Sets the value of the <code>lightWeightPopupEnabled</code> property,
 476      * which by default is <code>true</code>.
 477      * By default, when a look and feel displays a popup,
 478      * it can choose to
 479      * use a lightweight (all-Java) popup.
 480      * Lightweight popup windows are more efficient than heavyweight
 481      * (native peer) windows,
 482      * but lightweight and heavyweight components do not mix well in a GUI.
 483      * If your application mixes lightweight and heavyweight components,
 484      * you should disable lightweight popups.
 485      * Some look and feels might always use heavyweight popups,
 486      * no matter what the value of this property.
 487      *
 488      * @param aFlag  <code>false</code> to disable lightweight popups
 489      *
 490      * @see #isLightWeightPopupEnabled
 491      */
 492     @BeanProperty(bound = false, expert = true, description
 493             = "Determines whether lightweight popups are used when possible")
 494     public void setLightWeightPopupEnabled(boolean aFlag) {
 495         // NOTE: this use to set the flag on a shared JPopupMenu, which meant
 496         // this effected ALL JPopupMenus.
 497         lightWeightPopup = aFlag;
 498     }
 499 
 500     /**
 501      * Gets the <code>lightWeightPopupEnabled</code> property.
 502      *
 503      * @return the value of the <code>lightWeightPopupEnabled</code> property
 504      * @see #setLightWeightPopupEnabled
 505      */
 506     public boolean isLightWeightPopupEnabled() {
 507         return lightWeightPopup;
 508     }
 509 
 510     /**
 511      * Returns the popup menu's label
 512      *
 513      * @return a string containing the popup menu's label
 514      * @see #setLabel
 515      */
 516     public String getLabel() {
 517         return label;
 518     }
 519 
 520     /**
 521      * Sets the popup menu's label.  Different look and feels may choose
 522      * to display or not display this.
 523      *
 524      * @param label a string specifying the label for the popup menu
 525      *
 526      * @see #setLabel
 527      */
 528     @BeanProperty(description
 529             = "The label for the popup menu.")
 530     public void setLabel(String label) {
 531         String oldValue = this.label;
 532         this.label = label;
 533         firePropertyChange("label", oldValue, label);
 534         if (accessibleContext != null) {
 535             accessibleContext.firePropertyChange(
 536                 AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY,
 537                 oldValue, label);
 538         }
 539         invalidate();
 540         repaint();
 541     }
 542 
 543     /**
 544      * Appends a new separator at the end of the menu.
 545      */
 546     public void addSeparator() {
 547         add( new JPopupMenu.Separator() );
 548     }
 549 
 550     /**
 551      * Inserts a menu item for the specified <code>Action</code> object at
 552      * a given position.
 553      *
 554      * @param a  the <code>Action</code> object to insert
 555      * @param index      specifies the position at which to insert the
 556      *                   <code>Action</code>, where 0 is the first
 557      * @exception IllegalArgumentException if <code>index</code> &lt; 0
 558      * @see Action
 559      */
 560     public void insert(Action a, int index) {
 561         JMenuItem mi = createActionComponent(a);
 562         mi.setAction(a);
 563         insert(mi, index);
 564     }
 565 
 566     /**
 567      * Inserts the specified component into the menu at a given
 568      * position.
 569      *
 570      * @param component  the <code>Component</code> to insert
 571      * @param index      specifies the position at which
 572      *                   to insert the component, where 0 is the first
 573      * @exception IllegalArgumentException if <code>index</code> &lt; 0
 574      */
 575     public void insert(Component component, int index) {
 576         if (index < 0) {
 577             throw new IllegalArgumentException("index less than zero.");
 578         }
 579 
 580         int nitems = getComponentCount();
 581         // PENDING(ges): Why not use an array?
 582         Vector<Component> tempItems = new Vector<Component>();
 583 
 584         /* Remove the item at index, nitems-index times
 585            storing them in a temporary vector in the
 586            order they appear on the menu.
 587            */
 588         for (int i = index ; i < nitems; i++) {
 589             tempItems.addElement(getComponent(index));
 590             remove(index);
 591         }
 592 
 593         add(component);
 594 
 595         /* Add the removed items back to the menu, they are
 596            already in the correct order in the temp vector.
 597            */
 598         for (Component tempItem : tempItems) {
 599             add(tempItem);
 600         }
 601     }
 602 
 603     /**
 604      *  Adds a <code>PopupMenu</code> listener.
 605      *
 606      *  @param l  the <code>PopupMenuListener</code> to add
 607      */
 608     public void addPopupMenuListener(PopupMenuListener l) {
 609         listenerList.add(PopupMenuListener.class,l);
 610     }
 611 
 612     /**
 613      * Removes a <code>PopupMenu</code> listener.
 614      *
 615      * @param l  the <code>PopupMenuListener</code> to remove
 616      */
 617     public void removePopupMenuListener(PopupMenuListener l) {
 618         listenerList.remove(PopupMenuListener.class,l);
 619     }
 620 
 621     /**
 622      * Returns an array of all the <code>PopupMenuListener</code>s added
 623      * to this JMenuItem with addPopupMenuListener().
 624      *
 625      * @return all of the <code>PopupMenuListener</code>s added or an empty
 626      *         array if no listeners have been added
 627      * @since 1.4
 628      */
 629     @BeanProperty(bound = false)
 630     public PopupMenuListener[] getPopupMenuListeners() {
 631         return listenerList.getListeners(PopupMenuListener.class);
 632     }
 633 
 634     /**
 635      * Adds a <code>MenuKeyListener</code> to the popup menu.
 636      *
 637      * @param l the <code>MenuKeyListener</code> to be added
 638      * @since 1.5
 639      */
 640     public void addMenuKeyListener(MenuKeyListener l) {
 641         listenerList.add(MenuKeyListener.class, l);
 642     }
 643 
 644     /**
 645      * Removes a <code>MenuKeyListener</code> from the popup menu.
 646      *
 647      * @param l the <code>MenuKeyListener</code> to be removed
 648      * @since 1.5
 649      */
 650     public void removeMenuKeyListener(MenuKeyListener l) {
 651         listenerList.remove(MenuKeyListener.class, l);
 652     }
 653 
 654     /**
 655      * Returns an array of all the <code>MenuKeyListener</code>s added
 656      * to this JPopupMenu with addMenuKeyListener().
 657      *
 658      * @return all of the <code>MenuKeyListener</code>s added or an empty
 659      *         array if no listeners have been added
 660      * @since 1.5
 661      */
 662     @BeanProperty(bound = false)
 663     public MenuKeyListener[] getMenuKeyListeners() {
 664         return listenerList.getListeners(MenuKeyListener.class);
 665     }
 666 
 667     /**
 668      * Notifies <code>PopupMenuListener</code>s that this popup menu will
 669      * become visible.
 670      */
 671     protected void firePopupMenuWillBecomeVisible() {
 672         Object[] listeners = listenerList.getListenerList();
 673         PopupMenuEvent e=null;
 674         for (int i = listeners.length-2; i>=0; i-=2) {
 675             if (listeners[i]==PopupMenuListener.class) {
 676                 if (e == null)
 677                     e = new PopupMenuEvent(this);
 678                 ((PopupMenuListener)listeners[i+1]).popupMenuWillBecomeVisible(e);
 679             }
 680         }
 681     }
 682 
 683     /**
 684      * Notifies <code>PopupMenuListener</code>s that this popup menu will
 685      * become invisible.
 686      */
 687     protected void firePopupMenuWillBecomeInvisible() {
 688         Object[] listeners = listenerList.getListenerList();
 689         PopupMenuEvent e=null;
 690         for (int i = listeners.length-2; i>=0; i-=2) {
 691             if (listeners[i]==PopupMenuListener.class) {
 692                 if (e == null)
 693                     e = new PopupMenuEvent(this);
 694                 ((PopupMenuListener)listeners[i+1]).popupMenuWillBecomeInvisible(e);
 695             }
 696         }
 697     }
 698 
 699     /**
 700      * Notifies <code>PopupMenuListeners</code> that this popup menu is
 701      * cancelled.
 702      */
 703     protected void firePopupMenuCanceled() {
 704         Object[] listeners = listenerList.getListenerList();
 705         PopupMenuEvent e=null;
 706         for (int i = listeners.length-2; i>=0; i-=2) {
 707             if (listeners[i]==PopupMenuListener.class) {
 708                 if (e == null)
 709                     e = new PopupMenuEvent(this);
 710                 ((PopupMenuListener)listeners[i+1]).popupMenuCanceled(e);
 711             }
 712         }
 713     }
 714 
 715     /**
 716      * Always returns true since popups, by definition, should always
 717      * be on top of all other windows.
 718      * @return true
 719      */
 720     // package private
 721     boolean alwaysOnTop() {
 722         return true;
 723     }
 724 
 725     /**
 726      * Lays out the container so that it uses the minimum space
 727      * needed to display its contents.
 728      */
 729     public void pack() {
 730         if(popup != null) {
 731             Dimension pref = getPreferredSize();
 732 
 733             if (pref == null || pref.width != getWidth() ||
 734                                 pref.height != getHeight()) {
 735                 showPopup();
 736             } else {
 737                 validate();
 738             }
 739         }
 740     }
 741 
 742     /**
 743      * Sets the visibility of the popup menu.
 744      *
 745      * @param b true to make the popup visible, or false to
 746      *          hide it
 747      */
 748     @BeanProperty(description
 749             = "Makes the popup visible")
 750     public void setVisible(boolean b) {
 751         if (DEBUG) {
 752             System.out.println("JPopupMenu.setVisible " + b);
 753         }
 754 
 755         // Is it a no-op?
 756         if (b == isVisible())
 757             return;
 758 
 759         // if closing, first close all Submenus
 760         if (b == false) {
 761 
 762             // 4234793: This is a workaround because JPopupMenu.firePopupMenuCanceled is
 763             // a protected method and cannot be called from BasicPopupMenuUI directly
 764             // The real solution could be to make
 765             // firePopupMenuCanceled public and call it directly.
 766             Boolean doCanceled = (Boolean)getClientProperty("JPopupMenu.firePopupMenuCanceled");
 767             if (doCanceled != null && doCanceled == Boolean.TRUE) {
 768                 putClientProperty("JPopupMenu.firePopupMenuCanceled", Boolean.FALSE);
 769                 firePopupMenuCanceled();
 770             }
 771             getSelectionModel().clearSelection();
 772 
 773         } else {
 774             // This is a popup menu with MenuElement children,
 775             // set selection path before popping up!
 776             if (isPopupMenu()) {
 777                 MenuElement me[] = new MenuElement[1];
 778                 me[0] = this;
 779                 MenuSelectionManager.defaultManager().setSelectedPath(me);
 780             }
 781         }
 782 
 783         if(b) {
 784             firePopupMenuWillBecomeVisible();
 785             showPopup();
 786             firePropertyChange("visible", Boolean.FALSE, Boolean.TRUE);
 787 
 788 
 789         } else if(popup != null) {
 790             firePopupMenuWillBecomeInvisible();
 791             popup.hide();
 792             popup = null;
 793             firePropertyChange("visible", Boolean.TRUE, Boolean.FALSE);
 794             // 4694797: When popup menu is made invisible, selected path
 795             // should be cleared
 796             if (isPopupMenu()) {
 797                 MenuSelectionManager.defaultManager().clearSelectedPath();
 798             }
 799         }
 800     }
 801 
 802     /**
 803      * Retrieves <code>Popup</code> instance from the
 804      * <code>PopupMenuUI</code> that has had <code>show</code> invoked on
 805      * it. If the current <code>popup</code> is non-null,
 806      * this will invoke <code>dispose</code> of it, and then
 807      * <code>show</code> the new one.
 808      * <p>
 809      * This does NOT fire any events, it is up the caller to dispatch
 810      * the necessary events.
 811      */
 812     private void showPopup() {
 813         Popup oldPopup = popup;
 814 
 815         if (oldPopup != null) {
 816             oldPopup.hide();
 817         }
 818         PopupFactory popupFactory = PopupFactory.getSharedInstance();
 819 
 820         if (isLightWeightPopupEnabled()) {
 821             popupFactory.setPopupType(PopupFactory.LIGHT_WEIGHT_POPUP);
 822         }
 823         else {
 824             popupFactory.setPopupType(PopupFactory.HEAVY_WEIGHT_POPUP);
 825         }
 826 
 827         // adjust the location of the popup
 828         Point p = adjustPopupLocationToFitScreen(desiredLocationX,desiredLocationY);
 829         desiredLocationX = p.x;
 830         desiredLocationY = p.y;
 831 
 832         Popup newPopup = getUI().getPopup(this, desiredLocationX,
 833                                           desiredLocationY);
 834 
 835         popupFactory.setPopupType(PopupFactory.LIGHT_WEIGHT_POPUP);
 836         popup = newPopup;
 837         newPopup.show();
 838     }
 839 
 840     /**
 841      * Returns true if the popup menu is visible (currently
 842      * being displayed).
 843      */
 844     public boolean isVisible() {
 845         return popup != null;
 846     }
 847 
 848     /**
 849      * Sets the location of the upper left corner of the
 850      * popup menu using x, y coordinates.
 851      * <p>
 852      * The method changes the geometry-related data. Therefore,
 853      * the native windowing system may ignore such requests, or it may modify
 854      * the requested data, so that the {@code JPopupMenu} object is placed and sized
 855      * in a way that corresponds closely to the desktop settings.
 856      *
 857      * @param x the x coordinate of the popup's new position
 858      *          in the screen's coordinate space
 859      * @param y the y coordinate of the popup's new position
 860      *          in the screen's coordinate space
 861      */
 862     @BeanProperty(description
 863             = "The location of the popup menu.")
 864     public void setLocation(int x, int y) {
 865         int oldX = desiredLocationX;
 866         int oldY = desiredLocationY;
 867 
 868         desiredLocationX = x;
 869         desiredLocationY = y;
 870         if(popup != null && (x != oldX || y != oldY)) {
 871             showPopup();
 872         }
 873     }
 874 
 875     /**
 876      * Returns true if the popup menu is a standalone popup menu
 877      * rather than the submenu of a <code>JMenu</code>.
 878      *
 879      * @return true if this menu is a standalone popup menu, otherwise false
 880      */
 881     private boolean isPopupMenu() {
 882         return  ((invoker != null) && !(invoker instanceof JMenu));
 883     }
 884 
 885     /**
 886      * Returns the component which is the 'invoker' of this
 887      * popup menu.
 888      *
 889      * @return the <code>Component</code> in which the popup menu is displayed
 890      */
 891     public Component getInvoker() {
 892         return this.invoker;
 893     }
 894 
 895     /**
 896      * Sets the invoker of this popup menu -- the component in which
 897      * the popup menu menu is to be displayed.
 898      *
 899      * @param invoker the <code>Component</code> in which the popup
 900      *          menu is displayed
 901      */
 902     @BeanProperty(bound = false, expert = true, description
 903             = "The invoking component for the popup menu")
 904     public void setInvoker(Component invoker) {
 905         Component oldInvoker = this.invoker;
 906         this.invoker = invoker;
 907         if ((oldInvoker != this.invoker) && (ui != null)) {
 908             ui.uninstallUI(this);
 909             ui.installUI(this);
 910         }
 911         invalidate();
 912     }
 913 
 914     /**
 915      * Displays the popup menu at the position x,y in the coordinate
 916      * space of the component invoker.
 917      *
 918      * @param invoker the component in whose space the popup menu is to appear
 919      * @param x the x coordinate in invoker's coordinate space at which
 920      * the popup menu is to be displayed
 921      * @param y the y coordinate in invoker's coordinate space at which
 922      * the popup menu is to be displayed
 923      */
 924     public void show(Component invoker, int x, int y) {
 925         if (DEBUG) {
 926             System.out.println("in JPopupMenu.show " );
 927         }
 928         setInvoker(invoker);
 929         Frame newFrame = getFrame(invoker);
 930         if (newFrame != frame) {
 931             // Use the invoker's frame so that events
 932             // are propagated properly
 933             if (newFrame!=null) {
 934                 this.frame = newFrame;
 935                 if(popup != null) {
 936                     setVisible(false);
 937                 }
 938             }
 939         }
 940         Point invokerOrigin;
 941         if (invoker != null) {
 942             invokerOrigin = invoker.getLocationOnScreen();
 943 
 944             // To avoid integer overflow
 945             long lx, ly;
 946             lx = ((long) invokerOrigin.x) +
 947                  ((long) x);
 948             ly = ((long) invokerOrigin.y) +
 949                  ((long) y);
 950             if(lx > Integer.MAX_VALUE) lx = Integer.MAX_VALUE;
 951             if(lx < Integer.MIN_VALUE) lx = Integer.MIN_VALUE;
 952             if(ly > Integer.MAX_VALUE) ly = Integer.MAX_VALUE;
 953             if(ly < Integer.MIN_VALUE) ly = Integer.MIN_VALUE;
 954 
 955             setLocation((int) lx, (int) ly);
 956         } else {
 957             setLocation(x, y);
 958         }
 959         setVisible(true);
 960     }
 961 
 962     /**
 963      * Returns the popup menu which is at the root of the menu system
 964      * for this popup menu.
 965      *
 966      * @return the topmost grandparent <code>JPopupMenu</code>
 967      */
 968     JPopupMenu getRootPopupMenu() {
 969         JPopupMenu mp = this;
 970         while((mp!=null) && (mp.isPopupMenu()!=true) &&
 971               (mp.getInvoker() != null) &&
 972               (mp.getInvoker().getParent() != null) &&
 973               (mp.getInvoker().getParent() instanceof JPopupMenu)
 974               ) {
 975             mp = (JPopupMenu) mp.getInvoker().getParent();
 976         }
 977         return mp;
 978     }
 979 
 980     /**
 981      * Returns the component at the specified index.
 982      *
 983      * @param i  the index of the component, where 0 is the first
 984      * @return the <code>Component</code> at that index
 985      * @deprecated replaced by {@link java.awt.Container#getComponent(int)}
 986      */
 987     @Deprecated
 988     public Component getComponentAtIndex(int i) {
 989         return getComponent(i);
 990     }
 991 
 992     /**
 993      * Returns the index of the specified component.
 994      *
 995      * @param  c the <code>Component</code> to find
 996      * @return the index of the component, where 0 is the first;
 997      *         or -1 if the component is not found
 998      */
 999     public int getComponentIndex(Component c) {
1000         int ncomponents = this.getComponentCount();
1001         Component[] component = this.getComponents();
1002         for (int i = 0 ; i < ncomponents ; i++) {
1003             Component comp = component[i];
1004             if (comp == c)
1005                 return i;
1006         }
1007         return -1;
1008     }
1009 
1010     /**
1011      * Sets the size of the Popup window using a <code>Dimension</code> object.
1012      * This is equivalent to <code>setPreferredSize(d)</code>.
1013      *
1014      * @param d   the <code>Dimension</code> specifying the new size
1015      * of this component.
1016      */
1017     @BeanProperty(description
1018             = "The size of the popup menu")
1019     public void setPopupSize(Dimension d) {
1020         Dimension oldSize = getPreferredSize();
1021 
1022         setPreferredSize(d);
1023         if (popup != null) {
1024             Dimension newSize = getPreferredSize();
1025 
1026             if (!oldSize.equals(newSize)) {
1027                 showPopup();
1028             }
1029         }
1030     }
1031 
1032     /**
1033      * Sets the size of the Popup window to the specified width and
1034      * height. This is equivalent to
1035      *  <code>setPreferredSize(new Dimension(width, height))</code>.
1036      *
1037      * @param width the new width of the Popup in pixels
1038      * @param height the new height of the Popup in pixels
1039      */
1040     @BeanProperty(description
1041             = "The size of the popup menu")
1042     public void setPopupSize(int width, int height) {
1043         setPopupSize(new Dimension(width, height));
1044     }
1045 
1046     /**
1047      * Sets the currently selected component,  This will result
1048      * in a change to the selection model.
1049      *
1050      * @param sel the <code>Component</code> to select
1051      */
1052     @BeanProperty(expert = true, hidden = true, description
1053             = "The selected component on the popup menu")
1054     public void setSelected(Component sel) {
1055         SingleSelectionModel model = getSelectionModel();
1056         int index = getComponentIndex(sel);
1057         model.setSelectedIndex(index);
1058     }
1059 
1060     /**
1061      * Checks whether the border should be painted.
1062      *
1063      * @return true if the border is painted, false otherwise
1064      * @see #setBorderPainted
1065      */
1066     public boolean isBorderPainted() {
1067         return paintBorder;
1068     }
1069 
1070     /**
1071      * Sets whether the border should be painted.
1072      *
1073      * @param b if true, the border is painted.
1074      * @see #isBorderPainted
1075      */
1076     @BeanProperty(bound = false, description
1077             = "Is the border of the popup menu painted")
1078     public void setBorderPainted(boolean b) {
1079         paintBorder = b;
1080         repaint();
1081     }
1082 
1083     /**
1084      * Paints the popup menu's border if the <code>borderPainted</code>
1085      * property is <code>true</code>.
1086      * @param g  the <code>Graphics</code> object
1087      *
1088      * @see JComponent#paint
1089      * @see JComponent#setBorder
1090      */
1091     protected void paintBorder(Graphics g) {
1092         if (isBorderPainted()) {
1093             super.paintBorder(g);
1094         }
1095     }
1096 
1097     /**
1098      * Returns the margin, in pixels, between the popup menu's border and
1099      * its containers.
1100      *
1101      * @return an <code>Insets</code> object containing the margin values.
1102      */
1103     @BeanProperty(bound = false)
1104     public Insets getMargin() {
1105         if(margin == null) {
1106             return new Insets(0,0,0,0);
1107         } else {
1108             return margin;
1109         }
1110     }
1111 
1112 
1113     /**
1114      * Examines the list of menu items to determine whether
1115      * <code>popup</code> is a popup menu.
1116      *
1117      * @param popup  a <code>JPopupMenu</code>
1118      * @return true if <code>popup</code>
1119      */
1120     boolean isSubPopupMenu(JPopupMenu popup) {
1121         int ncomponents = this.getComponentCount();
1122         Component[] component = this.getComponents();
1123         for (int i = 0 ; i < ncomponents ; i++) {
1124             Component comp = component[i];
1125             if (comp instanceof JMenu) {
1126                 JMenu menu = (JMenu)comp;
1127                 JPopupMenu subPopup = menu.getPopupMenu();
1128                 if (subPopup == popup)
1129                     return true;
1130                 if (subPopup.isSubPopupMenu(popup))
1131                     return true;
1132             }
1133         }
1134         return false;
1135     }
1136 
1137 
1138     private static Frame getFrame(Component c) {
1139         Component w = c;
1140 
1141         while(!(w instanceof Frame) && (w!=null)) {
1142             w = w.getParent();
1143         }
1144         return (Frame)w;
1145     }
1146 
1147 
1148     /**
1149      * Returns a string representation of this <code>JPopupMenu</code>.
1150      * This method
1151      * is intended to be used only for debugging purposes, and the
1152      * content and format of the returned string may vary between
1153      * implementations. The returned string may be empty but may not
1154      * be <code>null</code>.
1155      *
1156      * @return  a string representation of this <code>JPopupMenu</code>.
1157      */
1158     protected String paramString() {
1159         String labelString = (label != null ?
1160                               label : "");
1161         String paintBorderString = (paintBorder ?
1162                                     "true" : "false");
1163         String marginString = (margin != null ?
1164                               margin.toString() : "");
1165         String lightWeightPopupEnabledString = (isLightWeightPopupEnabled() ?
1166                                                 "true" : "false");
1167         return super.paramString() +
1168             ",desiredLocationX=" + desiredLocationX +
1169             ",desiredLocationY=" + desiredLocationY +
1170         ",label=" + labelString +
1171         ",lightWeightPopupEnabled=" + lightWeightPopupEnabledString +
1172         ",margin=" + marginString +
1173         ",paintBorder=" + paintBorderString;
1174     }
1175 
1176 /////////////////
1177 // Accessibility support
1178 ////////////////
1179 
1180     /**
1181      * Gets the AccessibleContext associated with this JPopupMenu.
1182      * For JPopupMenus, the AccessibleContext takes the form of an
1183      * AccessibleJPopupMenu.
1184      * A new AccessibleJPopupMenu instance is created if necessary.
1185      *
1186      * @return an AccessibleJPopupMenu that serves as the
1187      *         AccessibleContext of this JPopupMenu
1188      */
1189     @BeanProperty(bound = false)
1190     public AccessibleContext getAccessibleContext() {
1191         if (accessibleContext == null) {
1192             accessibleContext = new AccessibleJPopupMenu();
1193         }
1194         return accessibleContext;
1195     }
1196 
1197     /**
1198      * This class implements accessibility support for the
1199      * <code>JPopupMenu</code> class.  It provides an implementation of the
1200      * Java Accessibility API appropriate to popup menu user-interface
1201      * elements.
1202      */
1203     @SuppressWarnings("serial")
1204     protected class AccessibleJPopupMenu extends AccessibleJComponent
1205         implements PropertyChangeListener {
1206 
1207         /**
1208          * AccessibleJPopupMenu constructor
1209          *
1210          * @since 1.5
1211          */
1212         protected AccessibleJPopupMenu() {
1213             JPopupMenu.this.addPropertyChangeListener(this);
1214         }
1215 
1216         /**
1217          * Get the role of this object.
1218          *
1219          * @return an instance of AccessibleRole describing the role of
1220          * the object
1221          */
1222         public AccessibleRole getAccessibleRole() {
1223             return AccessibleRole.POPUP_MENU;
1224         }
1225 
1226         /**
1227          * This method gets called when a bound property is changed.
1228          * @param e A <code>PropertyChangeEvent</code> object describing
1229          * the event source and the property that has changed. Must not be null.
1230          *
1231          * @throws NullPointerException if the parameter is null.
1232          * @since 1.5
1233          */
1234         public void propertyChange(PropertyChangeEvent e) {
1235             String propertyName = e.getPropertyName();
1236             if (propertyName == "visible") {
1237                 if (e.getOldValue() == Boolean.FALSE &&
1238                     e.getNewValue() == Boolean.TRUE) {
1239                     handlePopupIsVisibleEvent(true);
1240 
1241                 } else if (e.getOldValue() == Boolean.TRUE &&
1242                            e.getNewValue() == Boolean.FALSE) {
1243                     handlePopupIsVisibleEvent(false);
1244                 }
1245             }
1246         }
1247 
1248         /*
1249          * Handles popup "visible" PropertyChangeEvent
1250          */
1251         private void handlePopupIsVisibleEvent(boolean visible) {
1252             if (visible) {
1253                 // notify listeners that the popup became visible
1254                 firePropertyChange(ACCESSIBLE_STATE_PROPERTY,
1255                                    null, AccessibleState.VISIBLE);
1256                 // notify listeners that a popup list item is selected
1257                 fireActiveDescendant();
1258             } else {
1259                 // notify listeners that the popup became hidden
1260                 firePropertyChange(ACCESSIBLE_STATE_PROPERTY,
1261                                    AccessibleState.VISIBLE, null);
1262             }
1263         }
1264 
1265         /*
1266          * Fires AccessibleActiveDescendant PropertyChangeEvent to notify listeners
1267          * on the popup menu invoker that a popup list item has been selected
1268          */
1269         private void fireActiveDescendant() {
1270             if (JPopupMenu.this instanceof BasicComboPopup) {
1271                 // get the popup list
1272                 JList<?> popupList = ((BasicComboPopup)JPopupMenu.this).getList();
1273                 if (popupList == null) {
1274                     return;
1275                 }
1276 
1277                 // get the first selected item
1278                 AccessibleContext ac = popupList.getAccessibleContext();
1279                 AccessibleSelection selection = ac.getAccessibleSelection();
1280                 if (selection == null) {
1281                     return;
1282                 }
1283                 Accessible a = selection.getAccessibleSelection(0);
1284                 if (a == null) {
1285                     return;
1286                 }
1287                 AccessibleContext selectedItem = a.getAccessibleContext();
1288 
1289                 // fire the event with the popup invoker as the source.
1290                 if (selectedItem != null && invoker != null) {
1291                     AccessibleContext invokerContext = invoker.getAccessibleContext();
1292                     if (invokerContext != null) {
1293                         // Check invokerContext because Component.getAccessibleContext
1294                         // returns null. Classes that extend Component are responsible
1295                         // for returning a non-null AccessibleContext.
1296                         invokerContext.firePropertyChange(
1297                             ACCESSIBLE_ACTIVE_DESCENDANT_PROPERTY,
1298                             null, selectedItem);
1299                     }
1300                 }
1301             }
1302         }
1303     } // inner class AccessibleJPopupMenu
1304 
1305 
1306 ////////////
1307 // Serialization support.
1308 ////////////
1309     private void writeObject(ObjectOutputStream s) throws IOException {
1310         Vector<Object> values = new Vector<Object>();
1311 
1312         s.defaultWriteObject();
1313         // Save the invoker, if its Serializable.
1314         if(invoker != null && invoker instanceof Serializable) {
1315             values.addElement("invoker");
1316             values.addElement(invoker);
1317         }
1318         // Save the popup, if its Serializable.
1319         if(popup != null && popup instanceof Serializable) {
1320             values.addElement("popup");
1321             values.addElement(popup);
1322         }
1323         s.writeObject(values);
1324 
1325         if (getUIClassID().equals(uiClassID)) {
1326             byte count = JComponent.getWriteObjCounter(this);
1327             JComponent.setWriteObjCounter(this, --count);
1328             if (count == 0 && ui != null) {
1329                 ui.installUI(this);
1330             }
1331         }
1332     }
1333 
1334     // implements javax.swing.MenuElement
1335     private void readObject(ObjectInputStream s)
1336         throws IOException, ClassNotFoundException {
1337         ObjectInputStream.GetField f = s.readFields();
1338 
1339         int newDesiredLocationX = f.get("desiredLocationX", 0);
1340         int newDesiredLocationY = f.get("desiredLocationY", 0);
1341         Point p = adjustPopupLocationToFitScreen(
1342                 newDesiredLocationX, newDesiredLocationY);
1343         desiredLocationX = p.x;
1344         desiredLocationY = p.y;
1345 
1346         label = (String) f.get("label", null);
1347         paintBorder = f.get("paintBorder", false);
1348         margin = (Insets) f.get("margin", null);
1349         lightWeightPopup = f.get("lightWeightPopup", false);
1350         selectionModel = (SingleSelectionModel) f.get("selectionModel", null);
1351 
1352         Vector<?>          values = (Vector)s.readObject();
1353         int             indexCounter = 0;
1354         int             maxCounter = values.size();
1355 
1356         if(indexCounter < maxCounter && values.elementAt(indexCounter).
1357            equals("invoker")) {
1358             invoker = (Component)values.elementAt(++indexCounter);
1359             indexCounter++;
1360         }
1361         if(indexCounter < maxCounter && values.elementAt(indexCounter).
1362            equals("popup")) {
1363             popup = (Popup)values.elementAt(++indexCounter);
1364             indexCounter++;
1365         }
1366     }
1367 
1368 
1369     /**
1370      * This method is required to conform to the
1371      * <code>MenuElement</code> interface, but it not implemented.
1372      * @see MenuElement#processMouseEvent(MouseEvent, MenuElement[], MenuSelectionManager)
1373      */
1374     public void processMouseEvent(MouseEvent event,MenuElement path[],MenuSelectionManager manager) {}
1375 
1376     /**
1377      * Processes a key event forwarded from the
1378      * <code>MenuSelectionManager</code> and changes the menu selection,
1379      * if necessary, by using <code>MenuSelectionManager</code>'s API.
1380      * <p>
1381      * Note: you do not have to forward the event to sub-components.
1382      * This is done automatically by the <code>MenuSelectionManager</code>.
1383      *
1384      * @param e  a <code>KeyEvent</code>
1385      * @param path the <code>MenuElement</code> path array
1386      * @param manager   the <code>MenuSelectionManager</code>
1387      */
1388     public void processKeyEvent(KeyEvent e, MenuElement path[],
1389                                 MenuSelectionManager manager) {
1390         MenuKeyEvent mke = new MenuKeyEvent(e.getComponent(), e.getID(),
1391                                              e.getWhen(), e.getModifiers(),
1392                                              e.getKeyCode(), e.getKeyChar(),
1393                                              path, manager);
1394         processMenuKeyEvent(mke);
1395 
1396         if (mke.isConsumed())  {
1397             e.consume();
1398     }
1399     }
1400 
1401     /**
1402      * Handles a keystroke in a menu.
1403      *
1404      * @param e  a <code>MenuKeyEvent</code> object
1405      * @since 1.5
1406      */
1407     private void processMenuKeyEvent(MenuKeyEvent e) {
1408         switch (e.getID()) {
1409         case KeyEvent.KEY_PRESSED:
1410             fireMenuKeyPressed(e); break;
1411         case KeyEvent.KEY_RELEASED:
1412             fireMenuKeyReleased(e); break;
1413         case KeyEvent.KEY_TYPED:
1414             fireMenuKeyTyped(e); break;
1415         default:
1416             break;
1417         }
1418     }
1419 
1420     /**
1421      * Notifies all listeners that have registered interest for
1422      * notification on this event type.
1423      *
1424      * @param event a <code>MenuKeyEvent</code>
1425      * @see EventListenerList
1426      */
1427     private void fireMenuKeyPressed(MenuKeyEvent event) {
1428         Object[] listeners = listenerList.getListenerList();
1429         for (int i = listeners.length-2; i>=0; i-=2) {
1430             if (listeners[i]==MenuKeyListener.class) {
1431                 ((MenuKeyListener)listeners[i+1]).menuKeyPressed(event);
1432             }
1433         }
1434     }
1435 
1436     /**
1437      * Notifies all listeners that have registered interest for
1438      * notification on this event type.
1439      *
1440      * @param event a <code>MenuKeyEvent</code>
1441      * @see EventListenerList
1442      */
1443     private void fireMenuKeyReleased(MenuKeyEvent event) {
1444         Object[] listeners = listenerList.getListenerList();
1445         for (int i = listeners.length-2; i>=0; i-=2) {
1446             if (listeners[i]==MenuKeyListener.class) {
1447                 ((MenuKeyListener)listeners[i+1]).menuKeyReleased(event);
1448             }
1449         }
1450     }
1451 
1452     /**
1453      * Notifies all listeners that have registered interest for
1454      * notification on this event type.
1455      *
1456      * @param event a <code>MenuKeyEvent</code>
1457      * @see EventListenerList
1458      */
1459     private void fireMenuKeyTyped(MenuKeyEvent event) {
1460         Object[] listeners = listenerList.getListenerList();
1461         for (int i = listeners.length-2; i>=0; i-=2) {
1462             if (listeners[i]==MenuKeyListener.class) {
1463                 ((MenuKeyListener)listeners[i+1]).menuKeyTyped(event);
1464             }
1465         }
1466     }
1467 
1468     /**
1469      * Messaged when the menubar selection changes to activate or
1470      * deactivate this menu. This implements the
1471      * <code>javax.swing.MenuElement</code> interface.
1472      * Overrides <code>MenuElement.menuSelectionChanged</code>.
1473      *
1474      * @param isIncluded  true if this menu is active, false if
1475      *        it is not
1476      * @see MenuElement#menuSelectionChanged(boolean)
1477      */
1478     public void menuSelectionChanged(boolean isIncluded) {
1479         if (DEBUG) {
1480             System.out.println("In JPopupMenu.menuSelectionChanged " + isIncluded);
1481         }
1482         if(invoker instanceof JMenu) {
1483             JMenu m = (JMenu) invoker;
1484             if(isIncluded)
1485                 m.setPopupMenuVisible(true);
1486             else
1487                 m.setPopupMenuVisible(false);
1488         }
1489         if (isPopupMenu() && !isIncluded)
1490           setVisible(false);
1491     }
1492 
1493     /**
1494      * Returns an array of <code>MenuElement</code>s containing the submenu
1495      * for this menu component.  It will only return items conforming to
1496      * the <code>JMenuElement</code> interface.
1497      * If popup menu is <code>null</code> returns
1498      * an empty array.  This method is required to conform to the
1499      * <code>MenuElement</code> interface.
1500      *
1501      * @return an array of <code>MenuElement</code> objects
1502      * @see MenuElement#getSubElements
1503      */
1504     @BeanProperty(bound = false)
1505     public MenuElement[] getSubElements() {
1506         MenuElement result[];
1507         Vector<MenuElement> tmp = new Vector<MenuElement>();
1508         int c = getComponentCount();
1509         int i;
1510         Component m;
1511 
1512         for(i=0 ; i < c ; i++) {
1513             m = getComponent(i);
1514             if(m instanceof MenuElement)
1515                 tmp.addElement((MenuElement) m);
1516         }
1517 
1518         result = new MenuElement[tmp.size()];
1519         for(i=0,c=tmp.size() ; i < c ; i++)
1520             result[i] = tmp.elementAt(i);
1521         return result;
1522     }
1523 
1524     /**
1525      * Returns this <code>JPopupMenu</code> component.
1526      * @return this <code>JPopupMenu</code> object
1527      * @see MenuElement#getComponent
1528      */
1529     public Component getComponent() {
1530         return this;
1531     }
1532 
1533 
1534     /**
1535      * A popup menu-specific separator.
1536      */
1537     @SuppressWarnings("serial")
1538     public static class Separator extends JSeparator
1539     {
1540         /**
1541          * Constructs a popup menu-specific Separator.
1542          */
1543         public Separator( )
1544         {
1545             super( JSeparator.HORIZONTAL );
1546         }
1547 
1548         /**
1549          * Returns the name of the L&amp;F class that renders this component.
1550          *
1551          * @return the string "PopupMenuSeparatorUI"
1552          * @see JComponent#getUIClassID
1553          * @see UIDefaults#getUI
1554          */
1555         public String getUIClassID()
1556         {
1557             return "PopupMenuSeparatorUI";
1558 
1559         }
1560     }
1561 
1562     /**
1563      * Returns true if the <code>MouseEvent</code> is considered a popup trigger
1564      * by the <code>JPopupMenu</code>'s currently installed UI.
1565      *
1566      * @param e a {@code MouseEvent}
1567      * @return true if the mouse event is a popup trigger
1568      * @since 1.3
1569      */
1570     public boolean isPopupTrigger(MouseEvent e) {
1571         return getUI().isPopupTrigger(e);
1572     }
1573 }