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

[macOS] Accelerator assigned to button in dialog fires menuItem in owning stage

XMLWordPrintable

    • x86_64
    • os_x

        ADDITIONAL SYSTEM INFORMATION :
        System Version: macOS 10.13.5 (17F77)
        Kernel Version: Darwin 17.6.0

        $java -version
        java version "1.8.0_172"
        Java(TM) SE Runtime Environment (build 1.8.0_172-b11)
        Java HotSpot(TM) 64-Bit Server VM (build 25.172-b11, mixed mode)



        A DESCRIPTION OF THE PROBLEM :
        A main window of a Java FX application has a "File > Save" menu item with Command-S as its accelerator. A "preview" dialog is opened, and it has a button with text, "Save to PDF". A "Command-S" accelerator and an eventFilter are registered, either with the button, or the dialog window, so that the user can use the keyboard to save the preview to PDF.

        On OS X, when the user presses Command-S in this preview dialog, the generated event leaks back to the owning stage, and fires the "File > Save" menu item. It also fires the "Save to PDF" button, but the fact that the owning stage receives the event and handles it is not correct behavior.

        I have tested on OS X High Sierra and Windows 10. The latter behaves correctly. So, as far as I can tell, the issue is specific to OS X HS (10.13.5).

        STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
        See the attached code.

        EXPECTED VERSUS ACTUAL BEHAVIOR :
        EXPECTED -
        Pressing an accelerator in a dialog to fire a button should not result in the firing of a control (button/menu item) assigned to the same accelerator in the owning window.
        ACTUAL -
        Pressing an accelerator in a dialog to fire a button in that dialog actually fires a control (button/menu item) assigned to the same accelerator in the owning window, as well as the button in the dialog.

        ---------- BEGIN SOURCE ----------
        package show.the.bug;

        import java.util.Optional;

        import org.slf4j.Logger;
        import org.slf4j.LoggerFactory;

        import javafx.application.Application;
        import javafx.application.Platform;
        import javafx.event.ActionEvent;
        import javafx.geometry.Insets;
        import javafx.geometry.Pos;
        import javafx.scene.Scene;
        import javafx.scene.control.Alert;
        import javafx.scene.control.Alert.AlertType;
        import javafx.scene.control.Button;
        import javafx.scene.control.ButtonBar.ButtonData;
        import javafx.scene.control.ButtonType;
        import javafx.scene.control.Label;
        import javafx.scene.control.Menu;
        import javafx.scene.control.MenuBar;
        import javafx.scene.control.MenuItem;
        import javafx.scene.input.KeyCode;
        import javafx.scene.input.KeyCodeCombination;
        import javafx.scene.input.KeyCombination;
        import javafx.scene.layout.Pane;
        import javafx.scene.layout.VBox;
        import javafx.stage.Modality;
        import javafx.stage.Stage;
        import javafx.stage.Window;

        public class Main extends Application {

            private static final Logger log = LoggerFactory.getLogger(Main.class);
            
            private Label label;
            private Stage stage;
            private MenuItem saveMenuItem;

            final private String showPdfPreviewText = "Show PDF Preview";

            public static void main(String[] args) {
                Application.launch(args);
            }

            @Override
            public void start(Stage primaryStage) {
                stage = primaryStage;
                stage.setTitle("Accelerator Generated Events Leak From Dialog!");

                final Button button = new Button(showPdfPreviewText);
                final String ifThisChangesText = "If this text changes after pressing Command-S/Control-S "
                    + "in the \"Show PDF Preview\" dialog, there's a problem! It will indicate that File > Save was fired.";
                label = new Label(ifThisChangesText);
                label.setWrapText(true);

                button.setOnAction(actionEvent -> {
                    label.setText(ifThisChangesText);
                    showAlert(stage);
                });

                final VBox vBox = new VBox(button, label);
                VBox.setMargin(vBox, new Insets(12));
                vBox.setSpacing(24.0);
                vBox.setAlignment(Pos.CENTER);

                final Scene scene = new Scene(new VBox(vBox), 400, 250);
                ((Pane) scene.getRoot()).getChildren().add(0, configureMenuBar());
                stage.setScene(scene);
                stage.sizeToScene();
                stage.show();
            }

            private MenuBar configureMenuBar() {
                MenuBar menuBar = new MenuBar();
                Menu fileMenu = new Menu("File");
                menuBar.getMenus().add(fileMenu);
                saveMenuItem = new MenuItem("Save");
                saveMenuItem.setAccelerator(new KeyCodeCombination(KeyCode.S, KeyCombination.SHORTCUT_DOWN));
                saveMenuItem.setOnAction(ae -> {
                    log.debug("saveTtem fired: {}", ae);
                    Platform.runLater(() -> label.setText(
                        "Whoa! Not good.\n\n"
                            + "Save menu item action was fired from the \"PDF Preview\" dialog.\n\n"
                            + "Events should not leak from a dialog back to the owning stage/window."));
                });
                fileMenu.getItems().add(saveMenuItem);
                return menuBar;
            }

            private void showAlert(final Window owner) {
                final ButtonType saveButtonType = new ButtonType("Save to PDF...", ButtonData.LEFT);
                final Alert previewAlert = new Alert(
                    AlertType.INFORMATION,
                    "Just some text. Press Command-S to test whether expected behavior occurs.",
                    saveButtonType,
                    ButtonType.CLOSE);

                previewAlert.setHeaderText(null);
                previewAlert.setGraphic(null);
                previewAlert.setTitle("PDF Preview");
                previewAlert.setContentText("\n\nLorem ipsum dolor sit amet, consetetur sadipscing elitr, ...\n\n"
                    + "Hit Command-S (Mac), Control-S (Windows) to show the problem.\n\n");
                previewAlert.initOwner(owner);
                // The result is the same with Modality.APPLICATION_MODAL
                previewAlert.initModality(Modality.WINDOW_MODAL);
                positionAlert(previewAlert);

                final Button saveButton = (Button) previewAlert.getDialogPane().lookupButton(saveButtonType);

                /*
                 * Configure necessary event filter for the alert/dialog. Add SHORTCUT_DOWN+S to fire the saveButton.
                 */
                saveButton.sceneProperty().addListener((obs, oldScene, newScene) -> {
                    if (oldScene == null && newScene != null) {
                        newScene.getWindow().addEventFilter(ActionEvent.ACTION, ae -> {
                            log.debug("event filter caught actionEvent: {}", ae);
                            previewAlert.setResult(saveButtonType);
                            Platform.runLater(() -> {
                                // Using an alert for convenience. Dialogs are essentially the same.
                                Alert saveAlert = new Alert(AlertType.INFORMATION, "This is the \"Save as PDF...\" dialog.\n\n"
                                    + "In a real app, it would be a FileChooser.");
                                saveAlert.setTitle("Save as PDF");
                                saveAlert.setHeaderText("Save as PDF...");
                                positionAlert(saveAlert);
                                saveAlert.showAndWait();
                                label.setText(
                                    "See the work-around involving disabling the saveMenuItem "
                                    + "before showing the \"PDF Preview\" dialog.");
                            });
                            previewAlert.close();
                            ae.consume();
                        });
                        log.debug("setting up saveButton: {}", newScene);
                        newScene.getAccelerators().put(
                            new KeyCodeCombination(KeyCode.S, KeyCombination.SHORTCUT_DOWN),
                            saveButton::fire);
                    }
                });

                /*
                 * To work around the leaking event, disable the saveMenuItem prior to showing the
                 * alert/dialog. Then re-enable it after the dialog has been closed. Uncomment
                 * the next two commented lines and run again to see the effect.
                 */
        // saveMenuItem.setDisable(true);
                final Optional<ButtonType> result = previewAlert.showAndWait();
        // saveMenuItem.setDisable(false);

                log.debug("result from Command-S: {}", result);
                result
                    .filter(bt -> ButtonData.LEFT.equals(bt.getButtonData()))
                    .ifPresent(bt -> log.debug("alert result: {}", bt));
            }

            /**
             * Reposition the alert so that it doesn't cover the text shown in the main window. That text
             * tells you what to expect and what actually happens.
             *
             * @param alert
             */
            protected void positionAlert(Alert alert) {
                alert.setOnShown(ev -> {
                    alert.getDialogPane().setPrefWidth(stage.getWidth());
                    alert.setX(stage.getX());
                    alert.setWidth(stage.getWidth());
                    alert.setY(stage.getY() + stage.getHeight() + 20.0);
                });
            }

        }

        ---------- END SOURCE ----------

        CUSTOMER SUBMITTED WORKAROUND :
        See the attached code, specifically near the bottom of the file where the comment tells you to uncomment two lines surrounding the "showAndWait()" invocation.

        FREQUENCY : always


          1. Main.java
            7 kB
            Priyanka Mangal

              mfox Martin Fox
              webbuggrp Webbug Group
              Votes:
              1 Vote for this issue
              Watchers:
              7 Start watching this issue

                Created:
                Updated:
                Resolved: