Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8225698

[macos] On changing focus of internal frame by clicking on a menu, the menu does not open on the first click

XMLWordPrintable

      ADDITIONAL SYSTEM INFORMATION :
      openjdk version "11.0.3" 2019-04-16
      OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.3+7)
      OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.3+7, mixed mode)
      OSX Sierra 10.12.6

      A DESCRIPTION OF THE PROBLEM :
      Create a Swing desktop application with two internal frames, both with a menu bar.
      Give one frame focus by clicking in it, then click on the menu in the other frame. The menu is not opened, and instead, the focus simply just changes to the other frame.

      REGRESSION : Last worked in version 8u192

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Run the test class provided (a modified Oracle InternalFrameDemo with menu bars added).
      Open two frames (Document | New). Click the 'Weather' menu in one - menu should open.
      Next click the 'Weather' menu in the other - menu appears then closes immediately.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Menu should open normally when clicked in either frame. This is the behaviour with Java 1.8.0_162.
      ACTUAL -
      The focus changes to the new frame, but the menu does not open on the first click. Needs to be clicked again after the focus switches. Subsequent clicks behave normally.

      ---------- BEGIN SOURCE ----------
      package jalview.gui;

      import java.awt.Color;
      import java.awt.Dimension;
      import java.awt.Toolkit;
      import java.awt.event.ActionEvent;
      import java.awt.event.ActionListener;
      import java.awt.event.KeyEvent;

      import javax.swing.JDesktopPane;
      import javax.swing.JFrame;
      import javax.swing.JInternalFrame;
      import javax.swing.JMenu;
      import javax.swing.JMenuBar;
      import javax.swing.JMenuItem;
      import javax.swing.KeyStroke;

      /*
       * Modified from: https://docs.oracle.com/javase/tutorial/displayCode.html?code=https://docs.oracle.com/javase/tutorial/uiswing/examples/components/InternalFrameDemoProject/src/components/InternalFrameDemo.java
       */
      public class InternalFrameDemo extends JFrame implements ActionListener
      {
        static int openFrameCount = 0;

        class MyInternalFrame extends JInternalFrame
        {

          static final int xOffset = 30, yOffset = 30;

          public MyInternalFrame()
          {
            super("Document #" + (++openFrameCount), true, // resizable
                    true, // closable
                    true, // maximizable
                    true);// iconifiable

            // additions for bug demo
            JMenuBar menuBar = new JMenuBar();
            menuBar.setBackground(Color.white);
            menuBar.setFont(new java.awt.Font("Verdana", 0, 11));
            JMenu menu = new JMenu("Weather");
            menu.add(new JMenuItem("Fine"));
            menu.add(new JMenuItem("Wet"));
            menuBar.add(menu);
            setJMenuBar(menuBar);
            // end of additions

            // ...Create the GUI and put it in the window...

            // ...Then set the window size or call pack...
            setSize(300, 300);

            // Set the window's location.
            setLocation(xOffset * openFrameCount, yOffset * openFrameCount);
          }
        }
        JDesktopPane desktop;

        public InternalFrameDemo()
        {
          super("InternalFrameDemo");

          // Make the big window be indented 50 pixels from each edge
          // of the screen.
          int inset = 50;
          Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
          setBounds(inset, inset, screenSize.width - inset * 2,
                  screenSize.height - inset * 2);

          // Set up the GUI.
          desktop = new JDesktopPane(); // a specialized layered pane
          createFrame(); // create first "window"
          setContentPane(desktop);
          setJMenuBar(createMenuBar());

          // Make dragging a little faster but perhaps uglier.
          desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
        }

        protected JMenuBar createMenuBar()
        {
          JMenuBar menuBar = new JMenuBar();

          // Set up the lone menu.
          JMenu menu = new JMenu("Document");
          menu.setMnemonic(KeyEvent.VK_D);
          menuBar.add(menu);

          // Set up the first menu item.
          JMenuItem menuItem = new JMenuItem("New");
          menuItem.setMnemonic(KeyEvent.VK_N);
          menuItem.setAccelerator(
                  KeyStroke.getKeyStroke(KeyEvent.VK_N, ActionEvent.ALT_MASK));
          menuItem.setActionCommand("new");
          menuItem.addActionListener(this);
          menu.add(menuItem);

          // Set up the second menu item.
          menuItem = new JMenuItem("Quit");
          menuItem.setMnemonic(KeyEvent.VK_Q);
          menuItem.setAccelerator(
                  KeyStroke.getKeyStroke(KeyEvent.VK_Q, ActionEvent.ALT_MASK));
          menuItem.setActionCommand("quit");
          menuItem.addActionListener(this);
          menu.add(menuItem);

          return menuBar;
        }

        // React to menu selections.
        @Override
        public void actionPerformed(ActionEvent e)
        {
          if ("new".equals(e.getActionCommand()))
          { // new
            createFrame();
          }
          else
          { // quit
            quit();
          }
        }

        // Create a new internal frame.
        protected void createFrame()
        {
          MyInternalFrame frame = new MyInternalFrame();

          frame.setVisible(true); // necessary as of 1.3
          desktop.add(frame);
          try
          {
            frame.setSelected(true);
          } catch (java.beans.PropertyVetoException e)
          {
          }
        }

        // Quit the application.
        protected void quit()
        {
          System.exit(0);
        }

        /**
         * Create the GUI and show it. For thread safety, this method should be invoked
         * from the event-dispatching thread.
         */
        private static void createAndShowGUI()
        {
          // Make sure we have nice window decorations.
          JFrame.setDefaultLookAndFeelDecorated(true);

          // Create and set up the window.
          InternalFrameDemo frame = new InternalFrameDemo();
          frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

          // Display the window.
          frame.setVisible(true);
        }

        public static void main(String[] args)
        {
          // Schedule a job for the event-dispatching thread:
          // creating and showing this application's GUI.
          javax.swing.SwingUtilities.invokeLater(new Runnable()
          {
            @Override
            public void run()
            {
              createAndShowGUI();
            }
          });
        }
      }
      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      Not workaround but diagnosis:
      1) On click on menu:
       javax.swing.plaf.basic.BasicLookAndFeel$AWTEventHelper.eventDispatched, MOUSE_PRESSED event in JMenu traverses its parent hierarchy and for each JInternalFrame found, calls setSelected(true) on the frame
      2) JInternalFrame.setSelected(true) calls firePropertyChange("selected", ...)
      3) BasicInternalFrameUI$Handler.propertyChange() calls activateFrame, delegated to DefaultDesktopManager
      4) this calls setSelected(false) on the currentlyActiveFrame
      5) JInternalFrame.setSelected(false) posts a new sun.awt.UngrabEvent // this event is not raised in Java 8!
      6) AWT event listener BasicPopupMenuUI$MouseGrabber calls cancelPopupMenu
      7) the menu is closed
      In brief, changing selected frame by clicking on a menu has the side effect of raising an UngrabEvent for the other frame, and this cancels the menu.
      From what I can tell, the only relevant change in Java is the new UngrabEvent - no doubt added for a reason.
      Perhaps MouseGrabber.eventDispatched() should check whether the UngrabEvent's source is a parent of the menu before calling cancelPopupMenu?

      FREQUENCY : always


            dnguyen Damon Nguyen
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            5 Start watching this issue

              Created:
              Updated: