6 Replies - 434 Views - Last Post: 07 October 2013 - 08:00 PM Rate Topic: -----

#1 grimpirate  Icon User is offline

  • Pirate King
  • member icon

Reputation: 149
  • View blog
  • Posts: 714
  • Joined: 03-August 06

JPopupMenu and the Escape Key

Posted 06 October 2013 - 01:14 PM

Here's my test case:

import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.JPopupMenu;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JComponent;

public class Test {

	public static void main(String[] args) {

		SwingUtilities.invokeLater(new Runnable() {

			@Override
			public void run() {
				JFrame frame = new JFrame();
				frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
				JMenu subMenu3 = new JMenu("Sub3");
				JMenu subMenu2 = new JMenu("Sub2");
				JMenu subMenu1 = new JMenu("Sub1");
				subMenu3.add(new JMenuItem("Item1"));
				subMenu3.add(new JMenuItem("Item2"));
				subMenu2.add(subMenu3);
				subMenu2.addSeparator();
				subMenu2.add(new JMenuItem("Item3"));
				subMenu2.add(new JMenuItem("Item4"));
				subMenu1.add(subMenu2);
				subMenu1.add(new JMenuItem("Item5"));
				JPopupMenu menu = new JPopupMenu();
				menu.add(subMenu1);
				menu.add(new JMenuItem("Item6"));
				menu.pack();
				((JComponent)frame.getContentPane()).setComponentPopupMenu(menu);
				frame.getContentPane().setPreferredSize(menu.getPreferredSize());
				frame.setVisible(true);
			}
		});
	}
}


The behavior I'm observing is that when I expand a hierarchy of menus/submenus, upon pressing the Escape key I can collapse the most recent. However, when the topmost JPopupMenu has only the first submenu expanded, if press the Escape key, both menus are collapsed. Is this behavior normal for other people?

Assuming it IS normal, what would be the bets way to go about preventing this "double collapse"? I figure I have to override a key binding of some sort, but when I attempted to retrieve the ActionMap/InputMap of the JPopupMenuItem I wasn't sure how to uncover the relevant mapping.

Is This A Good Question/Topic? 0
  • +

Replies To: JPopupMenu and the Escape Key

#2 CasiOo  Icon User is online

  • D.I.C Lover
  • member icon

Reputation: 1277
  • View blog
  • Posts: 2,846
  • Joined: 05-April 11

Re: JPopupMenu and the Escape Key

Posted 06 October 2013 - 03:35 PM

That is how it behave on my end as well
You could, as you write, roll your own algorithm that closes the menus as needed

I have hacked a little something together here
Maybe you can improve it even more. There might be an easier way :)
import java.awt.event.KeyEvent;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.JPopupMenu;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JComponent;
import javax.swing.event.MenuKeyEvent;
import javax.swing.event.MenuKeyListener;

public class Test {

	public static void main(String[] args) {
		new Test();
	}
	
	public Test() {
		SwingUtilities.invokeLater(new Runnable() {

			@Override
			public void run() {
				JFrame frame = new JFrame();
				frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
				JMenu subMenu3 = createMenuItem("Sub3", false);
				JMenu subMenu2 = createMenuItem("Sub2", false);
				JMenu subMenu1 = createMenuItem("Sub1", true);
				subMenu3.add(new JMenuItem("Item1"));
				subMenu3.add(new JMenuItem("Item2"));
				subMenu2.add(subMenu3);
				subMenu2.addSeparator();
				subMenu2.add(new JMenuItem("Item3"));
				subMenu2.add(new JMenuItem("Item4"));
				subMenu1.add(subMenu2);
				subMenu1.add(new JMenuItem("Item5"));
				JPopupMenu menu = new JPopupMenu();
				menu.add(subMenu1);
				menu.add(new JMenuItem("Item6"));
				menu.pack();
				((JComponent)frame.getContentPane()).setComponentPopupMenu(menu);
				frame.getContentPane().setPreferredSize(menu.getPreferredSize());
				frame.setVisible(true);
			}
		});
	}
	
	private JMenu createMenuItem(String title, boolean isTopMenu) {
		JMenu menu = new JMenu(title);
		menu.addMenuKeyListener(new EscapeMenuListener(menu, isTopMenu));
		return menu;
	}
	
	private class EscapeMenuListener implements MenuKeyListener {
		private JMenu menu;
		private boolean isTopMenu;
		
		public EscapeMenuListener(JMenu menu, boolean isTopMenu) {
			this.menu = menu;
			this.isTopMenu = isTopMenu;
		}
		
		@Override
		public void menuKeyPressed(MenuKeyEvent e) {
			if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
				if (menu.isPopupMenuVisible()) {
					menu.setPopupMenuVisible(false);
					e.consume();
				}
				else if (menu.isSelected() && isTopMenu) {
					//Hide the JPopupMenu... or dispose it
					menu.getParent().setVisible(false);
				}
			}
		}

		@Override
		public void menuKeyReleased(MenuKeyEvent e) {}

		@Override
		public void menuKeyTyped(MenuKeyEvent e) {}
	}
	
}


Was This Post Helpful? 2
  • +
  • -

#3 pbl  Icon User is offline

  • There is nothing you can't do with a JTable
  • member icon

Reputation: 8316
  • View blog
  • Posts: 31,836
  • Joined: 06-March 08

Re: JPopupMenu and the Escape Key

Posted 06 October 2013 - 05:01 PM

Your main() method is way too long
The main() is just to instantiate and Object so your Test class may extends JFrame no need to create a JFrame inside it
Was This Post Helpful? 1
  • +
  • -

