1 /*
   2  * Copyright (c) 2010, 2015 Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javafx.scene.control.skin;
  27 
  28 import java.util.ArrayList;
  29 import java.util.Collections;
  30 import java.util.List;
  31 
  32 import com.sun.javafx.scene.control.behavior.BehaviorBase;
  33 import com.sun.javafx.scene.traversal.Algorithm;
  34 import com.sun.javafx.scene.traversal.ParentTraversalEngine;
  35 import com.sun.javafx.scene.traversal.TraversalContext;
  36 
  37 import javafx.beans.property.ObjectProperty;
  38 import javafx.beans.property.DoubleProperty;
  39 import javafx.beans.value.WritableValue;
  40 import javafx.collections.FXCollections;
  41 import javafx.collections.ListChangeListener;
  42 import javafx.collections.ObservableList;
  43 import javafx.geometry.HPos;
  44 import javafx.geometry.Orientation;
  45 import javafx.geometry.Pos;
  46 import javafx.geometry.Side;
  47 import javafx.geometry.VPos;
  48 import javafx.scene.AccessibleAction;
  49 import javafx.scene.AccessibleAttribute;
  50 import javafx.scene.AccessibleRole;
  51 import javafx.scene.Node;
  52 import javafx.scene.Parent;
  53 import javafx.scene.control.ContextMenu;
  54 import javafx.scene.control.Control;
  55 import javafx.scene.control.MenuItem;
  56 import javafx.scene.control.CustomMenuItem;
  57 import javafx.scene.control.Separator;
  58 import javafx.scene.control.SeparatorMenuItem;
  59 import javafx.scene.control.SkinBase;
  60 import javafx.scene.control.ToolBar;
  61 import javafx.scene.input.KeyCode;
  62 import javafx.scene.layout.HBox;
  63 import javafx.scene.layout.Pane;
  64 import javafx.scene.layout.StackPane;
  65 import javafx.scene.layout.VBox;
  66 import javafx.css.StyleableDoubleProperty;
  67 import javafx.css.StyleableObjectProperty;
  68 import javafx.css.StyleableProperty;
  69 import javafx.css.CssMetaData;
  70 
  71 import javafx.css.converter.EnumConverter;
  72 import javafx.css.converter.SizeConverter;
  73 import com.sun.javafx.scene.control.behavior.ToolBarBehavior;
  74 import com.sun.javafx.scene.traversal.Direction;
  75 
  76 import javafx.css.Styleable;
  77 
  78 import static com.sun.javafx.scene.control.skin.resources.ControlResources.getString;
  79 
  80 /**
  81  * Default skin implementation for the {@link ToolBar} control.
  82  *
  83  * @see ToolBar
  84  * @since 9
  85  */
  86 public class ToolBarSkin extends SkinBase<ToolBar> {
  87 
  88     /***************************************************************************
  89      *                                                                         *
  90      * Private fields                                                          *
  91      *                                                                         *
  92      **************************************************************************/
  93 
  94     private Pane box;
  95     private ToolBarOverflowMenu overflowMenu;
  96     private boolean overflow = false;
  97     private double previousWidth = 0;
  98     private double previousHeight = 0;
  99     private double savedPrefWidth = 0;
 100     private double savedPrefHeight = 0;
 101     private ObservableList<MenuItem> overflowMenuItems;
 102     private boolean needsUpdate = false;
 103     private final ParentTraversalEngine engine;
 104     private final BehaviorBase<ToolBar> behavior;
 105 
 106 
 107 
 108     /***************************************************************************
 109      *                                                                         *
 110      * Constructors                                                            *
 111      *                                                                         *
 112      **************************************************************************/
 113 
 114     /**
 115      * Creates a new ToolBarSkin instance, installing the necessary child
 116      * nodes into the Control {@link Control#getChildren() children} list, as
 117      * well as the necessary input mappings for handling key, mouse, etc events.
 118      *
 119      * @param control The control that this skin should be installed onto.
 120      */
 121     public ToolBarSkin(ToolBar control) {
 122         super(control);
 123 
 124         // install default input map for the ToolBar control
 125         behavior = new ToolBarBehavior(control);
 126 //        control.setInputMap(behavior.getInputMap());
 127 
 128         overflowMenuItems = FXCollections.observableArrayList();
 129         initialize();
 130         registerChangeListener(control.orientationProperty(), e -> initialize());
 131 
 132         engine = new ParentTraversalEngine(getSkinnable(), new Algorithm() {
 133 
 134             private Node selectPrev(int from, TraversalContext context) {
 135                 for (int i = from; i >= 0; --i) {
 136                     Node n = box.getChildren().get(i);
 137                     if (n.isDisabled() || !n.impl_isTreeVisible()) continue;
 138                     if (n instanceof Parent) {
 139                         Node selected = context.selectLastInParent((Parent)n);
 140                         if (selected != null) return selected;
 141                     }
 142                     if (n.isFocusTraversable() ) {
 143                         return n;
 144                     }
 145                 }
 146                 return null;
 147             }
 148 
 149             private Node selectNext(int from, TraversalContext context) {
 150                 for (int i = from, max = box.getChildren().size(); i < max; ++i) {
 151                     Node n = box.getChildren().get(i);
 152                     if (n.isDisabled() || !n.impl_isTreeVisible()) continue;
 153                     if (n.isFocusTraversable()) {
 154                         return n;
 155                     }
 156                     if (n instanceof Parent) {
 157                         Node selected = context.selectFirstInParent((Parent)n);
 158                         if (selected != null) return selected;
 159                     }
 160                 }
 161                 return null;
 162             }
 163 
 164             @Override
 165             public Node select(Node owner, Direction dir, TraversalContext context) {
 166                 final ObservableList<Node> boxChildren = box.getChildren();
 167                 if (owner == overflowMenu) {
 168                     if (dir.isForward()) {
 169                         return null;
 170                     } else {
 171                         Node selected = selectPrev(boxChildren.size() - 1, context);
 172                         if (selected != null) return selected;
 173                     }
 174                 }
 175 
 176                 int idx = boxChildren.indexOf(owner);
 177 
 178                 if (idx < 0) {
 179                     // The current focus owner is a child of some Toolbar's item
 180                     Parent item = owner.getParent();
 181                     while (!boxChildren.contains(item)) {
 182                         item = item.getParent();
 183                     }
 184                     Node selected = context.selectInSubtree(item, owner, dir);
 185                     if (selected != null) return selected;
 186                     idx = boxChildren.indexOf(owner);
 187                     if (dir == Direction.NEXT) dir = Direction.NEXT_IN_LINE;
 188                 }
 189 
 190                 if (idx >= 0) {
 191                     if (dir.isForward()) {
 192                         Node selected = selectNext(idx + 1, context);
 193                         if (selected != null) return selected;
 194                         if (overflow) {
 195                             overflowMenu.requestFocus();
 196                             return overflowMenu;
 197                         }
 198                     } else {
 199                         Node selected = selectPrev(idx - 1, context);
 200                         if (selected != null) return selected;
 201                     }
 202                 }
 203                 return null;
 204             }
 205 
 206             @Override
 207             public Node selectFirst(TraversalContext context) {
 208                 Node selected = selectNext(0, context);
 209                 if (selected != null) return selected;
 210                 if (overflow) {
 211                     return overflowMenu;
 212                 }
 213                 return null;
 214             }
 215 
 216             @Override
 217             public Node selectLast(TraversalContext context) {
 218                 if (overflow) {
 219                     return overflowMenu;
 220                 }
 221                 return selectPrev(box.getChildren().size() - 1, context);
 222             }
 223         });
 224         getSkinnable().setImpl_traversalEngine(engine);
 225 
 226         control.focusedProperty().addListener((observable, oldValue, newValue) -> {
 227             if (newValue) {
 228                 // TODO need to detect the focus direction
 229                 // to selected the first control in the toolbar when TAB is pressed
 230                 // or select the last control in the toolbar when SHIFT TAB is pressed.
 231                 if (!box.getChildren().isEmpty()) {
 232                     box.getChildren().get(0).requestFocus();
 233                 } else {
 234                     overflowMenu.requestFocus();
 235                 }
 236             }
 237         });
 238 
 239         control.getItems().addListener((ListChangeListener<Node>) c -> {
 240             while (c.next()) {
 241                 for (Node n: c.getRemoved()) {
 242                     box.getChildren().remove(n);
 243                 }
 244                 box.getChildren().addAll(c.getAddedSubList());
 245             }
 246             needsUpdate = true;
 247             getSkinnable().requestLayout();
 248         });
 249     }
 250 
 251 
 252 
 253     /***************************************************************************
 254      *                                                                         *
 255      * Properties                                                              *
 256      *                                                                         *
 257      **************************************************************************/
 258 
 259     // --- spacing
 260     private DoubleProperty spacing;
 261     private final void setSpacing(double value) {
 262         spacingProperty().set(snapSpace(value));
 263     }
 264 
 265     private final double getSpacing() {
 266         return spacing == null ? 0.0 : snapSpace(spacing.get());
 267     }
 268 
 269     private final DoubleProperty spacingProperty() {
 270         if (spacing == null) {
 271             spacing = new StyleableDoubleProperty() {
 272 
 273                 @Override
 274                 protected void invalidated() {
 275                     final double value = get();
 276                     if (getSkinnable().getOrientation() == Orientation.VERTICAL) {
 277                         ((VBox)box).setSpacing(value);
 278                     } else {
 279                         ((HBox)box).setSpacing(value);
 280                     }
 281                 }
 282 
 283                 @Override
 284                 public Object getBean() {
 285                     return ToolBarSkin.this;
 286                 }
 287 
 288                 @Override
 289                 public String getName() {
 290                     return "spacing";
 291                 }
 292 
 293                 @Override
 294                 public CssMetaData<ToolBar,Number> getCssMetaData() {
 295                     return StyleableProperties.SPACING;
 296                 }
 297             };
 298         }
 299         return spacing;
 300     }
 301 
 302     // --- box alignment
 303     private ObjectProperty<Pos> boxAlignment;
 304     private final void setBoxAlignment(Pos value) {
 305         boxAlignmentProperty().set(value);
 306     }
 307 
 308     private final Pos getBoxAlignment() {
 309         return boxAlignment == null ? Pos.TOP_LEFT : boxAlignment.get();
 310     }
 311 
 312     private final ObjectProperty<Pos> boxAlignmentProperty() {
 313         if (boxAlignment == null) {
 314             boxAlignment = new StyleableObjectProperty<Pos>(Pos.TOP_LEFT) {
 315 
 316                 @Override
 317                 public void invalidated() {
 318                     final Pos value = get();
 319                     if (getSkinnable().getOrientation() == Orientation.VERTICAL) {
 320                         ((VBox)box).setAlignment(value);
 321                     } else {
 322                         ((HBox)box).setAlignment(value);
 323                     }
 324                 }
 325 
 326                 @Override
 327                 public Object getBean() {
 328                     return ToolBarSkin.this;
 329                 }
 330 
 331                 @Override
 332                 public String getName() {
 333                     return "boxAlignment";
 334                 }
 335 
 336                 @Override
 337                 public CssMetaData<ToolBar,Pos> getCssMetaData() {
 338                     return StyleableProperties.ALIGNMENT;
 339                 }
 340             };
 341         }
 342         return boxAlignment;
 343     }
 344 
 345 
 346 
 347     /***************************************************************************
 348      *                                                                         *
 349      * Public API                                                              *
 350      *                                                                         *
 351      **************************************************************************/
 352 
 353     /** {@inheritDoc} */
 354     @Override public void dispose() {
 355         super.dispose();
 356 
 357         if (behavior != null) {
 358             behavior.dispose();
 359         }
 360     }
 361 
 362     /** {@inheritDoc} */
 363     @Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 364         final ToolBar toolbar = getSkinnable();
 365         return toolbar.getOrientation() == Orientation.VERTICAL ?
 366             computePrefWidth(-1, topInset, rightInset, bottomInset, leftInset) :
 367             snapSize(overflowMenu.prefWidth(-1)) + leftInset + rightInset;
 368     }
 369 
 370     /** {@inheritDoc} */
 371     @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 372         final ToolBar toolbar = getSkinnable();
 373         return toolbar.getOrientation() == Orientation.VERTICAL?
 374             snapSize(overflowMenu.prefHeight(-1)) + topInset + bottomInset :
 375             computePrefHeight(-1, topInset, rightInset, bottomInset, leftInset);
 376     }
 377 
 378     /** {@inheritDoc} */
 379     @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 380         double prefWidth = 0;
 381         final ToolBar toolbar = getSkinnable();
 382 
 383         if (toolbar.getOrientation() == Orientation.HORIZONTAL) {
 384             for (Node node : toolbar.getItems()) {
 385                 prefWidth += snapSize(node.prefWidth(-1)) + getSpacing();
 386             }
 387             prefWidth -= getSpacing();
 388         } else {
 389             for (Node node : toolbar.getItems()) {
 390                 prefWidth = Math.max(prefWidth, snapSize(node.prefWidth(-1)));
 391             }
 392             if (toolbar.getItems().size() > 0) {
 393                 savedPrefWidth = prefWidth;
 394             } else {
 395                 prefWidth = savedPrefWidth;
 396             }
 397         }
 398         return leftInset + prefWidth + rightInset;
 399     }
 400 
 401     /** {@inheritDoc} */
 402     @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 403         double prefHeight = 0;
 404         final ToolBar toolbar = getSkinnable();
 405         
 406         if(toolbar.getOrientation() == Orientation.VERTICAL) {
 407             for (Node node: toolbar.getItems()) {
 408                 prefHeight += snapSize(node.prefHeight(-1)) + getSpacing();
 409             }
 410             prefHeight -= getSpacing();
 411         } else {
 412             for (Node node : toolbar.getItems()) {
 413                 prefHeight = Math.max(prefHeight, snapSize(node.prefHeight(-1)));
 414             }
 415             if (toolbar.getItems().size() > 0) {
 416                 savedPrefHeight = prefHeight;
 417             } else {
 418                 prefHeight = savedPrefHeight;
 419             }
 420         }
 421         return topInset + prefHeight + bottomInset;
 422     }
 423 
 424     /** {@inheritDoc} */
 425     @Override protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 426         return getSkinnable().getOrientation() == Orientation.VERTICAL ?
 427                 snapSize(getSkinnable().prefWidth(-1)) : Double.MAX_VALUE;
 428     }
 429 
 430     /** {@inheritDoc} */
 431     @Override protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 432         return getSkinnable().getOrientation() == Orientation.VERTICAL ?
 433                 Double.MAX_VALUE : snapSize(getSkinnable().prefHeight(-1));
 434     }
 435 
 436     /** {@inheritDoc} */
 437     @Override protected void layoutChildren(final double x,final double y,
 438             final double w, final double h) {
 439 //        super.layoutChildren();
 440         final ToolBar toolbar = getSkinnable();
 441 
 442         if (toolbar.getOrientation() == Orientation.VERTICAL) {
 443             if (snapSize(toolbar.getHeight()) != previousHeight || needsUpdate) {
 444                 ((VBox)box).setSpacing(getSpacing());
 445                 ((VBox)box).setAlignment(getBoxAlignment());
 446                 previousHeight = snapSize(toolbar.getHeight());
 447                 addNodesToToolBar();
 448             }
 449         } else {
 450             if (snapSize(toolbar.getWidth()) != previousWidth || needsUpdate) {
 451                 ((HBox)box).setSpacing(getSpacing());
 452                 ((HBox)box).setAlignment(getBoxAlignment());
 453                 previousWidth = snapSize(toolbar.getWidth());
 454                 addNodesToToolBar();
 455             }
 456         }
 457         needsUpdate = false;
 458 
 459         double toolbarWidth = w;
 460         double toolbarHeight = h;
 461 
 462         if (getSkinnable().getOrientation() == Orientation.VERTICAL) {
 463             toolbarHeight -= (overflow ? snapSize(overflowMenu.prefHeight(-1)) : 0);
 464         } else {
 465             toolbarWidth -= (overflow ? snapSize(overflowMenu.prefWidth(-1)) : 0);
 466         }
 467 
 468         box.resize(toolbarWidth, toolbarHeight);
 469         positionInArea(box, x, y,
 470                 toolbarWidth, toolbarHeight, /*baseline ignored*/0, HPos.CENTER, VPos.CENTER);
 471 
 472         // If popup menu is not null show the overflowControl
 473         if (overflow) {
 474             double overflowMenuWidth = snapSize(overflowMenu.prefWidth(-1));
 475             double overflowMenuHeight = snapSize(overflowMenu.prefHeight(-1));
 476             double overflowX = x;
 477             double overflowY = x;
 478             if (getSkinnable().getOrientation() == Orientation.VERTICAL) {
 479                 // This is to prevent the overflow menu from moving when there
 480                 // are no items in the toolbar.
 481                 if (toolbarWidth == 0) {
 482                     toolbarWidth = savedPrefWidth;
 483                 }
 484                 HPos pos = ((VBox)box).getAlignment().getHpos();
 485                 if (HPos.LEFT.equals(pos)) {
 486                     overflowX = x + Math.abs((toolbarWidth - overflowMenuWidth)/2);
 487                 } else if (HPos.RIGHT.equals(pos)) {
 488                     overflowX = (snapSize(toolbar.getWidth()) - snappedRightInset() - toolbarWidth) +
 489                         Math.abs((toolbarWidth - overflowMenuWidth)/2);
 490                 } else {
 491                     overflowX = x +
 492                         Math.abs((snapSize(toolbar.getWidth()) - (x) +
 493                         snappedRightInset() - overflowMenuWidth)/2);
 494                 }
 495                 overflowY = snapSize(toolbar.getHeight()) - overflowMenuHeight - y;
 496             } else {
 497                 // This is to prevent the overflow menu from moving when there
 498                 // are no items in the toolbar.
 499                 if (toolbarHeight == 0) {
 500                     toolbarHeight = savedPrefHeight;
 501                 }
 502                 VPos pos = ((HBox)box).getAlignment().getVpos();
 503                 if (VPos.TOP.equals(pos)) {
 504                     overflowY = y +
 505                         Math.abs((toolbarHeight - overflowMenuHeight)/2);
 506                 } else if (VPos.BOTTOM.equals(pos)) {
 507                     overflowY = (snapSize(toolbar.getHeight()) - snappedBottomInset() - toolbarHeight) +
 508                         Math.abs((toolbarHeight - overflowMenuHeight)/2);
 509                 } else {
 510                     overflowY = y + Math.abs((toolbarHeight - overflowMenuHeight)/2);
 511                 }
 512                overflowX = snapSize(toolbar.getWidth()) - overflowMenuWidth - snappedRightInset();
 513             }
 514             overflowMenu.resize(overflowMenuWidth, overflowMenuHeight);
 515             positionInArea(overflowMenu, overflowX, overflowY, overflowMenuWidth, overflowMenuHeight, /*baseline ignored*/0,
 516                     HPos.CENTER, VPos.CENTER);
 517         }
 518     }
 519 
 520 
 521 
 522     /***************************************************************************
 523      *                                                                         *
 524      * Private implementation                                                  *
 525      *                                                                         *
 526      **************************************************************************/
 527 
 528     private void initialize() {
 529         if (getSkinnable().getOrientation() == Orientation.VERTICAL) {
 530             box = new VBox();
 531         } else {
 532             box = new HBox();
 533         }
 534         box.getStyleClass().add("container");
 535         box.getChildren().addAll(getSkinnable().getItems());
 536         overflowMenu = new ToolBarOverflowMenu(overflowMenuItems);
 537         overflowMenu.setVisible(false);
 538         overflowMenu.setManaged(false);
 539 
 540         getChildren().clear();
 541         getChildren().add(box);
 542         getChildren().add(overflowMenu);
 543 
 544         previousWidth = 0;
 545         previousHeight = 0;
 546         savedPrefWidth = 0;
 547         savedPrefHeight = 0;
 548         needsUpdate = true;
 549         getSkinnable().requestLayout();
 550     }
 551 
 552     private void addNodesToToolBar() {
 553         final ToolBar toolbar = getSkinnable();
 554         double length = 0;
 555         if (getSkinnable().getOrientation() == Orientation.VERTICAL) {
 556             length = snapSize(toolbar.getHeight()) - snappedTopInset() - snappedBottomInset() + getSpacing();
 557         } else {
 558             length = snapSize(toolbar.getWidth()) - snappedLeftInset() - snappedRightInset() + getSpacing();
 559         }
 560 
 561         // Is there overflow ?
 562         double x = 0;
 563         boolean hasOverflow = false;
 564         for (Node node : getSkinnable().getItems()) {
 565             if (getSkinnable().getOrientation() == Orientation.VERTICAL) {
 566                 x += snapSize(node.prefHeight(-1)) + getSpacing();
 567             } else {
 568                 x += snapSize(node.prefWidth(-1)) + getSpacing();
 569             }
 570             if (x > length) {
 571                 hasOverflow = true;
 572                 break;
 573             }
 574         }
 575 
 576         if (hasOverflow) {
 577             if (getSkinnable().getOrientation() == Orientation.VERTICAL) {
 578                 length -= snapSize(overflowMenu.prefHeight(-1));
 579             } else {
 580                 length -= snapSize(overflowMenu.prefWidth(-1));
 581             }
 582             length -= getSpacing();
 583         }
 584 
 585         // Determine which node goes to the toolbar and which goes to the overflow.
 586         x = 0;
 587         overflowMenuItems.clear();
 588         box.getChildren().clear();
 589         for (Node node : getSkinnable().getItems()) {
 590             node.getStyleClass().remove("menu-item");
 591             node.getStyleClass().remove("custom-menu-item");
 592             if (getSkinnable().getOrientation() == Orientation.VERTICAL) {
 593                 x += snapSize(node.prefHeight(-1)) + getSpacing();
 594             } else {
 595                 x += snapSize(node.prefWidth(-1)) + getSpacing();
 596             }
 597             if (x <= length) {
 598                 box.getChildren().add(node);
 599             } else {
 600                 if (node.isFocused()) {
 601                     if (!box.getChildren().isEmpty()) {
 602                         Node last = engine.selectLast();
 603                         if (last != null) {
 604                             last.requestFocus();
 605                         }
 606                     } else {
 607                         overflowMenu.requestFocus();                        
 608                     }
 609                 }
 610                 if (node instanceof Separator) {
 611                     overflowMenuItems.add(new SeparatorMenuItem());
 612                 } else {
 613                     CustomMenuItem customMenuItem = new CustomMenuItem(node);
 614 
 615                     // RT-36455:
 616                     // We can't be totally certain of all nodes, but for the
 617                     // most common nodes we can check to see whether we should
 618                     // hide the menu when the node is clicked on. The common
 619                     // case is for TextField or Slider.
 620                     // This list won't be exhaustive (there is no point really
 621                     // considering the ListView case), but it should try to
 622                     // include most common control types that find themselves
 623                     // placed in menus.
 624                     final String nodeType = node.getTypeSelector();
 625                     switch (nodeType) {
 626                         case "Button":
 627                         case "Hyperlink":
 628                         case "Label":
 629                             customMenuItem.setHideOnClick(true);
 630                             break;
 631                         case "CheckBox":
 632                         case "ChoiceBox":
 633                         case "ColorPicker":
 634                         case "ComboBox":
 635                         case "DatePicker":
 636                         case "MenuButton":
 637                         case "PasswordField":
 638                         case "RadioButton":
 639                         case "ScrollBar":
 640                         case "ScrollPane":
 641                         case "Slider":
 642                         case "SplitMenuButton":
 643                         case "SplitPane":
 644                         case "TextArea":
 645                         case "TextField":
 646                         case "ToggleButton":
 647                         case "ToolBar":
 648                             customMenuItem.setHideOnClick(false);
 649                             break;
 650                     }
 651 
 652                     overflowMenuItems.add(customMenuItem);
 653                 }
 654             }
 655         }
 656 
 657         // Check if we overflowed.
 658         overflow = overflowMenuItems.size() > 0;
 659         if (!overflow && overflowMenu.isFocused()) {
 660             Node last = engine.selectLast();
 661             if (last != null) {
 662                 last.requestFocus();
 663             }
 664         }
 665         overflowMenu.setVisible(overflow);
 666         overflowMenu.setManaged(overflow);
 667     }
 668 
 669 
 670 
 671     /***************************************************************************
 672      *                                                                         *
 673      * Support classes                                                         *
 674      *                                                                         *
 675      **************************************************************************/
 676 
 677     class ToolBarOverflowMenu extends StackPane {
 678         private StackPane downArrow;
 679         private ContextMenu popup;
 680         private ObservableList<MenuItem> menuItems;
 681 
 682         public ToolBarOverflowMenu(ObservableList<MenuItem> items) {
 683             getStyleClass().setAll("tool-bar-overflow-button");
 684             setAccessibleRole(AccessibleRole.BUTTON);
 685             setAccessibleText(getString("Accessibility.title.ToolBar.OverflowButton"));
 686             setFocusTraversable(true);
 687             this.menuItems = items;
 688             downArrow = new StackPane();
 689             downArrow.getStyleClass().setAll("arrow");
 690             downArrow.setOnMousePressed(me -> {
 691                 fire();
 692             });
 693 
 694             setOnKeyPressed(ke -> {
 695                 if (KeyCode.SPACE.equals(ke.getCode())) {
 696                     if (!popup.isShowing()) {
 697                         popup.getItems().clear();
 698                         popup.getItems().addAll(menuItems);
 699                         popup.show(downArrow, Side.BOTTOM, 0, 0);
 700                     }
 701                     ke.consume();
 702                 } else if (KeyCode.ESCAPE.equals(ke.getCode())) {
 703                     if (popup.isShowing()) {
 704                         popup.hide();
 705                     }
 706                     ke.consume();
 707                 } else if (KeyCode.ENTER.equals(ke.getCode())) {
 708                     fire();
 709                     ke.consume();
 710                 }
 711             });
 712 
 713             visibleProperty().addListener((observable, oldValue, newValue) -> {
 714                     if (newValue) {
 715                         if (box.getChildren().isEmpty()) {
 716                             setFocusTraversable(true);
 717                         }
 718                     }
 719             });
 720             popup = new ContextMenu();
 721             setVisible(false);
 722             setManaged(false);            
 723             getChildren().add(downArrow);            
 724         }
 725 
 726         private void fire() {
 727             if (popup.isShowing()) {
 728                 popup.hide();
 729             } else {
 730                 popup.getItems().clear();
 731                 popup.getItems().addAll(menuItems);
 732                 popup.show(downArrow, Side.BOTTOM, 0, 0);
 733             }
 734         }
 735 
 736         @Override protected double computePrefWidth(double height) {
 737             return snappedLeftInset() + snappedRightInset();
 738         }
 739 
 740         @Override protected double computePrefHeight(double width) {
 741             return snappedTopInset() + snappedBottomInset();
 742         }
 743 
 744         @Override protected void layoutChildren() {
 745             double w = snapSize(downArrow.prefWidth(-1));
 746             double h = snapSize(downArrow.prefHeight(-1));
 747             double x = (snapSize(getWidth()) - w)/2;
 748             double y = (snapSize(getHeight()) - h)/2;
 749 
 750             // TODO need to provide support for when the toolbar is on the right
 751             // or bottom
 752             if (getSkinnable().getOrientation() == Orientation.VERTICAL) {
 753                 downArrow.setRotate(0);
 754             }
 755 
 756             downArrow.resize(w, h);
 757             positionInArea(downArrow, x, y, w, h,
 758                     /*baseline ignored*/0, HPos.CENTER, VPos.CENTER);
 759         }
 760 
 761         @Override
 762         public void executeAccessibleAction(AccessibleAction action, Object... parameters) {
 763             switch (action) {
 764                 case FIRE: fire(); break;
 765                 default: super.executeAccessibleAction(action); break;
 766             }
 767         }
 768     }
 769 
 770     /***************************************************************************
 771      *                                                                         *
 772      *                         Stylesheet Handling                             *
 773      *                                                                         *
 774      **************************************************************************/
 775 
 776      /**
 777       * Super-lazy instantiation pattern from Bill Pugh.
 778       * @treatAsPrivate implementation detail
 779       */
 780      private static class StyleableProperties {
 781          private static final CssMetaData<ToolBar,Number> SPACING =
 782              new CssMetaData<ToolBar,Number>("-fx-spacing",
 783                  SizeConverter.getInstance(), 0.0) {
 784 
 785             @Override
 786             public boolean isSettable(ToolBar n) {
 787                 final ToolBarSkin skin = (ToolBarSkin) n.getSkin();
 788                 return skin.spacing == null || !skin.spacing.isBound();
 789             }
 790 
 791             @Override
 792             public StyleableProperty<Number> getStyleableProperty(ToolBar n) {
 793                 final ToolBarSkin skin = (ToolBarSkin) n.getSkin();
 794                 return (StyleableProperty<Number>)(WritableValue<Number>)skin.spacingProperty();
 795             }
 796         };
 797          
 798         private static final CssMetaData<ToolBar,Pos>ALIGNMENT =
 799                 new CssMetaData<ToolBar,Pos>("-fx-alignment",
 800                 new EnumConverter<Pos>(Pos.class), Pos.TOP_LEFT ) {
 801 
 802             @Override
 803             public boolean isSettable(ToolBar n) {
 804                 final ToolBarSkin skin = (ToolBarSkin) n.getSkin();
 805                 return skin.boxAlignment == null || !skin.boxAlignment.isBound();
 806             }
 807 
 808             @Override
 809             public StyleableProperty<Pos> getStyleableProperty(ToolBar n) {
 810                 final ToolBarSkin skin = (ToolBarSkin) n.getSkin();
 811                 return (StyleableProperty<Pos>)(WritableValue<Pos>)skin.boxAlignmentProperty();
 812             }
 813         };
 814 
 815          
 816          private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
 817          static {
 818 
 819             final List<CssMetaData<? extends Styleable, ?>> styleables =
 820                 new ArrayList<CssMetaData<? extends Styleable, ?>>(SkinBase.getClassCssMetaData());
 821             
 822             // StackPane also has -fx-alignment. Replace it with 
 823             // ToolBarSkin's. 
 824             // TODO: Really should be able to reference StackPane.StyleableProperties.ALIGNMENT
 825             final String alignmentProperty = ALIGNMENT.getProperty();
 826             for (int n=0, nMax=styleables.size(); n<nMax; n++) {
 827                 final CssMetaData<?,?> prop = styleables.get(n);
 828                 if (alignmentProperty.equals(prop.getProperty())) styleables.remove(prop);
 829             }
 830             
 831             styleables.add(SPACING);
 832             styleables.add(ALIGNMENT);
 833             STYLEABLES = Collections.unmodifiableList(styleables);
 834 
 835          }
 836     }
 837 
 838     /**
 839      * Returns the CssMetaData associated with this class, which may include the
 840      * CssMetaData of its super classes.
 841      */
 842     public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
 843         return StyleableProperties.STYLEABLES;
 844     }
 845 
 846     /**
 847      * {@inheritDoc}
 848      */
 849     @Override
 850     public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
 851         return getClassCssMetaData();
 852     }
 853 
 854     @Override
 855     protected Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
 856         switch (attribute) {
 857             case OVERFLOW_BUTTON: return overflowMenu;
 858             default: return super.queryAccessibleAttribute(attribute, parameters);
 859         }
 860     }
 861 
 862     @Override
 863     protected void executeAccessibleAction(AccessibleAction action, Object... parameters) {
 864         switch (action) {
 865             case SHOW_MENU: 
 866                 overflowMenu.fire();
 867                 break;
 868             default: super.executeAccessibleAction(action, parameters);
 869         }
 870     }
 871 }