/*
* Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package javax.swing;
import java.awt.*;
import java.awt.event.*;
import java.io.
IOException;
import java.io.
ObjectInputStream;
import java.io.
ObjectOutputStream;
import java.io.
Serializable;
import java.beans.*;
import java.util.
Locale;
import java.util.
Vector;
import java.util.
Hashtable;
import javax.accessibility.*;
import javax.swing.plaf.
PopupMenuUI;
import javax.swing.plaf.
ComponentUI;
import javax.swing.plaf.basic.
BasicComboPopup;
import javax.swing.event.*;
import sun.awt.
SunToolkit;
import sun.security.util.
SecurityConstants;
import java.applet.
Applet;
/**
* An implementation of a popup menu -- a small window that pops up
* and displays a series of choices. A <code>JPopupMenu</code> is used for the
* menu that appears when the user selects an item on the menu bar.
* It is also used for "pull-right" menu that appears when the
* selects a menu item that activates it. Finally, a <code>JPopupMenu</code>
* can also be used anywhere else you want a menu to appear. For
* example, when the user right-clicks in a specified area.
* <p>
* For information and examples of using popup menus, see
* <a
href="https://docs.oracle.com/javase/tutorial/uiswing/components/menu.html">How to Use Menus</a>
* in <em>The Java Tutorial.</em>
* <p>
* <strong>Warning:</strong> Swing is not thread safe. For more
* information see <a
* href="package-summary.html#threading">Swing's Threading
* Policy</a>.
* <p>
* <strong>Warning:</strong>
* Serialized objects of this class will not be compatible with
* future Swing releases. The current serialization support is
* appropriate for short term storage or RMI between applications running
* the same version of Swing. As of 1.4, support for long term storage
* of all JavaBeans™
* has been added to the <code>java.beans</code> package.
* Please see {@link java.beans.XMLEncoder}.
*
* @beaninfo
* attribute: isContainer false
* description: A small window that pops up and displays a series of choices.
*
* @author Georges Saab
* @author David Karlton
* @author Arnaud Weber
*/
@
SuppressWarnings("serial")
public class
JPopupMenu extends
JComponent implements
Accessible,
MenuElement {
/**
* @see #getUIClassID
* @see #readObject
*/
private static final
String uiClassID = "PopupMenuUI";
/**
* Key used in AppContext to determine if light way popups are the default.
*/
private static final
Object defaultLWPopupEnabledKey =
new
StringBuffer("JPopupMenu.defaultLWPopupEnabledKey");
/** Bug#4425878-Property javax.swing.adjustPopupLocationToFit introduced */
static boolean
popupPostionFixDisabled = false;
static {
popupPostionFixDisabled = java.security.
AccessController.
doPrivileged(
new sun.security.action.
GetPropertyAction(
"javax.swing.adjustPopupLocationToFit","")).
equals("false");
}
transient
Component invoker;
transient
Popup popup;
transient
Frame frame;
private int
desiredLocationX,
desiredLocationY;
private
String label = null;
private boolean
paintBorder = true;
private
Insets margin = null;
/**
* Used to indicate if lightweight popups should be used.
*/
private boolean
lightWeightPopup = true;
/*
* Model for the selected subcontrol.
*/
private
SingleSelectionModel selectionModel;
/* Lock object used in place of class object for synchronization.
* (4187686)
*/
private static final
Object classLock = new
Object();
/* diagnostic aids -- should be false for production builds. */
private static final boolean
TRACE = false; // trace creates and disposes
private static final boolean
VERBOSE = false; // show reuse hits/misses
private static final boolean
DEBUG = false; // show bad params, misc.
/**
* Sets the default value of the <code>lightWeightPopupEnabled</code>
* property.
*
* @param aFlag <code>true</code> if popups can be lightweight,
* otherwise <code>false</code>
* @see #getDefaultLightWeightPopupEnabled
* @see #setLightWeightPopupEnabled
*/
public static void
setDefaultLightWeightPopupEnabled(boolean
aFlag) {
SwingUtilities.
appContextPut(
defaultLWPopupEnabledKey,
Boolean.
valueOf(
aFlag));
}
/**
* Gets the <code>defaultLightWeightPopupEnabled</code> property,
* which by default is <code>true</code>.
*
* @return the value of the <code>defaultLightWeightPopupEnabled</code>
* property
*
* @see #setDefaultLightWeightPopupEnabled
*/
public static boolean
getDefaultLightWeightPopupEnabled() {
Boolean b = (
Boolean)
SwingUtilities.
appContextGet(
defaultLWPopupEnabledKey);
if (
b == null) {
SwingUtilities.
appContextPut(
defaultLWPopupEnabledKey,
Boolean.
TRUE);
return true;
}
return
b.
booleanValue();
}
/**
* Constructs a <code>JPopupMenu</code> without an "invoker".
*/
public
JPopupMenu() {
this(null);
}
/**
* Constructs a <code>JPopupMenu</code> with the specified title.
*
* @param label the string that a UI may use to display as a title
* for the popup menu.
*/
public
JPopupMenu(
String label) {
this.
label =
label;
lightWeightPopup =
getDefaultLightWeightPopupEnabled();
setSelectionModel(new
DefaultSingleSelectionModel());
enableEvents(
AWTEvent.
MOUSE_EVENT_MASK);
setFocusTraversalKeysEnabled(false);
updateUI();
}
/**
* Returns the look and feel (L&F) object that renders this component.
*
* @return the <code>PopupMenuUI</code> object that renders this component
*/
public
PopupMenuUI getUI() {
return (
PopupMenuUI)
ui;
}
/**
* Sets the L&F object that renders this component.
*
* @param ui the new <code>PopupMenuUI</code> L&F object
* @see UIDefaults#getUI
* @beaninfo
* bound: true
* hidden: true
* attribute: visualUpdate true
* description: The UI object that implements the Component's LookAndFeel.
*/
public void
setUI(
PopupMenuUI ui) {
super.setUI(
ui);
}
/**
* Resets the UI property to a value from the current look and feel.
*
* @see JComponent#updateUI
*/
public void
updateUI() {
setUI((
PopupMenuUI)
UIManager.
getUI(this));
}
/**
* Returns the name of the L&F class that renders this component.
*
* @return the string "PopupMenuUI"
* @see JComponent#getUIClassID
* @see UIDefaults#getUI
*/
public
String getUIClassID() {
return
uiClassID;
}
protected void
processFocusEvent(
FocusEvent evt) {
super.processFocusEvent(
evt);
}
/**
* Processes key stroke events such as mnemonics and accelerators.
*
* @param evt the key event to be processed
*/
protected void
processKeyEvent(
KeyEvent evt) {
MenuSelectionManager.
defaultManager().
processKeyEvent(
evt);
if (
evt.
isConsumed()) {
return;
}
super.processKeyEvent(
evt);
}
/**
* Returns the model object that handles single selections.
*
* @return the <code>selectionModel</code> property
* @see SingleSelectionModel
*/
public
SingleSelectionModel getSelectionModel() {
return
selectionModel;
}
/**
* Sets the model object to handle single selections.
*
* @param model the new <code>SingleSelectionModel</code>
* @see SingleSelectionModel
* @beaninfo
* description: The selection model for the popup menu
* expert: true
*/
public void
setSelectionModel(
SingleSelectionModel model) {
selectionModel =
model;
}
/**
* Appends the specified menu item to the end of this menu.
*
* @param menuItem the <code>JMenuItem</code> to add
* @return the <code>JMenuItem</code> added
*/
public
JMenuItem add(
JMenuItem menuItem) {
super.add(
menuItem);
return
menuItem;
}
/**
* Creates a new menu item with the specified text and appends
* it to the end of this menu.
*
* @param s the string for the menu item to be added
*/
public
JMenuItem add(
String s) {
return
add(new
JMenuItem(
s));
}
/**
* Appends a new menu item to the end of the menu which
* dispatches the specified <code>Action</code> object.
*
* @param a the <code>Action</code> to add to the menu
* @return the new menu item
* @see Action
*/
public
JMenuItem add(
Action a) {
JMenuItem mi =
createActionComponent(
a);
mi.
setAction(
a);
add(
mi);
return
mi;
}
/**
* Returns an point which has been adjusted to take into account of the
* desktop bounds, taskbar and multi-monitor configuration.
* <p>
* This adustment may be cancelled by invoking the application with
* -Djavax.swing.adjustPopupLocationToFit=false
*/
Point adjustPopupLocationToFitScreen(int
xPosition, int
yPosition) {
Point popupLocation = new
Point(
xPosition,
yPosition);
if(
popupPostionFixDisabled == true ||
GraphicsEnvironment.
isHeadless()) {
return
popupLocation;
}
// Get screen bounds
Rectangle scrBounds;
GraphicsConfiguration gc =
getCurrentGraphicsConfiguration(
popupLocation);
Toolkit toolkit =
Toolkit.
getDefaultToolkit();
if(
gc != null) {
// If we have GraphicsConfiguration use it to get screen bounds
scrBounds =
gc.
getBounds();
} else {
// If we don't have GraphicsConfiguration use primary screen
scrBounds = new
Rectangle(
toolkit.
getScreenSize());
}
// Calculate the screen size that popup should fit
Dimension popupSize =
JPopupMenu.this.
getPreferredSize();
long
popupRightX = (long)
popupLocation.
x + (long)
popupSize.
width;
long
popupBottomY = (long)
popupLocation.
y + (long)
popupSize.
height;
int
scrWidth =
scrBounds.
width;
int
scrHeight =
scrBounds.
height;
if (!
canPopupOverlapTaskBar()) {
// Insets include the task bar. Take them into account.
Insets scrInsets =
toolkit.
getScreenInsets(
gc);
scrBounds.
x +=
scrInsets.
left;
scrBounds.
y +=
scrInsets.
top;
scrWidth -=
scrInsets.
left +
scrInsets.
right;
scrHeight -=
scrInsets.
top +
scrInsets.
bottom;
}
int
scrRightX =
scrBounds.
x +
scrWidth;
int
scrBottomY =
scrBounds.
y +
scrHeight;
// Ensure that popup menu fits the screen
if (
popupRightX > (long)
scrRightX) {
popupLocation.
x =
scrRightX -
popupSize.
width;
}
if (
popupBottomY > (long)
scrBottomY) {
popupLocation.
y =
scrBottomY -
popupSize.
height;
}
if (
popupLocation.
x <
scrBounds.
x) {
popupLocation.
x =
scrBounds.
x;
}
if (
popupLocation.
y <
scrBounds.
y) {
popupLocation.
y =
scrBounds.
y;
}
return
popupLocation;
}
/**
* Tries to find GraphicsConfiguration
* that contains the mouse cursor position.
* Can return null.
*/
private
GraphicsConfiguration getCurrentGraphicsConfiguration(
Point popupLocation) {
GraphicsConfiguration gc = null;
GraphicsEnvironment ge =
GraphicsEnvironment.
getLocalGraphicsEnvironment();
GraphicsDevice[]
gd =
ge.
getScreenDevices();
for(int
i = 0;
i <
gd.length;
i++) {
if(
gd[
i].
getType() ==
GraphicsDevice.
TYPE_RASTER_SCREEN) {
GraphicsConfiguration dgc =
gd[
i].
getDefaultConfiguration();
if(
dgc.
getBounds().
contains(
popupLocation)) {
gc =
dgc;
break;
}
}
}
// If not found and we have invoker, ask invoker about his gc
if(
gc == null &&
getInvoker() != null) {
gc =
getInvoker().
getGraphicsConfiguration();
}
return
gc;
}
/**
* Returns whether popup is allowed to be shown above the task bar.
*/
static boolean
canPopupOverlapTaskBar() {
boolean
result = true;
Toolkit tk =
Toolkit.
getDefaultToolkit();
if (
tk instanceof
SunToolkit) {
result = ((
SunToolkit)
tk).
canPopupOverlapTaskBar();
}
return
result;
}
/**
* Factory method which creates the <code>JMenuItem</code> for
* <code>Actions</code> added to the <code>JPopupMenu</code>.
*
* @param a the <code>Action</code> for the menu item to be added
* @return the new menu item
* @see Action
*
* @since 1.3
*/
protected
JMenuItem createActionComponent(
Action a) {
JMenuItem mi = new
JMenuItem() {
protected
PropertyChangeListener createActionPropertyChangeListener(
Action a) {
PropertyChangeListener pcl =
createActionChangeListener(this);
if (
pcl == null) {
pcl = super.createActionPropertyChangeListener(
a);
}
return
pcl;
}
};
mi.
setHorizontalTextPosition(
JButton.
TRAILING);
mi.
setVerticalTextPosition(
JButton.
CENTER);
return
mi;
}
/**
* Returns a properly configured <code>PropertyChangeListener</code>
* which updates the control as changes to the <code>Action</code> occur.
*/
protected
PropertyChangeListener createActionChangeListener(
JMenuItem b) {
return
b.
createActionPropertyChangeListener0(
b.
getAction());
}
/**
* Removes the component at the specified index from this popup menu.
*
* @param pos the position of the item to be removed
* @exception IllegalArgumentException if the value of
* <code>pos</code> < 0, or if the value of
* <code>pos</code> is greater than the
* number of items
*/
public void
remove(int
pos) {
if (
pos < 0) {
throw new
IllegalArgumentException("index less than zero.");
}
if (
pos >
getComponentCount() -1) {
throw new
IllegalArgumentException("index greater than the number of items.");
}
super.remove(
pos);
}
/**
* Sets the value of the <code>lightWeightPopupEnabled</code> property,
* which by default is <code>true</code>.
* By default, when a look and feel displays a popup,
* it can choose to
* use a lightweight (all-Java) popup.
* Lightweight popup windows are more efficient than heavyweight
* (native peer) windows,
* but lightweight and heavyweight components do not mix well in a GUI.
* If your application mixes lightweight and heavyweight components,
* you should disable lightweight popups.
* Some look and feels might always use heavyweight popups,
* no matter what the value of this property.
*
* @param aFlag <code>false</code> to disable lightweight popups
* @beaninfo
* description: Determines whether lightweight popups are used when possible
* expert: true
*
* @see #isLightWeightPopupEnabled
*/
public void
setLightWeightPopupEnabled(boolean
aFlag) {
// NOTE: this use to set the flag on a shared JPopupMenu, which meant
// this effected ALL JPopupMenus.
lightWeightPopup =
aFlag;
}
/**
* Gets the <code>lightWeightPopupEnabled</code> property.
*
* @return the value of the <code>lightWeightPopupEnabled</code> property
* @see #setLightWeightPopupEnabled
*/
public boolean
isLightWeightPopupEnabled() {
return
lightWeightPopup;
}
/**
* Returns the popup menu's label
*
* @return a string containing the popup menu's label
* @see #setLabel
*/
public
String getLabel() {
return
label;
}
/**
* Sets the popup menu's label. Different look and feels may choose
* to display or not display this.
*
* @param label a string specifying the label for the popup menu
*
* @see #setLabel
* @beaninfo
* description: The label for the popup menu.
* bound: true
*/
public void
setLabel(
String label) {
String oldValue = this.
label;
this.
label =
label;
firePropertyChange("label",
oldValue,
label);
if (
accessibleContext != null) {
accessibleContext.
firePropertyChange(
AccessibleContext.
ACCESSIBLE_VISIBLE_DATA_PROPERTY,
oldValue,
label);
}
invalidate();
repaint();
}
/**
* Appends a new separator at the end of the menu.
*/
public void
addSeparator() {
add( new
JPopupMenu.
Separator() );
}
/**
* Inserts a menu item for the specified <code>Action</code> object at
* a given position.
*
* @param a the <code>Action</code> object to insert
* @param index specifies the position at which to insert the
* <code>Action</code>, where 0 is the first
* @exception IllegalArgumentException if <code>index</code> < 0
* @see Action
*/
public void
insert(
Action a, int
index) {
JMenuItem mi =
createActionComponent(
a);
mi.
setAction(
a);
insert(
mi,
index);
}
/**
* Inserts the specified component into the menu at a given
* position.
*
* @param component the <code>Component</code> to insert
* @param index specifies the position at which
* to insert the component, where 0 is the first
* @exception IllegalArgumentException if <code>index</code> < 0
*/
public void
insert(
Component component, int
index) {
if (
index < 0) {
throw new
IllegalArgumentException("index less than zero.");
}
int
nitems =
getComponentCount();
// PENDING(ges): Why not use an array?
Vector<
Component>
tempItems = new
Vector<
Component>();
/* Remove the item at index, nitems-index times
storing them in a temporary vector in the
order they appear on the menu.
*/
for (int
i =
index ;
i <
nitems;
i++) {
tempItems.
addElement(
getComponent(
index));
remove(
index);
}
add(
component);
/* Add the removed items back to the menu, they are
already in the correct order in the temp vector.
*/
for (
Component tempItem :
tempItems) {
add(
tempItem);
}
}
/**
* Adds a <code>PopupMenu</code> listener.
*
* @param l the <code>PopupMenuListener</code> to add
*/
public void
addPopupMenuListener(
PopupMenuListener l) {
listenerList.
add(
PopupMenuListener.class,
l);
}
/**
* Removes a <code>PopupMenu</code> listener.
*
* @param l the <code>PopupMenuListener</code> to remove
*/
public void
removePopupMenuListener(
PopupMenuListener l) {
listenerList.
remove(
PopupMenuListener.class,
l);
}
/**
* Returns an array of all the <code>PopupMenuListener</code>s added
* to this JMenuItem with addPopupMenuListener().
*
* @return all of the <code>PopupMenuListener</code>s added or an empty
* array if no listeners have been added
* @since 1.4
*/
public
PopupMenuListener[]
getPopupMenuListeners() {
return
listenerList.
getListeners(
PopupMenuListener.class);
}
/**
* Adds a <code>MenuKeyListener</code> to the popup menu.
*
* @param l the <code>MenuKeyListener</code> to be added
* @since 1.5
*/
public void
addMenuKeyListener(
MenuKeyListener l) {
listenerList.
add(
MenuKeyListener.class,
l);
}
/**
* Removes a <code>MenuKeyListener</code> from the popup menu.
*
* @param l the <code>MenuKeyListener</code> to be removed
* @since 1.5
*/
public void
removeMenuKeyListener(
MenuKeyListener l) {
listenerList.
remove(
MenuKeyListener.class,
l);
}
/**
* Returns an array of all the <code>MenuKeyListener</code>s added
* to this JPopupMenu with addMenuKeyListener().
*
* @return all of the <code>MenuKeyListener</code>s added or an empty
* array if no listeners have been added
* @since 1.5
*/
public
MenuKeyListener[]
getMenuKeyListeners() {
return
listenerList.
getListeners(
MenuKeyListener.class);
}
/**
* Notifies <code>PopupMenuListener</code>s that this popup menu will
* become visible.
*/
protected void
firePopupMenuWillBecomeVisible() {
Object[]
listeners =
listenerList.
getListenerList();
PopupMenuEvent e=null;
for (int
i =
listeners.length-2;
i>=0;
i-=2) {
if (
listeners[
i]==
PopupMenuListener.class) {
if (
e == null)
e = new
PopupMenuEvent(this);
((
PopupMenuListener)
listeners[
i+1]).
popupMenuWillBecomeVisible(
e);
}
}
}
/**
* Notifies <code>PopupMenuListener</code>s that this popup menu will
* become invisible.
*/
protected void
firePopupMenuWillBecomeInvisible() {
Object[]
listeners =
listenerList.
getListenerList();
PopupMenuEvent e=null;
for (int
i =
listeners.length-2;
i>=0;
i-=2) {
if (
listeners[
i]==
PopupMenuListener.class) {
if (
e == null)
e = new
PopupMenuEvent(this);
((
PopupMenuListener)
listeners[
i+1]).
popupMenuWillBecomeInvisible(
e);
}
}
}
/**
* Notifies <code>PopupMenuListeners</code> that this popup menu is
* cancelled.
*/
protected void
firePopupMenuCanceled() {
Object[]
listeners =
listenerList.
getListenerList();
PopupMenuEvent e=null;
for (int
i =
listeners.length-2;
i>=0;
i-=2) {
if (
listeners[
i]==
PopupMenuListener.class) {
if (
e == null)
e = new
PopupMenuEvent(this);
((
PopupMenuListener)
listeners[
i+1]).
popupMenuCanceled(
e);
}
}
}
/**
* Always returns true since popups, by definition, should always
* be on top of all other windows.
* @return true
*/
// package private
boolean
alwaysOnTop() {
return true;
}
/**
* Lays out the container so that it uses the minimum space
* needed to display its contents.
*/
public void
pack() {
if(
popup != null) {
Dimension pref =
getPreferredSize();
if (
pref == null ||
pref.
width !=
getWidth() ||
pref.
height !=
getHeight()) {
showPopup();
} else {
validate();
}
}
}
/**
* Sets the visibility of the popup menu.
*
* @param b true to make the popup visible, or false to
* hide it
* @beaninfo
* bound: true
* description: Makes the popup visible
*/
public void
setVisible(boolean
b) {
if (
DEBUG) {
System.
out.
println("JPopupMenu.setVisible " +
b);
}
// Is it a no-op?
if (
b ==
isVisible())
return;
// if closing, first close all Submenus
if (
b == false) {
// 4234793: This is a workaround because JPopupMenu.firePopupMenuCanceled is
// a protected method and cannot be called from BasicPopupMenuUI directly
// The real solution could be to make
// firePopupMenuCanceled public and call it directly.
Boolean doCanceled = (
Boolean)
getClientProperty("JPopupMenu.firePopupMenuCanceled");
if (
doCanceled != null &&
doCanceled ==
Boolean.
TRUE) {
putClientProperty("JPopupMenu.firePopupMenuCanceled",
Boolean.
FALSE);
firePopupMenuCanceled();
}
getSelectionModel().
clearSelection();
} else {
// This is a popup menu with MenuElement children,
// set selection path before popping up!
if (
isPopupMenu()) {
MenuElement me[] = new
MenuElement[1];
me[0] = this;
MenuSelectionManager.
defaultManager().
setSelectedPath(
me);
}
}
if(
b) {
firePopupMenuWillBecomeVisible();
showPopup();
firePropertyChange("visible",
Boolean.
FALSE,
Boolean.
TRUE);
} else if(
popup != null) {
firePopupMenuWillBecomeInvisible();
popup.
hide();
popup = null;
firePropertyChange("visible",
Boolean.
TRUE,
Boolean.
FALSE);
// 4694797: When popup menu is made invisible, selected path
// should be cleared
if (
isPopupMenu()) {
MenuSelectionManager.
defaultManager().
clearSelectedPath();
}
}
}
/**
* Retrieves <code>Popup</code> instance from the
* <code>PopupMenuUI</code> that has had <code>show</code> invoked on
* it. If the current <code>popup</code> is non-null,
* this will invoke <code>dispose</code> of it, and then
* <code>show</code> the new one.
* <p>
* This does NOT fire any events, it is up the caller to dispatch
* the necessary events.
*/
private void
showPopup() {
Popup oldPopup =
popup;
if (
oldPopup != null) {
oldPopup.
hide();
}
PopupFactory popupFactory =
PopupFactory.
getSharedInstance();
if (
isLightWeightPopupEnabled()) {
popupFactory.
setPopupType(
PopupFactory.
LIGHT_WEIGHT_POPUP);
}
else {
popupFactory.
setPopupType(
PopupFactory.
HEAVY_WEIGHT_POPUP);
}
// adjust the location of the popup
Point p =
adjustPopupLocationToFitScreen(
desiredLocationX,
desiredLocationY);
desiredLocationX =
p.
x;
desiredLocationY =
p.
y;
Popup newPopup =
getUI().
getPopup(this,
desiredLocationX,
desiredLocationY);
popupFactory.
setPopupType(
PopupFactory.
LIGHT_WEIGHT_POPUP);
popup =
newPopup;
newPopup.
show();
}
/**
* Returns true if the popup menu is visible (currently
* being displayed).
*/
public boolean
isVisible() {
return
popup != null;
}
/**
* Sets the location of the upper left corner of the
* popup menu using x, y coordinates.
* <p>
* The method changes the geometry-related data. Therefore,
* the native windowing system may ignore such requests, or it may modify
* the requested data, so that the {@code JPopupMenu} object is placed and sized
* in a way that corresponds closely to the desktop settings.
*
* @param x the x coordinate of the popup's new position
* in the screen's coordinate space
* @param y the y coordinate of the popup's new position
* in the screen's coordinate space
* @beaninfo
* description: The location of the popup menu.
*/
public void
setLocation(int
x, int
y) {
int
oldX =
desiredLocationX;
int
oldY =
desiredLocationY;
desiredLocationX =
x;
desiredLocationY =
y;
if(
popup != null && (
x !=
oldX ||
y !=
oldY)) {
showPopup();
}
}
/**
* Returns true if the popup menu is a standalone popup menu
* rather than the submenu of a <code>JMenu</code>.
*
* @return true if this menu is a standalone popup menu, otherwise false
*/
private boolean
isPopupMenu() {
return ((
invoker != null) && !(
invoker instanceof
JMenu));
}
/**
* Returns the component which is the 'invoker' of this
* popup menu.
*
* @return the <code>Component</code> in which the popup menu is displayed
*/
public
Component getInvoker() {
return this.
invoker;
}
/**
* Sets the invoker of this popup menu -- the component in which
* the popup menu menu is to be displayed.
*
* @param invoker the <code>Component</code> in which the popup
* menu is displayed
* @beaninfo
* description: The invoking component for the popup menu
* expert: true
*/
public void
setInvoker(
Component invoker) {
Component oldInvoker = this.
invoker;
this.
invoker =
invoker;
if ((
oldInvoker != this.
invoker) && (
ui != null)) {
ui.
uninstallUI(this);
ui.
installUI(this);
}
invalidate();
}
/**
* Displays the popup menu at the position x,y in the coordinate
* space of the component invoker.
*
* @param invoker the component in whose space the popup menu is to appear
* @param x the x coordinate in invoker's coordinate space at which
* the popup menu is to be displayed
* @param y the y coordinate in invoker's coordinate space at which
* the popup menu is to be displayed
*/
public void
show(
Component invoker, int
x, int
y) {
if (
DEBUG) {
System.
out.
println("in JPopupMenu.show " );
}
setInvoker(
invoker);
Frame newFrame =
getFrame(
invoker);
if (
newFrame !=
frame) {
// Use the invoker's frame so that events
// are propagated properly
if (
newFrame!=null) {
this.
frame =
newFrame;
if(
popup != null) {
setVisible(false);
}
}
}
Point invokerOrigin;
if (
invoker != null) {
invokerOrigin =
invoker.
getLocationOnScreen();
// To avoid integer overflow
long
lx,
ly;
lx = ((long)
invokerOrigin.
x) +
((long)
x);
ly = ((long)
invokerOrigin.
y) +
((long)
y);
if(
lx >
Integer.
MAX_VALUE)
lx =
Integer.
MAX_VALUE;
if(
lx <
Integer.
MIN_VALUE)
lx =
Integer.
MIN_VALUE;
if(
ly >
Integer.
MAX_VALUE)
ly =
Integer.
MAX_VALUE;
if(
ly <
Integer.
MIN_VALUE)
ly =
Integer.
MIN_VALUE;
setLocation((int)
lx, (int)
ly);
} else {
setLocation(
x,
y);
}
setVisible(true);
}
/**
* Returns the popup menu which is at the root of the menu system
* for this popup menu.
*
* @return the topmost grandparent <code>JPopupMenu</code>
*/
JPopupMenu getRootPopupMenu() {
JPopupMenu mp = this;
while((
mp!=null) && (
mp.
isPopupMenu()!=true) &&
(
mp.
getInvoker() != null) &&
(
mp.
getInvoker().
getParent() != null) &&
(
mp.
getInvoker().
getParent() instanceof
JPopupMenu)
) {
mp = (
JPopupMenu)
mp.
getInvoker().
getParent();
}
return
mp;
}
/**
* Returns the component at the specified index.
*
* @param i the index of the component, where 0 is the first
* @return the <code>Component</code> at that index
* @deprecated replaced by {@link java.awt.Container#getComponent(int)}
*/
@
Deprecated
public
Component getComponentAtIndex(int
i) {
return
getComponent(
i);
}
/**
* Returns the index of the specified component.
*
* @param c the <code>Component</code> to find
* @return the index of the component, where 0 is the first;
* or -1 if the component is not found
*/
public int
getComponentIndex(
Component c) {
int
ncomponents = this.
getComponentCount();
Component[]
component = this.
getComponents();
for (int
i = 0 ;
i <
ncomponents ;
i++) {
Component comp =
component[
i];
if (
comp ==
c)
return
i;
}
return -1;
}
/**
* Sets the size of the Popup window using a <code>Dimension</code> object.
* This is equivalent to <code>setPreferredSize(d)</code>.
*
* @param d the <code>Dimension</code> specifying the new size
* of this component.
* @beaninfo
* description: The size of the popup menu
*/
public void
setPopupSize(
Dimension d) {
Dimension oldSize =
getPreferredSize();
setPreferredSize(
d);
if (
popup != null) {
Dimension newSize =
getPreferredSize();
if (!
oldSize.
equals(
newSize)) {
showPopup();
}
}
}
/**
* Sets the size of the Popup window to the specified width and
* height. This is equivalent to
* <code>setPreferredSize(new Dimension(width, height))</code>.
*
* @param width the new width of the Popup in pixels
* @param height the new height of the Popup in pixels
* @beaninfo
* description: The size of the popup menu
*/
public void
setPopupSize(int
width, int
height) {
setPopupSize(new
Dimension(
width,
height));
}
/**
* Sets the currently selected component, This will result
* in a change to the selection model.
*
* @param sel the <code>Component</code> to select
* @beaninfo
* description: The selected component on the popup menu
* expert: true
* hidden: true
*/
public void
setSelected(
Component sel) {
SingleSelectionModel model =
getSelectionModel();
int
index =
getComponentIndex(
sel);
model.
setSelectedIndex(
index);
}
/**
* Checks whether the border should be painted.
*
* @return true if the border is painted, false otherwise
* @see #setBorderPainted
*/
public boolean
isBorderPainted() {
return
paintBorder;
}
/**
* Sets whether the border should be painted.
*
* @param b if true, the border is painted.
* @see #isBorderPainted
* @beaninfo
* description: Is the border of the popup menu painted
*/
public void
setBorderPainted(boolean
b) {
paintBorder =
b;
repaint();
}
/**
* Paints the popup menu's border if the <code>borderPainted</code>
* property is <code>true</code>.
* @param g the <code>Graphics</code> object
*
* @see JComponent#paint
* @see JComponent#setBorder
*/
protected void
paintBorder(
Graphics g) {
if (
isBorderPainted()) {
super.paintBorder(
g);
}
}
/**
* Returns the margin, in pixels, between the popup menu's border and
* its containers.
*
* @return an <code>Insets</code> object containing the margin values.
*/
public
Insets getMargin() {
if(
margin == null) {
return new
Insets(0,0,0,0);
} else {
return
margin;
}
}
/**
* Examines the list of menu items to determine whether
* <code>popup</code> is a popup menu.
*
* @param popup a <code>JPopupMenu</code>
* @return true if <code>popup</code>
*/
boolean
isSubPopupMenu(
JPopupMenu popup) {
int
ncomponents = this.
getComponentCount();
Component[]
component = this.
getComponents();
for (int
i = 0 ;
i <
ncomponents ;
i++) {
Component comp =
component[
i];
if (
comp instanceof
JMenu) {
JMenu menu = (
JMenu)
comp;
JPopupMenu subPopup =
menu.
getPopupMenu();
if (
subPopup ==
popup)
return true;
if (
subPopup.
isSubPopupMenu(
popup))
return true;
}
}
return false;
}
private static
Frame getFrame(
Component c) {
Component w =
c;
while(!(
w instanceof
Frame) && (
w!=null)) {
w =
w.
getParent();
}
return (
Frame)
w;
}
/**
* Returns a string representation of this <code>JPopupMenu</code>.
* This method
* is intended to be used only for debugging purposes, and the
* content and format of the returned string may vary between
* implementations. The returned string may be empty but may not
* be <code>null</code>.
*
* @return a string representation of this <code>JPopupMenu</code>.
*/
protected
String paramString() {
String labelString = (
label != null ?
label : "");
String paintBorderString = (
paintBorder ?
"true" : "false");
String marginString = (
margin != null ?
margin.
toString() : "");
String lightWeightPopupEnabledString = (
isLightWeightPopupEnabled() ?
"true" : "false");
return super.paramString() +
",desiredLocationX=" +
desiredLocationX +
",desiredLocationY=" +
desiredLocationY +
",label=" +
labelString +
",lightWeightPopupEnabled=" +
lightWeightPopupEnabledString +
",margin=" +
marginString +
",paintBorder=" +
paintBorderString;
}
/////////////////
// Accessibility support
////////////////
/**
* Gets the AccessibleContext associated with this JPopupMenu.
* For JPopupMenus, the AccessibleContext takes the form of an
* AccessibleJPopupMenu.
* A new AccessibleJPopupMenu instance is created if necessary.
*
* @return an AccessibleJPopupMenu that serves as the
* AccessibleContext of this JPopupMenu
*/
public
AccessibleContext getAccessibleContext() {
if (
accessibleContext == null) {
accessibleContext = new
AccessibleJPopupMenu();
}
return
accessibleContext;
}
/**
* This class implements accessibility support for the
* <code>JPopupMenu</code> class. It provides an implementation of the
* Java Accessibility API appropriate to popup menu user-interface
* elements.
*/
@
SuppressWarnings("serial")
protected class
AccessibleJPopupMenu extends
AccessibleJComponent
implements
PropertyChangeListener {
/**
* AccessibleJPopupMenu constructor
*
* @since 1.5
*/
protected
AccessibleJPopupMenu() {
JPopupMenu.this.
addPropertyChangeListener(this);
}
/**
* Get the role of this object.
*
* @return an instance of AccessibleRole describing the role of
* the object
*/
public
AccessibleRole getAccessibleRole() {
return
AccessibleRole.
POPUP_MENU;
}
/**
* This method gets called when a bound property is changed.
* @param e A <code>PropertyChangeEvent</code> object describing
* the event source and the property that has changed. Must not be null.
*
* @throws NullPointerException if the parameter is null.
* @since 1.5
*/
public void
propertyChange(
PropertyChangeEvent e) {
String propertyName =
e.
getPropertyName();
if (
propertyName == "visible") {
if (
e.
getOldValue() ==
Boolean.
FALSE &&
e.
getNewValue() ==
Boolean.
TRUE) {
handlePopupIsVisibleEvent(true);
} else if (
e.
getOldValue() ==
Boolean.
TRUE &&
e.
getNewValue() ==
Boolean.
FALSE) {
handlePopupIsVisibleEvent(false);
}
}
}
/*
* Handles popup "visible" PropertyChangeEvent
*/
private void
handlePopupIsVisibleEvent(boolean
visible) {
if (
visible) {
// notify listeners that the popup became visible
firePropertyChange(
ACCESSIBLE_STATE_PROPERTY,
null,
AccessibleState.
VISIBLE);
// notify listeners that a popup list item is selected
fireActiveDescendant();
} else {
// notify listeners that the popup became hidden
firePropertyChange(
ACCESSIBLE_STATE_PROPERTY,
AccessibleState.
VISIBLE, null);
}
}
/*
* Fires AccessibleActiveDescendant PropertyChangeEvent to notify listeners
* on the popup menu invoker that a popup list item has been selected
*/
private void
fireActiveDescendant() {
if (
JPopupMenu.this instanceof
BasicComboPopup) {
// get the popup list
JList<?>
popupList = ((
BasicComboPopup)
JPopupMenu.this).
getList();
if (
popupList == null) {
return;
}
// get the first selected item
AccessibleContext ac =
popupList.
getAccessibleContext();
AccessibleSelection selection =
ac.
getAccessibleSelection();
if (
selection == null) {
return;
}
Accessible a =
selection.
getAccessibleSelection(0);
if (
a == null) {
return;
}
AccessibleContext selectedItem =
a.
getAccessibleContext();
// fire the event with the popup invoker as the source.
if (
selectedItem != null &&
invoker != null) {
AccessibleContext invokerContext =
invoker.
getAccessibleContext();
if (
invokerContext != null) {
// Check invokerContext because Component.getAccessibleContext
// returns null. Classes that extend Component are responsible
// for returning a non-null AccessibleContext.
invokerContext.
firePropertyChange(
ACCESSIBLE_ACTIVE_DESCENDANT_PROPERTY,
null,
selectedItem);
}
}
}
}
} // inner class AccessibleJPopupMenu
////////////
// Serialization support.
////////////
private void
writeObject(
ObjectOutputStream s) throws
IOException {
Vector<
Object>
values = new
Vector<
Object>();
s.
defaultWriteObject();
// Save the invoker, if its Serializable.
if(
invoker != null &&
invoker instanceof
Serializable) {
values.
addElement("invoker");
values.
addElement(
invoker);
}
// Save the popup, if its Serializable.
if(
popup != null &&
popup instanceof
Serializable) {
values.
addElement("popup");
values.
addElement(
popup);
}
s.
writeObject(
values);
if (
getUIClassID().
equals(
uiClassID)) {
byte
count =
JComponent.
getWriteObjCounter(this);
JComponent.
setWriteObjCounter(this, --
count);
if (
count == 0 &&
ui != null) {
ui.
installUI(this);
}
}
}
// implements javax.swing.MenuElement
private void
readObject(
ObjectInputStream s)
throws
IOException,
ClassNotFoundException {
s.
defaultReadObject();
Vector<?>
values = (
Vector)
s.
readObject();
int
indexCounter = 0;
int
maxCounter =
values.
size();
if(
indexCounter <
maxCounter &&
values.
elementAt(
indexCounter).
equals("invoker")) {
invoker = (
Component)
values.
elementAt(++
indexCounter);
indexCounter++;
}
if(
indexCounter <
maxCounter &&
values.
elementAt(
indexCounter).
equals("popup")) {
popup = (
Popup)
values.
elementAt(++
indexCounter);
indexCounter++;
}
}
/**
* This method is required to conform to the
* <code>MenuElement</code> interface, but it not implemented.
* @see MenuElement#processMouseEvent(MouseEvent, MenuElement[], MenuSelectionManager)
*/
public void
processMouseEvent(
MouseEvent event,
MenuElement path[],
MenuSelectionManager manager) {}
/**
* Processes a key event forwarded from the
* <code>MenuSelectionManager</code> and changes the menu selection,
* if necessary, by using <code>MenuSelectionManager</code>'s API.
* <p>
* Note: you do not have to forward the event to sub-components.
* This is done automatically by the <code>MenuSelectionManager</code>.
*
* @param e a <code>KeyEvent</code>
* @param path the <code>MenuElement</code> path array
* @param manager the <code>MenuSelectionManager</code>
*/
public void
processKeyEvent(
KeyEvent e,
MenuElement path[],
MenuSelectionManager manager) {
MenuKeyEvent mke = new
MenuKeyEvent(
e.
getComponent(),
e.
getID(),
e.
getWhen(),
e.
getModifiers(),
e.
getKeyCode(),
e.
getKeyChar(),
path,
manager);
processMenuKeyEvent(
mke);
if (
mke.
isConsumed()) {
e.
consume();
}
}
/**
* Handles a keystroke in a menu.
*
* @param e a <code>MenuKeyEvent</code> object
* @since 1.5
*/
private void
processMenuKeyEvent(
MenuKeyEvent e) {
switch (
e.
getID()) {
case
KeyEvent.
KEY_PRESSED:
fireMenuKeyPressed(
e); break;
case
KeyEvent.
KEY_RELEASED:
fireMenuKeyReleased(
e); break;
case
KeyEvent.
KEY_TYPED:
fireMenuKeyTyped(
e); break;
default:
break;
}
}
/**
* Notifies all listeners that have registered interest for
* notification on this event type.
*
* @param event a <code>MenuKeyEvent</code>
* @see EventListenerList
*/
private void
fireMenuKeyPressed(
MenuKeyEvent event) {
Object[]
listeners =
listenerList.
getListenerList();
for (int
i =
listeners.length-2;
i>=0;
i-=2) {
if (
listeners[
i]==
MenuKeyListener.class) {
((
MenuKeyListener)
listeners[
i+1]).
menuKeyPressed(
event);
}
}
}
/**
* Notifies all listeners that have registered interest for
* notification on this event type.
*
* @param event a <code>MenuKeyEvent</code>
* @see EventListenerList
*/
private void
fireMenuKeyReleased(
MenuKeyEvent event) {
Object[]
listeners =
listenerList.
getListenerList();
for (int
i =
listeners.length-2;
i>=0;
i-=2) {
if (
listeners[
i]==
MenuKeyListener.class) {
((
MenuKeyListener)
listeners[
i+1]).
menuKeyReleased(
event);
}
}
}
/**
* Notifies all listeners that have registered interest for
* notification on this event type.
*
* @param event a <code>MenuKeyEvent</code>
* @see EventListenerList
*/
private void
fireMenuKeyTyped(
MenuKeyEvent event) {
Object[]
listeners =
listenerList.
getListenerList();
for (int
i =
listeners.length-2;
i>=0;
i-=2) {
if (
listeners[
i]==
MenuKeyListener.class) {
((
MenuKeyListener)
listeners[
i+1]).
menuKeyTyped(
event);
}
}
}
/**
* Messaged when the menubar selection changes to activate or
* deactivate this menu. This implements the
* <code>javax.swing.MenuElement</code> interface.
* Overrides <code>MenuElement.menuSelectionChanged</code>.
*
* @param isIncluded true if this menu is active, false if
* it is not
* @see MenuElement#menuSelectionChanged(boolean)
*/
public void
menuSelectionChanged(boolean
isIncluded) {
if (
DEBUG) {
System.
out.
println("In JPopupMenu.menuSelectionChanged " +
isIncluded);
}
if(
invoker instanceof
JMenu) {
JMenu m = (
JMenu)
invoker;
if(
isIncluded)
m.
setPopupMenuVisible(true);
else
m.
setPopupMenuVisible(false);
}
if (
isPopupMenu() && !
isIncluded)
setVisible(false);
}
/**
* Returns an array of <code>MenuElement</code>s containing the submenu
* for this menu component. It will only return items conforming to
* the <code>JMenuElement</code> interface.
* If popup menu is <code>null</code> returns
* an empty array. This method is required to conform to the
* <code>MenuElement</code> interface.
*
* @return an array of <code>MenuElement</code> objects
* @see MenuElement#getSubElements
*/
public
MenuElement[]
getSubElements() {
MenuElement result[];
Vector<
MenuElement>
tmp = new
Vector<
MenuElement>();
int
c =
getComponentCount();
int
i;
Component m;
for(
i=0 ;
i <
c ;
i++) {
m =
getComponent(
i);
if(
m instanceof
MenuElement)
tmp.
addElement((
MenuElement)
m);
}
result = new
MenuElement[
tmp.
size()];
for(
i=0,
c=
tmp.
size() ;
i <
c ;
i++)
result[
i] =
tmp.
elementAt(
i);
return
result;
}
/**
* Returns this <code>JPopupMenu</code> component.
* @return this <code>JPopupMenu</code> object
* @see MenuElement#getComponent
*/
public
Component getComponent() {
return this;
}
/**
* A popup menu-specific separator.
*/
@
SuppressWarnings("serial")
static public class
Separator extends
JSeparator
{
public
Separator( )
{
super(
JSeparator.
HORIZONTAL );
}
/**
* Returns the name of the L&F class that renders this component.
*
* @return the string "PopupMenuSeparatorUI"
* @see JComponent#getUIClassID
* @see UIDefaults#getUI
*/
public
String getUIClassID()
{
return "PopupMenuSeparatorUI";
}
}
/**
* Returns true if the <code>MouseEvent</code> is considered a popup trigger
* by the <code>JPopupMenu</code>'s currently installed UI.
*
* @return true if the mouse event is a popup trigger
* @since 1.3
*/
public boolean
isPopupTrigger(
MouseEvent e) {
return
getUI().
isPopupTrigger(
e);
}
}