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 }
|