Search code examples
javaworldwind

Getting a JPopupMenu in a World Wind display


I need to add a right-click JPopupMenu to a World Wind display. The World Wind display is in a JPanel. I pretty much just copied the member variables and methods of the ApplicationTemplate.AppPanel inner-class out of World Wind's example ApplicationTemplate class, pasted it into a GUI I need the WW-display in, and changed this.add(component) references from the copied code to myJPanel.add(component).

It worked great other than the lack of the popup-menu; I have a World Wind display embedded into my application and am controlling it from my application's dialog's.

After adding a JPopupMenu to the world wind display's JPanel, it doesn't seem to show at all. I right-click, and nothing pops up. I don't think this is a heavyweight vs lightweight Java component issue hiding the menu, since I can attach the menu to a component above the World Wind display (WWD is in a BorderLayout CENTER, other component is at its NORTH) instead and the menu will happily enter the World Wind display's space without getting hidden by it. Just to be safe, I set the JPopupMenu's setLightWeightPopupEnabled(false) and in a static initializer top of the main class I did JPopupMenu.setDefaultLightWeightPopupEnabled(false)

I did a test with a MouseListener attached to the JPanel that contains the World Wind display, and none of the MouseListener events are triggering. So my best guess is that I should not be adding the JPopupMenu to the JPanel but instead should be adding it to some specific sub-component of the wwd object. The wwd object itself does not appear to have a method for adding the popup menu, and I don't see anything like a "getGLCanvas" in the wwd's methods. If I am on the right track here, what component should the menu be added to, and how do I access that component?

So my question is pretty simple, or so it would seem: How do I get a JPopupMenu onto the World Wind display?

Secondarily, this question also begs the same of getting a MouseListener on the display, but I assume the answer to that will fall out of the answer to getting a JPopupMenu on the display.

Below is the World Wind template code which I inserted, along with my modifications to it. Another class elsewhere uses the getComponent() to add the JPanel containing the World Wind display to my application's user interface. I left in the default World Wind stuff that I commented out in case that is somehow significant. The loop through the String[] of layer names was just a way for me to easily show only the map and the unit scale. The JPopupMenu code is halfway down the constructor. Apologies for the messy code, but I thought you should see it as-is for best assistance.

class MyClass
{

protected JComponent component;
public JComponent getComponent() { return component; }

protected WorldWindow wwd;
protected StatusBar statusBar;
protected ToolTipController toolTipController;
protected HighlightController highlightController;

MyClass()
{
    boolean includeStatusBar = false;
    component = new JPanel(new BorderLayout());

    this.wwd = this.createWorldWindow();
    ((Component) this.wwd).setPreferredSize(new Dimension(200,200));//canvasSize);

    // Create the default model as described in the current worldwind properties.
    Model m = (Model) WorldWind.createConfigurationComponent(AVKey.MODEL_CLASS_NAME);
    this.wwd.setModel(m);

    // Setup a select listener for the worldmap click-and-go feature
//            this.wwd.addSelectListener(new ClickAndGoSelectListener(this.getWwd(), WorldMapLayer.class));

    component.add((Component) this.wwd, BorderLayout.CENTER);
    if (includeStatusBar)
    {
        this.statusBar = new StatusBar();
        component.add(statusBar, BorderLayout.PAGE_END);
        this.statusBar.setEventSource(wwd);
    }

    // Add controllers to manage highlighting and tool tips.
//            this.toolTipController = new ToolTipController(this.getWwd(), AVKey.DISPLAY_NAME, null);
//            this.highlightController = new HighlightController(this.getWwd(), SelectEvent.ROLLOVER);

    java.util.List<String> desiredLayers = Arrays.asList(
        new String[] { "Blue Marble May 2004", /*"i-cubed Landsat",*/ "Scale bar"
        });
    for(Layer layer : getWwd().getModel().getLayers())
    {
        if(desiredLayers.contains( layer.getName() ))
        {
            System.out.println("INCLUDE " + layer.getName());
            layer.setEnabled(true);
        }
        else
        {
            System.out.println("EXCLUDE " + layer.getName());
            layer.setEnabled(false);
        }
    }


    JMenu menuZoom = new JMenu("Zoom");
    JMenuItem menuZoom_1028 = new JMenuItem("1028");
    menuZoom.add(menuZoom_1028);
    JMenuItem menuZoom_512 = new JMenuItem("512");
    menuZoom.add(menuZoom_512);
    JMenuItem menuZoom_256 = new JMenuItem("256");
    menuZoom.add(menuZoom_256);
    JMenuItem menuZoom_128 = new JMenuItem("128");
    menuZoom.add(menuZoom_128);
    JMenuItem menuZoom_64 = new JMenuItem("64");
    menuZoom.add(menuZoom_64);
    JMenuItem menuZoom_32 = new JMenuItem("32");
    menuZoom.add(menuZoom_32);
    JPopupMenu rclickMenu = new JPopupMenu();
    rclickMenu.add(menuZoom);
    component.setComponentPopupMenu(rclickMenu);

    menuZoom.getPopupMenu().setLightWeightPopupEnabled(false);

    component.addMouseListener(new MouseListener()
    {
        @Override
        public void mouseClicked(MouseEvent e)
        {
            System.out.println("mouseClicked");
        }

        @Override
        public void mousePressed(MouseEvent e)
        {
            System.out.println("mousePressed");
        }

        @Override
        public void mouseReleased(MouseEvent e)
        {
            System.out.println("mouseReleased");
        }

        @Override
        public void mouseEntered(MouseEvent e)
        {
            System.out.println("mouseEntered");
        }

        @Override
        public void mouseExited(MouseEvent e)
        {
            System.out.println("mouseExited");
        }
    });
}

protected WorldWindow createWorldWindow()
{
    return new WorldWindowGLCanvas();
}

public WorldWindow getWwd()
{
    return wwd;
}

public StatusBar getStatusBar()
{
    return statusBar;
}
}

