Swing popup menu on a component and its content - java

I have a component (Widget - extends a JPanel) on which I have implemented a simple popup menu. It works when clicking on the border of the panel and basically everywhere except for the places where the panel layout contains some other component inside the panel.
So if there is a JTable inside the panel, I can invoke the menu when clicking next to it (if there is nothing else) but when clicking on the JTable, nothing happens (the table is obviously on top of the panel preventing the MouseAdapter to register the click).
Can I somehow make the popup menu invoked when right clicking even on the components inside the panel? Here is the sample code how I create and invoke the menu:
private void initPopupMenu() {
popup = new JPopupMenu();
JMenuItem closeItem = new JMenuItem("Close");
closeItem.setActionCommand(WidgetConstants.Actions.CLOSE.name());
closeItem.addActionListener(this);
popup.add(closeItem);
JMenuItem minimizeItem = new JMenuItem("Minimize");
minimizeItem.setActionCommand(WidgetConstants.Actions.MINIMIZE.name());
minimizeItem.addActionListener(this);
popup.add(minimizeItem);
}
MouseInputListener componentListener = new MouseInputAdapter() {
#Override
public void mousePressed(MouseEvent me) {
// popup
if (me.isPopupTrigger()) {
popup.show(me.getComponent(), me.getX(), me.getY());
}
}
#Override
public void mouseReleased(MouseEvent ev) {
if (ev.isPopupTrigger()) {
popup.show(ev.getComponent(), ev.getX(), ev.getY());
}
}
}
#Override
public void setBorder(Border border) {
removeMouseListener(componentListener);
removeMouseMotionListener(componentListener);
if (border instanceof WidgetBorder) {
addMouseListener(componentListener);
addMouseMotionListener(componentListener);
}
super.setBorder(border);
}
Thank you for any tips.

First of all: you don't need to use a mouse listener. Each JComponent has the method setComponentPopupMenu(JPopupMenu). Secon: you can traverse the component tree an register the popup menu for each component.
Here is example:
/**
* Provides component hierarchy traversal.
*
* #param aContainer start node for the traversal.
*/
private void traverse(Container aContainer, JPopupMenu aMenu) {
for (final Component comp : aContainer.getComponents()) {
if (comp instanceof JComponent) {
((JComponent) comp).setComponentPopupMenu(aMenu);
}
if (comp instanceof Container) {
traverse((Container) comp, aMenu);
}
}
}

Related

Component array returned by getComponents() method can be changed

I'm trying to set a pressed button invisible and set the rest visible. I used getComponents() method to get the three buttons and change its visibility state but something goes wrong.
#Override
public void actionPerformed(ActionEvent e) {
Component button = ((Component) e.getSource());
for (Component component : view.getComponents()) {
if (component instanceof JButton) {
if (component.getName().equals(button.getName())) {
System.out.format("Pressed button: %s%n", button.getName());
component.setVisible(false);
} else {
component.setVisible(true);
}
}
}
java.awt.Toolkit.getDefaultToolkit().beep();
view.revalidate();
for (Component component : view.getComponents()) {
System.out.format("%s is visible: %s%n", component.getName(), component.isVisible());
}
}
I've used format() methods to get components visibility state. After launch the program buttons doesn't disappear but states are correct.
If I use button variable to set its visibility state it runs successfully but I can't change the rest of the buttons visibility state.
I'm triying to set a pressed button invisible
You have the button that was clicked so just make it invisible.
#Override
public void actionPerformed(ActionEvent e) {
Component button = ((Component) e.getSource());
button.setVisible(false);
java.awt.Toolkit.getDefaultToolkit().beep();
}
There is no need to iterate through all the buttons on the panel.

Passing mouse events from Jframe glass to menu bar?

