Skip to content

Commit 4a33d5e

Browse files
author
Andy Goryachev
committed
8330304: MenuBar: Invisible Menu works incorrectly with keyboard arrows
Reviewed-by: mstrauss, kcr
1 parent 3e768dc commit 4a33d5e

File tree

3 files changed

+75
-16
lines changed

3 files changed

+75
-16
lines changed

modules/javafx.controls/src/main/java/javafx/scene/control/skin/MenuBarSkin.java

+9-14
Original file line numberDiff line numberDiff line change
@@ -121,17 +121,15 @@ public class MenuBarSkin extends SkinBase<MenuBar> {
121121
* *
122122
**************************************************************************/
123123

124+
/** contains MenuBarButton's children only */
124125
private final HBox container;
126+
/** index of *focused* MenuBarButton */
127+
private int focusedMenuIndex = -1;
125128

126129
// represents the currently _open_ menu
127130
private Menu openMenu;
128131
private MenuBarButton openMenuButton;
129132

130-
// represents the currently _focused_ menu. If openMenu is non-null, this should equal
131-
// openMenu. If openMenu is null, this can be any menu in the menu bar.
132-
private Menu focusedMenu;
133-
private int focusedMenuIndex = -1;
134-
135133
private static WeakHashMap<Stage, Reference<MenuBarSkin>> systemMenuMap;
136134
private static List<MenuBase> wrappedDefaultMenus = new ArrayList<>();
137135
private static Stage currentMenuBarStage;
@@ -295,7 +293,7 @@ public MenuBarSkin(final MenuBar control) {
295293
// Key navigation
296294
sceneListenerHelper.addEventFilter(scene, KeyEvent.KEY_PRESSED, (ev) -> {
297295
// process right left and may be tab key events
298-
if (focusedMenu != null) {
296+
if (focusedMenuIndex >= 0) {
299297
switch (ev.getCode()) {
300298
case LEFT: {
301299
boolean isRTL = control.getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT;
@@ -429,7 +427,7 @@ private void showMenu(Menu menu, boolean selectFirstItem) {
429427
if (!menu.isShowing() && !isMenuEmpty(menu)) {
430428
if (selectFirstItem) {
431429
// put selection / focus on first item in menu
432-
MenuButton menuButton = getNodeForMenu(focusedMenuIndex);
430+
MenuButton menuButton = menuBarButtonAt(focusedMenuIndex);
433431
Skin<?> skin = menuButton.getSkin();
434432
if (skin instanceof MenuButtonSkinBase) {
435433
((MenuButtonSkinBase)skin).requestFocusOnFirstMenuItem();
@@ -445,7 +443,6 @@ private void showMenu(Menu menu, boolean selectFirstItem) {
445443
*/
446444
void setFocusedMenuIndex(int index) {
447445
focusedMenuIndex = (index >= -1 && index < getSkinnable().getMenus().size()) ? index : -1;
448-
focusedMenu = (focusedMenuIndex != -1) ? getSkinnable().getMenus().get(index) : null;
449446

450447
if (focusedMenuIndex != -1) {
451448
openMenuButton = (MenuBarButton)container.getChildren().get(focusedMenuIndex);
@@ -733,12 +730,9 @@ public void dispose() {
733730
* *
734731
**************************************************************************/
735732

736-
// For testing purpose only.
737-
MenuButton getNodeForMenu(int i) {
738-
if (i < container.getChildren().size()) {
739-
return (MenuBarButton)container.getChildren().get(i);
740-
}
741-
return null;
733+
// package protected for testing purposes
734+
MenuBarButton menuBarButtonAt(int i) {
735+
return (MenuBarButton)container.getChildren().get(i);
742736
}
743737

744738
int getFocusedMenuIndex() {
@@ -1083,6 +1077,7 @@ private void menuModeEnd() {
10831077
}
10841078

10851079
private void moveToMenu(Direction dir, boolean doShow) {
1080+
Menu focusedMenu = menuBarButtonAt(focusedMenuIndex).menu;
10861081
boolean showNextMenu = doShow && focusedMenu.isShowing();
10871082
findSibling(dir, focusedMenuIndex).ifPresent(p -> {
10881083
setFocusedMenuIndex(p.getValue());

modules/javafx.controls/src/shims/java/javafx/scene/control/skin/MenuBarSkinShim.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public class MenuBarSkinShim {
3737

3838
// can only access the getNodeForMenu method in MenuBarSkin from this package.
3939
public static MenuButton getNodeForMenu(MenuBarSkin skin, int i) {
40-
return skin.getNodeForMenu(i);
40+
return skin.menuBarButtonAt(i);
4141
}
4242

4343
public static Skin getPopupSkin(MenuButton mb) {

modules/javafx.controls/src/test/java/test/javafx/scene/control/MenuBarTest.java

+65-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -716,4 +716,68 @@ protected void startApp(Parent root) {
716716
int focusedIndex = MenuBarSkinShim.getFocusedMenuIndex(skin);
717717
assertEquals(0, focusedIndex);
718718
}
719+
720+
/** Tests keyboard navigation with invisible menu JDK-8330304 */
721+
@Test
722+
public void testKeyNavigationWithInvisibleMenuItem() {
723+
Menu menu1 = mkMenu("1", 3);
724+
Menu menu2 = mkMenu("2", 3);
725+
Menu menu3 = mkMenu("3", 3);
726+
Menu menu4 = mkMenu("4", 3);
727+
menuBar.getMenus().setAll(menu1, menu2, menu3, menu4);
728+
729+
menu2.setVisible(false);
730+
731+
VBox root = new VBox(menuBar);
732+
startApp(root);
733+
tk.firePulse();
734+
735+
MenuBarSkin skin = (MenuBarSkin)menuBar.getSkin();
736+
assertTrue(skin != null);
737+
738+
double x = (menuBar.localToScene(menuBar.getLayoutBounds())).getMinX();
739+
double y = (menuBar.localToScene(menuBar.getLayoutBounds())).getMinY();
740+
741+
// show menu1 using mouse
742+
MenuButton mb = MenuBarSkinShim.getNodeForMenu(skin, 0);
743+
mb.getScene().getWindow().requestFocus();
744+
SceneHelper.processMouseEvent(scene,
745+
MouseEventGenerator.generateMouseEvent(MouseEvent.MOUSE_PRESSED, x+20, y+20));
746+
SceneHelper.processMouseEvent(scene,
747+
MouseEventGenerator.generateMouseEvent(MouseEvent.MOUSE_RELEASED, x+20, y+20));
748+
assertTrue(menu1.isShowing());
749+
750+
// show menu3 with RIGHT key press, menu2 is hidden and should be skipped
751+
KeyEventFirer keyboard = new KeyEventFirer(mb.getScene());
752+
keyboard.doKeyPress(KeyCode.RIGHT);
753+
tk.firePulse();
754+
755+
assertTrue(menu3.isShowing());
756+
757+
// show menu4
758+
keyboard.doKeyPress(KeyCode.RIGHT);
759+
tk.firePulse();
760+
761+
assertTrue(menu4.isShowing());
762+
763+
// show menu3
764+
keyboard.doKeyPress(KeyCode.LEFT);
765+
tk.firePulse();
766+
767+
assertTrue(menu3.isShowing());
768+
769+
// show menu1 (menu2 is hidden)
770+
keyboard.doKeyPress(KeyCode.LEFT);
771+
tk.firePulse();
772+
773+
assertTrue(menu1.isShowing());
774+
}
775+
776+
private Menu mkMenu(String name, int itemCount) {
777+
Menu m = new Menu("Menu" + name);
778+
for (int i = 0; i < itemCount; i++) {
779+
m.getItems().add(new MenuItem("Menu" + name + "Item" + i));
780+
}
781+
return m;
782+
}
719783
}

0 commit comments

Comments
 (0)