JDK-8205915 : [macOS] Accelerator assigned to button in dialog fires menuItem in owning stage
  • Type: Bug
  • Component: javafx
  • Sub-Component: controls
  • Affected Version: 8u60,9,10,openjfx11
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: os_x
  • CPU: x86_64
  • Submitted: 2018-06-21
  • Updated: 2022-05-12
  • Resolved: 2022-01-20
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 8 Other
8u341Fixed openjfx18Fixed
Related Reports
Blocks :  
Blocks :  
Description
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



Comments
Changeset: 217e086b Author: Martin Fox <beldenfox@users.noreply.github.com> Committer: Kevin Rushforth <kcr@openjdk.org> Date: 2022-01-20 17:49:11 +0000 URL: https://git.openjdk.java.net/jfx/commit/217e086b3493dfc7d419d8fa632a9d3091e7f823
20-01-2022

A pull request was submitted for review. URL: https://git.openjdk.java.net/jfx/pull/715 Date: 2022-01-13 19:18:33 +0000
13-01-2022

This is probably the root cause for all these critical still open issues: https://github.com/gluonhq/scenebuilder/issues/221 https://github.com/gluonhq/scenebuilder/issues/263 https://github.com/gluonhq/scenebuilder/issues/306 https://github.com/javafxports/openjdk-jfx/issues/370 Maybe that gives it a little higher priority.
15-01-2021

Issue reproducible on macOS 10.13.1 with JDK 10+46 and 11-ea+13.
29-06-2018

Issue is reproducible in macOS 10.13.5 (and not in windows) and its a regression introduced in 8u60 macOS 10.13.5, 64-bit JDK results -------------------------------- 8u52 : Pass 8u60 : Fail 8u172 : Fail 10+46 : Fail 11-ea+13 : Fail on macOS, 'save' menu item is also fired along with button in dialog, program output logs as below : ---------------- setting up saveButton: javafx.scene.Scene@46d2c6b3 event filter caught actionEvent: javafx.event.ActionEvent[source=javafx.scene.control.HeavyweightDialog$1@28b3bb75] saveTtem fired: javafx.event.ActionEvent[source=MenuItem@1839b4f0[styleClass=[menu-item]]] result from Command-S: Optional[ButtonType [text=Save to PDF..., buttonData=LEFT]] alert result: ButtonType [text=Save to PDF..., buttonData=LEFT] ----------------
27-06-2018