Based on previous stack questions I have written the following code to capture mouse input from the glass pane push it to a series of panels underneath.
MouseMotionListener mml = new MouseMotionListener() {
private void DispatchMouseEvent(MouseEvent e) {
Point glassPanePoint = e.getPoint();
Container container = jf.getContentPane();
Point containerPoint = SwingUtilities.convertPoint(
glass, glassPanePoint, container);
if (containerPoint.y >= 0) {
for (Component ml : mouseListeners) {
Point componentPoint = SwingUtilities
.convertPoint( glass, glassPanePoint, ml);
ml.dispatchEvent(new MouseEvent(ml, e.getID(),
e.getWhen(), e.getModifiers(),
componentPoint.x, componentPoint.y,
e.getClickCount(), e.isPopupTrigger()));
}
}
}
#Override
public void mouseDragged(MouseEvent me) {
DispatchMouseEvent(me);
}
#Override
public void mouseMoved(MouseEvent me) {
DispatchMouseEvent(me);
}
};
glass.addMouseMotionListener(mml);
But all of the tutorials I can find say something like this when it comes to the menu bar.
if (containerPoint.y < 0) { //we're not in the content pane
if (containerPoint.y + menuBar.getHeight() >= 0) {
//The mouse event is over the menu bar.
//Could handle specially.
} else {
//The mouse event is over non-system window
//decorations, such as the ones provided by
//the Java look and feel.
//Could handle specially.
}
Could someone point me to somewhere so I can see how to handle that specifically? Thanks.

JPopupMenu not showing text of JMenuItem

I'm trying to create a JPopupMenu, but for some reason, it doesn't show the text I've set on the JMenuItems. The menu itself works, there are menuitems in it and they are responsive, but the text is not showing. I'm creating the menu like this:
private void createPopupMenu() {
this.popupMenu = new JPopupMenu();
this.addMouseListener(new PopupListener(this));
JMenuItem addPlaceMenuItem = new JMenuItem(SketchPad.ADD_PLACE_POPUP_TEXT);
addPlaceMenuItem.setAction(new PopupAction(ActionType.AddPlace));
this.popupMenu.add(addPlaceMenuItem);
JMenuItem addTransitionMenuItem = new JMenuItem(SketchPad.ADD_TRANSITION_POPUP_TEXT);
addTransitionMenuItem.setAction(new PopupAction(ActionType.AddTransition));
this.popupMenu.add(addTransitionMenuItem);
}
In case it matters, here is the PopupListener:
class PopupListener extends MouseAdapter {
SketchPad pad;
public PopupListener(SketchPad pad)
{
this.pad = pad;
}
public void mousePressed(MouseEvent e) {
maybeShowPopup(e);
}
public void mouseReleased(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1)
{
this.pad.getController().deselectAllNodes();
}
else
{
maybeShowPopup(e);
}
}
private void maybeShowPopup(MouseEvent e) {
if (e.isPopupTrigger()) {
pad.popupPosition = new Point(e.getX(), e.getY());
pad.popupMenu.show(e.getComponent(), e.getX(), e.getY());
}
}
}
What am I missing here?
but for some reason, it doesn't show the text I've set on the JMenuItems.
addPlaceMenuItem.setAction(new PopupAction(ActionType.AddPlace));
The setAction(...) method reset the properties of the menu item with the properties of the Action. So you need to make sure you set the NAME property of the Action to set the text of the menu item.
So in your case it looks like the value of the NAME property should be:
SketchPad.ADD_PLACE_POPUP_TEXT
Or the other approach is to reset the text of the menu item after you set the Action
JMenuItem addPlaceMenuItem = new JMenuItem( new PopupAction(ActionType.AddPlace) );
addPlaceMenuItem.setText(SketchPad.ADD_PLACE_POPUP_TEXT);
The effect is platform specific. In particular, "In Microsoft Windows, the user by convention brings up a popup menu by releasing the right mouse button while the cursor is over a component that is popup-enabled." Your implementation of mouseReleased() precludes even checking isPopupTrigger(). Instead, handle the selection and check the trigger. A similar approach is shown in GraphPanel in order to handle multiple selection and a context menu.

Custom JComboBox hiding JPopupMenu

I'm having a little headache with a situation. Maybe some of you have been through this before and can show me another way or even my error here.
I need to add a JTree inside a JComboBox and the code below works like a charm.
public class HierarchyComboBox extends JComboBox {
HierarchyTree ht = new HierarchyTree();
HierarchyComboBox box;
JPopupMenu popup;
MouseAdapter adapter = new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent arg0) {
if (arg0.getClickCount() == 1) {
removeAllItems();
addItem(ht.getSelectedLevel());
// ((JPopupMenu) comp).setVisible(false);
}
}
};
PopupMenuListener listener = new PopupMenuListener() {
#Override
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
if (box == null) {
box = (HierarchyComboBox) e.getSource();
if (popup == null) {
final Object comp = box.getUI().getAccessibleChild(box, 0);
if (!(comp instanceof JPopupMenu))
return;
popup = (JPopupMenu) comp;
}
popup.removeAll();
ht.getTreePane().setBorder(null);
ht.getTreePane().setPreferredSize(new Dimension(box.getWidth(), 200));
MyTree tree = (MyTree)ht.getTreePane().getViewport().getComponent(0);
tree.addMouseListener(adapter);
popup.add(ht.getTreePane());
}
}
#Override
public void popupMenuCanceled(PopupMenuEvent arg0) { }
#Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent arg0) { }
};
public HierarchyComboBox() {
setEditable(true);
addPopupMenuListener(listener);
}
}
but I added this component to 2 different dialogs.
The first one I can click and the selection is added to the JComboBox
and the second, doing EXACTLY the same instantiation, and the same tests
The component has a different behaviour:
- The JPopupMenu disappears
- It doesn't add the selection to the combo
Any ideas here?
Thanks in advance..
As shown in Providing a Custom Renderer, "A combo box uses a renderer to display each item in its menu." You could render the tree in a custom ListCellRenderer. Alternatively,
Render the tree in an adjacent component in response to an ActionListener.
Use a hierarchical model, shown here.
I noticed that the JPopupMenu was loosing it's focus.
The solution was to add the component as the last component of the Panel.

