8158566: Provide a Swing property to not close toggle menu items on mouse click
authoralexsch
Thu, 30 Jun 2016 23:49:00 +0300
changeset 39542 1073ea3df2c7
parent 39541 2d0b64d48027
child 39543 f382e19d6c63
8158566: Provide a Swing property to not close toggle menu items on mouse click Reviewed-by: prr, ssadetsky
jdk/src/java.desktop/share/classes/com/sun/java/swing/plaf/motif/MotifCheckBoxMenuItemUI.java
jdk/src/java.desktop/share/classes/com/sun/java/swing/plaf/motif/MotifRadioButtonMenuItemUI.java
jdk/src/java.desktop/share/classes/javax/swing/JCheckBoxMenuItem.java
jdk/src/java.desktop/share/classes/javax/swing/JRadioButtonMenuItem.java
jdk/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicLookAndFeel.java
jdk/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicMenuItemUI.java
jdk/test/javax/swing/JMenuItem/8158566/CloseOnMouseClickPropertyTest.java
--- a/jdk/src/java.desktop/share/classes/com/sun/java/swing/plaf/motif/MotifCheckBoxMenuItemUI.java	Thu Jun 30 22:27:28 2016 +0300
+++ b/jdk/src/java.desktop/share/classes/com/sun/java/swing/plaf/motif/MotifCheckBoxMenuItemUI.java	Thu Jun 30 23:49:00 2016 +0300
@@ -89,7 +89,9 @@
             Point p = e.getPoint();
             if(p.x >= 0 && p.x < menuItem.getWidth() &&
                p.y >= 0 && p.y < menuItem.getHeight()) {
-                manager.clearSelectedPath();
+                if (UIManager.getBoolean("CheckBoxMenuItem.closeOnMouseClick")) {
+                    manager.clearSelectedPath();
+                }
                 menuItem.doClick(0);
             } else {
                 manager.processMouseEvent(e);
--- a/jdk/src/java.desktop/share/classes/com/sun/java/swing/plaf/motif/MotifRadioButtonMenuItemUI.java	Thu Jun 30 22:27:28 2016 +0300
+++ b/jdk/src/java.desktop/share/classes/com/sun/java/swing/plaf/motif/MotifRadioButtonMenuItemUI.java	Thu Jun 30 23:49:00 2016 +0300
@@ -97,7 +97,10 @@
             Point p = e.getPoint();
             if(p.x >= 0 && p.x < menuItem.getWidth() &&
                p.y >= 0 && p.y < menuItem.getHeight()) {
-                manager.clearSelectedPath();
+                String property = "RadioButtonMenuItem.closeOnMouseClick";
+                if (UIManager.getBoolean(property)) {
+                    manager.clearSelectedPath();
+                }
                 menuItem.doClick(0);
             } else {
                 manager.processMouseEvent(e);
--- a/jdk/src/java.desktop/share/classes/javax/swing/JCheckBoxMenuItem.java	Thu Jun 30 22:27:28 2016 +0300
+++ b/jdk/src/java.desktop/share/classes/javax/swing/JCheckBoxMenuItem.java	Thu Jun 30 23:49:00 2016 +0300
@@ -56,6 +56,15 @@
  * href="http://docs.oracle.com/javase/tutorial/uiswing/misc/action.html">How
  * to Use Actions</a>, a section in <em>The Java Tutorial</em>.
  * <p>
+ * Some times it is required to select several check box menu items from a menu.
+ * In this case it is useful that clicking on one check box menu item does not
+ * close the menu. Such behavior can be controlled by the Look and Feel property
+ * named {@code "CheckBoxMenuItem.closeOnMouseClick"}. The default value is
+ * {@code true}. Setting the property to {@code false} prevents the menu from
+ * closing when it is clicked by the mouse.
+ * Note: some {@code L&F}s may ignore this property. All built-in {@code L&F}s
+ * inherit this behaviour.
+ * <p>
  * For further information and examples of using check box menu items,
  * see <a
  href="http://docs.oracle.com/javase/tutorial/uiswing/components/menu.html">How to Use Menus</a>,
--- a/jdk/src/java.desktop/share/classes/javax/swing/JRadioButtonMenuItem.java	Thu Jun 30 22:27:28 2016 +0300
+++ b/jdk/src/java.desktop/share/classes/javax/swing/JRadioButtonMenuItem.java	Thu Jun 30 23:49:00 2016 +0300
@@ -50,6 +50,15 @@
  * href="http://docs.oracle.com/javase/tutorial/uiswing/misc/action.html">How
  * to Use Actions</a>, a section in <em>The Java Tutorial</em>.
  * <p>
+ * Some menus can have several button groups with radio button menu items. In
+ * this case it is useful that clicking on one radio button menu item does not
+ * close the menu. Such behavior can be controlled by the Look and Feel property
+ * named {@code "RadioButtonMenuItem.closeOnMouseClick"}. The default value is
+ * {@code true}. Setting the property to {@code false} prevents the menu from
+ * closing when it is clicked by the mouse.
+ * Note: some {@code L&F}s may ignore this property. All built-in {@code L&F}s
+ * inherit this behaviour.
+ * <p>
  * For further documentation and examples see
  * <a
  href="http://docs.oracle.com/javase/tutorial/uiswing/components/menu.html">How to Use Menus</a>,
--- a/jdk/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicLookAndFeel.java	Thu Jun 30 22:27:28 2016 +0300
+++ b/jdk/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicLookAndFeel.java	Thu Jun 30 23:49:00 2016 +0300
@@ -1055,6 +1055,7 @@
             "RadioButtonMenuItem.checkIcon", radioButtonMenuItemIcon,
             "RadioButtonMenuItem.arrowIcon", menuItemArrowIcon,
             "RadioButtonMenuItem.commandSound", null,
+            "RadioButtonMenuItem.closeOnMouseClick", Boolean.TRUE,
 
             "CheckBoxMenuItem.font", dialogPlain12,
             "CheckBoxMenuItem.acceleratorFont", dialogPlain12,
@@ -1071,6 +1072,7 @@
             "CheckBoxMenuItem.checkIcon", checkBoxMenuItemIcon,
             "CheckBoxMenuItem.arrowIcon", menuItemArrowIcon,
             "CheckBoxMenuItem.commandSound", null,
+            "CheckBoxMenuItem.closeOnMouseClick", Boolean.TRUE,
 
             "Menu.font", dialogPlain12,
             "Menu.acceleratorFont", dialogPlain12,
--- a/jdk/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicMenuItemUI.java	Thu Jun 30 22:27:28 2016 +0300
+++ b/jdk/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicMenuItemUI.java	Thu Jun 30 23:49:00 2016 +0300
@@ -936,6 +936,15 @@
         }
     }
 
