1 /*
   2  * Copyright (c) 2010, 2015, 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                                 } else {
 573                                     if (curMBSkin != null && curMBSkin.getSkinnable() != null &&
 574                                             curMBSkin.getSkinnable().isUseSystemMenuBar()) {
 575                                         curMBSkin.getSkinnable().setUseSystemMenuBar(false);
 576                                     }
 577                                 }
 578                             }
 579                         }
 580 
 581                         if (newValue != null) {
 582                             if (getSkinnable().isUseSystemMenuBar() && !menusContainCustomMenuItem()) {
 583                                 if (newValue.getWindow() instanceof Stage) {
 584                                     final Stage stage = (Stage) newValue.getWindow();
 585                                     if (systemMenuMap == null) {
 586                                         initSystemMenuBar();
 587                                     }
 588                                     wrappedMenus = new ArrayList<>();
 589                                     systemMenuMap.put(stage, new WeakReference<>(this));
 590                                     for (Menu menu : getSkinnable().getMenus()) {
 591                                         wrappedMenus.add(GlobalMenuAdapter.adapt(menu));
 592                                     }
 593                                     currentMenuBarStage = null;
 594                                     setSystemMenu(stage);
 595 
 596                                     // TODO: Why two request layout calls here?
 597                                     getSkinnable().requestLayout();
 598                                     javafx.application.Platform.runLater(() -> getSkinnable().requestLayout());
 599                                 }
 600                             }
 601                         }
 602                     };
 603                     getSkinnable().sceneProperty().addListener(sceneChangeListener);
 604                 }
 605 
 606                 // Fake a change event to trigger an update to the system menu.
 607                 sceneChangeListener.changed(getSkinnable().sceneProperty(), scene, scene);
 608 
 609                 // If the system menu references this MenuBarSkin, then we're done with rebuilding the UI.
 610                 // If the system menu does not reference this MenuBarSkin, then the MenuBar is a child of the scene
 611                 // and we continue with the update.
 612                 // If there is no system menu but this skinnable uses the system menu bar, then the
 613                 // stage just isn't focused yet (see setSystemMenu) and we're done rebuilding the UI.
 614                 if (currentMenuBarStage != null ? getMenuBarSkin(currentMenuBarStage) == MenuBarSkin.this : getSkinnable().isUseSystemMenuBar()) {
 615                     return;
 616                 }
 617 
 618             } else {
 619                 // if scene is null, make sure this MenuBarSkin isn't left behind as the system menu
 620                 if (currentMenuBarStage != null) {
 621                     final MenuBarSkin curMBSkin = getMenuBarSkin(currentMenuBarStage);
 622                     if (curMBSkin == MenuBarSkin.this) {
 623                         setSystemMenu(null);
 624                     }
 625                 }
 626             }
 627         }
 628 
 629         getSkinnable().focusedProperty().addListener(menuBarFocusedPropertyListener);
 630         for (final Menu menu : getSkinnable().getMenus()) {
 631             if (!menu.isVisible()) continue;
 632             final MenuBarButton menuButton = new MenuBarButton(this, menu);
 633             menuButton.setFocusTraversable(false);
 634             menuButton.getStyleClass().add("menu");
 635             menuButton.setStyle(menu.getStyle()); // copy style
 636 
 637             menuButton.getItems().setAll(menu.getItems());
 638             container.getChildren().add(menuButton);
 639 
 640             menuButton.menuListener = (observable, oldValue, newValue) -> {
 641                 if (menu.isShowing()) {
 642                     menuButton.show();
 643                     menuModeStart(container.getChildren().indexOf(menuButton));
 644                 } else {
 645                     menuButton.hide();
 646                 }
 647             };
 648             menuButton.menu = menu;
 649             menu.showingProperty().addListener(menuButton.menuListener);
 650             menuButton.disableProperty().bindBidirectional(menu.disableProperty());
 651             menuButton.textProperty().bind(menu.textProperty());
 652             menuButton.graphicProperty().bind(menu.graphicProperty());
 653             menuButton.styleProperty().bind(menu.styleProperty());
 654             menuButton.getProperties().addListener((MapChangeListener<Object, Object>) c -> {
 655                  if (c.wasAdded() && MenuButtonSkin.AUTOHIDE.equals(c.getKey())) {
 656                     menuButton.getProperties().remove(MenuButtonSkin.AUTOHIDE);
 657                     menu.hide();
 658                 }
 659             });
 660             menuButton.showingProperty().addListener((observable, oldValue, isShowing) -> {
 661                 if (isShowing) {
 662                     if (openMenuButton != null && openMenuButton != menuButton) {
 663                         openMenuButton.hide();
 664                     }
 665                     openMenuButton = menuButton;
 666                     openMenu = menu;
 667                     if (!menu.isShowing())menu.show();
 668                 }
 669             });
 670 
 671             menuButton.setOnMousePressed(event -> {
 672                 pendingDismiss = menuButton.isShowing();
 673 
 674                 // check if the owner window has focus
 675                 if (menuButton.getScene().getWindow().isFocused()) {
 676                     openMenu = menu;
 677                     if (!isMenuEmpty(menu)){
 678                         openMenu.show();
 679                     }
 680                     // update FocusedIndex
 681                     menuModeStart(getMenuBarButtonIndex(menuButton));
 682                 }
 683             });
 684             
 685             menuButton.setOnMouseReleased(event -> {
 686                 // check if the owner window has focus
 687                 if (menuButton.getScene().getWindow().isFocused()) {
 688                     if (pendingDismiss) {
 689                         resetOpenMenu();
 690 //                            menuButton.hide();
 691                     }
 692                 }
 693                 pendingDismiss = false;
 694             });
 695 
 696 //            menuButton. setOnKeyPressed(new EventHandler<javafx.scene.input.KeyEvent>() {
 697 //                @Override public void handle(javafx.scene.input.KeyEvent ke) {
 698 //                    switch (ke.getCode()) {
 699 //                        case LEFT:
 700 //                            if (menuButton.getScene().getWindow().isFocused()) {
 701 //                                Menu prevMenu = findPreviousSibling();
 702 //                                if (openMenu == null || ! openMenu.isShowing()) {
 703 //                                    return;
 704 //                                }
 705 ////                                if (focusedMenuIndex == container.getChildren().size() - 1) {
 706 ////                                   ((MenuBarButton)container.getChildren().get(focusedMenuIndex)).requestFocus();
 707 ////                                }
 708 //                                 // hide the currently visible menu, and move to the previous one
 709 //                                openMenu.hide();
 710 //                                if (!isMenuEmpty(prevMenu)) {
 711 //                                    openMenu = prevMenu;
 712 //                                    openMenu.show();
 713 //                                } else {
 714 //                                    openMenu = null;
 715 //                                }
 716 //                            }
 717 //                            ke.consume();
 718 //                            break;
 719 //                        case RIGHT:
 720 //                            if (menuButton.getScene().getWindow().isFocused()) {
 721 //                                Menu nextMenu = findNextSibling();
 722 //                                if (openMenu == null || ! openMenu.isShowing()) {
 723 //                                    return;
 724 //                                }
 725 ////                                if (focusedMenuIndex == 0) {
 726 ////                                    ((MenuBarButton)container.getChildren().get(focusedMenuIndex)).requestFocus();
 727 ////                                }
 728 //                                 // hide the currently visible menu, and move to the next one
 729 //                                openMenu.hide();
 730 //                                if (!isMenuEmpty(nextMenu)) {
 731 //                                    openMenu = nextMenu;
 732 //                                    openMenu.show();
 733 //                                } else {
 734 //                                    openMenu = null;
 735 //                                }
 736 //                            }
 737 //                            ke.consume();
 738 //                            break;
 739 //
 740 //                        case DOWN:
 741 //                        case SPACE:
 742 //                        case ENTER:
 743 //                            if (menuButton.getScene().getWindow().isFocused()) {
 744 //                                if (focusedMenuIndex != -1) {
 745 //                                    if (!isMenuEmpty(getSkinnable().getMenus().get(focusedMenuIndex))) {
 746 //                                        openMenu = getSkinnable().getMenus().get(focusedMenuIndex);
 747 //                                        openMenu.show();
 748 //                                    } else {
 749 //                                        openMenu = null;
 750 //                                    }
 751 //                                    ke.consume();
 752 //                                }
 753 //                            }
 754 //                            break;
 755 //                    }
 756 //                }
 757 //            });
 758             menuButton.setOnMouseEntered(event -> {
 759                 // check if the owner window has focus
 760                 if (menuButton.getScene() != null && menuButton.getScene().getWindow() != null &&
 761                         menuButton.getScene().getWindow().isFocused()) {
 762                     if (openMenuButton != null && openMenuButton != menuButton) {
 763                             openMenuButton.clearHover();
 764                             openMenuButton = null;
 765                             openMenuButton = menuButton;
 766                     }
 767                     updateFocusedIndex();
 768                     if (openMenu != null && openMenu != menu) {
 769                      // hide the currently visible menu, and move to the new one
 770                         openMenu.hide();
 771                         openMenu = menu;
 772                         updateFocusedIndex();
 773                         if (!isMenuEmpty(menu)) {
 774                             openMenu.show();
 775                         }
 776                     }
 777                 }
 778             });
 779             updateActionListeners(menu, true);
 780         }
 781         getSkinnable().requestLayout();
 782     }
 783     
 784     /*
 785      *  if (openMenu == null) return;
 786                             if ( !openMenu.isShowing()) {
 787                                 selectPrevMenu(); // just move the selection bar
 788                                 return;
 789                             }
 790                             showPrevMenu();
 791                         }
 792      */
 793     private DoubleProperty spacing;
 794     public final void setSpacing(double value) {
 795         spacingProperty().set(snapSpace(value));
 796     }
 797 
 798     public final double getSpacing() {
 799         return spacing == null ? 0.0 : snapSpace(spacing.get());
 800     }
 801 
 802     public final DoubleProperty spacingProperty() {
 803         if (spacing == null) {
 804             spacing = new StyleableDoubleProperty() {
 805 
 806                 @Override
 807                 protected void invalidated() {
 808                     final double value = get();
 809                     container.setSpacing(value);
 810                 }
 811 
 812                 @Override
 813                 public Object getBean() {
 814                     return MenuBarSkin.this;
 815                 }
 816 
 817                 @Override
 818                 public String getName() {
 819                     return "spacing";
 820                 }
 821 
 822                 @Override
 823                 public CssMetaData<MenuBar,Number> getCssMetaData() {
 824                     return SPACING;
 825                 }
 826             };
 827         }
 828         return spacing;
 829     }
 830 
 831     private ObjectProperty<Pos> containerAlignment;
 832     public final void setContainerAlignment(Pos value) {
 833         containerAlignmentProperty().set(value);
 834     }
 835 
 836     public final Pos getContainerAlignment() {
 837         return containerAlignment == null ? Pos.TOP_LEFT : containerAlignment.get();
 838     }
 839 
 840     public final ObjectProperty<Pos> containerAlignmentProperty() {
 841         if (containerAlignment == null) {
 842             containerAlignment = new StyleableObjectProperty<Pos>(Pos.TOP_LEFT) {
 843 
 844                 @Override
 845                 public void invalidated() {
 846                     final Pos value = get();
 847                     container.setAlignment(value);
 848                 }
 849 
 850                 @Override
 851                 public Object getBean() {
 852                     return MenuBarSkin.this;
 853                 }
 854 
 855                 @Override
 856                 public String getName() {
 857                     return "containerAlignment";
 858                 }
 859 
 860                 @Override
 861                 public CssMetaData<MenuBar,Pos> getCssMetaData() {
 862                     return ALIGNMENT;
 863                 }
 864             };
 865         }
 866         return containerAlignment;
 867     }
 868 
 869     @Override
 870     public void dispose() {
 871         cleanUpSystemMenu();
 872         // call super.dispose last since it sets control to null
 873         super.dispose();
 874     }
 875 
 876     private void cleanUpSystemMenu() {
 877 
 878         if (sceneChangeListener != null && getSkinnable() != null) {
 879             getSkinnable().sceneProperty().removeListener(sceneChangeListener);
 880             // rebuildUI creates sceneChangeListener and adds sceneChangeListener to sceneProperty,
 881             // so sceneChangeListener needs to be reset to null in the off chance that this
 882             // skin instance is reused.
 883             sceneChangeListener = null;
 884         }
 885 
 886         if (currentMenuBarStage != null && getMenuBarSkin(currentMenuBarStage) == MenuBarSkin.this) {
 887             setSystemMenu(null);
 888         }
 889 
 890         if (systemMenuMap != null) {
 891             Iterator<Map.Entry<Stage,Reference<MenuBarSkin>>> iterator = systemMenuMap.entrySet().iterator();
 892             while (iterator.hasNext()) {
 893                 Map.Entry<Stage,Reference<MenuBarSkin>> entry = iterator.next();
 894                 Reference<MenuBarSkin> ref = entry.getValue();
 895                 MenuBarSkin skin = ref != null ? ref.get() : null;
 896                 if (skin == null || skin == MenuBarSkin.this) {
 897                     iterator.remove();
 898                 }
 899             }
 900         }
 901     }
 902 
 903     private boolean isMenuEmpty(Menu menu) {
 904         boolean retVal = true;
 905         if (menu != null) {
 906             for (MenuItem m : menu.getItems()) {
 907                 if (m != null && m.isVisible()) retVal = false;
 908             }
 909         }
 910         return retVal;
 911     }
 912 
 913     private void resetOpenMenu() {
 914         if (openMenu != null) {
 915             openMenu.hide();
 916             openMenu = null;
 917             openMenuButton = (MenuBarButton)container.getChildren().get(focusedMenuIndex);
 918             openMenuButton.clearHover();
 919             openMenuButton = null;
 920             menuModeEnd();
 921         }
 922     }
 923     
 924     private void unSelectMenus() {
 925         clearMenuButtonHover();
 926         if (focusedMenuIndex == -1) return;
 927         if (openMenu != null) {
 928             openMenu.hide();
 929             openMenu = null;
 930         }
 931         if (openMenuButton != null) {
 932             openMenuButton.clearHover();
 933             openMenuButton = null;
 934         }
 935         menuModeEnd();
 936     }
 937 
 938     private void menuModeStart(int newIndex) {
 939         if (focusedMenuIndex == -1) {
 940             SceneHelper.getSceneAccessor().setTransientFocusContainer(getSkinnable().getScene(), getSkinnable());
 941         }
 942         focusedMenuIndex = newIndex;
 943     }
 944 
 945     private void menuModeEnd() {
 946         if (focusedMenuIndex != -1) {
 947             SceneHelper.getSceneAccessor().setTransientFocusContainer(getSkinnable().getScene(), null);
 948 
 949             /* Return the a11y focus to a control in the scene. */
 950             getSkinnable().notifyAccessibleAttributeChanged(AccessibleAttribute.FOCUS_NODE);
 951         }
 952         focusedMenuIndex = -1;
 953     }
 954     
 955     private void selectNextMenu() {
 956         Menu nextMenu = findNextSibling();
 957         if (nextMenu != null && focusedMenuIndex != -1) {
 958             openMenuButton = (MenuBarButton)container.getChildren().get(focusedMenuIndex);
 959             openMenuButton.setHover();
 960             openMenu = nextMenu;
 961         }
 962     }
 963     
 964     private void selectPrevMenu() {
 965         Menu prevMenu = findPreviousSibling();
 966         if (prevMenu != null && focusedMenuIndex != -1) {
 967             openMenuButton = (MenuBarButton)container.getChildren().get(focusedMenuIndex);
 968             openMenuButton.setHover();
 969             openMenu = prevMenu;
 970         }
 971     }
 972     
 973     private void showNextMenu() {
 974         Menu nextMenu = findNextSibling();
 975         // hide the currently visible menu, and move to the next one
 976         if (openMenu != null) openMenu.hide();
 977         openMenu = nextMenu;
 978         if (!isMenuEmpty(nextMenu)) {
 979             openMenu.show();
 980         } 
 981     }
 982 
 983     private void showPrevMenu() {
 984         Menu prevMenu = findPreviousSibling();
 985         // hide the currently visible menu, and move to the next one
 986         if (openMenu != null) openMenu.hide();
 987         openMenu = prevMenu;
 988         if (!isMenuEmpty(prevMenu)) {
 989             openMenu.show();
 990         } 
 991     }
 992     
 993     private Menu findPreviousSibling() {
 994         if (focusedMenuIndex == -1) return null;
 995         if (focusedMenuIndex == 0) {
 996             focusedMenuIndex = container.getChildren().size() - 1;
 997         } else {
 998             focusedMenuIndex--;
 999         }
1000         // RT-19359
1001         if (getSkinnable().getMenus().get(focusedMenuIndex).isDisable()) return findPreviousSibling();
1002         clearMenuButtonHover();
1003         return getSkinnable().getMenus().get(focusedMenuIndex);
1004     }
1005 
1006     private Menu findNextSibling() {
1007         if (focusedMenuIndex == -1) return null;
1008         if (focusedMenuIndex == container.getChildren().size() - 1) {
1009             focusedMenuIndex = 0;
1010         } else {
1011             focusedMenuIndex++;
1012         }
1013         // RT_19359
1014         if (getSkinnable().getMenus().get(focusedMenuIndex).isDisable()) return findNextSibling();
1015         clearMenuButtonHover();
1016         return getSkinnable().getMenus().get(focusedMenuIndex);
1017     }
1018 
1019     private void updateFocusedIndex() {
1020         int index = 0;
1021         for(Node n : container.getChildren()) {
1022             if (n.isHover()) {
1023                 focusedMenuIndex = index;
1024                 return;
1025             }
1026             index++;
1027         }
1028         menuModeEnd();
1029     }
1030 
1031     private void clearMenuButtonHover() {
1032          for(Node n : container.getChildren()) {
1033             if (n.isHover()) {
1034                 ((MenuBarButton)n).clearHover();
1035                 return;
1036             }
1037         }
1038     }
1039 
1040     @Override
1041     public void onTraverse(Node node, Bounds bounds) {
1042         if (openMenu != null) openMenu.hide();
1043         focusedMenuIndex = 0;
1044     }
1045 
1046     static class MenuBarButton extends MenuButton {
1047         private ChangeListener<Boolean> menuListener;
1048         private MenuBarSkin menuBarSkin;
1049         private Menu menu;
1050 
1051         private final ListChangeListener<MenuItem> itemsListener;
1052         private final ListChangeListener<String> styleClassListener;
1053 
1054         public MenuBarButton(MenuBarSkin menuBarSkin, Menu menu) {
1055             super(menu.getText(), menu.getGraphic());
1056             this.menuBarSkin = menuBarSkin;
1057             setAccessibleRole(AccessibleRole.MENU);
1058 
1059             // listen to changes in menu items & update menuButton items
1060             menu.getItems().addListener(itemsListener = c -> {
1061                 while (c.next()) {
1062                     getItems().removeAll(c.getRemoved());
1063                     getItems().addAll(c.getFrom(), c.getAddedSubList());
1064                 }
1065             });
1066             menu.getStyleClass().addListener(styleClassListener = c -> {
1067                 while(c.next()) {
1068                     for(int i=c.getFrom(); i<c.getTo(); i++) {
1069                         getStyleClass().add(menu.getStyleClass().get(i));
1070                     }
1071                     for (String str : c.getRemoved()) {
1072                         getStyleClass().remove(str);
1073                     }
1074                 }
1075             });
1076             idProperty().bind(menu.idProperty());
1077         }
1078 
1079         public MenuBarSkin getMenuBarSkin() {
1080             return menuBarSkin;
1081         }
1082 
1083         private void clearHover() {
1084             setHover(false);
1085         }
1086         
1087         private void setHover() {
1088             setHover(true);
1089 
1090             /* Transfer the a11y focus to an item in the menu bar. */
1091             menuBarSkin.getSkinnable().notifyAccessibleAttributeChanged(AccessibleAttribute.FOCUS_NODE);
1092         }
1093 
1094         void dispose() {
1095             menu.getItems().removeListener(itemsListener);
1096             menu.getStyleClass().removeListener(styleClassListener);
1097             idProperty().unbind();
1098         }
1099 
1100         @Override
1101         public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
1102             switch (attribute) {
1103                 case FOCUS_ITEM: return MenuBarButton.this;
1104                 default: return super.queryAccessibleAttribute(attribute, parameters);
1105             }
1106         }
1107     }
1108 
1109     /***************************************************************************
1110      *                                                                         *
1111      * Layout                                                                  *
1112      *                                                                         *
1113      **************************************************************************/
1114 
1115     // Return empty insets when "container" is empty, which happens
1116     // when using the system menu bar.
1117 
1118     @Override protected double snappedTopInset() {
1119         return container.getChildren().isEmpty() ? 0 : super.snappedTopInset();
1120     }
1121     @Override protected double snappedBottomInset() {
1122         return container.getChildren().isEmpty() ? 0 : super.snappedBottomInset();
1123     }
1124     @Override protected double snappedLeftInset() {
1125         return container.getChildren().isEmpty() ? 0 : super.snappedLeftInset();
1126     }
1127     @Override protected double snappedRightInset() {
1128         return container.getChildren().isEmpty() ? 0 : super.snappedRightInset();
1129     }
1130 
1131     /**
1132      * Layout the menu bar. This is a simple horizontal layout like an hbox.
1133      * Any menu items which don't fit into it will simply be made invisible.
1134      */
1135     @Override protected void layoutChildren(final double x, final double y,
1136             final double w, final double h) {
1137         // layout the menus one after another
1138         container.resizeRelocate(x, y, w, h);
1139     }
1140 
1141     @Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
1142         return container.minWidth(height) + snappedLeftInset() + snappedRightInset();
1143     }
1144 
1145     @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
1146         return container.prefWidth(height) + snappedLeftInset() + snappedRightInset();
1147     }
1148 
1149     @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
1150         return container.minHeight(width) + snappedTopInset() + snappedBottomInset();
1151     }
1152 
1153     @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
1154         return container.prefHeight(width) + snappedTopInset() + snappedBottomInset();
1155     }
1156 
1157     // grow horizontally, but not vertically
1158     @Override protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
1159         return getSkinnable().prefHeight(-1);
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      * @return The CssMetaData associated with this class, which may include the
1227      * CssMetaData of its super classes.
1228      */
1229     public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
1230         return STYLEABLES;
1231     }
1232 
1233     /**
1234      * {@inheritDoc}
1235      */
1236     @Override
1237     public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
1238         return getClassCssMetaData();
1239     }
1240 
1241     /***************************************************************************
1242      *                                                                         *
1243      * Accessibility handling                                                  *
1244      *                                                                         *
1245      **************************************************************************/
1246 
1247     @Override
1248     protected Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
1249         switch (attribute) {
1250             case FOCUS_NODE: return openMenuButton;
1251             default: return super.queryAccessibleAttribute(attribute, parameters);
1252         }
1253     }
1254 }