Search code examples
javaswingnimbus

Changing nimbus JPopupmenu behaviour


I need help about nimbus behaviour for JTree and JPopupMenu. I am setting a right click menu to a JTree. If I left click to a node after open the right click menu with another node, clicked node becoming selected. but in nimbus look and feel, a second click needed for select another node. My code is below, you can try it with default look and feel with comment the nimbus part.

public class JTreeDemo {

    public static void main(String[] args) {

        try {
            for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (Exception e) {
            try {
                UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
            } catch (Exception ex) {
            }
        }

        DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode("Root");
        rootNode.add(new DefaultMutableTreeNode("Child1"));
        rootNode.add(new DefaultMutableTreeNode("Child2"));
        rootNode.add(new DefaultMutableTreeNode("Child3"));

        DefaultTreeModel model = new DefaultTreeModel(rootNode);

        JTree tree = new JTree(model);
        tree.addMouseListener(new TreeMouseListener());

        JFrame jf = new JFrame();
        jf.getContentPane().add(new JScrollPane(tree));

        jf.setSize(new Dimension(300, 300));

        jf.setVisible(true);
    }
}

class TreeMouseListener extends MouseAdapter {

    @Override
    public void mouseReleased(MouseEvent e) {
        if(SwingUtilities.isRightMouseButton(e)) {
            JTree tree = (JTree) e.getSource();

            TreePath jClickedPath = tree.getPathForLocation(e.getX(), e.getY());
            tree.setSelectionPath(jClickedPath);

            JPopupMenu menu = new JPopupMenu();
            menu.add(new JMenuItem("menu1"));
            menu.show(tree,  e.getX(), e.getY());
        }
    }
}

Solution

  • If you print out the pressed, released and clicked mouse events you will see that with the default L&F you get

    // right click
    tree: pressed
    tree: released
    tree: clicked
    // click on node
    tree: pressed
    tree: released
    tree: clicked
    

    whereas with the Nimbus L&F you get

    // right click
    tree: pressed
    tree: released
    tree: clicked
    // first click on node, the pressed event is not passed to the listener
    tree: released
    tree: clicked
    // second click on node
    tree: pressed
    tree: released
    tree: clicked
    

    This is the desired behavior of Nimbus popups to consume the event on close. (see the explanation in the bug report #JDK-6770445)

    You can change this behavior after setting the L&F.

    UIManager.setLookAndFeel(info.getClassName());
    UIManager.put("PopupMenu.consumeEventOnClose", false);
    

    edit Snippet to change the default behavior only for a specific JTree

    // instruct the JTree not to close the popup
    tree.putClientProperty("doNotCancelPopup",
        new JComboBox().getClientProperty("doNotCancelPopup"));
    
    // create the popup menu not inside the listener
    JPopupMenu popup = new JPopupMenu();
    popup.add(new JMenuItem("menu1"));
    
    // add the listener to the JTree
    MouseListener popupListener = new PopupListener(popup);
    tree.addMouseListener(popupListener);
    

    Show and hide the popup programmatically

    static class PopupListener extends MouseAdapter {
    
        JPopupMenu popup;
    
        PopupListener(JPopupMenu popupMenu) {
            popup = popupMenu;
        }
    
        @Override
        public void mousePressed(MouseEvent e) {
            togglePopup(e);
        }
    
        @Override
        public void mouseReleased(MouseEvent e) {
            togglePopup(e);
        }
    
        private void togglePopup(MouseEvent e) {
            if (e.isPopupTrigger()) {
                popup.show(e.getComponent(), e.getX(), e.getY());
            } else if (popup.isVisible()) {
                popup.setVisible(false);
            }
        }
    }