+    boolean closeOnMouseClick() {
+        if (menuItem instanceof JCheckBoxMenuItem) {
+            return UIManager.getBoolean("CheckBoxMenuItem.closeOnMouseClick");
+        } else if (menuItem instanceof JRadioButtonMenuItem) {
+            return UIManager.getBoolean("RadioButtonMenuItem.closeOnMouseClick");
+        }
+        return true;
+    }
+
     /**
      * Call this method when a menu item is to be activated.
      * This method handles some of the details of menu item activation
@@ -958,11 +967,14 @@
             BasicLookAndFeel.playSound(menuItem, getPropertyPrefix() +
                                        ".commandSound");
         }
-        // Visual feedback
-        if (msm == null) {
-            msm = MenuSelectionManager.defaultManager();
+        if (closeOnMouseClick()) {
+            // Visual feedback
+            if (msm == null) {
+                msm = MenuSelectionManager.defaultManager();
+            }
+
+            msm.clearSelectedPath();
         }
-        msm.clearSelectedPath();
         menuItem.doClick(0);
     }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/swing/JMenuItem/8158566/CloseOnMouseClickPropertyTest.java	Thu Jun 30 23:49:00 2016 +0300
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.Robot;
+import java.awt.event.InputEvent;
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JComponent;
+import javax.swing.JFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JRadioButtonMenuItem;
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+
+/*
+ * @test
+ * @bug 8158566
+ * @summary Provide a Swing property which modifies MenuItemUI behaviour
+ */
+public class CloseOnMouseClickPropertyTest {
+
+    private static JFrame frame;
+    private static JMenu menu;
+
+    public static void main(String[] args) throws Exception {
+
+        for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
+            UIManager.setLookAndFeel(info.getClassName());
+            test(true);
+
+            setProperty(false);
+            test(false);
+
+            setProperty(true);
+            test(true);
+        }
+    }
+
+    private static void setProperty(boolean closeOnMouseClick) {
+        UIManager.put("CheckBoxMenuItem.closeOnMouseClick", closeOnMouseClick);
+        UIManager.put("RadioButtonMenuItem.closeOnMouseClick", closeOnMouseClick);
+    }
+
+    private static void test(boolean closeOnMouseClick) throws Exception {
+        for (TestType testType : TestType.values()) {
+            test(testType, closeOnMouseClick);
+        }
+    }
+
+    private static void test(TestType testType, boolean closeOnMouseClick)
+            throws Exception {
+
+        Robot robot = new Robot();
+        robot.setAutoDelay(50);
+        SwingUtilities.invokeAndWait(() -> createAndShowGUI(testType));
+        robot.waitForIdle();
+
+        Point point = getClickPoint(true);
+        robot.mouseMove(point.x, point.y);
+        robot.mousePress(InputEvent.BUTTON1_MASK);
+        robot.mouseRelease(InputEvent.BUTTON1_MASK);
+        robot.waitForIdle();
+
+        point = getClickPoint(false);
+        robot.mouseMove(point.x, point.y);
+        robot.mousePress(InputEvent.BUTTON1_MASK);
+        robot.mouseRelease(InputEvent.BUTTON1_MASK);
+        robot.waitForIdle();
+
+        SwingUtilities.invokeAndWait(() -> {
+            JMenuItem menuItem = menu.getItem(0);
+            boolean isShowing = menuItem.isShowing();
+            frame.dispose();
+
+            if (TestType.MENU_ITEM.equals(testType)) {
+                if (isShowing) {
+                    throw new RuntimeException("Menu Item is not closed!");
+                }
+            } else {
+                if (isShowing ^ !closeOnMouseClick) {
+                    throw new RuntimeException("Property is not taken into account:"
+                            + " closeOnMouseClick");
+                }
+            }
+        });
+    }
+
+    private static void createAndShowGUI(TestType testType) {
+
+        frame = new JFrame();
+        frame.setSize(300, 300);
+        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+        JMenuBar menuBar = new JMenuBar();
+        menu = new JMenu("Menu");
+        menu.add(getMenuItem(testType));
+        menuBar.add(menu);
+        frame.setJMenuBar(menuBar);
+        frame.setVisible(true);
+    }
+
+    private static JMenuItem getMenuItem(TestType testType) {
+        switch (testType) {
+            case CHECK_BOX_MENU_ITEM:
+                return new JCheckBoxMenuItem("Check Box");
+            case RADIO_BUTTON_MENU_ITEM:
+                return new JRadioButtonMenuItem("Radio Button");
+            default:
+                return new JMenuItem("Menu Item");
+        }
+    }
+
+    private static Point getClickPoint(boolean parent) throws Exception {
+        Point points[] = new Point[1];
+
+        SwingUtilities.invokeAndWait(() -> {
+
+            JComponent comp = parent ? menu : menu.getItem(0);
+
+            Point point = comp.getLocationOnScreen();
+            Rectangle bounds = comp.getBounds();
+            point.x += bounds.getWidth() / 2;
+            point.y += bounds.getHeight() / 2;
+
+            points[0] = point;
+        });
+
+        return points[0];
+    }
+
+    enum TestType {
+
+        MENU_ITEM,
+        CHECK_BOX_MENU_ITEM,
+        RADIO_BUTTON_MENU_ITEM
+    }
+}