Skip to content

8354539: [macOS] ComboBox and Spinner disable system menu bar shortcuts #1848

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

beldenfox
Copy link
Contributor

@beldenfox beldenfox commented Jul 14, 2025

The Mac platform code sends a KeyEvent into the scene graph and if the event is not consumed it gets sent on to the system menu. But ComboBox and Spinner always consume the original event and fire a copy which the system menu ignores.

In the past the platform code sent key events to the system menu even if the scene graph consumed them. This caused various bugs and was fixed in PR #1528 leading to this issue.

One could argue that a ComboBox or Spinner shouldn’t consume all key events but one could also argue that the system menu shouldn’t behave so differently from a standard MenuBar which will respond to any KeyEvent that reaches the top-level Scene no matter where it came from.

This PR installs an event dispatcher which forwards KEY_PRESSED events on to the platform so any event bubbling through the dispatch chain can trigger the system menu. The dispatcher is placed by the top-level (non-popup) Window such that it’s the last dispatcher encountered while bubbling.

In this PR once the key event reaches the GlassSystemMenu it passes the JavaFX key code and modifiers into the Mac platform code. This isn’t enough information to construct an NSEvent to pass to the main menu. Instead the code uses the code and modifiers to verify that the originating key down NSEvent (which it retained) should be sent on to NSApp.mainMenu.

(There are other ways this could work. GlassSystemMenu could take the KeyEvent and perform its own accelerator matching entirely inside Java. This would match the way the standard MenuBar finds accelerators instead of using Apple’s matching algorithm. This PR is the more conservative approach, basically just shifting the timing of system menu matching without changing how it’s done.)


Progress

  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue
  • Change must be properly reviewed (2 reviews required, with at least 1 Reviewer, 1 Author)

Issue

  • JDK-8354539: [macOS] ComboBox and Spinner disable system menu bar shortcuts (Bug - P3)

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jfx.git pull/1848/head:pull/1848
$ git checkout pull/1848

Update a local copy of the PR:
$ git checkout pull/1848
$ git pull https://git.openjdk.org/jfx.git pull/1848/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 1848

View PR using the GUI difftool:
$ git pr show -t 1848

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jfx/pull/1848.diff

Using Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Jul 14, 2025

👋 Welcome back mfox! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Jul 14, 2025

❗ This change is not yet ready to be integrated.
See the Progress checklist in the description for automated requirements.

@openjdk
Copy link

openjdk bot commented Jul 14, 2025

⚠️ @beldenfox This pull request contains merges that bring in commits not present in the target repository. Since this is not a "merge style" pull request, these changes will be squashed when this pull request in integrated. If this is your intention, then please ignore this message. If you want to preserve the commit structure, you must change the title of this pull request to Merge <project>:<branch> where <project> is the name of another project in the OpenJDK organization (for example Merge jdk:master).

@openjdk openjdk bot added the rfr Ready for review label Jul 14, 2025
@mlbridge
Copy link

mlbridge bot commented Jul 14, 2025

Webrevs

@kevinrushforth
Copy link
Member

/reviewers 2

@openjdk
Copy link

openjdk bot commented Jul 14, 2025

@kevinrushforth
The total number of required reviews for this PR (including the jcheck configuration and the last /reviewers command) is now set to 2 (with at least 1 Reviewer, 1 Author).

