-
Bug
-
Resolution: Duplicate
-
P4
-
None
-
1.4.1
-
x86
-
windows_nt
width + xOffset; // Prefer placement to the right
if (position.x + x + pmSize.width >= screenBounds.width
+ screenBounds.x &&
// popup doesn't fit - place it wherever there's more room
screenBounds.width - s.width < 2*(position.x
- screenBounds.x)) {
x = 0 - xOffset - pmSize.width;
}
} else {
// First determine x:
x = 0 - xOffset - pmSize.width; // Prefer placement to the left
if (position.x + x < screenBounds.x &&
// popup doesn't fit - place it wherever there's more room
screenBounds.width - s.width > 2*(position.x -
screenBounds.x)) {
x = s.width + xOffset;
}
}
// Then the y:
y = yOffset; // Prefer dropping down
if (position.y + y + pmSize.height >= screenBounds.height
+ screenBounds.y &&
// popup doesn't fit - place it wherever there's more room
screenBounds.height - s.height < 2*(position.y
- screenBounds.y)) {
y = s.height - yOffset - pmSize.height;
}
} else {
// We are a toplevel menu (pull-down)
int xOffset = UIManager.getInt("Menu.menuPopupOffsetX");
int yOffset = UIManager.getInt("Menu.menuPopupOffsetY");
if( SwingUtilities.isLeftToRight(this) ) {
// First determine the x:
x = xOffset; // Extend to the right
if (position.x + x + pmSize.width >= screenBounds.width
+ screenBounds.x &&
// popup doesn't fit - place it wherever there's more room
screenBounds.width - s.width < 2*(position.x
- screenBounds.x)) {
x = s.width - xOffset - pmSize.width;
}
} else {
// First determine the x:
x = s.width - xOffset - pmSize.width; // Extend to the left
if (position.x + x < screenBounds.x &&
// popup doesn't fit - place it wherever there's more room
screenBounds.width - s.width > 2*(position.x
- screenBounds.x)) {
x = xOffset;
}
}
// Then the y:
y = s.height + yOffset; // Prefer dropping down
if (position.y + y + pmSize.height >= screenBounds.height &&
// popup doesn't fit - place it wherever there's more room
screenBounds.height - s.height < 2*(position.y
- screenBounds.y)) {
y = 0 - yOffset - pmSize.height; // Otherwise drop 'up'
}
}
return new Point(x,y);
}
/**
* Returns the suggested delay, in milliseconds, before submenus
* are popped up or down.
* Each look and feel (L&F) may determine its own policy for
* observing the <code>delay</code> property.
* In most cases, the delay is not observed for top level menus
* or while dragging. The default for <code>delay</code> is 0.
* This method is a property of the look and feel code and is used
* to manage the idiosyncracies of the various UI implementations.
*
*
* @return the <code>delay</code> property
*/
public int getDelay() {
return delay;
}
/**
* Sets the suggested delay before the menu's <code>PopupMenu</code>
* is popped up or down. Each look and feel (L&F) may determine
* it's own policy for observing the delay property. In most cases,
* the delay is not observed for top level menus or while dragging.
* This method is a property of the look and feel code and is used
* to manage the idiosyncracies of the various UI implementations.
*
* @param d the number of milliseconds to delay
* @exception IllegalArgumentException if <code>d</code>
* is less than 0
* @beaninfo
* description: The delay between menu selection and making the popup menu visible
* expert: true
*/
public void setDelay(int d) {
if (d < 0)
throw new IllegalArgumentException("Delay must be a positive integer");
delay = d;
}
/**
* The window-closing listener for the popup.
*
* @see WinListener
*/
protected WinListener popupListener;
////// NEW //////////////////
protected JPopupMenu createPopupMenu()
{
return new JPopupMenu();
}
private void createAndConfigurePopupMenu() {
JPopupMenu popupMenu = createPopupMenu();
popupMenu.setInvoker(this);
popupListener = createWinListener(popupMenu);
popupMenu.addPopupMenuListener(new PopupMenuListener() {
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
}
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
}
public void popupMenuCanceled(PopupMenuEvent e) {
fireMenuCanceled();
}
});
}
}
/**
* Returns the popupmenu associated with this menu. If there is
* no popupmenu, it will create one.
*/
public JPopupMenu getPopupMenu() {
if (popupMenu == null)
popupMenu = createAndConfigurePopupMenu();
/** could double check for null here and throw RuntimeException...
if (popupMenu == null)
throw new RuntimeException("PopupMenu cannot be null.")
**/
return popupMenu;
}
/*
* Return the customized location of the popup component.
*/
private Point getCustomMenuLocation() {
return customMenuLocation;
}
/**
* Sets the location of the popup component.
*
* @param x the x coordinate of the popup's new position
* @param y the y coordinate of the popup's new position
*/
public void setMenuLocation(int x, int y) {
customMenuLocation = new Point(x, y);
getPopupMenu().setLocation(x, y);
}
/**
* Appends a menu item to the end of this menu.
* Returns the menu item added.
*
* @param menuItem the <code>JMenuitem</code> to be added
* @return the <code>JMenuItem</code> added
*/
public JMenuItem add(JMenuItem menuItem) {
AccessibleContext ac = menuItem.getAccessibleContext();
ac.setAccessibleParent(this);
return getPopupMenu().add(menuItem);
}
/**
* Appends a component to the end of this menu.
* Returns the component added.
*
* @param c the <code>Component</code> to add
* @return the <code>Component</code> added
*/
public Component add(Component c) {
if (c instanceof JComponent) {
AccessibleContext ac = ((JComponent) c).getAccessibleContext();
if (ac != null) {
ac.setAccessibleParent(this);
}
}
getPopupMenu().add(c);
return c;
}
/**
* Adds the specified component to this container at the given
* position. If <code>index</code> equals -1, the component will
* be appended to the end.
* @param c the <code>Component</code> to add
* @param index the position at which to insert the component
* @return the <code>Component</code> added
* @see #remove
* @see java.awt.Container#add(Component, int)
*/
public Component add(Component c, int index) {
if (c instanceof JComponent) {
AccessibleContext ac = ((JComponent) c).getAccessibleContext();
if (ac != null) {
ac.setAccessibleParent(this);
}
}
getPopupMenu().add(c, index);
return c;
}
/**
* 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));
}
/**
* Creates a new menu item attached to the specified
* <code>Action</code> object and appends it to the end of this menu.
* As of 1.3, this is no longer the preferred method for adding
* <code>Actions</code> to
* a container. Instead it is recommended to configure a control with
* an action using <code>setAction</code>,
* and then add that control directly
* to the <code>Container</code>.
*
* @param a the <code>Action</code> for the menu item to be added
* @see Action
*/
public JMenuItem add(Action a) {
JMenuItem mi = createActionComponent(a);
mi.setAction(a);
add(mi);
return mi;
}
/**
* Factory method which creates the <code>JMenuItem</code> for
* <code>Action</code>s added to the <code>JMenu</code>.
* As of 1.3, this is no
* longer the preferred method. Instead it is recommended to configure
* a control with an action using <code>setAction</code>,
* and then adding that
* control directly to the <code>Container</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((String)a.getValue(Action.NAME),
(Icon)a.getValue(Action.SMALL_ICON)){
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);
mi.setEnabled(a.isEnabled());
return mi;
}
/**
* Returns a properly configured <code>PropertyChangeListener</code>
* which updates the control as changes to the <code>Action</code> occur.
* As of 1.3, this is no longer the preferred method for adding
* <code>Action</code>s to a <code>Container</code>.
* Instead it is recommended to configure a control with
* an action using <code>setAction</code>, and then add that
* control directly
* to the <code>Container</code>.
*/
protected PropertyChangeListener createActionChangeListener(JMenuItem b) {
return new ActionChangedListener(b);
}
private class ActionChangedListener implements PropertyChangeListener {
WeakReference menuItem;
ActionChangedListener(JMenuItem mi) {
super();
setTarget(mi);
}
public void propertyChange(PropertyChangeEvent e) {
String propertyName = e.getPropertyName();
JMenuItem mi = (JMenuItem)getTarget();
if (mi == null) {
Action action = (Action)e.getSource();
action.removePropertyChangeListener(this);
} else {
if (propertyName.equals(Action.NAME)) {
String text = (String) e.getNewValue();
mi.setText(text);
} else if (propertyName.equals("enabled")) {
Boolean enabledState = (Boolean) e.getNewValue();
mi.setEnabled(enabledState.booleanValue());
} else if (propertyName.equals(Action.SMALL_ICON)) {
Icon icon = (Icon) e.getNewValue();
mi.setIcon(icon);
mi.invalidate();
mi.repaint();
} else if (propertyName.equals(Action.ACTION_COMMAND_KEY)) {
mi.setActionCommand((String)e.getNewValue());
}
}
}
public void setTarget(JMenuItem b) {
menuItem = new WeakReference(b);
}
public JMenuItem getTarget() {
return (JMenuItem)menuItem.get();
}
}
/**
* Appends a new separator to the end of the menu.
*/
public void addSeparator()
{
getPopupMenu().addSeparator();
}
/**
* Inserts a new menu item with the specified text at a
* given position.
*
* @param s the text for the menu item to add
* @param pos an integer specifying the position at which to add the
* new menu item
* @exception IllegalArgumentException when the value of
* <code>pos</code> < 0
*/
public void insert(String s, int pos) {
if (pos < 0) {
throw new IllegalArgumentException("index less than zero.");
}
getPopupMenu().insert(new JMenuItem(s), pos);
}
/**
* Inserts the specified <code>JMenuitem</code> at a given position.
*
* @param mi the <code>JMenuitem</code> to add
* @param pos an integer specifying the position at which to add the
* new <code>JMenuitem</code>
* @return the new menu item
* @exception IllegalArgumentException if the value of
* <code>pos</code> < 0
*/
public JMenuItem insert(JMenuItem mi, int pos) {
if (pos < 0) {
throw new IllegalArgumentException("index less than zero.");
}
AccessibleContext ac = mi.getAccessibleContext();
ac.setAccessibleParent(this);
getPopupMenu().insert(mi, pos);
return mi;
}
/**
* Inserts a new menu item attached to the specified <code>Action</code>
* object at a given position.
*
* @param a the <code>Action</code> object for the menu item to add
* @param pos an integer specifying the position at which to add the
* new menu item
* @exception IllegalArgumentException if the value of
* <code>pos</code> < 0
*/
public JMenuItem insert(Action a, int pos) {
if (pos < 0) {
throw new IllegalArgumentException("index less than zero.");
}
JMenuItem mi = new JMenuItem((String)a.getValue(Action.NAME),
(Icon)a.getValue(Action.SMALL_ICON));
mi.setHorizontalTextPosition(JButton.TRAILING);
mi.setVerticalTextPosition(JButton.CENTER);
mi.setEnabled(a.isEnabled());
mi.setAction(a);
getPopupMenu().insert(mi, pos);
return mi;
}
(continued in workaround section) -->
---------- END SOURCE ----------
(Review ID: 185966)
======================================================================
Name: jk109818 Date: 05/27/2003
FULL PRODUCT VERSION :
java version "1.4.1_02"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.1_02-b06)
Java HotSpot(TM) Client VM (build 1.4.1_02-b06, mixed mode)
FULL OS VERSION :
Windows NT Version 4.0
A DESCRIPTION OF THE PROBLEM :
The JMenu class is extremely difficult or simply impossible to extend.
The reasons for this are
1 - popupMenu variable is private
2 - ensurePopupMenuCreated is private
3 - some methods in JMenu use getPopupMenu to get the instance while others
use the popupMenu instance directly.
The API for JMenu is _inconsistent_ with the rest of the Swing components which
in most circumstances offer a protected factory method that creates the default
model or other objects that the class needs to operate. Since the
creation of the JPopupMenu is confined to a private method and different
methods use different ways to access the popup menu, the JMenu is impossible
to extend to provide a custom JPopupMenu implementation.
setPopupMenuVisible method for example calls is isPopupMenuVisible(), which
makes sure that popup is created. Then setPopupMenuVisible calls ensurePopupMenuCreated() again just in case the call to that method from
isPopupMenuVisible() did not work.
I attach the proposed implementation of JMenu below. Notice that
getPopupMenu checks if the internal popupMenu reference is null and
calls a private createAndConfigurePopupMenu method which calls
the protected factory method createPopupMenu which can be nicely
overwritten in JMenu subclasses to provide a custom JPopupMenu
implementation.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Inspect the javax.swing.JMenu code
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
1- ensurePopupMenuCreated can remain private but it calls a new protected createPopupMenu method OR
2 - ensurePopupMenuCreate becomes protected AND
3 - ALL methods inside JMenu use the same way to access the popup menu instance
ACTUAL -
JMenu cannot be extended to provide a custom implementation of JPopupMenu
for the reasons stated above.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
Inspect javax.swing.JMenu code.
Proposed implementation of JMenu.java
/*
* @(#)JMenu.java 1.166 02/04/18
*
* Copyright 2002 Sun Microsystems, Inc. All rights reserved.
* SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package javax.swing;
import java.awt.AWTEvent;
import java.awt.Component;
import java.awt.ComponentOrientation;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.*;
import java.beans.*;
import java.util.*;
import java.io.Serializable;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.IOException;
import javax.swing.event.*;
import javax.swing.plaf.*;
import javax.swing.plaf.basic.*;
import javax.accessibility.*;
import java.lang.ref.WeakReference;
/**
* An implementation of a menu -- a popup window containing
* <code>JMenuItem</code>s that
* is displayed when the user selects an item on the <code>JMenuBar</code>.
* In addition to <code>JMenuItem</code>s, a <code>JMenu</code> can
* also contain <code>JSeparator</code>s.
* <p>
* In essence, a menu is a button with an associated <code>JPopupMenu</code>.
* When the "button" is pressed, the <code>JPopupMenu</code> appears. If the
* "button" is on the <code>JMenuBar</code>, the menu is a top-level window.
* If the "button" is another menu item, then the <code>JPopupMenu</code> is
* "pull-right" menu.
* <p>
* For information and examples of using menus see
* <a href="http://java.sun.com/doc/books/tutorial/uiswing/components/menu.html">How to Use Menus</a>,
* a section in <em>The Java Tutorial.</em>
* For the keyboard keys used by this component in the standard Look and
* Feel (L&F) renditions, see the
* <a href="doc-files/Key-Index.html#JMenu"><code>JMenu</code> key assignments</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<sup><font size="-2">TM</font></sup>
* has been added to the <code>java.beans</code> package.
* Please see {@link java.beans.XMLEncoder}.
*
* @beaninfo
* attribute: isContainer true
* description: A popup window containing menu items displayed in a menu bar.
*
* @version 1.166 04/18/02
* @author Georges Saab
* @author David Karlton
* @author Arnaud Weber
* @see JMenuItem
* @see JSeparator
* @see JMenuBar
* @see JPopupMenu
*/
public class JMenu extends JMenuItem implements Accessible,MenuElement
{
/**
* @see #getUIClassID
* @see #readObject
*/
private static final String uiClassID = "MenuUI";
/*
* The popup menu portion of the menu.
*/
private JPopupMenu popupMenu;
/*
* The button's model listeners. Default is <code>null</code>.
*/
private ChangeListener menuChangeListener = null;
/*
* Only one <code>MenuEvent</code> is needed for each menu since the
* event's only state is the source property. The source of events
* generated is always "this". Default is <code>null</code>.
*/
private MenuEvent menuEvent = null;
/* Registry of listeners created for <code>Action-JMenuItem</code>
* linkage. This is needed so that references can
* be cleaned up at remove time to allow garbage collection
* Default is <code>null</code>.
*/
private static Hashtable listenerRegistry = null;
/*
* Used by the look and feel (L&F) code to handle
* implementation specific menu behaviors.
*/
private int delay;
/*
* Location of the popup component. Location is <code>null</code>
* if it was not customized by <code>setMenuLocation</code>
*/
private Point customMenuLocation = null;
/* 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.
/**
* Constructs a new <code>JMenu</code> with no text.
*/
public JMenu() {
this("");
}
/**
* Constructs a new <code>JMenu</code> with the supplied string
* as its text.
*
* @param s the text for the menu label
*/
public JMenu(String s) {
super(s);
}
/**
* Constructs a menu whose properties are taken from the
* <code>Action</code> supplied.
* @param a an <code>Action</code>
*
* @since 1.3
*/
public JMenu(Action a) {
this();
setAction(a);
}
/**
* Constructs a new <code>JMenu</code> with the supplied string as
* its text and specified as a tear-off menu or not.
*
* @param s the text for the menu label
* @param b can the menu be torn off (not yet implemented)
*/
public JMenu(String s, boolean b) {
this(s);
}
/**
* Overriden to do nothing. We want JMenu to be focusable, but
* <code>JMenuItem</code> doesn't want to be, thus we override this
* do nothing. We don't invoke <code>setFocusable(true)</code> after
* super's constructor has completed as this has the side effect that
* <code>JMenu</code> will be considered traversable via the
* keyboard, which we don't want. Making a Component traversable by
* the keyboard after invoking <code>setFocusable(true)</code> is OK,
* as <code>setFocusable</code> is new API
* and is speced as such, but internally we don't want to use it like
* this else we change the keyboard traversability.
*/
void initFocusability() {
}
/**
* Resets the UI property with a value from the current look and feel.
*
* @see JComponent#updateUI
*/
public void updateUI() {
setUI((MenuItemUI)UIManager.getUI(this));
if ( popupMenu != null )
{
popupMenu.setUI((PopupMenuUI)UIManager.getUI(popupMenu));
}
}
/**
* Returns the name of the L&F class that renders this component.
*
* @return the string "MenuUI"
* @see JComponent#getUIClassID
* @see UIDefaults#getUI
*/
public String getUIClassID() {
return uiClassID;
}
// public void repaint(long tm, int x, int y, int width, int height) {
// Thread.currentThread().dumpStack();
// super.repaint(tm,x,y,width,height);
// }
/**
* Sets the data model for the "menu button" -- the label
* that the user clicks to open or close the menu.
*
* @param newModel the <code>ButtonModel</code>
* @see #getModel
* @beaninfo
* description: The menu's model
* bound: true
* expert: true
* hidden: true
*/
public void setModel(ButtonModel newModel) {
ButtonModel oldModel = getModel();
super.setModel(newModel);
if (oldModel != null && menuChangeListener != null) {
oldModel.removeChangeListener(menuChangeListener);
menuChangeListener = null;
}
model = newModel;
if (newModel != null) {
menuChangeListener = createMenuChangeListener();
newModel.addChangeListener(menuChangeListener);
}
}
/**
* Returns true if the menu is currently selected (highlighted).
*
* @return true if the menu is selected, else false
*/
public boolean isSelected() {
return getModel().isSelected();
}
/**
* Sets the selection status of the menu.
*
* @param b true to select (highlight) the menu; false to de-select
* the menu
* @beaninfo
* description: When the menu is selected, its popup child is shown.
* expert: true
* hidden: true
*/
public void setSelected(boolean b) {
ButtonModel model = getModel();
boolean oldValue = model.isSelected();
if ((accessibleContext != null) && (oldValue != b)) {
if (b) {
accessibleContext.firePropertyChange(
AccessibleContext.ACCESSIBLE_STATE_PROPERTY,
null, AccessibleState.SELECTED);
} else {
accessibleContext.firePropertyChange(
AccessibleContext.ACCESSIBLE_STATE_PROPERTY,
AccessibleState.SELECTED, null);
}
}
if (b != model.isSelected()) {
getModel().setSelected(b);
}
}
/**
* Returns true if the menu's popup window is visible.
*
* @return true if the menu is visible, else false
*/
public boolean isPopupMenuVisible() {
JPopupMenu popup = getPopupMenu();
return (popup != null) ? popup.isVisible() : false;
}
/**
* Sets the visibility of the menu's popup. If the menu is
* not enabled, this method will have no effect.
*
* @param b a boolean value -- true to make the menu visible,
* false to hide it
* @beaninfo
* description: The popup menu's visibility
* expert: true
* hidden: true
*/
public void setPopupMenuVisible(boolean b) {
if (DEBUG) {
System.out.println("in JMenu.setPopupMenuVisible " + b);
// Thread.dumpStack();
}
boolean isVisible = isPopupMenuVisible();
if (b != isVisible && (isEnabled() || !b)) {
if ((b==true) && isShowing()) {
// Set location of popupMenu (pulldown or pullright)
Point p = getCustomMenuLocation();
if (p == null) {
p = getPopupMenuOrigin();
}
getPopupMenu().show(this, p.x, p.y);
} else {
getPopupMenu().setVisible(false);
}
}
}
/**
* Computes the origin for the <code>JMenu</code>'s popup menu.
* This method uses Look and Feel properties named
* <code>Menu.menuPopupOffsetX</code>,
* <code>Menu.menuPopupOffsetY</code>,
* <code>Menu.submenuPopupOffsetX</code>, and
* <code>Menu.submenuPopupOffsetY</code>
* to adjust the exact location of popup.
*
* @return a <code>Point</code> in the coordinate space of the
* menu which should be used as the origin
* of the <code>JMenu</code>'s popup menu
*
* @since 1.3
*/
protected Point getPopupMenuOrigin() {
int x = 0;
int y = 0;
JPopupMenu pm = getPopupMenu();
// Figure out the sizes needed to caclulate the menu position
Dimension s = getSize();
Dimension pmSize = pm.getSize();
// For the first time the menu is popped up,
// the size has not yet been initiated
if (pmSize.width==0) {
pmSize = pm.getPreferredSize();
}
Point position = getLocationOnScreen();
Toolkit toolkit = Toolkit.getDefaultToolkit();
GraphicsConfiguration gc = getGraphicsConfiguration();
Rectangle screenBounds = new Rectangle(toolkit.getScreenSize());
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(position)) {
gc = dgc;
break;
}
}
}
if (gc != null) {
screenBounds = gc.getBounds();
// take screen insets (e.g. taskbar) into account
Insets screenInsets = toolkit.getScreenInsets(gc);
screenBounds.width -=
Math.abs(screenInsets.left + screenInsets.right);
screenBounds.height -=
Math.abs(screenInsets.top + screenInsets.bottom);
position.x -= Math.abs(screenInsets.left);
position.y -= Math.abs(screenInsets.top);
}
Container parent = getParent();
if (parent instanceof JPopupMenu) {
// We are a submenu (pull-right)
int xOffset = UIManager.getInt("Menu.submenuPopupOffsetX");
int yOffset = UIManager.getInt("Menu.submenuPopupOffsetY");
if( SwingUtilities.isLeftToRight(this) ) {
// First determine x:
x = s.
if (position.x + x + pmSize.width >= screenBounds.width
+ screenBounds.x &&
// popup doesn't fit - place it wherever there's more room
screenBounds.width - s.width < 2*(position.x
- screenBounds.x)) {
x = 0 - xOffset - pmSize.width;
}
} else {
// First determine x:
x = 0 - xOffset - pmSize.width; // Prefer placement to the left
if (position.x + x < screenBounds.x &&
// popup doesn't fit - place it wherever there's more room
screenBounds.width - s.width > 2*(position.x -
screenBounds.x)) {
x = s.width + xOffset;
}
}
// Then the y:
y = yOffset; // Prefer dropping down
if (position.y + y + pmSize.height >= screenBounds.height
+ screenBounds.y &&
// popup doesn't fit - place it wherever there's more room
screenBounds.height - s.height < 2*(position.y
- screenBounds.y)) {
y = s.height - yOffset - pmSize.height;
}
} else {
// We are a toplevel menu (pull-down)
int xOffset = UIManager.getInt("Menu.menuPopupOffsetX");
int yOffset = UIManager.getInt("Menu.menuPopupOffsetY");
if( SwingUtilities.isLeftToRight(this) ) {
// First determine the x:
x = xOffset; // Extend to the right
if (position.x + x + pmSize.width >= screenBounds.width
+ screenBounds.x &&
// popup doesn't fit - place it wherever there's more room
screenBounds.width - s.width < 2*(position.x
- screenBounds.x)) {
x = s.width - xOffset - pmSize.width;
}
} else {
// First determine the x:
x = s.width - xOffset - pmSize.width; // Extend to the left
if (position.x + x < screenBounds.x &&
// popup doesn't fit - place it wherever there's more room
screenBounds.width - s.width > 2*(position.x
- screenBounds.x)) {
x = xOffset;
}
}
// Then the y:
y = s.height + yOffset; // Prefer dropping down
if (position.y + y + pmSize.height >= screenBounds.height &&
// popup doesn't fit - place it wherever there's more room
screenBounds.height - s.height < 2*(position.y
- screenBounds.y)) {
y = 0 - yOffset - pmSize.height; // Otherwise drop 'up'
}
}
return new Point(x,y);
}
/**
* Returns the suggested delay, in milliseconds, before submenus
* are popped up or down.
* Each look and feel (L&F) may determine its own policy for
* observing the <code>delay</code> property.
* In most cases, the delay is not observed for top level menus
* or while dragging. The default for <code>delay</code> is 0.
* This method is a property of the look and feel code and is used
* to manage the idiosyncracies of the various UI implementations.
*
*
* @return the <code>delay</code> property
*/
public int getDelay() {
return delay;
}
/**
* Sets the suggested delay before the menu's <code>PopupMenu</code>
* is popped up or down. Each look and feel (L&F) may determine
* it's own policy for observing the delay property. In most cases,
* the delay is not observed for top level menus or while dragging.
* This method is a property of the look and feel code and is used
* to manage the idiosyncracies of the various UI implementations.
*
* @param d the number of milliseconds to delay
* @exception IllegalArgumentException if <code>d</code>
* is less than 0
* @beaninfo
* description: The delay between menu selection and making the popup menu visible
* expert: true
*/
public void setDelay(int d) {
if (d < 0)
throw new IllegalArgumentException("Delay must be a positive integer");
delay = d;
}
/**
* The window-closing listener for the popup.
*
* @see WinListener
*/
protected WinListener popupListener;
////// NEW //////////////////
protected JPopupMenu createPopupMenu()
{
return new JPopupMenu();
}
private void createAndConfigurePopupMenu() {
JPopupMenu popupMenu = createPopupMenu();
popupMenu.setInvoker(this);
popupListener = createWinListener(popupMenu);
popupMenu.addPopupMenuListener(new PopupMenuListener() {
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
}
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
}
public void popupMenuCanceled(PopupMenuEvent e) {
fireMenuCanceled();
}
});
}
}
/**
* Returns the popupmenu associated with this menu. If there is
* no popupmenu, it will create one.
*/
public JPopupMenu getPopupMenu() {
if (popupMenu == null)
popupMenu = createAndConfigurePopupMenu();
/** could double check for null here and throw RuntimeException...
if (popupMenu == null)
throw new RuntimeException("PopupMenu cannot be null.")
**/
return popupMenu;
}
/*
* Return the customized location of the popup component.
*/
private Point getCustomMenuLocation() {
return customMenuLocation;
}
/**
* Sets the location of the popup component.
*
* @param x the x coordinate of the popup's new position
* @param y the y coordinate of the popup's new position
*/
public void setMenuLocation(int x, int y) {
customMenuLocation = new Point(x, y);
getPopupMenu().setLocation(x, y);
}
/**
* Appends a menu item to the end of this menu.
* Returns the menu item added.
*
* @param menuItem the <code>JMenuitem</code> to be added
* @return the <code>JMenuItem</code> added
*/
public JMenuItem add(JMenuItem menuItem) {
AccessibleContext ac = menuItem.getAccessibleContext();
ac.setAccessibleParent(this);
return getPopupMenu().add(menuItem);
}
/**
* Appends a component to the end of this menu.
* Returns the component added.
*
* @param c the <code>Component</code> to add
* @return the <code>Component</code> added
*/
public Component add(Component c) {
if (c instanceof JComponent) {
AccessibleContext ac = ((JComponent) c).getAccessibleContext();
if (ac != null) {
ac.setAccessibleParent(this);
}
}
getPopupMenu().add(c);
return c;
}
/**
* Adds the specified component to this container at the given
* position. If <code>index</code> equals -1, the component will
* be appended to the end.
* @param c the <code>Component</code> to add
* @param index the position at which to insert the component
* @return the <code>Component</code> added
* @see #remove
* @see java.awt.Container#add(Component, int)
*/
public Component add(Component c, int index) {
if (c instanceof JComponent) {
AccessibleContext ac = ((JComponent) c).getAccessibleContext();
if (ac != null) {
ac.setAccessibleParent(this);
}
}
getPopupMenu().add(c, index);
return c;
}
/**
* 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));
}
/**
* Creates a new menu item attached to the specified
* <code>Action</code> object and appends it to the end of this menu.
* As of 1.3, this is no longer the preferred method for adding
* <code>Actions</code> to
* a container. Instead it is recommended to configure a control with
* an action using <code>setAction</code>,
* and then add that control directly
* to the <code>Container</code>.
*
* @param a the <code>Action</code> for the menu item to be added
* @see Action
*/
public JMenuItem add(Action a) {
JMenuItem mi = createActionComponent(a);
mi.setAction(a);
add(mi);
return mi;
}
/**
* Factory method which creates the <code>JMenuItem</code> for
* <code>Action</code>s added to the <code>JMenu</code>.
* As of 1.3, this is no
* longer the preferred method. Instead it is recommended to configure
* a control with an action using <code>setAction</code>,
* and then adding that
* control directly to the <code>Container</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((String)a.getValue(Action.NAME),
(Icon)a.getValue(Action.SMALL_ICON)){
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);
mi.setEnabled(a.isEnabled());
return mi;
}
/**
* Returns a properly configured <code>PropertyChangeListener</code>
* which updates the control as changes to the <code>Action</code> occur.
* As of 1.3, this is no longer the preferred method for adding
* <code>Action</code>s to a <code>Container</code>.
* Instead it is recommended to configure a control with
* an action using <code>setAction</code>, and then add that
* control directly
* to the <code>Container</code>.
*/
protected PropertyChangeListener createActionChangeListener(JMenuItem b) {
return new ActionChangedListener(b);
}
private class ActionChangedListener implements PropertyChangeListener {
WeakReference menuItem;
ActionChangedListener(JMenuItem mi) {
super();
setTarget(mi);
}
public void propertyChange(PropertyChangeEvent e) {
String propertyName = e.getPropertyName();
JMenuItem mi = (JMenuItem)getTarget();
if (mi == null) {
Action action = (Action)e.getSource();
action.removePropertyChangeListener(this);
} else {
if (propertyName.equals(Action.NAME)) {
String text = (String) e.getNewValue();
mi.setText(text);
} else if (propertyName.equals("enabled")) {
Boolean enabledState = (Boolean) e.getNewValue();
mi.setEnabled(enabledState.booleanValue());
} else if (propertyName.equals(Action.SMALL_ICON)) {
Icon icon = (Icon) e.getNewValue();
mi.setIcon(icon);
mi.invalidate();
mi.repaint();
} else if (propertyName.equals(Action.ACTION_COMMAND_KEY)) {
mi.setActionCommand((String)e.getNewValue());
}
}
}
public void setTarget(JMenuItem b) {
menuItem = new WeakReference(b);
}
public JMenuItem getTarget() {
return (JMenuItem)menuItem.get();
}
}
/**
* Appends a new separator to the end of the menu.
*/
public void addSeparator()
{
getPopupMenu().addSeparator();
}
/**
* Inserts a new menu item with the specified text at a
* given position.
*
* @param s the text for the menu item to add
* @param pos an integer specifying the position at which to add the
* new menu item
* @exception IllegalArgumentException when the value of
* <code>pos</code> < 0
*/
public void insert(String s, int pos) {
if (pos < 0) {
throw new IllegalArgumentException("index less than zero.");
}
getPopupMenu().insert(new JMenuItem(s), pos);
}
/**
* Inserts the specified <code>JMenuitem</code> at a given position.
*
* @param mi the <code>JMenuitem</code> to add
* @param pos an integer specifying the position at which to add the
* new <code>JMenuitem</code>
* @return the new menu item
* @exception IllegalArgumentException if the value of
* <code>pos</code> < 0
*/
public JMenuItem insert(JMenuItem mi, int pos) {
if (pos < 0) {
throw new IllegalArgumentException("index less than zero.");
}
AccessibleContext ac = mi.getAccessibleContext();
ac.setAccessibleParent(this);
getPopupMenu().insert(mi, pos);
return mi;
}
/**
* Inserts a new menu item attached to the specified <code>Action</code>
* object at a given position.
*
* @param a the <code>Action</code> object for the menu item to add
* @param pos an integer specifying the position at which to add the
* new menu item
* @exception IllegalArgumentException if the value of
* <code>pos</code> < 0
*/
public JMenuItem insert(Action a, int pos) {
if (pos < 0) {
throw new IllegalArgumentException("index less than zero.");
}
JMenuItem mi = new JMenuItem((String)a.getValue(Action.NAME),
(Icon)a.getValue(Action.SMALL_ICON));
mi.setHorizontalTextPosition(JButton.TRAILING);
mi.setVerticalTextPosition(JButton.CENTER);
mi.setEnabled(a.isEnabled());
mi.setAction(a);
getPopupMenu().insert(mi, pos);
return mi;
}
(continued in workaround section) -->
---------- END SOURCE ----------
(Review ID: 185966)
======================================================================
Name: jk109818 Date: 05/27/2003
FULL PRODUCT VERSION :
java version "1.4.1_02"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.1_02-b06)
Java HotSpot(TM) Client VM (build 1.4.1_02-b06, mixed mode)
FULL OS VERSION :
Windows NT Version 4.0
A DESCRIPTION OF THE PROBLEM :
The JMenu class is extremely difficult or simply impossible to extend.
The reasons for this are
1 - popupMenu variable is private
2 - ensurePopupMenuCreated is private
3 - some methods in JMenu use getPopupMenu to get the instance while others
use the popupMenu instance directly.
The API for JMenu is _inconsistent_ with the rest of the Swing components which
in most circumstances offer a protected factory method that creates the default
model or other objects that the class needs to operate. Since the
creation of the JPopupMenu is confined to a private method and different
methods use different ways to access the popup menu, the JMenu is impossible
to extend to provide a custom JPopupMenu implementation.
setPopupMenuVisible method for example calls is isPopupMenuVisible(), which
makes sure that popup is created. Then setPopupMenuVisible calls ensurePopupMenuCreated() again just in case the call to that method from
isPopupMenuVisible() did not work.
I attach the proposed implementation of JMenu below. Notice that
getPopupMenu checks if the internal popupMenu reference is null and
calls a private createAndConfigurePopupMenu method which calls
the protected factory method createPopupMenu which can be nicely
overwritten in JMenu subclasses to provide a custom JPopupMenu
implementation.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Inspect the javax.swing.JMenu code
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
1- ensurePopupMenuCreated can remain private but it calls a new protected createPopupMenu method OR
2 - ensurePopupMenuCreate becomes protected AND
3 - ALL methods inside JMenu use the same way to access the popup menu instance
ACTUAL -
JMenu cannot be extended to provide a custom implementation of JPopupMenu
for the reasons stated above.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
Inspect javax.swing.JMenu code.
Proposed implementation of JMenu.java
/*
* @(#)JMenu.java 1.166 02/04/18
*
* Copyright 2002 Sun Microsystems, Inc. All rights reserved.
* SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package javax.swing;
import java.awt.AWTEvent;
import java.awt.Component;
import java.awt.ComponentOrientation;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.*;
import java.beans.*;
import java.util.*;
import java.io.Serializable;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.IOException;
import javax.swing.event.*;
import javax.swing.plaf.*;
import javax.swing.plaf.basic.*;
import javax.accessibility.*;
import java.lang.ref.WeakReference;
/**
* An implementation of a menu -- a popup window containing
* <code>JMenuItem</code>s that
* is displayed when the user selects an item on the <code>JMenuBar</code>.
* In addition to <code>JMenuItem</code>s, a <code>JMenu</code> can
* also contain <code>JSeparator</code>s.
* <p>
* In essence, a menu is a button with an associated <code>JPopupMenu</code>.
* When the "button" is pressed, the <code>JPopupMenu</code> appears. If the
* "button" is on the <code>JMenuBar</code>, the menu is a top-level window.
* If the "button" is another menu item, then the <code>JPopupMenu</code> is
* "pull-right" menu.
* <p>
* For information and examples of using menus see
* <a href="http://java.sun.com/doc/books/tutorial/uiswing/components/menu.html">How to Use Menus</a>,
* a section in <em>The Java Tutorial.</em>
* For the keyboard keys used by this component in the standard Look and
* Feel (L&F) renditions, see the
* <a href="doc-files/Key-Index.html#JMenu"><code>JMenu</code> key assignments</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<sup><font size="-2">TM</font></sup>
* has been added to the <code>java.beans</code> package.
* Please see {@link java.beans.XMLEncoder}.
*
* @beaninfo
* attribute: isContainer true
* description: A popup window containing menu items displayed in a menu bar.
*
* @version 1.166 04/18/02
* @author Georges Saab
* @author David Karlton
* @author Arnaud Weber
* @see JMenuItem
* @see JSeparator
* @see JMenuBar
* @see JPopupMenu
*/
public class JMenu extends JMenuItem implements Accessible,MenuElement
{
/**
* @see #getUIClassID
* @see #readObject
*/
private static final String uiClassID = "MenuUI";
/*
* The popup menu portion of the menu.
*/
private JPopupMenu popupMenu;
/*
* The button's model listeners. Default is <code>null</code>.
*/
private ChangeListener menuChangeListener = null;
/*
* Only one <code>MenuEvent</code> is needed for each menu since the
* event's only state is the source property. The source of events
* generated is always "this". Default is <code>null</code>.
*/
private MenuEvent menuEvent = null;
/* Registry of listeners created for <code>Action-JMenuItem</code>
* linkage. This is needed so that references can
* be cleaned up at remove time to allow garbage collection
* Default is <code>null</code>.
*/
private static Hashtable listenerRegistry = null;
/*
* Used by the look and feel (L&F) code to handle
* implementation specific menu behaviors.
*/
private int delay;
/*
* Location of the popup component. Location is <code>null</code>
* if it was not customized by <code>setMenuLocation</code>
*/
private Point customMenuLocation = null;
/* 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.
/**
* Constructs a new <code>JMenu</code> with no text.
*/
public JMenu() {
this("");
}
/**
* Constructs a new <code>JMenu</code> with the supplied string
* as its text.
*
* @param s the text for the menu label
*/
public JMenu(String s) {
super(s);
}
/**
* Constructs a menu whose properties are taken from the
* <code>Action</code> supplied.
* @param a an <code>Action</code>
*
* @since 1.3
*/
public JMenu(Action a) {
this();
setAction(a);
}
/**
* Constructs a new <code>JMenu</code> with the supplied string as
* its text and specified as a tear-off menu or not.
*
* @param s the text for the menu label
* @param b can the menu be torn off (not yet implemented)
*/
public JMenu(String s, boolean b) {
this(s);
}
/**
* Overriden to do nothing. We want JMenu to be focusable, but
* <code>JMenuItem</code> doesn't want to be, thus we override this
* do nothing. We don't invoke <code>setFocusable(true)</code> after
* super's constructor has completed as this has the side effect that
* <code>JMenu</code> will be considered traversable via the
* keyboard, which we don't want. Making a Component traversable by
* the keyboard after invoking <code>setFocusable(true)</code> is OK,
* as <code>setFocusable</code> is new API
* and is speced as such, but internally we don't want to use it like
* this else we change the keyboard traversability.
*/
void initFocusability() {
}
/**
* Resets the UI property with a value from the current look and feel.
*
* @see JComponent#updateUI
*/
public void updateUI() {
setUI((MenuItemUI)UIManager.getUI(this));
if ( popupMenu != null )
{
popupMenu.setUI((PopupMenuUI)UIManager.getUI(popupMenu));
}
}
/**
* Returns the name of the L&F class that renders this component.
*
* @return the string "MenuUI"
* @see JComponent#getUIClassID
* @see UIDefaults#getUI
*/
public String getUIClassID() {
return uiClassID;
}
// public void repaint(long tm, int x, int y, int width, int height) {
// Thread.currentThread().dumpStack();
// super.repaint(tm,x,y,width,height);
// }
/**
* Sets the data model for the "menu button" -- the label
* that the user clicks to open or close the menu.
*
* @param newModel the <code>ButtonModel</code>
* @see #getModel
* @beaninfo
* description: The menu's model
* bound: true
* expert: true
* hidden: true
*/
public void setModel(ButtonModel newModel) {
ButtonModel oldModel = getModel();
super.setModel(newModel);
if (oldModel != null && menuChangeListener != null) {
oldModel.removeChangeListener(menuChangeListener);
menuChangeListener = null;
}
model = newModel;
if (newModel != null) {
menuChangeListener = createMenuChangeListener();
newModel.addChangeListener(menuChangeListener);
}
}
/**
* Returns true if the menu is currently selected (highlighted).
*
* @return true if the menu is selected, else false
*/
public boolean isSelected() {
return getModel().isSelected();
}
/**
* Sets the selection status of the menu.
*
* @param b true to select (highlight) the menu; false to de-select
* the menu
* @beaninfo
* description: When the menu is selected, its popup child is shown.
* expert: true
* hidden: true
*/
public void setSelected(boolean b) {
ButtonModel model = getModel();
boolean oldValue = model.isSelected();
if ((accessibleContext != null) && (oldValue != b)) {
if (b) {
accessibleContext.firePropertyChange(
AccessibleContext.ACCESSIBLE_STATE_PROPERTY,
null, AccessibleState.SELECTED);
} else {
accessibleContext.firePropertyChange(
AccessibleContext.ACCESSIBLE_STATE_PROPERTY,
AccessibleState.SELECTED, null);
}
}
if (b != model.isSelected()) {
getModel().setSelected(b);
}
}
/**
* Returns true if the menu's popup window is visible.
*
* @return true if the menu is visible, else false
*/
public boolean isPopupMenuVisible() {
JPopupMenu popup = getPopupMenu();
return (popup != null) ? popup.isVisible() : false;
}
/**
* Sets the visibility of the menu's popup. If the menu is
* not enabled, this method will have no effect.
*
* @param b a boolean value -- true to make the menu visible,
* false to hide it
* @beaninfo
* description: The popup menu's visibility
* expert: true
* hidden: true
*/
public void setPopupMenuVisible(boolean b) {
if (DEBUG) {
System.out.println("in JMenu.setPopupMenuVisible " + b);
// Thread.dumpStack();
}
boolean isVisible = isPopupMenuVisible();
if (b != isVisible && (isEnabled() || !b)) {
if ((b==true) && isShowing()) {
// Set location of popupMenu (pulldown or pullright)
Point p = getCustomMenuLocation();
if (p == null) {
p = getPopupMenuOrigin();
}
getPopupMenu().show(this, p.x, p.y);
} else {
getPopupMenu().setVisible(false);
}
}
}
/**
* Computes the origin for the <code>JMenu</code>'s popup menu.
* This method uses Look and Feel properties named
* <code>Menu.menuPopupOffsetX</code>,
* <code>Menu.menuPopupOffsetY</code>,
* <code>Menu.submenuPopupOffsetX</code>, and
* <code>Menu.submenuPopupOffsetY</code>
* to adjust the exact location of popup.
*
* @return a <code>Point</code> in the coordinate space of the
* menu which should be used as the origin
* of the <code>JMenu</code>'s popup menu
*
* @since 1.3
*/
protected Point getPopupMenuOrigin() {
int x = 0;
int y = 0;
JPopupMenu pm = getPopupMenu();
// Figure out the sizes needed to caclulate the menu position
Dimension s = getSize();
Dimension pmSize = pm.getSize();
// For the first time the menu is popped up,
// the size has not yet been initiated
if (pmSize.width==0) {
pmSize = pm.getPreferredSize();
}
Point position = getLocationOnScreen();
Toolkit toolkit = Toolkit.getDefaultToolkit();
GraphicsConfiguration gc = getGraphicsConfiguration();
Rectangle screenBounds = new Rectangle(toolkit.getScreenSize());
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(position)) {
gc = dgc;
break;
}
}
}
if (gc != null) {
screenBounds = gc.getBounds();
// take screen insets (e.g. taskbar) into account
Insets screenInsets = toolkit.getScreenInsets(gc);
screenBounds.width -=
Math.abs(screenInsets.left + screenInsets.right);
screenBounds.height -=
Math.abs(screenInsets.top + screenInsets.bottom);
position.x -= Math.abs(screenInsets.left);
position.y -= Math.abs(screenInsets.top);
}
Container parent = getParent();
if (parent instanceof JPopupMenu) {
// We are a submenu (pull-right)
int xOffset = UIManager.getInt("Menu.submenuPopupOffsetX");
int yOffset = UIManager.getInt("Menu.submenuPopupOffsetY");
if( SwingUtilities.isLeftToRight(this) ) {
// First determine x:
x = s.
- duplicates
-
JDK-4688783 JPopupMenu hardcoded i JMenu
-
- Open
-