swing mouse listeners being intercepted by child components

I have a swing component that has several sub components. What I want to do change some label if the mouse is over any of those components, and then change it to something else if the mouse moves off all of the components. I'm trying to find a more efficient way to do this.
Currently I have mouse listeners over all of the child components that look something like:
class AMouseListener extends MouseAdapter {
private boolean mouseOver;
mouseEntered(MouseEvent e) { mouseOver = true; updateLabel(); }
mouseExited(MouseEvent e) { mouseOver = false; updateLabel(); }
void updateLabel() {
String text = "not-over-any-components";
// listeners are each of the listeners added to the child components
for ( AMouseListener listener :listeners ) {
if ( listener.mouseOver ) {
text = "over-a-component";
break;
}
}
}
}
This works, but I feel like there should be a better way to handle this by only handling mouseEntered and mouseExited events on the parent container, but since the child components intercept these events, I'm not sure how to go about doing this (I don't necessarily have control over the child components so I Can't forward the mouse events to the parent event if I wanted to).
For example
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.*;
public class TestMouseListener {
public static void main(String[] args) {
final JComboBox combo = new JComboBox();
combo.setEditable(true);
for (int i = 0; i < 10; i++) {
combo.addItem(i);
}
final JLabel tip = new JLabel();
tip.setPreferredSize(new Dimension(300, 20));
JPanel panel = new JPanel();
panel.add(combo);
panel.add(tip);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.add(panel);
frame.pack();
frame.setVisible(true);
panel.addMouseListener(new MouseAdapter() {
#Override
public void mouseEntered(MouseEvent e) {
tip.setText("Outside combobox");
}
#Override
public void mouseExited(MouseEvent e) {
Component c = SwingUtilities.getDeepestComponentAt(
e.getComponent(), e.getX(), e.getY());
// doesn't work if you move your mouse into the combobox popup
tip.setText(c != null && SwingUtilities.isDescendingFrom(
c, combo) ? "Inside combo box" : "Outside combobox");
}
});
}
private TestMouseListener() {
}
}
Check out the docs and examples for the "glass pane".
This should give you what you need: The Glass Pane
I know this is very old, but here's a simple solution with which you can create a mouse listener for a component and all components inside it's bounds (without adding the listener to all components individually):
/**
* Creates an {#link AWTEventListener} that will call the given listener if
* the {#link MouseEvent} occurred inside the given component, one of its
* children or the children's children etc. (recursive).
*
* #param component
* the component the {#link MouseEvent} has to occur inside
* #param listener
* the listener to be called if that is the case
*/
public static void addRecursiveMouseListener(final Component component, final MouseListener listener) {
Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
#Override
public void eventDispatched(AWTEvent event) {
if(event instanceof MouseEvent) {
MouseEvent mouseEvent = (MouseEvent) event;
if(mouseEvent.getComponent().isShowing() && component.isShowing()){
if (containsScreenLocation(component, mouseEvent.getLocationOnScreen())) {
if(event.getID() == MouseEvent.MOUSE_PRESSED) {
listener.mousePressed(mouseEvent);
}
if(event.getID() == MouseEvent.MOUSE_RELEASED) {
listener.mouseReleased(mouseEvent);
}
if(event.getID() == MouseEvent.MOUSE_ENTERED) {
listener.mouseEntered(mouseEvent);
}
if(event.getID() == MouseEvent.MOUSE_EXITED) {
listener.mouseExited(mouseEvent);
}
if(event.getID() == MouseEvent.MOUSE_CLICKED){
listener.mouseClicked(mouseEvent);
}
}
}
}
}
}, AWTEvent.MOUSE_EVENT_MASK);
}
/**
* Checks if the given location (relative to the screen) is inside the given component
* #param component the component to check with
* #param screenLocation the location, relative to the screen
* #return true if it is inside the component, false otherwise
*/
public static boolean containsScreenLocation(Component component, Point screenLocation){
Point compLocation = component.getLocationOnScreen();
Dimension compSize = component.getSize();
int relativeX = screenLocation.x - compLocation.x;
int relativeY = screenLocation.y - compLocation.y;
return (relativeX >= 0 && relativeX < compSize.width && relativeY >= 0 && relativeY < compSize.height);
}
Note: Once the mouse exits the root component of this listener the mouseExited(mouseEvent) will probably not fire, however you can just add the mouse listener to the root component itself and it should fire.
mouseExited(mouseEvent) is unreliable in general though.
You could initiate a single instance of the listener and add that instance to each component.
Like this:
AMouseListener aMouseListener=new AMouseListener();
for each(Component c:components) {
caddMouseListener(aMouseListener);
}

Categories