@@ -901,7 +901,6 @@ WindowEventDispatcher createInternalEventDispatcher() {
return new WindowEventDispatcher(new PopupEventRedirector(this),
new WindowCloseRequestHandler(this),
new EventHandlerManager(this));

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should remove this unrelated change.

@@ -31,4 +31,8 @@ public interface MenuBarDelegate {
// removes a submenu at {@code pos} which delegate is {@code menu} parameter
public boolean remove(MenuDelegate menu, int pos);
public long getNativeMenu();
// Returns true if the key event was processed
default public boolean handleKeyEvent(int code, int modifiers) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be public default or just default as everything is always public in an interface anyway.

Copy link
Collaborator

@hjohn hjohn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Java part looks like a simple enough change. I can't comment on the native code.

Comment on lines 65 to 67
if (supported == SupportedState.TRUE && event.getEventType() == KeyEvent.KEY_PRESSED && (event instanceof KeyEvent)) {
Toolkit.getToolkit().getSystemMenu().handleKeyEvent((KeyEvent)event);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (supported == SupportedState.TRUE && event.getEventType() == KeyEvent.KEY_PRESSED && (event instanceof KeyEvent)) {
Toolkit.getToolkit().getSystemMenu().handleKeyEvent((KeyEvent)event);
}
if (supported == SupportedState.TRUE && event.getEventType() == KeyEvent.KEY_PRESSED && event instanceof KeyEvent ke) {
Toolkit.getToolkit().getSystemMenu().handleKeyEvent(ke);
}

Or perhaps the instanceof is even superfluous, as you already verify it is a KEY_PRESSED event.

@hjohn
Copy link
Collaborator

hjohn commented Jul 14, 2025

One could argue that a ComboBox or Spinner shouldn’t consume all key events

They definitely shouldn't, but I think that's how the poor man's focus/event delegation is currently implemented in these controls.

@andy-goryachev-oracle
Copy link
Contributor

On a surface, this looks like a rather intrusive change (I can't comment on the merits of this PR yet).

Would it make sense to attempt fixing event consumption bugs in the ComboBox and Spinner first?
And perhaps also look at fixing the dispatching of consumed events such as JDK-8303209 or JDK-8229467 (the latter has been by @mstr2 , I think prematurely)?

@beldenfox
Copy link
Contributor Author

Would it make sense to attempt fixing event consumption bugs in the ComboBox and Spinner first?

When a Spinner is the focus owner it receives all KeyEvents but KeyEvents still need to be delivered to its editor. There's no good way of doing that now short of consuming the original event and firing a copy. To fix this we would have to introduce new API's (like some form of PR #1632).

And perhaps also look at fixing the dispatching of consumed events such as JDK-8303209 or JDK-8229467 (the latter has been by @mstr2 , I think prematurely)?

Those bugs have to do with how consumption is signaled which isn't relevant. All that's relevant is that the original event is being consumed, not how we find out about it.

JavaFX handles events using dispatch chains and currently the system menu doesn't participate in that machinery. This causes it to work very differently from a standard MenuBar (which places its accelerators in the Scene which is in the chain). One could argue that it's a bug that the system menu doesn't respond to KeyEvents the way a standard MenuBar would.

Another way to look at it: do we want to outlaw what ComboBox and Spinner are doing? It's not illegal to create a KeyEvent and fire it at a control. It's not illegal to consume a KeyEvent that originated in Glass. Things only fail when the system menu is in effect and, again, only because it doesn't participate in dispatch.

Because I agree with you, this PR is intrusive. I'm not fan and I wrote it. I think the system menu needs to participate in event dispatch and I haven't found any clean, tidy way of doing that.

@andy-goryachev-oracle
Copy link
Contributor

To fix this we would have to introduce new API's (like some form of PR #1632).

I am not a fan of #1632, I think it's an attempt to band-aid over a fundamental design issue, but this is a different topic altogether.

Why would a Spinner or ComboBox consume the key events that it is not supposed to? Ok, let's assume the outer control (Spinner or ComboBox) gets the event and fires a copy to the inner control (TextField). This is perfectly legal, but now it must either consume the event if the secondary event is consumed. More importantly, it should not consume the event if the secondary event is not consumed, isn't that the issue?

So it it a responsibility of the entity that creates the secondary event to either consume the original event or let it go, or am I mistaken?

@hjohn
Copy link
Collaborator

hjohn commented Jul 16, 2025

Another way to look at it: do we want to outlaw what ComboBox and Spinner are doing? It's not illegal to create a KeyEvent and fire it at a control. It's not illegal to consume a KeyEvent that originated in Glass. Things only fail when the system menu is in effect and, again, only because it doesn't participate in dispatch.

Good point, it's not and shouldn't be strictly illegal. I only commented earlier that I think they shouldn't be doing it but currently have little recourse.

It should be perfectly fine to create your own KeyEvent or MouseEvent, and IMHO such events should be able to do anything that system created events can do, including firing system menus. That ship may have sailed though as I think there's already data in these events that users can't create or replicate.

@mstr2
Copy link
Collaborator

mstr2 commented Jul 16, 2025

Another way to look at it: do we want to outlaw what ComboBox and Spinner are doing? It's not illegal to create a KeyEvent and fire it at a control. It's not illegal to consume a KeyEvent that originated in Glass. Things only fail when the system menu is in effect and, again, only because it doesn't participate in dispatch.

A control can't just fire an event "to the inner control" using the usual Event.fireEvent(). These events are routed through the scene graph starting from the root, so an outside observer will see two key events, not one. There's no way of knowing that the two events are not actually two separate user inputs.

I don't know what "outlaw" means in this case. Throw an exception when user code tries to fire input events? No, probably not. But in my view we should consider it as a control bug, not as a legitimate use of the event system.

@beldenfox
Copy link
Contributor Author

But in my view we should consider it as a control bug, not as a legitimate use of the event system.

If we decided to fix this in controls and discard this PR as unnecessary I would not shed any tears. I knew this would be clunky but felt we at least needed to get a look at a fix like this. I also wanted this in hand since the timeline for fixing this in controls is uncertain.

@beldenfox
Copy link
Contributor Author

So it it a responsibility of the entity that creates the secondary event to either consume the original event or let it go, or am I mistaken?

This would mean two unconsumed KeyEvents would bubble all the way up to the scene. This could trigger the same MenuItem twice even though the user only pressed the accelerator once.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
rfr Ready for review
Development

Successfully merging this pull request may close these issues.

5 participants