1 /*
   2  * Copyright (c) 2010, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javafx.scene.control.skin;
  27 
  28 import static com.sun.javafx.FXPermissions.ACCESS_WINDOW_LIST_PERMISSION;
  29 
  30 import com.sun.javafx.scene.traversal.Direction;
  31 import javafx.css.converter.EnumConverter;
  32 import javafx.css.converter.SizeConverter;
  33 import com.sun.javafx.scene.control.MenuBarButton;
  34 import com.sun.javafx.scene.control.skin.Utils;
  35 import com.sun.javafx.scene.traversal.ParentTraversalEngine;
  36 import javafx.beans.InvalidationListener;
  37 import javafx.beans.property.DoubleProperty;
  38 import javafx.beans.property.ObjectProperty;
  39 import javafx.beans.property.ReadOnlyProperty;
  40 import javafx.beans.value.ChangeListener;
  41 import javafx.beans.value.WeakChangeListener;
  42 import javafx.beans.value.WritableValue;
  43 import javafx.collections.ListChangeListener;
  44 import javafx.collections.MapChangeListener;
  45 import javafx.collections.ObservableList;
  46 import javafx.css.CssMetaData;
  47 import javafx.css.Styleable;
  48 import javafx.css.StyleableDoubleProperty;
  49 import javafx.css.StyleableObjectProperty;
  50 import javafx.css.StyleableProperty;
  51 import javafx.event.ActionEvent;
  52 import javafx.event.EventHandler;
  53 import javafx.event.WeakEventHandler;
  54 import javafx.geometry.Bounds;
  55 import javafx.geometry.NodeOrientation;
  56 import javafx.geometry.Pos;
  57 import javafx.scene.AccessibleAttribute;
  58 import javafx.scene.Node;
  59 import javafx.scene.Scene;
  60 import javafx.scene.control.Control;
  61 import javafx.scene.control.CustomMenuItem;
  62 import javafx.scene.control.Menu;
  63 import javafx.scene.control.MenuBar;
  64 import javafx.scene.control.MenuButton;
  65 import javafx.scene.control.MenuItem;
  66 import javafx.scene.control.SeparatorMenuItem;
  67 import javafx.scene.control.Skin;
  68 import javafx.scene.control.SkinBase;
  69 import javafx.scene.input.KeyCombination;
  70 import javafx.scene.input.KeyEvent;
  71 import javafx.scene.input.MouseEvent;
  72 import javafx.scene.layout.HBox;
  73 import javafx.stage.Stage;
  74 
  75 import static javafx.scene.input.KeyCode.*;
  76 
  77 import java.lang.ref.Reference;
  78 import java.lang.ref.WeakReference;
  79 import java.util.ArrayList;
  80 import java.util.Collections;
  81 import java.util.Iterator;
  82 import java.util.List;
  83 import java.util.Map;
  84 import java.util.Optional;
  85 import java.util.WeakHashMap;
  86 
  87 import com.sun.javafx.menu.MenuBase;
  88 import com.sun.javafx.scene.ParentHelper;
  89 import com.sun.javafx.scene.SceneHelper;
  90 import com.sun.javafx.scene.control.GlobalMenuAdapter;
  91 import com.sun.javafx.tk.Toolkit;
  92 import java.util.function.Predicate;
  93 import javafx.stage.Window;
  94 import javafx.util.Pair;
  95 
  96 import java.security.AccessController;
  97 import java.security.PrivilegedAction;
  98 
  99 /**
 100  * Default skin implementation for the {@link MenuBar} control. In essence it is
 101  * a simple toolbar. For the time being there is no overflow behavior and we just
 102  * hide nodes which fall outside the bounds.
 103  *
 104  * @see MenuBar
 105  * @since 9
 106  */
 107 public class MenuBarSkin extends SkinBase<MenuBar> {
 108 
 109     private static final ObservableList<Window> stages;
 110 
 111     static {
 112         final Predicate<Window> findStage = (w) -> w instanceof Stage;
 113         ObservableList<Window> windows = AccessController.doPrivileged(
 114             (PrivilegedAction<ObservableList<Window>>) () -> Window.getWindows(),
 115             null,
 116             ACCESS_WINDOW_LIST_PERMISSION);
 117         stages = windows.filtered(findStage);
 118     }
 119 
 120     /***************************************************************************
 121      *                                                                         *
 122      * Private fields                                                          *
 123      *                                                                         *
 124      **************************************************************************/
 125 
 126     private final HBox container;
 127 
 128     // represents the currently _open_ menu
 129     private Menu openMenu;
 130     private MenuBarButton openMenuButton;
 131 
 132     // represents the currently _focused_ menu. If openMenu is non-null, this should equal
 133     // openMenu. If openMenu is null, this can be any menu in the menu bar.
 134     private Menu focusedMenu;
 135     private int focusedMenuIndex = -1;
 136 
 137     private static WeakHashMap<Stage, Reference<MenuBarSkin>> systemMenuMap;
 138     private static List<MenuBase> wrappedDefaultMenus = new ArrayList<>();
 139     private static Stage currentMenuBarStage;
 140     private List<MenuBase> wrappedMenus;
 141 
 142     private WeakEventHandler<KeyEvent> weakSceneKeyEventHandler;
 143     private WeakEventHandler<MouseEvent> weakSceneMouseEventHandler;
 144     private WeakEventHandler<KeyEvent> weakSceneAltKeyEventHandler;
 145     private WeakChangeListener<Boolean> weakWindowFocusListener;
 146     private WeakChangeListener<Window> weakWindowSceneListener;
 147     private EventHandler<KeyEvent> keyEventHandler;
 148     private EventHandler<KeyEvent> altKeyEventHandler;
 149     private EventHandler<MouseEvent> mouseEventHandler;
 150     private ChangeListener<Boolean> menuBarFocusedPropertyListener;
 151     private ChangeListener<Scene> sceneChangeListener;
 152     private ChangeListener<Boolean> menuVisibilityChangeListener;
 153 
 154     private boolean pendingDismiss = false;
 155 
 156     private boolean altKeyPressed = false;
 157 
 158 
 159     /***************************************************************************
 160      *                                                                         *
 161      * Listeners / Callbacks                                                   *
 162      *                                                                         *
 163      **************************************************************************/
 164 
 165     // RT-20411 : reset menu selected/focused state
 166     private EventHandler<ActionEvent> menuActionEventHandler = t -> {
 167         if (t.getSource() instanceof CustomMenuItem) {
 168             // RT-29614 If CustomMenuItem hideOnClick is false, dont hide
 169             CustomMenuItem cmi = (CustomMenuItem)t.getSource();
 170             if (!cmi.isHideOnClick()) return;
 171         }
 172         unSelectMenus();
 173     };
 174 
 175     private ListChangeListener<MenuItem> menuItemListener = (c) -> {
 176         while (c.next()) {
 177             for (MenuItem mi : c.getAddedSubList()) {
 178                 updateActionListeners(mi, true);
 179             }
 180             for (MenuItem mi: c.getRemoved()) {
 181                 updateActionListeners(mi, false);
 182             }
 183         }
 184     };
 185 
 186     Runnable firstMenuRunnable = new Runnable() {
 187         public void run() {
 188             /*
 189             ** check that this menubar's container has contents,
 190             ** and that the first item is a MenuButton....
 191             ** otherwise the transfer is off!
 192             */
 193             if (container.getChildren().size() > 0) {
 194                 if (container.getChildren().get(0) instanceof MenuButton) {
 195 //                        container.getChildren().get(0).requestFocus();
 196                     if (focusedMenuIndex != 0) {
 197                         unSelectMenus();
 198                         menuModeStart(0);
 199                         openMenuButton = ((MenuBarButton)container.getChildren().get(0));
 200 //                        openMenu = getSkinnable().getMenus().get(0);
 201                         openMenuButton.setHover();
 202                     }
 203                     else {
 204                         unSelectMenus();
 205                     }
 206                 }
 207             }
 208         }
 209     };
 210 
 211 
 212 
 213     /***************************************************************************
 214      *                                                                         *
 215      * Constructors                                                            *
 216      *                                                                         *
 217      **************************************************************************/
 218 
 219     /**
 220      * Creates a new MenuBarSkin instance, installing the necessary child
 221      * nodes into the Control {@link Control#getChildren() children} list, as
 222      * well as the necessary input mappings for handling key, mouse, etc events.
 223      *
 224      * @param control The control that this skin should be installed onto.
 225      */
 226     public MenuBarSkin(final MenuBar control) {
 227         super(control);
 228 
 229         container = new HBox();
 230         container.getStyleClass().add("container");
 231         getChildren().add(container);
 232 
 233         // Key navigation
 234         keyEventHandler = event -> {
 235             // process right left and may be tab key events
 236             if (focusedMenu != null) {
 237                 switch (event.getCode()) {
 238                     case LEFT: {
 239                         boolean isRTL = control.getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT;
 240                         if (control.getScene().getWindow().isFocused()) {
 241                             if (openMenu != null && !openMenu.isShowing()) {
 242                                 if (isRTL) {
 243                                     moveToMenu(Direction.NEXT, false); // just move the selection bar
 244                                 } else {
 245                                     moveToMenu(Direction.PREVIOUS, false); // just move the selection bar
 246                                 }
 247                                 event.consume();
 248                                 return;
 249                             }
 250                             if (isRTL) {
 251                                 moveToMenu(Direction.NEXT, true);
 252                             } else {
 253                                 moveToMenu(Direction.PREVIOUS, true);
 254                             }
 255                         }
 256                         event.consume();
 257                         break;
 258                     }
 259                     case RIGHT:
 260                     {
 261                         boolean isRTL = control.getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT;
 262                         if (control.getScene().getWindow().isFocused()) {
 263                             if (openMenu != null && !openMenu.isShowing()) {
 264                                 if (isRTL) {
 265                                     moveToMenu(Direction.PREVIOUS, false); // just move the selection bar
 266                                 } else {
 267                                     moveToMenu(Direction.NEXT, false); // just move the selection bar
 268                                 }
 269                                 event.consume();
 270                                 return;
 271                             }
 272                             if (isRTL) {
 273                                 moveToMenu(Direction.PREVIOUS, true);
 274                             } else {
 275                                 moveToMenu(Direction.NEXT, true);
 276                             }
 277                         }
 278                         event.consume();
 279                         break;
 280                     }
 281                     case DOWN:
 282                     //case SPACE:
 283                     //case ENTER:
 284                         // RT-18859: Doing nothing for space and enter
 285                         if (control.getScene().getWindow().isFocused()) {
 286                             if (focusedMenuIndex != -1) {
 287                                 Menu menuToOpen = getSkinnable().getMenus().get(focusedMenuIndex);
 288                                 showMenu(menuToOpen, true);
 289                                 event.consume();
 290                             }
 291                         }
 292                         break;
 293                     case ESCAPE:
 294                         unSelectMenus();
 295                         event.consume();
 296                         break;
 297                 default:
 298                     break;
 299                 }
 300             }
 301         };
 302         menuBarFocusedPropertyListener = (ov, t, t1) -> {
 303             if (t1) {
 304                 // RT-23147 when MenuBar's focusTraversable is true the first
 305                 // menu will visually indicate focus
 306                 unSelectMenus();
 307                 menuModeStart(0);
 308                 openMenuButton = ((MenuBarButton)container.getChildren().get(0));
 309                 setFocusedMenuIndex(0);
 310                 openMenuButton.setHover();
 311             } else {
 312                 unSelectMenus();
 313              }
 314          };
 315         weakSceneKeyEventHandler = new WeakEventHandler<KeyEvent>(keyEventHandler);
 316         Utils.executeOnceWhenPropertyIsNonNull(control.sceneProperty(), (Scene scene) -> {
 317             scene.addEventFilter(KeyEvent.KEY_PRESSED, weakSceneKeyEventHandler);
 318         });
 319 
 320         // When we click else where in the scene - menu selection should be cleared.
 321         mouseEventHandler = t -> {
 322             Bounds containerScreenBounds = container.localToScreen(container.getLayoutBounds());
 323             if (containerScreenBounds == null || !containerScreenBounds.contains(t.getScreenX(), t.getScreenY())) {
 324                 unSelectMenus();
 325             }
 326         };
 327         weakSceneMouseEventHandler = new WeakEventHandler<MouseEvent>(mouseEventHandler);
 328         Utils.executeOnceWhenPropertyIsNonNull(control.sceneProperty(), (Scene scene) -> {
 329             scene.addEventFilter(MouseEvent.MOUSE_CLICKED, weakSceneMouseEventHandler);
 330         });
 331 
 332         weakWindowFocusListener = new WeakChangeListener<Boolean>((ov, t, t1) -> {
 333             if (!t1) {
 334               unSelectMenus();
 335             }
 336         });
 337         // When the parent window looses focus - menu selection should be cleared
 338         Utils.executeOnceWhenPropertyIsNonNull(control.sceneProperty(), (Scene scene) -> {
 339             if (scene.getWindow() != null) {
 340                 scene.getWindow().focusedProperty().addListener(weakWindowFocusListener);
 341             } else {
 342                 ChangeListener<Window> sceneWindowListener = (observable, oldValue, newValue) -> {
 343                     if (oldValue != null)
 344                         oldValue.focusedProperty().removeListener(weakWindowFocusListener);
 345                     if (newValue != null)
 346                         newValue.focusedProperty().addListener(weakWindowFocusListener);
 347                 };
 348                 weakWindowSceneListener = new WeakChangeListener<>(sceneWindowListener);
 349                 scene.windowProperty().addListener(weakWindowSceneListener);
 350             }
 351         });
 352 
 353         menuVisibilityChangeListener = (ov, t, t1) -> {
 354             rebuildUI();
 355         };
 356 
 357         rebuildUI();
 358         control.getMenus().addListener((ListChangeListener<Menu>) c -> {
 359             rebuildUI();
 360         });
 361 
 362         if (Toolkit.getToolkit().getSystemMenu().isSupported()) {
 363             control.useSystemMenuBarProperty().addListener(valueModel -> {
 364                 rebuildUI();
 365             });
 366         }
 367 
 368         // When the mouse leaves the menu, the last hovered item should lose
 369         // it's focus so that it is no longer selected. This code returns focus
 370         // to the MenuBar itself, such that keyboard navigation can continue.
 371           // fix RT-12254 : menu bar should not request focus on mouse exit.
 372 //        addEventFilter(MouseEvent.MOUSE_EXITED, new EventHandler<MouseEvent>() {
 373 //            @Override
 374 //            public void handle(MouseEvent event) {
 375 //                requestFocus();
 376 //            }
 377 //        });
 378 
 379         /*
 380         ** add an accelerator for F10 on windows and ctrl+F10 on mac/linux
 381         ** pressing f10 will select the first menu button on a menubar
 382         */
 383         final KeyCombination acceleratorKeyCombo;
 384         if (com.sun.javafx.util.Utils.isMac()) {
 385            acceleratorKeyCombo = KeyCombination.keyCombination("ctrl+F10");
 386         } else {
 387            acceleratorKeyCombo = KeyCombination.keyCombination("F10");
 388         }
 389 
 390         altKeyEventHandler = e -> {
 391             if (e.getEventType() == KeyEvent.KEY_PRESSED) {
 392                 // Clear menu selection when ALT is pressed by itself
 393                 altKeyPressed = false;
 394                 if (e.getCode() == ALT && !e.isConsumed()) {
 395                     if (focusedMenuIndex == -1) {
 396                         altKeyPressed = true;
 397                     }
 398                     unSelectMenus();
 399                 }
 400             } else if (e.getEventType() == KeyEvent.KEY_RELEASED) {
 401                 // Put focus on the first menu when ALT is released
 402                 // directly after being pressed by itself
 403                 if (altKeyPressed && e.getCode() == ALT && !e.isConsumed()) {
 404                     firstMenuRunnable.run();
 405                 }
 406                 altKeyPressed = false;
 407             }
 408         };
 409         weakSceneAltKeyEventHandler = new WeakEventHandler<>(altKeyEventHandler);
 410 
 411         Utils.executeOnceWhenPropertyIsNonNull(control.sceneProperty(), (Scene scene) -> {
 412             scene.getAccelerators().put(acceleratorKeyCombo, firstMenuRunnable);
 413             scene.addEventHandler(KeyEvent.ANY, weakSceneAltKeyEventHandler);
 414         });
 415 
 416         ParentTraversalEngine engine = new ParentTraversalEngine(getSkinnable());
 417         engine.addTraverseListener((node, bounds) -> {
 418             if (openMenu != null) openMenu.hide();
 419             setFocusedMenuIndex(0);
 420         });
 421         ParentHelper.setTraversalEngine(getSkinnable(), engine);
 422 
 423         control.sceneProperty().addListener((ov, t, t1) -> {
 424             // remove event handlers / filters from the old scene (t)
 425             if (t != null) {
 426                 if (weakSceneKeyEventHandler != null) {
 427                     t.removeEventFilter(KeyEvent.KEY_PRESSED, weakSceneKeyEventHandler);
 428                 }
 429                 if (weakSceneMouseEventHandler != null) {
 430                     t.removeEventFilter(MouseEvent.MOUSE_CLICKED, weakSceneMouseEventHandler);
 431                 }
 432                 if (weakSceneAltKeyEventHandler != null) {
 433                     t.removeEventHandler(KeyEvent.ANY, weakSceneAltKeyEventHandler);
 434                 }
 435             }
 436 
 437             /**
 438              * remove the f10 accelerator from the old scene
 439              * add it to the new scene
 440              */
 441             if (t != null) {
 442                 t.getAccelerators().remove(acceleratorKeyCombo);
 443             }
 444             if (t1 != null ) {
 445                 t1.getAccelerators().put(acceleratorKeyCombo, firstMenuRunnable);
 446             }
 447         });
 448     }
 449 
 450     private void showMenu(Menu menu) {
 451         showMenu(menu, false);
 452     }
 453 
 454     private void showMenu(Menu menu, boolean selectFirstItem) {
 455         // hide the currently visible menu, and move to the next one
 456         if (openMenu == menu) return;
 457         if (openMenu != null) {
 458             openMenu.hide();
 459         }
 460 
 461         openMenu = menu;
 462         if (!menu.isShowing() && !isMenuEmpty(menu)) {
 463             if (selectFirstItem) {
 464                 // put selection / focus on first item in menu
 465                 MenuButton menuButton = getNodeForMenu(focusedMenuIndex);
 466                 Skin<?> skin = menuButton.getSkin();
 467                 if (skin instanceof MenuButtonSkinBase) {
 468                     ((MenuButtonSkinBase)skin).requestFocusOnFirstMenuItem();
 469                 }
 470             }
 471 
 472             openMenu.show();
 473         }
 474     }
 475 
 476     private void setFocusedMenuIndex(int index) {
 477         this.focusedMenuIndex = index;
 478         focusedMenu = index == -1 ? null : getSkinnable().getMenus().get(index);
 479 
 480         if (focusedMenu != null && focusedMenuIndex != -1) {
 481             openMenuButton = (MenuBarButton)container.getChildren().get(focusedMenuIndex);
 482             openMenuButton.setHover();
 483         }
 484     }
 485 
 486 
 487 
 488     /***************************************************************************
 489      *                                                                         *
 490      * Static methods                                                          *
 491      *                                                                         *
 492      **************************************************************************/
 493 
 494     // RT-22480: This is intended as private API for SceneBuilder,
 495     // pending fix for RT-19857: Keeping menu in the Mac menu bar when
 496     // there is no more stage
 497     /**
 498      * Set the default system menu bar. This allows an application to keep menu
 499      * in the system menu bar after the last Window is closed.
 500      * @param menuBar the menu bar
 501      */
 502     public static void setDefaultSystemMenuBar(final MenuBar menuBar) {
 503         if (Toolkit.getToolkit().getSystemMenu().isSupported()) {
 504             wrappedDefaultMenus.clear();
 505             for (Menu menu : menuBar.getMenus()) {
 506                 wrappedDefaultMenus.add(GlobalMenuAdapter.adapt(menu));
 507             }
 508             menuBar.getMenus().addListener((ListChangeListener<Menu>) c -> {
 509                 wrappedDefaultMenus.clear();
 510                 for (Menu menu : menuBar.getMenus()) {
 511                     wrappedDefaultMenus.add(GlobalMenuAdapter.adapt(menu));
 512                 }
 513             });
 514         }
 515     }
 516 
 517     private static MenuBarSkin getMenuBarSkin(Stage stage) {
 518         if (systemMenuMap == null) return null;
 519         Reference<MenuBarSkin> skinRef = systemMenuMap.get(stage);
 520         return skinRef == null ? null : skinRef.get();
 521     }
 522 
 523     private static void setSystemMenu(Stage stage) {
 524         if (stage != null && stage.isFocused()) {
 525             while (stage != null && stage.getOwner() instanceof Stage) {
 526                 MenuBarSkin skin = getMenuBarSkin(stage);
 527                 if (skin != null && skin.wrappedMenus != null) {
 528                     break;
 529                 } else {
 530                     // This is a secondary stage (dialog) that doesn't
 531                     // have own menu bar.
 532                     //
 533                     // Continue looking for a menu bar in the parent stage.
 534                     stage = (Stage)stage.getOwner();
 535                 }
 536             }
 537         } else {
 538             stage = null;
 539         }
 540 
 541         if (stage != currentMenuBarStage) {
 542             List<MenuBase> menuList = null;
 543             if (stage != null) {
 544                 MenuBarSkin skin = getMenuBarSkin(stage);
 545                 if (skin != null) {
 546                     menuList = skin.wrappedMenus;
 547                 }
 548             }
 549             if (menuList == null) {
 550                 menuList = wrappedDefaultMenus;
 551             }
 552             Toolkit.getToolkit().getSystemMenu().setMenus(menuList);
 553             currentMenuBarStage = stage;
 554         }
 555     }
 556 
 557     private static void initSystemMenuBar() {
 558         systemMenuMap = new WeakHashMap<>();
 559 
 560         final InvalidationListener focusedStageListener = ov -> {
 561             setSystemMenu((Stage)((ReadOnlyProperty<?>)ov).getBean());
 562         };
 563 
 564         for (Window stage : stages) {
 565             stage.focusedProperty().addListener(focusedStageListener);
 566         }
 567         stages.addListener((ListChangeListener<Window>) c -> {
 568             while (c.next()) {
 569                 for (Window stage : c.getRemoved()) {
 570                     stage.focusedProperty().removeListener(focusedStageListener);
 571                 }
 572                 for (Window stage : c.getAddedSubList()) {
 573                     stage.focusedProperty().addListener(focusedStageListener);
 574                     setSystemMenu((Stage) stage);
 575                 }
 576             }
 577         });
 578     }
 579 
 580 
 581 
 582     /***************************************************************************
 583      *                                                                         *
 584      * Properties                                                              *
 585      *                                                                         *
 586      **************************************************************************/
 587 
 588     /**
 589      * Specifies the spacing between menu buttons on the MenuBar.
 590      */
 591     // --- spacing
 592     private DoubleProperty spacing;
 593     public final void setSpacing(double value) {
 594         spacingProperty().set(snapSpaceX(value));
 595     }
 596 
 597     public final double getSpacing() {
 598         return spacing == null ? 0.0 : snapSpaceX(spacing.get());
 599     }
 600 
 601     public final DoubleProperty spacingProperty() {
 602         if (spacing == null) {
 603             spacing = new StyleableDoubleProperty() {
 604 
 605                 @Override
 606                 protected void invalidated() {
 607                     final double value = get();
 608                     container.setSpacing(value);
 609                 }
 610 
 611                 @Override
 612                 public Object getBean() {
 613                     return MenuBarSkin.this;
 614                 }
 615 
 616                 @Override
 617                 public String getName() {
 618                     return "spacing";
 619                 }
 620 
 621                 @Override
 622                 public CssMetaData<MenuBar,Number> getCssMetaData() {
 623                     return SPACING;
 624                 }
 625             };
 626         }
 627         return spacing;
 628     }
 629 
 630     /**
 631      * Specifies the alignment of the menu buttons inside the MenuBar (by default
 632      * it is Pos.TOP_LEFT).
 633      */
 634     // --- container alignment
 635     private ObjectProperty<Pos> containerAlignment;
 636     public final void setContainerAlignment(Pos value) {
 637         containerAlignmentProperty().set(value);
 638     }
 639 
 640     public final Pos getContainerAlignment() {
 641         return containerAlignment == null ? Pos.TOP_LEFT : containerAlignment.get();
 642     }
 643 
 644     public final ObjectProperty<Pos> containerAlignmentProperty() {
 645         if (containerAlignment == null) {
 646             containerAlignment = new StyleableObjectProperty<Pos>(Pos.TOP_LEFT) {
 647 
 648                 @Override
 649                 public void invalidated() {
 650                     final Pos value = get();
 651                     container.setAlignment(value);
 652                 }
 653 
 654                 @Override
 655                 public Object getBean() {
 656                     return MenuBarSkin.this;
 657                 }
 658 
 659                 @Override
 660                 public String getName() {
 661                     return "containerAlignment";
 662                 }
 663 
 664                 @Override
 665                 public CssMetaData<MenuBar,Pos> getCssMetaData() {
 666                     return ALIGNMENT;
 667                 }
 668             };
 669         }
 670         return containerAlignment;
 671     }
 672 
 673 
 674 
 675     /***************************************************************************
 676      *                                                                         *
 677      * Public API                                                              *
 678      *                                                                         *
 679      **************************************************************************/
 680 
 681     /** {@inheritDoc} */
 682     @Override public void dispose() {
 683         cleanUpSystemMenu();
 684         // call super.dispose last since it sets control to null
 685         super.dispose();
 686     }
 687 
 688     // Return empty insets when "container" is empty, which happens
 689     // when using the system menu bar.
 690 
 691     /** {@inheritDoc} */
 692     @Override protected double snappedTopInset() {
 693         return container.getChildren().isEmpty() ? 0 : super.snappedTopInset();
 694     }
 695     /** {@inheritDoc} */
 696     @Override protected double snappedBottomInset() {
 697         return container.getChildren().isEmpty() ? 0 : super.snappedBottomInset();
 698     }
 699     /** {@inheritDoc} */
 700     @Override protected double snappedLeftInset() {
 701         return container.getChildren().isEmpty() ? 0 : super.snappedLeftInset();
 702     }
 703     /** {@inheritDoc} */
 704     @Override protected double snappedRightInset() {
 705         return container.getChildren().isEmpty() ? 0 : super.snappedRightInset();
 706     }
 707 
 708     /**
 709      * Layout the menu bar. This is a simple horizontal layout like an hbox.
 710      * Any menu items which don't fit into it will simply be made invisible.
 711      */
 712     /** {@inheritDoc} */
 713     @Override protected void layoutChildren(final double x, final double y,
 714                                             final double w, final double h) {
 715         // layout the menus one after another
 716         container.resizeRelocate(x, y, w, h);
 717     }
 718 
 719     /** {@inheritDoc} */
 720     @Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 721         return container.minWidth(height) + snappedLeftInset() + snappedRightInset();
 722     }
 723 
 724     /** {@inheritDoc} */
 725     @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 726         return container.prefWidth(height) + snappedLeftInset() + snappedRightInset();
 727     }
 728 
 729     /** {@inheritDoc} */
 730     @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 731         return container.minHeight(width) + snappedTopInset() + snappedBottomInset();
 732     }
 733 
 734     /** {@inheritDoc} */
 735     @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 736         return container.prefHeight(width) + snappedTopInset() + snappedBottomInset();
 737     }
 738 
 739     // grow horizontally, but not vertically
 740     /** {@inheritDoc} */
 741     @Override protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 742         return getSkinnable().prefHeight(-1);
 743     }
 744 
 745 
 746 
 747     /***************************************************************************
 748      *                                                                         *
 749      * Private implementation                                                  *
 750      *                                                                         *
 751      **************************************************************************/
 752 
 753     // For testing purpose only.
 754     MenuButton getNodeForMenu(int i) {
 755         if (i < container.getChildren().size()) {
 756             return (MenuBarButton)container.getChildren().get(i);
 757         }
 758         return null;
 759     }
 760 
 761     int getFocusedMenuIndex() {
 762         return focusedMenuIndex;
 763     }
 764 
 765     private boolean menusContainCustomMenuItem() {
 766         for (Menu menu : getSkinnable().getMenus()) {
 767             if (menuContainsCustomMenuItem(menu)) {
 768                 System.err.println("Warning: MenuBar ignored property useSystemMenuBar because menus contain CustomMenuItem");
 769                 return true;
 770             }
 771         }
 772         return false;
 773     }
 774 
 775     private boolean menuContainsCustomMenuItem(Menu menu) {
 776         for (MenuItem mi : menu.getItems()) {
 777             if (mi instanceof CustomMenuItem && !(mi instanceof SeparatorMenuItem)) {
 778                 return true;
 779             } else if (mi instanceof Menu) {
 780                 if (menuContainsCustomMenuItem((Menu)mi)) {
 781                     return true;
 782                 }
 783             }
 784         }
 785         return false;
 786     }
 787 
 788     private int getMenuBarButtonIndex(MenuBarButton m) {
 789         for (int i= 0; i < container.getChildren().size(); i++) {
 790             MenuBarButton menuButton = (MenuBarButton)container.getChildren().get(i);
 791             if (m == menuButton) {
 792                 return i;
 793             }
 794         }
 795         return -1;
 796     }
 797 
 798     private void updateActionListeners(MenuItem item, boolean add) {
 799         if (item instanceof Menu) {
 800             Menu menu = (Menu) item;
 801 
 802             if (add) {
 803                 menu.getItems().addListener(menuItemListener);
 804             } else {
 805                 menu.getItems().removeListener(menuItemListener);
 806             }
 807 
 808             for (MenuItem mi : menu.getItems()) {
 809                 updateActionListeners(mi, add);
 810             }
 811         } else {
 812             if (add) {
 813                 item.addEventHandler(ActionEvent.ACTION, menuActionEventHandler);
 814             } else {
 815                 item.removeEventHandler(ActionEvent.ACTION, menuActionEventHandler);
 816             }
 817         }
 818     }
 819 
 820     private void rebuildUI() {
 821         getSkinnable().focusedProperty().removeListener(menuBarFocusedPropertyListener);
 822         for (Menu m : getSkinnable().getMenus()) {
 823             // remove action listeners
 824             updateActionListeners(m, false);
 825 
 826             m.visibleProperty().removeListener(menuVisibilityChangeListener);
 827         }
 828         for (Node n : container.getChildren()) {
 829             // Stop observing menu's showing & disable property for changes.
 830             // Need to unbind before clearing container's children.
 831             MenuBarButton menuButton = (MenuBarButton)n;
 832             menuButton.hide();
 833             menuButton.menu.showingProperty().removeListener(menuButton.menuListener);
 834             menuButton.disableProperty().unbind();
 835             menuButton.textProperty().unbind();
 836             menuButton.graphicProperty().unbind();
 837             menuButton.styleProperty().unbind();
 838 
 839             menuButton.dispose();
 840 
 841             // RT-29729 : old instance of context menu window/popup for this MenuButton needs
 842             // to be cleaned up. Setting the skin to null - results in a call to dispose()
 843             // on the skin which in this case MenuButtonSkinBase - does the subsequent
 844             // clean up to ContextMenu/popup window.
 845             menuButton.setSkin(null);
 846             menuButton = null;
 847         }
 848         container.getChildren().clear();
 849 
 850         if (Toolkit.getToolkit().getSystemMenu().isSupported()) {
 851             final Scene scene = getSkinnable().getScene();
 852             if (scene != null) {
 853                 // RT-36554 - make sure system menu is updated when this MenuBar's scene changes.
 854                 if (sceneChangeListener == null) {
 855                     sceneChangeListener = (observable, oldValue, newValue) -> {
 856 
 857                         if (oldValue != null) {
 858                             if (oldValue.getWindow() instanceof Stage) {
 859                                 final Stage stage = (Stage) oldValue.getWindow();
 860                                 final MenuBarSkin curMBSkin = getMenuBarSkin(stage);
 861                                 if (curMBSkin == MenuBarSkin.this) {
 862                                     curMBSkin.wrappedMenus = null;
 863                                     systemMenuMap.remove(stage);
 864                                     if (currentMenuBarStage == stage) {
 865                                         currentMenuBarStage = null;
 866                                         setSystemMenu(stage);
 867                                     }
 868                                 } else {
 869                                     if (curMBSkin != null && curMBSkin.getSkinnable() != null &&
 870                                             curMBSkin.getSkinnable().isUseSystemMenuBar()) {
 871                                         curMBSkin.getSkinnable().setUseSystemMenuBar(false);
 872                                     }
 873                                 }
 874                             }
 875                         }
 876 
 877                         if (newValue != null) {
 878                             if (getSkinnable().isUseSystemMenuBar() && !menusContainCustomMenuItem()) {
 879                                 if (newValue.getWindow() instanceof Stage) {
 880                                     final Stage stage = (Stage) newValue.getWindow();
 881                                     if (systemMenuMap == null) {
 882                                         initSystemMenuBar();
 883                                     }
 884                                     wrappedMenus = new ArrayList<>();
 885                                     systemMenuMap.put(stage, new WeakReference<>(this));
 886                                     for (Menu menu : getSkinnable().getMenus()) {
 887                                         wrappedMenus.add(GlobalMenuAdapter.adapt(menu));
 888                                     }
 889                                     currentMenuBarStage = null;
 890                                     setSystemMenu(stage);
 891 
 892                                     // TODO: Why two request layout calls here?
 893                                     getSkinnable().requestLayout();
 894                                     javafx.application.Platform.runLater(() -> getSkinnable().requestLayout());
 895                                 }
 896                             }
 897                         }
 898                     };
 899                     getSkinnable().sceneProperty().addListener(sceneChangeListener);
 900                 }
 901 
 902                 // Fake a change event to trigger an update to the system menu.
 903                 sceneChangeListener.changed(getSkinnable().sceneProperty(), scene, scene);
 904 
 905                 // If the system menu references this MenuBarSkin, then we're done with rebuilding the UI.
 906                 // If the system menu does not reference this MenuBarSkin, then the MenuBar is a child of the scene
 907                 // and we continue with the update.
 908                 // If there is no system menu but this skinnable uses the system menu bar, then the
 909                 // stage just isn't focused yet (see setSystemMenu) and we're done rebuilding the UI.
 910                 if (currentMenuBarStage != null ? getMenuBarSkin(currentMenuBarStage) == MenuBarSkin.this : getSkinnable().isUseSystemMenuBar()) {
 911                     return;
 912                 }
 913 
 914             } else {
 915                 // if scene is null, make sure this MenuBarSkin isn't left behind as the system menu
 916                 if (currentMenuBarStage != null) {
 917                     final MenuBarSkin curMBSkin = getMenuBarSkin(currentMenuBarStage);
 918                     if (curMBSkin == MenuBarSkin.this) {
 919                         setSystemMenu(null);
 920                     }
 921                 }
 922             }
 923         }
 924 
 925         getSkinnable().focusedProperty().addListener(menuBarFocusedPropertyListener);
 926         for (final Menu menu : getSkinnable().getMenus()) {
 927 
 928             menu.visibleProperty().addListener(menuVisibilityChangeListener);
 929 
 930             if (!menu.isVisible()) continue;
 931             final MenuBarButton menuButton = new MenuBarButton(this, menu);
 932             menuButton.setFocusTraversable(false);
 933             menuButton.getStyleClass().add("menu");
 934             menuButton.setStyle(menu.getStyle()); // copy style
 935 
 936             menuButton.getItems().setAll(menu.getItems());
 937             container.getChildren().add(menuButton);
 938 
 939             menuButton.menuListener = (observable, oldValue, newValue) -> {
 940                 if (menu.isShowing()) {
 941                     menuButton.show();
 942                     menuModeStart(container.getChildren().indexOf(menuButton));
 943                 } else {
 944                     menuButton.hide();
 945                 }
 946             };
 947             menuButton.menu = menu;
 948             menu.showingProperty().addListener(menuButton.menuListener);
 949             menuButton.disableProperty().bindBidirectional(menu.disableProperty());
 950             menuButton.textProperty().bind(menu.textProperty());
 951             menuButton.graphicProperty().bind(menu.graphicProperty());
 952             menuButton.styleProperty().bind(menu.styleProperty());
 953             menuButton.getProperties().addListener((MapChangeListener<Object, Object>) c -> {
 954                  if (c.wasAdded() && MenuButtonSkin.AUTOHIDE.equals(c.getKey())) {
 955                     menuButton.getProperties().remove(MenuButtonSkin.AUTOHIDE);
 956                     menu.hide();
 957                 }
 958             });
 959             menuButton.showingProperty().addListener((observable, oldValue, isShowing) -> {
 960                 if (isShowing) {
 961                     if(openMenuButton == null && focusedMenuIndex != -1)
 962                         openMenuButton = (MenuBarButton)container.getChildren().get(focusedMenuIndex);
 963 
 964                     if (openMenuButton != null && openMenuButton != menuButton) {
 965                         openMenuButton.clearHover();
 966                     }
 967                     openMenuButton = menuButton;
 968                     showMenu(menu);
 969                 } else {
 970                     // Fix for JDK-8167138 - we need to clear out the openMenu / openMenuButton
 971                     // when the menu is hidden (e.g. via autoHide), so that we can open it again
 972                     // the next time (if it is the first menu requested to show)
 973                     openMenu = null;
 974                     openMenuButton = null;
 975                 }
 976             });
 977 
 978             menuButton.setOnMousePressed(event -> {
 979                 pendingDismiss = menuButton.isShowing();
 980 
 981                 // check if the owner window has focus
 982                 if (menuButton.getScene().getWindow().isFocused()) {
 983                     showMenu(menu);
 984                     // update FocusedIndex
 985                     menuModeStart(getMenuBarButtonIndex(menuButton));
 986                 }
 987             });
 988 
 989             menuButton.setOnMouseReleased(event -> {
 990                 // check if the owner window has focus
 991                 if (menuButton.getScene().getWindow().isFocused()) {
 992                     if (pendingDismiss) {
 993                         resetOpenMenu();
 994                     }
 995                 }
 996                 pendingDismiss = false;
 997             });
 998 
 999             menuButton.setOnMouseEntered(event -> {
1000                 // check if the owner window has focus
1001                 if (menuButton.getScene() != null && menuButton.getScene().getWindow() != null &&
1002                         menuButton.getScene().getWindow().isFocused()) {
1003                     if (openMenuButton != null && openMenuButton != menuButton) {
1004                             openMenuButton.clearHover();
1005                             openMenuButton = null;
1006                             openMenuButton = menuButton;
1007                     }
1008                     updateFocusedIndex();
1009                     if (openMenu != null && openMenu != menu) {
1010                         showMenu(menu);
1011                     }
1012                 }
1013             });
1014             updateActionListeners(menu, true);
1015         }
1016         getSkinnable().requestLayout();
1017     }
1018 
1019     private void cleanUpSystemMenu() {
1020         if (sceneChangeListener != null && getSkinnable() != null) {
1021             getSkinnable().sceneProperty().removeListener(sceneChangeListener);
1022             // rebuildUI creates sceneChangeListener and adds sceneChangeListener to sceneProperty,
1023             // so sceneChangeListener needs to be reset to null in the off chance that this
1024             // skin instance is reused.
1025             sceneChangeListener = null;
1026         }
1027 
1028         if (currentMenuBarStage != null && getMenuBarSkin(currentMenuBarStage) == MenuBarSkin.this) {
1029             setSystemMenu(null);
1030         }
1031 
1032         if (systemMenuMap != null) {
1033             Iterator<Map.Entry<Stage,Reference<MenuBarSkin>>> iterator = systemMenuMap.entrySet().iterator();
1034             while (iterator.hasNext()) {
1035                 Map.Entry<Stage,Reference<MenuBarSkin>> entry = iterator.next();
1036                 Reference<MenuBarSkin> ref = entry.getValue();
1037                 MenuBarSkin skin = ref != null ? ref.get() : null;
1038                 if (skin == null || skin == MenuBarSkin.this) {
1039                     iterator.remove();
1040                 }
1041             }
1042         }
1043     }
1044 
1045     private boolean isMenuEmpty(Menu menu) {
1046         boolean retVal = true;
1047         if (menu != null) {
1048             for (MenuItem m : menu.getItems()) {
1049                 if (m != null && m.isVisible()) retVal = false;
1050             }
1051         }
1052         return retVal;
1053     }
1054 
1055     private void resetOpenMenu() {
1056         if (openMenu != null) {
1057             openMenu.hide();
1058             openMenu = null;
1059         }
1060     }
1061 
1062     private void unSelectMenus() {
1063         clearMenuButtonHover();
1064         if (focusedMenuIndex == -1) return;
1065         if (openMenu != null) {
1066             openMenu.hide();
1067             openMenu = null;
1068         }
1069         if (openMenuButton != null) {
1070             openMenuButton.clearHover();
1071             openMenuButton = null;
1072         }
1073         menuModeEnd();
1074     }
1075 
1076     private void menuModeStart(int newIndex) {
1077         if (focusedMenuIndex == -1) {
1078             SceneHelper.getSceneAccessor().setTransientFocusContainer(getSkinnable().getScene(), getSkinnable());
1079         }
1080         setFocusedMenuIndex(newIndex);
1081     }
1082 
1083     private void menuModeEnd() {
1084         if (focusedMenuIndex != -1) {
1085             SceneHelper.getSceneAccessor().setTransientFocusContainer(getSkinnable().getScene(), null);
1086 
1087             /* Return the a11y focus to a control in the scene. */
1088             getSkinnable().notifyAccessibleAttributeChanged(AccessibleAttribute.FOCUS_NODE);
1089         }
1090         setFocusedMenuIndex(-1);
1091     }
1092 
1093     private void moveToMenu(Direction dir, boolean doShow) {
1094         boolean showNextMenu = doShow && focusedMenu.isShowing();
1095         findSibling(dir, focusedMenuIndex).ifPresent(p -> {
1096             setFocusedMenuIndex(p.getValue());
1097             if (showNextMenu) {
1098                 // we explicitly do *not* allow selection - we are moving
1099                 // to a sibling menu, and therefore selection should be reset
1100                 showMenu(p.getKey(), false);
1101             }
1102         });
1103     }
1104 
1105     private Optional<Pair<Menu,Integer>> findSibling(Direction dir, int startIndex) {
1106         if (startIndex == -1) {
1107             return Optional.empty();
1108         }
1109 
1110         final int totalMenus = getSkinnable().getMenus().size();
1111         int i = 0;
1112         int nextIndex = 0;
1113 
1114         // Traverse all menus in menubar to find nextIndex
1115         while (i < totalMenus) {
1116             i++;
1117 
1118             nextIndex = (startIndex + (dir.isForward() ? 1 : -1)) % totalMenus;
1119 
1120             if (nextIndex == -1) {
1121                 // loop backwards to end
1122                 nextIndex = totalMenus - 1;
1123             }
1124 
1125             // if menu at nextIndex is disabled, skip it
1126             if (getSkinnable().getMenus().get(nextIndex).isDisable()) {
1127                 // Calculate new nextIndex by continuing loop
1128                 startIndex = nextIndex;
1129             } else {
1130                 // nextIndex is to be highlighted
1131                 break;
1132             }
1133         }
1134 
1135         clearMenuButtonHover();
1136         return Optional.of(new Pair<>(getSkinnable().getMenus().get(nextIndex), nextIndex));
1137     }
1138 
1139     private void updateFocusedIndex() {
1140         int index = 0;
1141         for(Node n : container.getChildren()) {
1142             if (n.isHover()) {
1143                 setFocusedMenuIndex(index);
1144                 return;
1145             }
1146             index++;
1147         }
1148         menuModeEnd();
1149     }
1150 
1151     private void clearMenuButtonHover() {
1152          for(Node n : container.getChildren()) {
1153             if (n.isHover()) {
1154                 ((MenuBarButton)n).clearHover();
1155                 ((MenuBarButton)n).disarm();
1156                 return;
1157             }
1158         }
1159     }
1160 
1161 
1162 
1163     /***************************************************************************
1164      *                                                                         *
1165      * CSS                                                                     *
1166      *                                                                         *
1167      **************************************************************************/
1168 
1169     private static final CssMetaData<MenuBar,Number> SPACING =
1170             new CssMetaData<MenuBar,Number>("-fx-spacing",
1171                     SizeConverter.getInstance(), 0.0) {
1172 
1173                 @Override
1174                 public boolean isSettable(MenuBar n) {
1175                     final MenuBarSkin skin = (MenuBarSkin) n.getSkin();
1176                     return skin.spacing == null || !skin.spacing.isBound();
1177                 }
1178 
1179                 @Override
1180                 public StyleableProperty<Number> getStyleableProperty(MenuBar n) {
1181                     final MenuBarSkin skin = (MenuBarSkin) n.getSkin();
1182                     return (StyleableProperty<Number>)(WritableValue<Number>)skin.spacingProperty();
1183                 }
1184             };
1185 
1186     private static final CssMetaData<MenuBar,Pos> ALIGNMENT =
1187             new CssMetaData<MenuBar,Pos>("-fx-alignment",
1188                     new EnumConverter<Pos>(Pos.class), Pos.TOP_LEFT ) {
1189 
1190                 @Override
1191                 public boolean isSettable(MenuBar n) {
1192                     final MenuBarSkin skin = (MenuBarSkin) n.getSkin();
1193                     return skin.containerAlignment == null || !skin.containerAlignment.isBound();
1194                 }
1195 
1196                 @Override
1197                 public StyleableProperty<Pos> getStyleableProperty(MenuBar n) {
1198                     final MenuBarSkin skin = (MenuBarSkin) n.getSkin();
1199                     return (StyleableProperty<Pos>)(WritableValue<Pos>)skin.containerAlignmentProperty();
1200                 }
1201             };
1202 
1203 
1204     private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
1205     static {
1206 
1207         final List<CssMetaData<? extends Styleable, ?>> styleables =
1208                 new ArrayList<CssMetaData<? extends Styleable, ?>>(SkinBase.getClassCssMetaData());
1209 
1210         // StackPane also has -fx-alignment. Replace it with
1211         // MenuBarSkin's.
1212         // TODO: Really should be able to reference StackPane.StyleableProperties.ALIGNMENT
1213         final String alignmentProperty = ALIGNMENT.getProperty();
1214         for (int n=0, nMax=styleables.size(); n<nMax; n++) {
1215             final CssMetaData<?,?> prop = styleables.get(n);
1216             if (alignmentProperty.equals(prop.getProperty())) styleables.remove(prop);
1217         }
1218 
1219         styleables.add(SPACING);
1220         styleables.add(ALIGNMENT);
1221         STYLEABLES = Collections.unmodifiableList(styleables);
1222 
1223     }
1224 
1225     /**
1226      * Returns the CssMetaData associated with this class, which may include the
1227      * CssMetaData of its superclasses.
1228      * @return the CssMetaData associated with this class, which may include the
1229      * CssMetaData of its superclasses
1230      */
1231     public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
1232         return STYLEABLES;
1233     }
1234 
1235     /**
1236      * {@inheritDoc}
1237      */
1238     @Override
1239     public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
1240         return getClassCssMetaData();
1241     }
1242 
1243     /***************************************************************************
1244      *                                                                         *
1245      * Accessibility handling                                                  *
1246      *                                                                         *
1247      **************************************************************************/
1248 
1249     /** {@inheritDoc} */
1250     @Override protected Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
1251         switch (attribute) {
1252             case FOCUS_NODE: return openMenuButton;
1253             default: return super.queryAccessibleAttribute(attribute, parameters);
1254         }
1255     }
1256 }