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