Solution

  • After a lot of digging, I have finally figured out a workaround.

    First I'll address my silly sub-question about getting the GLPanel from the World Wind wwd object: the wwd object is a GLPanel. Well, technically the WorldWindow is an interface, but in the case of the World Wind ApplicationTemplate application from which I am drawing, the implementation of the WorldWindow is a class which extends GLPanel. I did not notice that for a while.

    As stated in question, mouse listeners applied to the JPanel which contains the World Wind display aren't triggered my mouse actions over the map, and the World Wind display has no methods for adding mouse listeners to itself. I finally realized that the World Window has a getInputHandler() which returns an object which you can add the mouse listener to. So I had to do wwd.getInputHandler().addMouseListener(myMouseAdapter);

    As I believed, the JPopupMenu was not being hidden by the World Wind display; it just wasn't being triggered to pop up. With the above fix for mouse listeners, I remedied this by opening the pop-up myself instead of relying on Swing's built-in right-click JPopupMenu support. I did this by calling jPopupMenu.show(component, event.getX(), event.getY()); from within the mouse listener.

    Here is the code I made to pop-up the menu (in the initialization code for the World Wind stuff):

        wwd.getInputHandler().addMouseListener(new MouseAdapter()
        {
            @Override
            public void mouseClicked(MouseEvent event)
            {
                // right-click context-menu
                if(event.getButton() == MouseEvent.BUTTON3)
                {
                    // component is my JPanel which contains the WorldWindow
                    rclickMenu.show(component, event.getX(), event.getY());
                }
            }
        });
    

    I also added a couple things that I needed which I'll mention in case it is useful. I added a check to make sure the point right-clicked was on the world map specifically, and I also wanted to know later what spot was right-clicked since some menu items are for interacting with whatever was right-clicked on.

    After these additions, the code looked like this:

        wwd.getInputHandler().addMouseListener(new MouseAdapter()
        {
            @Override
            public void mouseClicked(MouseEvent event)
            {
                // right-click context-menu
                if(event.getButton() == MouseEvent.BUTTON3)
                {
                    Position p = wwd.getCurrentPosition();
                    // abort if not on-map
                    if(p == null) return;
    
                    // need later to know what was right-clicked on
                    mPositionAtRightClickMoment = wwd.getCurrentPosition();
    
                    rclickMenu.show(component, event.getX(), event.getY());
                }
            }
        });