-
Bug
-
Resolution: Unresolved
-
P4
-
None
-
6
-
x86
-
windows_xp
FULL PRODUCT VERSION :
java version "1.6.0-beta2"
Java(TM) SE Runtime Environment (build 1.6.0-beta2-b84)
Java HotSpot(TM) Client VM (build 1.6.0-beta2-b84, mixed mode, sharing)
java version "1.5.0_06"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_06-b05)
Java HotSpot(TM) Client VM (build 1.5.0_06-b05, mixed mode, sharing)
ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows XP [Version 5.1.2600]
A DESCRIPTION OF THE PROBLEM :
The javadocs for JComponent.getRootPane states that it returns "the
JRootPane that contains this component, or null if no JRootPane is
found." However, when I add a JMenuItem to a JMenu, its
getRootPane() method always returns null, even though it is clearly
contained by the same JRootPane as its menu. The reason for this is
that it doesn't strictly belong to the JMenu, it belongs to the
JMenu's JPopupMenu. However, I often need to get the root pane of a
JMenuItem. In the example here, I need the rootpane to set a wait
cursor, which is a very standard operation. (In my actual code, the
JMenuItem code is much farther removed from the container class, so
I can't simply use a reference to it, like I could here.) But the
call to getRootPane returns null, and the ActionListener doesn't
have access to any component in the heirarchy other than the
JMenuItem. I need to get the root pane from the menu item. Notice
that the actionListener is written to work fine from a toolbar
button as well, and it does, although this test case doesn't bother
to demonstrate that. Let me also say that, even though I have a
useful workaround, this should somehow be incorporated into the
standard JMenuItem, because developers shouldn't have to wrestle
with this issue. The getRootPane() method is very useful and
powerful, and it's relatively simple to make it work in a JMenuItem.
It makes no sense that it should be an exception to the rule.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run this test program. On the file menu, try both the broken and
fixed menu items. Then try the same things for each submenu. Also,
bring up the popup menu in the window and try both menu items
there, and on its submenu.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Both the "broken" and "fixed" menu items use the same
ActionListener, and should open a file-open dialog.
ACTUAL -
Only the "Fixed" menu item opens the file-open dialog. The "Broken"
item throws a NullPointerException.
ERROR MESSAGES/STACK TRACES THAT OCCUR :
getRootPane() returns null, so I get a NullPointerException as soon
as I try to use it.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
import java.awt.BorderLayout;
import java.awt.Cursor;
import java.awt.event.*;
import javax.swing.*;
@SuppressWarnings({"HardCodedStringLiteral", "MagicNumber"})
public class RootPaneBug extends JPanel {
private static final Cursor WAIT_CURSOR = Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR);
private static final Cursor DEFAULT_CURSOR = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR);
public static void main(String[] args) {
//noinspection StringConcatenation
System.err.println("Java Version " + System.getProperty("java.version")); // NON-NLS
makeFrame();
}
private static void makeFrame() {
JFrame mainFrame = new JFrame("RootPaneBug");
mainFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
RootPaneBug comp = new RootPaneBug();
mainFrame.add(comp, BorderLayout.CENTER);
comp.setUpMenus();
mainFrame.setBounds(10, 10, 600, 325);
mainFrame.setVisible(true);
}
public RootPaneBug() {
super(new BorderLayout());
}
private void setUpMenus() {
JMenu fileMenu = new JMenu("File");
addOpenItems(fileMenu);
JMenu subMenu = new JMenu("Open SubMenu");
addOpenItems(subMenu);
fileMenu.add(subMenu);
JMenu subSubMenu = new JMenu("Open Next Submenu");
addOpenItems(subSubMenu);
subMenu.add(subSubMenu);
JMenuBar mBar = new JMenuBar();
mBar.add(fileMenu);
((JFrame)getRootPane().getParent()).setJMenuBar(mBar);
final JPopupMenu popupMenu = new JPopupMenu();
addOpenItems(popupMenu);
JMenu popupSubMenu = new JMenu("Popup Submenu");
addOpenItems(popupSubMenu);
popupMenu.add(popupSubMenu);
MouseListener ml = new MouseAdapter() {
@Override public void mouseClicked(MouseEvent e) { showPopup(e); }
@Override public void mousePressed(MouseEvent e) { showPopup(e); }
@Override public void mouseReleased(MouseEvent e) { showPopup(e); }
private void showPopup(MouseEvent e) {
if (e.isPopupTrigger()) {
popupMenu.setInvoker(RootPaneBug.this);
popupMenu.setLocation(e.getLocationOnScreen());
popupMenu.setVisible(true);
}
}
};
addMouseListener(ml);
}
private void addOpenItems(JComponent pFileMenu) {
JMenuItem brokenOpenItem = new JMenuItem("Broken Open...");
JMenuItem fixedOpenItem = new FixedMenuItem("Fixed Open...");
pFileMenu.add(brokenOpenItem);
pFileMenu.add(fixedOpenItem);
ActionListener al = new ActionListener() {
public void actionPerformed(ActionEvent e) {
JComponent menuItem = (JComponent) e.getSource();
JRootPane rootPane = menuItem.getRootPane();
rootPane.setCursor(WAIT_CURSOR);
try {
new JFileChooser().showOpenDialog(rootPane);
} finally {
rootPane.setCursor(DEFAULT_CURSOR);
}
}
};
brokenOpenItem.addActionListener(al);
fixedOpenItem.addActionListener(al);
}
}
@SuppressWarnings({"ClassNameDiffersFromFileName"})
class FixedMenuItem extends JMenuItem {
public FixedMenuItem(String text) {
super(text);
}
@Override public JRootPane getRootPane() {
return getMenuItemRootPane(this);
}
public static JRootPane getMenuItemRootPane(JMenuItem cmp) {
JComponent invoker = ((JComponent) ((JPopupMenu) cmp.getParent()).getInvoker());
// I need to call this recursively in case this is a submenu.
return getRootPaneUniversally(invoker);
}
/**
* The JComponent.getRootPane() method fails for a JMenuItem. So we don't
* call it directly. Instead, we call this method, which works with any JComponent.
* @param cmp The component whose root pane you need.
* @return The root pane for the specified component.
*/
public static JRootPane getRootPaneUniversally(JComponent cmp) {
JRootPane rootPane;
if (cmp instanceof JMenuItem) {
// This may return a valid root pane if the JMenuItem is a JMenu but not a submenu
rootPane = SwingUtilities.getRootPane(cmp);
if (rootPane == null) {
rootPane = getMenuItemRootPane((JMenuItem)cmp);
}
} else {
rootPane = SwingUtilities.getRootPane(cmp);
}
return rootPane;
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
The workaround is contained in the FixedMenuItem class contained in
the submitted source code.
java version "1.6.0-beta2"
Java(TM) SE Runtime Environment (build 1.6.0-beta2-b84)
Java HotSpot(TM) Client VM (build 1.6.0-beta2-b84, mixed mode, sharing)
java version "1.5.0_06"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_06-b05)
Java HotSpot(TM) Client VM (build 1.5.0_06-b05, mixed mode, sharing)
ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows XP [Version 5.1.2600]
A DESCRIPTION OF THE PROBLEM :
The javadocs for JComponent.getRootPane states that it returns "the
JRootPane that contains this component, or null if no JRootPane is
found." However, when I add a JMenuItem to a JMenu, its
getRootPane() method always returns null, even though it is clearly
contained by the same JRootPane as its menu. The reason for this is
that it doesn't strictly belong to the JMenu, it belongs to the
JMenu's JPopupMenu. However, I often need to get the root pane of a
JMenuItem. In the example here, I need the rootpane to set a wait
cursor, which is a very standard operation. (In my actual code, the
JMenuItem code is much farther removed from the container class, so
I can't simply use a reference to it, like I could here.) But the
call to getRootPane returns null, and the ActionListener doesn't
have access to any component in the heirarchy other than the
JMenuItem. I need to get the root pane from the menu item. Notice
that the actionListener is written to work fine from a toolbar
button as well, and it does, although this test case doesn't bother
to demonstrate that. Let me also say that, even though I have a
useful workaround, this should somehow be incorporated into the
standard JMenuItem, because developers shouldn't have to wrestle
with this issue. The getRootPane() method is very useful and
powerful, and it's relatively simple to make it work in a JMenuItem.
It makes no sense that it should be an exception to the rule.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run this test program. On the file menu, try both the broken and
fixed menu items. Then try the same things for each submenu. Also,
bring up the popup menu in the window and try both menu items
there, and on its submenu.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Both the "broken" and "fixed" menu items use the same
ActionListener, and should open a file-open dialog.
ACTUAL -
Only the "Fixed" menu item opens the file-open dialog. The "Broken"
item throws a NullPointerException.
ERROR MESSAGES/STACK TRACES THAT OCCUR :
getRootPane() returns null, so I get a NullPointerException as soon
as I try to use it.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
import java.awt.BorderLayout;
import java.awt.Cursor;
import java.awt.event.*;
import javax.swing.*;
@SuppressWarnings({"HardCodedStringLiteral", "MagicNumber"})
public class RootPaneBug extends JPanel {
private static final Cursor WAIT_CURSOR = Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR);
private static final Cursor DEFAULT_CURSOR = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR);
public static void main(String[] args) {
//noinspection StringConcatenation
System.err.println("Java Version " + System.getProperty("java.version")); // NON-NLS
makeFrame();
}
private static void makeFrame() {
JFrame mainFrame = new JFrame("RootPaneBug");
mainFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
RootPaneBug comp = new RootPaneBug();
mainFrame.add(comp, BorderLayout.CENTER);
comp.setUpMenus();
mainFrame.setBounds(10, 10, 600, 325);
mainFrame.setVisible(true);
}
public RootPaneBug() {
super(new BorderLayout());
}
private void setUpMenus() {
JMenu fileMenu = new JMenu("File");
addOpenItems(fileMenu);
JMenu subMenu = new JMenu("Open SubMenu");
addOpenItems(subMenu);
fileMenu.add(subMenu);
JMenu subSubMenu = new JMenu("Open Next Submenu");
addOpenItems(subSubMenu);
subMenu.add(subSubMenu);
JMenuBar mBar = new JMenuBar();
mBar.add(fileMenu);
((JFrame)getRootPane().getParent()).setJMenuBar(mBar);
final JPopupMenu popupMenu = new JPopupMenu();
addOpenItems(popupMenu);
JMenu popupSubMenu = new JMenu("Popup Submenu");
addOpenItems(popupSubMenu);
popupMenu.add(popupSubMenu);
MouseListener ml = new MouseAdapter() {
@Override public void mouseClicked(MouseEvent e) { showPopup(e); }
@Override public void mousePressed(MouseEvent e) { showPopup(e); }
@Override public void mouseReleased(MouseEvent e) { showPopup(e); }
private void showPopup(MouseEvent e) {
if (e.isPopupTrigger()) {
popupMenu.setInvoker(RootPaneBug.this);
popupMenu.setLocation(e.getLocationOnScreen());
popupMenu.setVisible(true);
}
}
};
addMouseListener(ml);
}
private void addOpenItems(JComponent pFileMenu) {
JMenuItem brokenOpenItem = new JMenuItem("Broken Open...");
JMenuItem fixedOpenItem = new FixedMenuItem("Fixed Open...");
pFileMenu.add(brokenOpenItem);
pFileMenu.add(fixedOpenItem);
ActionListener al = new ActionListener() {
public void actionPerformed(ActionEvent e) {
JComponent menuItem = (JComponent) e.getSource();
JRootPane rootPane = menuItem.getRootPane();
rootPane.setCursor(WAIT_CURSOR);
try {
new JFileChooser().showOpenDialog(rootPane);
} finally {
rootPane.setCursor(DEFAULT_CURSOR);
}
}
};
brokenOpenItem.addActionListener(al);
fixedOpenItem.addActionListener(al);
}
}
@SuppressWarnings({"ClassNameDiffersFromFileName"})
class FixedMenuItem extends JMenuItem {
public FixedMenuItem(String text) {
super(text);
}
@Override public JRootPane getRootPane() {
return getMenuItemRootPane(this);
}
public static JRootPane getMenuItemRootPane(JMenuItem cmp) {
JComponent invoker = ((JComponent) ((JPopupMenu) cmp.getParent()).getInvoker());
// I need to call this recursively in case this is a submenu.
return getRootPaneUniversally(invoker);
}
/**
* The JComponent.getRootPane() method fails for a JMenuItem. So we don't
* call it directly. Instead, we call this method, which works with any JComponent.
* @param cmp The component whose root pane you need.
* @return The root pane for the specified component.
*/
public static JRootPane getRootPaneUniversally(JComponent cmp) {
JRootPane rootPane;
if (cmp instanceof JMenuItem) {
// This may return a valid root pane if the JMenuItem is a JMenu but not a submenu
rootPane = SwingUtilities.getRootPane(cmp);
if (rootPane == null) {
rootPane = getMenuItemRootPane((JMenuItem)cmp);
}
} else {
rootPane = SwingUtilities.getRootPane(cmp);
}
return rootPane;
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
The workaround is contained in the FixedMenuItem class contained in
the submitted source code.
- relates to
-
JDK-4231737 JDialogs not placed correctly when invoked from JPopups
- Open