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

Print this page
rev 9240 : 8076423: JEP 253: Prepare JavaFX UI Controls & CSS APIs for Modularization


   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                                 }


 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         }


 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() {


 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();


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) {


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 }


   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javafx.scene.control.skin;
  27 
  28 import javafx.css.converter.EnumConverter;
  29 import javafx.css.converter.SizeConverter;
  30 import com.sun.javafx.scene.control.MenuBarButton;
  31 import com.sun.javafx.scene.control.skin.Utils;
  32 import com.sun.javafx.scene.traversal.ParentTraversalEngine;
  33 import javafx.beans.InvalidationListener;
  34 import javafx.beans.property.DoubleProperty;
  35 import javafx.beans.property.ObjectProperty;
  36 import javafx.beans.property.ReadOnlyProperty;
  37 import javafx.beans.value.ChangeListener;
  38 import javafx.beans.value.WeakChangeListener;
  39 import javafx.beans.value.WritableValue;
  40 import javafx.collections.ListChangeListener;
  41 import javafx.collections.MapChangeListener;
  42 import javafx.collections.ObservableList;
  43 import javafx.css.CssMetaData;
  44 import javafx.css.Styleable;
  45 import javafx.css.StyleableDoubleProperty;
  46 import javafx.css.StyleableObjectProperty;
  47 import javafx.css.StyleableProperty;
  48 import javafx.event.ActionEvent;
  49 import javafx.event.EventHandler;
  50 import javafx.event.WeakEventHandler;

  51 import javafx.geometry.NodeOrientation;
  52 import javafx.geometry.Pos;
  53 import javafx.scene.AccessibleAttribute;

  54 import javafx.scene.Node;
  55 import javafx.scene.Scene;
  56 import javafx.scene.control.Control;
  57 import javafx.scene.control.CustomMenuItem;
  58 import javafx.scene.control.Menu;
  59 import javafx.scene.control.MenuBar;
  60 import javafx.scene.control.MenuButton;
  61 import javafx.scene.control.MenuItem;
  62 import javafx.scene.control.SeparatorMenuItem;
  63 import javafx.scene.control.SkinBase;
  64 import javafx.scene.input.KeyCombination;
  65 import javafx.scene.input.KeyEvent;
  66 import javafx.scene.input.MouseEvent;
  67 import javafx.scene.layout.HBox;
  68 import javafx.stage.Stage;
  69 
  70 import java.lang.ref.Reference;
  71 import java.lang.ref.WeakReference;
  72 import java.util.ArrayList;
  73 import java.util.Collections;
  74 import java.util.Iterator;
  75 import java.util.List;
  76 import java.util.Map;
  77 import java.util.WeakHashMap;
  78 
  79 import com.sun.javafx.menu.MenuBase;
  80 import com.sun.javafx.scene.SceneHelper;
  81 import com.sun.javafx.scene.control.GlobalMenuAdapter;


  82 import com.sun.javafx.stage.StageHelper;
  83 import com.sun.javafx.tk.Toolkit;
  84 import javafx.stage.Window;
  85 

  86 /**
  87  * Default skin implementation for the {@link MenuBar} control. In essence it is
  88  * a simple toolbar. For the time being there is no overflow behavior and we just
  89  * hide nodes which fall outside the bounds.
  90  *
  91  * @see MenuBar
  92  * @since 9
  93  */
  94 public class MenuBarSkin extends SkinBase<MenuBar> {
  95 
  96     /***************************************************************************
  97      *                                                                         *
  98      * Private fields                                                          *
  99      *                                                                         *
 100      **************************************************************************/
 101     
 102     private final HBox container;
 103 
 104     private Menu openMenu;
 105     private MenuBarButton openMenuButton;
 106     private int focusedMenuIndex = -1;
 107 
 108     private static WeakHashMap<Stage, Reference<MenuBarSkin>> systemMenuMap;
 109     private static List<MenuBase> wrappedDefaultMenus = new ArrayList<>();
 110     private static Stage currentMenuBarStage;
 111     private List<MenuBase> wrappedMenus;
 112 
 113     private WeakEventHandler<KeyEvent> weakSceneKeyEventHandler;
 114     private WeakEventHandler<MouseEvent> weakSceneMouseEventHandler;
 115     private WeakChangeListener<Boolean> weakWindowFocusListener;
 116     private WeakChangeListener<Window> weakWindowSceneListener;
 117     private EventHandler<KeyEvent> keyEventHandler;
 118     private EventHandler<MouseEvent> mouseEventHandler;
 119     private ChangeListener<Boolean> menuBarFocusedPropertyListener;
 120     private ChangeListener<Scene> sceneChangeListener;






 121 
 122     private boolean pendingDismiss = false;




 123 

















 124 
 125 
 126     /***************************************************************************
 127      *                                                                         *
 128      * Listeners / Callbacks                                                   *
 129      *                                                                         *
 130      **************************************************************************/
 131 
 132     // RT-20411 : reset menu selected/focused state
 133     private EventHandler<ActionEvent> menuActionEventHandler = t -> {
 134         if (t.getSource() instanceof CustomMenuItem) {
 135             // RT-29614 If CustomMenuItem hideOnClick is false, dont hide
 136             CustomMenuItem cmi = (CustomMenuItem)t.getSource();
 137             if (!cmi.isHideOnClick()) return;
 138         }
 139         unSelectMenus();
 140     };
 141 
 142     private ListChangeListener<MenuItem> menuItemListener = (c) -> {
 143         while (c.next()) {
 144             for (MenuItem mi : c.getAddedSubList()) {
 145                 mi.addEventHandler(ActionEvent.ACTION, menuActionEventHandler);
 146             }
 147             for (MenuItem mi: c.getRemoved()) {
 148                 mi.removeEventHandler(ActionEvent.ACTION, menuActionEventHandler);
 149             }
 150         }






 151     };
 152 
 153     Runnable firstMenuRunnable = new Runnable() {
 154         public void run() {
 155             /*
 156             ** check that this menubar's container has contents,
 157             ** and that the first item is a MenuButton....
 158             ** otherwise the transfer is off!
 159             */
 160             if (container.getChildren().size() > 0) {
 161                 if (container.getChildren().get(0) instanceof MenuButton) {
 162 //                        container.getChildren().get(0).requestFocus();
 163                     if (focusedMenuIndex != 0) {
 164                         unSelectMenus();
 165                         menuModeStart(0);
 166                         openMenuButton = ((MenuBarButton)container.getChildren().get(0));
 167                         openMenu = getSkinnable().getMenus().get(0);
 168                         openMenuButton.setHover();
 169                     }
 170                     else {
 171                         unSelectMenus();


 172                     }



 173                 }
 174             }

 175         }
 176     };
 177 








 178 



 179 
 180     /***************************************************************************
 181      *                                                                         *
 182      * Constructors                                                            *
 183      *                                                                         *
 184      **************************************************************************/
 185 
 186     /**
 187      * Creates a new MenuBarSkin instance, installing the necessary child
 188      * nodes into the Control {@link Control#getChildren() children} list, as
 189      * well as the necessary {@link Node#getInputMap() input mappings} for
 190      * handling key, mouse, etc events.
 191      *
 192      * @param control The control that this skin should be installed onto.
 193      */
 194     public MenuBarSkin(final MenuBar control) {
 195         super(control);
 196         
 197         container = new HBox();
 198         container.getStyleClass().add("container");
 199         getChildren().add(container);
 200         
 201         // Key navigation 
 202         keyEventHandler = event -> {
 203             // process right left and may be tab key events
 204             if (openMenu != null) {
 205                 switch (event.getCode()) {
 206                     case LEFT: {
 207                         boolean isRTL = control.getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT;
 208                         if (control.getScene().getWindow().isFocused()) {
 209                             if (openMenu == null) return;
 210                             if ( !openMenu.isShowing()) {
 211                                 if (isRTL) {
 212                                     selectNextMenu(); // just move the selection bar
 213                                 } else {
 214                                     selectPrevMenu(); // just move the selection bar
 215                                 }


 353         ** pressing f10 will select the first menu button on a menubar
 354         */
 355         final KeyCombination acceleratorKeyCombo;
 356         if (com.sun.javafx.util.Utils.isMac()) {
 357            acceleratorKeyCombo = KeyCombination.keyCombination("ctrl+F10");
 358         } else {
 359            acceleratorKeyCombo = KeyCombination.keyCombination("F10");
 360         }
 361         Utils.executeOnceWhenPropertyIsNonNull(control.sceneProperty(), (Scene scene) -> {
 362             scene.getAccelerators().put(acceleratorKeyCombo, firstMenuRunnable);
 363 
 364             // put focus on the first menu when the alt key is pressed
 365             scene.addEventHandler(KeyEvent.KEY_PRESSED, e -> {
 366                 if (e.isAltDown() && !e.isConsumed()) {
 367                     firstMenuRunnable.run();
 368                 }
 369             });
 370         });
 371 
 372         ParentTraversalEngine engine = new ParentTraversalEngine(getSkinnable());
 373         engine.addTraverseListener((node, bounds) -> {
 374             if (openMenu != null) openMenu.hide();
 375             focusedMenuIndex = 0;
 376         });
 377         getSkinnable().setImpl_traversalEngine(engine);
 378 
 379         control.sceneProperty().addListener((ov, t, t1) -> {
 380             if (weakSceneKeyEventHandler != null) {
 381                 // remove event filter from the old scene (t)
 382                 if (t != null)
 383                     t.removeEventFilter(KeyEvent.KEY_PRESSED, weakSceneKeyEventHandler);
 384             }
 385             if (weakSceneMouseEventHandler != null) {
 386                 // remove event filter from the old scene (t)
 387                 if (t != null)
 388                     t.removeEventFilter(MouseEvent.MOUSE_CLICKED, weakSceneMouseEventHandler);
 389             }
 390 
 391             /**
 392              * remove the f10 accelerator from the old scene
 393              * add it to the new scene
 394              */
 395             if (t != null) {
 396                 t.getAccelerators().remove(acceleratorKeyCombo);
 397             }
 398             if (t1 != null ) {
 399                 t1.getAccelerators().put(acceleratorKeyCombo, firstMenuRunnable);
 400             }
 401         });
 402     }
 403 
 404 
 405 
 406     /***************************************************************************
 407      *                                                                         *
 408      * Static methods                                                          *
 409      *                                                                         *
 410      **************************************************************************/
 411 
 412     // RT-22480: This is intended as private API for SceneBuilder,
 413     // pending fix for RT-19857: Keeping menu in the Mac menu bar when
 414     // there is no more stage
 415     public static void setDefaultSystemMenuBar(final MenuBar menuBar) {
 416         if (Toolkit.getToolkit().getSystemMenu().isSupported()) {
 417             wrappedDefaultMenus.clear();
 418             for (Menu menu : menuBar.getMenus()) {
 419                 wrappedDefaultMenus.add(GlobalMenuAdapter.adapt(menu));
 420             }
 421             menuBar.getMenus().addListener((ListChangeListener<Menu>) c -> {
 422                 wrappedDefaultMenus.clear();
 423                 for (Menu menu : menuBar.getMenus()) {
 424                     wrappedDefaultMenus.add(GlobalMenuAdapter.adapt(menu));
 425                 }
 426             });
 427         }
 428     }
 429 
 430     private static MenuBarSkin getMenuBarSkin(Stage stage) {
 431         if (systemMenuMap == null) return null;
 432         Reference<MenuBarSkin> skinRef = systemMenuMap.get(stage);
 433         return skinRef == null ? null : skinRef.get();
 434     }
 435 
 436     private static void setSystemMenu(Stage stage) {
 437         if (stage != null && stage.isFocused()) {
 438             while (stage != null && stage.getOwner() instanceof Stage) {
 439                 MenuBarSkin skin = getMenuBarSkin(stage);
 440                 if (skin != null && skin.wrappedMenus != null) {
 441                     break;
 442                 } else {
 443                     // This is a secondary stage (dialog) that doesn't
 444                     // have own menu bar.
 445                     //
 446                     // Continue looking for a menu bar in the parent stage.
 447                     stage = (Stage)stage.getOwner();
 448                 }
 449             }
 450         } else {
 451             stage = null;
 452         }
 453 
 454         if (stage != currentMenuBarStage) {
 455             List<MenuBase> menuList = null;
 456             if (stage != null) {
 457                 MenuBarSkin skin = getMenuBarSkin(stage);
 458                 if (skin != null) {
 459                     menuList = skin.wrappedMenus;
 460                 }
 461             }
 462             if (menuList == null) {
 463                 menuList = wrappedDefaultMenus;
 464             }
 465             Toolkit.getToolkit().getSystemMenu().setMenus(menuList);
 466             currentMenuBarStage = stage;
 467         }
 468     }
 469 
 470     private static void initSystemMenuBar() {
 471         systemMenuMap = new WeakHashMap<>();
 472 
 473         final InvalidationListener focusedStageListener = ov -> {
 474             setSystemMenu((Stage)((ReadOnlyProperty<?>)ov).getBean());
 475         };
 476 
 477         final ObservableList<Stage> stages = StageHelper.getStages();
 478         for (Stage stage : stages) {
 479             stage.focusedProperty().addListener(focusedStageListener);
 480         }
 481         stages.addListener((ListChangeListener<Stage>) c -> {
 482             while (c.next()) {
 483                 for (Stage stage : c.getRemoved()) {
 484                     stage.focusedProperty().removeListener(focusedStageListener);
 485                 }
 486                 for (Stage stage : c.getAddedSubList()) {
 487                     stage.focusedProperty().addListener(focusedStageListener);
 488                     setSystemMenu(stage);
 489                 }
 490             }
 491         });
 492     }
 493 
 494 
 495 
 496     /***************************************************************************
 497      *                                                                         *
 498      * Properties                                                              *
 499      *                                                                         *
 500      **************************************************************************/
 501 
 502     /**
 503      * Specifies the spacing between menu buttons on the MenuBar.
 504      */
 505     // --- spacing
 506     private DoubleProperty spacing;
 507     public final void setSpacing(double value) {
 508         spacingProperty().set(snapSpace(value));
 509     }
 510 
 511     public final double getSpacing() {
 512         return spacing == null ? 0.0 : snapSpace(spacing.get());
 513     }
 514 
 515     public final DoubleProperty spacingProperty() {
 516         if (spacing == null) {
 517             spacing = new StyleableDoubleProperty() {
 518 
 519                 @Override
 520                 protected void invalidated() {
 521                     final double value = get();
 522                     container.setSpacing(value);
 523                 }
 524 
 525                 @Override
 526                 public Object getBean() {
 527                     return MenuBarSkin.this;
 528                 }
 529 
 530                 @Override
 531                 public String getName() {
 532                     return "spacing";
 533                 }
 534 
 535                 @Override
 536                 public CssMetaData<MenuBar,Number> getCssMetaData() {
 537                     return SPACING;
 538                 }
 539             };
 540         }
 541         return spacing;
 542     }
 543 
 544     /**
 545      * Specifies the alignment of the menu buttons inside the MenuBar (by default
 546      * it is Pos.TOP_LEFT).
 547      */
 548     // --- container alignment
 549     private ObjectProperty<Pos> containerAlignment;
 550     public final void setContainerAlignment(Pos value) {
 551         containerAlignmentProperty().set(value);
 552     }
 553 
 554     public final Pos getContainerAlignment() {
 555         return containerAlignment == null ? Pos.TOP_LEFT : containerAlignment.get();
 556     }
 557 
 558     public final ObjectProperty<Pos> containerAlignmentProperty() {
 559         if (containerAlignment == null) {
 560             containerAlignment = new StyleableObjectProperty<Pos>(Pos.TOP_LEFT) {
 561 
 562                 @Override
 563                 public void invalidated() {
 564                     final Pos value = get();
 565                     container.setAlignment(value);
 566                 }
 567 
 568                 @Override
 569                 public Object getBean() {
 570                     return MenuBarSkin.this;
 571                 }
 572 
 573                 @Override
 574                 public String getName() {
 575                     return "containerAlignment";
 576                 }
 577 
 578                 @Override
 579                 public CssMetaData<MenuBar,Pos> getCssMetaData() {
 580                     return ALIGNMENT;
 581                 }
 582             };
 583         }
 584         return containerAlignment;
 585     }
 586 
 587 
 588 
 589     /***************************************************************************
 590      *                                                                         *
 591      * Public API                                                              *
 592      *                                                                         *
 593      **************************************************************************/
 594 
 595     /** {@inheritDoc} */
 596     @Override public void dispose() {
 597         cleanUpSystemMenu();
 598         // call super.dispose last since it sets control to null
 599         super.dispose();
 600     }
 601 
 602     // Return empty insets when "container" is empty, which happens
 603     // when using the system menu bar.
 604 
 605     /** {@inheritDoc} */
 606     @Override protected double snappedTopInset() {
 607         return container.getChildren().isEmpty() ? 0 : super.snappedTopInset();
 608     }
 609     /** {@inheritDoc} */
 610     @Override protected double snappedBottomInset() {
 611         return container.getChildren().isEmpty() ? 0 : super.snappedBottomInset();
 612     }
 613     /** {@inheritDoc} */
 614     @Override protected double snappedLeftInset() {
 615         return container.getChildren().isEmpty() ? 0 : super.snappedLeftInset();
 616     }
 617     /** {@inheritDoc} */
 618     @Override protected double snappedRightInset() {
 619         return container.getChildren().isEmpty() ? 0 : super.snappedRightInset();
 620     }
 621 
 622     /**
 623      * Layout the menu bar. This is a simple horizontal layout like an hbox.
 624      * Any menu items which don't fit into it will simply be made invisible.
 625      */
 626     /** {@inheritDoc} */
 627     @Override protected void layoutChildren(final double x, final double y,
 628                                             final double w, final double h) {
 629         // layout the menus one after another
 630         container.resizeRelocate(x, y, w, h);


 631     }
 632 
 633     /** {@inheritDoc} */
 634     @Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 635         return container.minWidth(height) + snappedLeftInset() + snappedRightInset();














 636     }
 637 
 638     /** {@inheritDoc} */
 639     @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 640         return container.prefWidth(height) + snappedLeftInset() + snappedRightInset();
 641     }
 642 
 643     /** {@inheritDoc} */
 644     @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 645         return container.minHeight(width) + snappedTopInset() + snappedBottomInset();
 646     }
 647 
 648     /** {@inheritDoc} */
 649     @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 650         return container.prefHeight(width) + snappedTopInset() + snappedBottomInset();
 651     }
 652 
 653     // grow horizontally, but not vertically
 654     /** {@inheritDoc} */
 655     @Override protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 656         return getSkinnable().prefHeight(-1);
 657     }

 658 
 659 
 660 
 661     /***************************************************************************
 662      *                                                                         *
 663      * Private implementation                                                  *
 664      *                                                                         *
 665      **************************************************************************/
 666 
 667     // For testing purpose only. 
 668     MenuButton getNodeForMenu(int i) {
 669         if (i < container.getChildren().size()) {
 670             return (MenuBarButton)container.getChildren().get(i);
 671         }
 672         return null;
 673     }
 674     
 675     int getFocusedMenuIndex() {
 676         return focusedMenuIndex;
 677     }
 678     
 679     private boolean menusContainCustomMenuItem() {
 680         for (Menu menu : getSkinnable().getMenus()) {
 681             if (menuContainsCustomMenuItem(menu)) {
 682                 System.err.println("Warning: MenuBar ignored property useSystemMenuBar because menus contain CustomMenuItem");
 683                 return true;
 684             }
 685         }


 692                 return true;
 693             } else if (mi instanceof Menu) {
 694                 if (menuContainsCustomMenuItem((Menu)mi)) {
 695                     return true;
 696                 }
 697             }
 698         }
 699         return false;
 700     }
 701 
 702     private int getMenuBarButtonIndex(MenuBarButton m) {
 703         for (int i= 0; i < container.getChildren().size(); i++) {
 704             MenuBarButton menuButton = (MenuBarButton)container.getChildren().get(i);
 705             if (m == menuButton) {
 706                 return i;
 707             }
 708         }
 709         return -1;
 710     }
 711     





















 712     private void updateActionListeners(Menu m, boolean add) {
 713         if (add) {
 714             m.getItems().addListener(menuItemListener);
 715         } else {
 716             m.getItems().removeListener(menuItemListener);
 717         }
 718         for (MenuItem mi : m.getItems()) {
 719             if (mi instanceof Menu) {
 720                 updateActionListeners((Menu)mi, add);
 721             } else {
 722                 if (add) {
 723                     mi.addEventHandler(ActionEvent.ACTION, menuActionEventHandler);
 724                 } else {
 725                     mi.removeEventHandler(ActionEvent.ACTION, menuActionEventHandler);
 726                 }
 727             }
 728         }
 729     }
 730     
 731     private void rebuildUI() {


 970                             openMenuButton = null;
 971                             openMenuButton = menuButton;
 972                     }
 973                     updateFocusedIndex();
 974                     if (openMenu != null && openMenu != menu) {
 975                      // hide the currently visible menu, and move to the new one
 976                         openMenu.hide();
 977                         openMenu = menu;
 978                         updateFocusedIndex();
 979                         if (!isMenuEmpty(menu)) {
 980                             openMenu.show();
 981                         }
 982                     }
 983                 }
 984             });
 985             updateActionListeners(menu, true);
 986         }
 987         getSkinnable().requestLayout();
 988     }
 989 




























































































 990     private void cleanUpSystemMenu() {

 991         if (sceneChangeListener != null && getSkinnable() != null) {
 992             getSkinnable().sceneProperty().removeListener(sceneChangeListener);
 993             // rebuildUI creates sceneChangeListener and adds sceneChangeListener to sceneProperty,
 994             // so sceneChangeListener needs to be reset to null in the off chance that this
 995             // skin instance is reused.
 996             sceneChangeListener = null;
 997         }
 998 
 999         if (currentMenuBarStage != null && getMenuBarSkin(currentMenuBarStage) == MenuBarSkin.this) {
1000             setSystemMenu(null);
1001         }
1002 
1003         if (systemMenuMap != null) {
1004             Iterator<Map.Entry<Stage,Reference<MenuBarSkin>>> iterator = systemMenuMap.entrySet().iterator();
1005             while (iterator.hasNext()) {
1006                 Map.Entry<Stage,Reference<MenuBarSkin>> entry = iterator.next();
1007                 Reference<MenuBarSkin> ref = entry.getValue();
1008                 MenuBarSkin skin = ref != null ? ref.get() : null;
1009                 if (skin == null || skin == MenuBarSkin.this) {
1010                     iterator.remove();


1133         int index = 0;
1134         for(Node n : container.getChildren()) {
1135             if (n.isHover()) {
1136                 focusedMenuIndex = index;
1137                 return;
1138             }
1139             index++;
1140         }
1141         menuModeEnd();
1142     }
1143 
1144     private void clearMenuButtonHover() {
1145          for(Node n : container.getChildren()) {
1146             if (n.isHover()) {
1147                 ((MenuBarButton)n).clearHover();
1148                 return;
1149             }
1150         }
1151     }
1152 

























































































































1153 
1154 
1155     /***************************************************************************
1156      *                                                                         *
1157      * CSS                                                                     *
1158      *                                                                         *
1159      **************************************************************************/
1160 
1161     private static final CssMetaData<MenuBar,Number> SPACING =
1162             new CssMetaData<MenuBar,Number>("-fx-spacing",
1163                     SizeConverter.getInstance(), 0.0) {
1164 
1165                 @Override
1166                 public boolean isSettable(MenuBar n) {
1167                     final MenuBarSkin skin = (MenuBarSkin) n.getSkin();
1168                     return skin.spacing == null || !skin.spacing.isBound();
1169                 }
1170 
1171                 @Override
1172                 public StyleableProperty<Number> getStyleableProperty(MenuBar n) {


1198 
1199         final List<CssMetaData<? extends Styleable, ?>> styleables =
1200                 new ArrayList<CssMetaData<? extends Styleable, ?>>(SkinBase.getClassCssMetaData());
1201 
1202         // StackPane also has -fx-alignment. Replace it with 
1203         // MenuBarSkin's. 
1204         // TODO: Really should be able to reference StackPane.StyleableProperties.ALIGNMENT
1205         final String alignmentProperty = ALIGNMENT.getProperty();
1206         for (int n=0, nMax=styleables.size(); n<nMax; n++) {
1207             final CssMetaData<?,?> prop = styleables.get(n);
1208             if (alignmentProperty.equals(prop.getProperty())) styleables.remove(prop);
1209         }
1210 
1211         styleables.add(SPACING);
1212         styleables.add(ALIGNMENT);
1213         STYLEABLES = Collections.unmodifiableList(styleables);
1214 
1215     }
1216 
1217     /**
1218      * Returns the CssMetaData associated with this class, which may include the
1219      * CssMetaData of its super classes.
1220      */
1221     public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
1222         return STYLEABLES;
1223     }
1224 
1225     /**
1226      * {@inheritDoc}
1227      */
1228     @Override
1229     public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
1230         return getClassCssMetaData();
1231     }
1232 
1233     /***************************************************************************
1234      *                                                                         *
1235      * Accessibility handling                                                  *
1236      *                                                                         *
1237      **************************************************************************/
1238 
1239     /** {@inheritDoc} */
1240     @Override protected Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
1241         switch (attribute) {
1242             case FOCUS_NODE: return openMenuButton;
1243             default: return super.queryAccessibleAttribute(attribute, parameters);
1244         }
1245     }
1246 }