#4 grimpirate  Icon User is offline

  • Pirate King
  • member icon

Reputation: 149
  • View blog
  • Posts: 714
  • Joined: 03-August 06

Re: JPopupMenu and the Escape Key

Posted 06 October 2013 - 07:58 PM

@CasiOo: Clever, though I would think this is precisely what action maps and input maps would do this very thing. In the interim though I'm more than satisfied with your solution, so many thanks.

@pbl: I'm aware of that, for the purposes of expedient forum exchanges I've provided what's commonly referred to as an Simple Self-Contained Correct Example (SSCCE).

This post has been edited by grimpirate: 06 October 2013 - 08:01 PM

Was This Post Helpful? 0
  • +
  • -

#5 schutzzz  Icon User is offline

  • D.I.C Regular
  • member icon

Reputation: 133
  • View blog
  • Posts: 338
  • Joined: 22-April 13

Re: JPopupMenu and the Escape Key

Posted 06 October 2013 - 09:33 PM

Stuffing everything in main though isn't exactly what we want to see. I understand you're trying to make it short and simple, but is something along the lines of this, really much longer?

import javax.swing.*;

public class Help extends JFrame {
    
    private Help() {
        
    }
    
    public static void main(String[] arguments) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new Help().setVisible(true);
            }
        });
    }
    
}


Was This Post Helpful? 0
  • +
  • -

#6 grimpirate  Icon User is offline

  • Pirate King
  • member icon

Reputation: 149
  • View blog
  • Posts: 714
  • Joined: 03-August 06

Re: JPopupMenu and the Escape Key

Posted 07 October 2013 - 04:46 PM

@schutzzz: I'll reply to this publicly and leave that as my final reply (as I've already received colorful commentary in my Inbox as to downvoting pbl). My original question is one that addresses the behavior of a JPopupMenu, not how to instantiate/extend a JFrame or whether or not my code is adequate for peer review. pbl's response does not address this, I grant you he is a moderator, but my code is readily understood, as demonstrated by CasiOo's response. In which he opted to do something similar to what you and pbl describe. I didn't downvote pbl and I'm not downvoting you because it's personal or I hate you or something or other. I'm downvoting you because the question that is asked via the reputation system is "Was This Post Helpful?" Neither yours nor pbl addresses the original topic in any manner. Therefore, they are not helpful, and thus a down vote is given; CasiOo's on the other hand was, and thus an up vote is given. This is the purpose of the reputation system. To highlight useful answers, and downvote non-useful ones. What you and pbl have addressed is a coding practice issue. Whether or not you agree with my code stylistically or pedagogically does not take away from the fact that it is correct, and that I've no doubt both you and he understood it readily, even if it makes your insides hurt or pain. It is my personal preference to not reply to topics wherein I cannot understand their code largely due to how they've written it, unless of course my reply is to ask a question for clarification purposes and further my understanding so as to help them. This was not the case with yours or pbl's question. Your responses boil down to off-topic critiques. I hope that settles matters, and whether you do or don't agree with me is of course your prerogative.

Back on topic
Upon reviewing openjdk source code I located in one of the comments that at runtime certain Actions are appended to whichever JRootPane owns the JPopupMenu. By default a JRootPane has three actions: press, release, postPopup. This can be seen by adding a PopupMenuListener and overriding its popupMenuWillBecomeVisible method as follows:
public void popupMenuWillBecomeVisible(PopupMenuEvent ev) {
				
	for(Object key : SwingUtilities.getRootPane(((JPopupMenu)ev.getSource()).getInvoker()).getActionMap().allKeys()) {
		System.out.println(key.toString());
	}
}

Wherein I can observe the five Actions that were added:
  • return
  • cancel
  • selectChild
  • selectPrevious
  • selectParent
  • selectNext

I'm guessing the cancel Action is what controls the behavior I'm attempting to modify. I will post my results as soon as I make the attempt to substitute said Action at runtime.

This post has been edited by grimpirate: 07 October 2013 - 04:49 PM

Was This Post Helpful? 0
  • +
  • -

#7 grimpirate  Icon User is offline

  • Pirate King
  • member icon

Reputation: 149
  • View blog
  • Posts: 714
  • Joined: 03-August 06

Re: JPopupMenu and the Escape Key

Posted 07 October 2013 - 08:00 PM

My initial guess proved fruitful. The solution I've come up with is adding a PopupMenuListener and overriding its popupMenuWillBecomeVisible method as follows:
public void popupMenuWillBecomeVisible(PopupMenuEvent ev) {
				
	Action action = new AbstractAction() {
			
		public void actionPerformed(ActionEvent ev) {
						
			MenuElement[] selectionPath = MenuSelectionManager.defaultManager().getSelectedPath();
						
			int length = selectionPath.length - 1;
						
			for(int i = length; i > -1; i--) {
							
				if(selectionPath[i] instanceof JPopupMenu) {
						
					length = i;
					break;
				}
			}
				
			selectionPath = Arrays.copyOf(selectionPath, length);
				
			MenuSelectionManager.defaultManager().setSelectedPath(selectionPath);
		}
	};
		
	ActionMap map = SwingUtilities.getRootPane(((JPopupMenu)ev.getSource()).getInvoker()).getActionMap();
	map.put("cancel", action);
}


There are a couple of things which may prove problematic however. There's no guarantee the "cancel" Action will be available in the JRootPane, and the javadocs issue a warning that while the setSelectedPath of the MenuSelectionManager class may be public, it is used by the look and feel engine and should not be called by client applications. Nevertheless, the menu does now behave as expected.
Was This Post Helpful? 1
  • +
  • -

Page 1 of 1