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

Print this page
rev 9240 : 8076423: JEP 253: Prepare JavaFX UI Controls & CSS APIs for Modularization
   1 /*
   2  * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.javafx.scene.control.skin;
  27 
  28 import java.util.ArrayList;
  29 import java.util.Collections;
  30 import java.util.List;
  31 

  32 import com.sun.javafx.scene.traversal.Algorithm;
  33 import com.sun.javafx.scene.traversal.ParentTraversalEngine;
  34 import com.sun.javafx.scene.traversal.TraversalContext;
  35 
  36 import javafx.beans.property.ObjectProperty;
  37 import javafx.beans.property.DoubleProperty;
  38 import javafx.beans.value.WritableValue;
  39 import javafx.collections.FXCollections;
  40 import javafx.collections.ListChangeListener;
  41 import javafx.collections.ObservableList;
  42 import javafx.geometry.HPos;
  43 import javafx.geometry.Orientation;
  44 import javafx.geometry.Pos;
  45 import javafx.geometry.Side;
  46 import javafx.geometry.VPos;
  47 import javafx.scene.AccessibleAction;
  48 import javafx.scene.AccessibleAttribute;
  49 import javafx.scene.AccessibleRole;
  50 import javafx.scene.Node;
  51 import javafx.scene.Parent;
  52 import javafx.scene.control.ContextMenu;

  53 import javafx.scene.control.MenuItem;
  54 import javafx.scene.control.CustomMenuItem;
  55 import javafx.scene.control.Separator;
  56 import javafx.scene.control.SeparatorMenuItem;
  57 import javafx.scene.control.SkinBase;
  58 import javafx.scene.control.ToolBar;
  59 import javafx.scene.input.KeyCode;
  60 import javafx.scene.layout.HBox;
  61 import javafx.scene.layout.Pane;
  62 import javafx.scene.layout.StackPane;
  63 import javafx.scene.layout.VBox;
  64 import javafx.css.StyleableDoubleProperty;
  65 import javafx.css.StyleableObjectProperty;
  66 import javafx.css.StyleableProperty;
  67 import javafx.css.CssMetaData;
  68 
  69 import com.sun.javafx.css.converters.EnumConverter;
  70 import com.sun.javafx.css.converters.SizeConverter;
  71 import com.sun.javafx.scene.control.behavior.ToolBarBehavior;
  72 import com.sun.javafx.scene.traversal.Direction;
  73 
  74 import javafx.css.Styleable;
  75 
  76 import static com.sun.javafx.scene.control.skin.resources.ControlResources.getString;
  77 
  78 public class ToolBarSkin extends BehaviorSkinBase<ToolBar, ToolBarBehavior> {












  79 
  80     private Pane box;
  81     private ToolBarOverflowMenu overflowMenu;
  82     private boolean overflow = false;
  83     private double previousWidth = 0;
  84     private double previousHeight = 0;
  85     private double savedPrefWidth = 0;
  86     private double savedPrefHeight = 0;
  87     private ObservableList<MenuItem> overflowMenuItems;
  88     private boolean needsUpdate = false;
  89     private final ParentTraversalEngine engine;























  90 
  91     public ToolBarSkin(ToolBar toolbar) {
  92         super(toolbar, new ToolBarBehavior(toolbar));
  93         overflowMenuItems = FXCollections.observableArrayList();
  94         initialize();
  95         registerChangeListener(toolbar.orientationProperty(), "ORIENTATION");
  96 
  97         engine = new ParentTraversalEngine(getSkinnable(), new Algorithm() {
  98 
  99             private Node selectPrev(int from, TraversalContext context) {
 100                 for (int i = from; i >= 0; --i) {
 101                     Node n = box.getChildren().get(i);
 102                     if (n.isDisabled() || !n.impl_isTreeVisible()) continue;
 103                     if (n instanceof Parent) {
 104                         Node selected = context.selectLastInParent((Parent)n);
 105                         if (selected != null) return selected;
 106                     }
 107                     if (n.isFocusTraversable() ) {
 108                         return n;
 109                     }
 110                 }
 111                 return null;
 112             }
 113 
 114             private Node selectNext(int from, TraversalContext context) {
 115                 for (int i = from, max = box.getChildren().size(); i < max; ++i) {


 171             @Override
 172             public Node selectFirst(TraversalContext context) {
 173                 Node selected = selectNext(0, context);
 174                 if (selected != null) return selected;
 175                 if (overflow) {
 176                     return overflowMenu;
 177                 }
 178                 return null;
 179             }
 180 
 181             @Override
 182             public Node selectLast(TraversalContext context) {
 183                 if (overflow) {
 184                     return overflowMenu;
 185                 }
 186                 return selectPrev(box.getChildren().size() - 1, context);
 187             }
 188         });
 189         getSkinnable().setImpl_traversalEngine(engine);
 190 
 191         toolbar.focusedProperty().addListener((observable, oldValue, newValue) -> {
 192             if (newValue) {
 193                 // TODO need to detect the focus direction
 194                 // to selected the first control in the toolbar when TAB is pressed
 195                 // or select the last control in the toolbar when SHIFT TAB is pressed.
 196                 if (!box.getChildren().isEmpty()) {
 197                     box.getChildren().get(0).requestFocus();
 198                 } else {
 199                     overflowMenu.requestFocus();
 200                 }
 201             }
 202         });
 203 
 204         toolbar.getItems().addListener((ListChangeListener<Node>) c -> {
 205             while (c.next()) {
 206                 for (Node n: c.getRemoved()) {
 207                     box.getChildren().remove(n);
 208                 }
 209                 box.getChildren().addAll(c.getAddedSubList());
 210             }
 211             needsUpdate = true;
 212             getSkinnable().requestLayout();
 213         });
 214     }
 215 









 216     private DoubleProperty spacing;
 217     public final void setSpacing(double value) {
 218         spacingProperty().set(snapSpace(value));
 219     }
 220 
 221     public final double getSpacing() {
 222         return spacing == null ? 0.0 : snapSpace(spacing.get());
 223     }
 224 
 225     public final DoubleProperty spacingProperty() {
 226         if (spacing == null) {
 227             spacing = new StyleableDoubleProperty() {
 228 
 229                 @Override
 230                 protected void invalidated() {
 231                     final double value = get();
 232                     if (getSkinnable().getOrientation() == Orientation.VERTICAL) {
 233                         ((VBox)box).setSpacing(value);
 234                     } else {
 235                         ((HBox)box).setSpacing(value);
 236                     }
 237                 }
 238 
 239                 @Override
 240                 public Object getBean() {
 241                     return ToolBarSkin.this;
 242                 }
 243 
 244                 @Override
 245                 public String getName() {
 246                     return "spacing";
 247                 }
 248 
 249                 @Override
 250                 public CssMetaData<ToolBar,Number> getCssMetaData() {
 251                     return StyleableProperties.SPACING;
 252                 }
 253             };
 254         }
 255         return spacing;
 256     }
 257 

 258     private ObjectProperty<Pos> boxAlignment;
 259     public final void setBoxAlignment(Pos value) {
 260         boxAlignmentProperty().set(value);
 261     }
 262 
 263     public final Pos getBoxAlignment() {
 264         return boxAlignment == null ? Pos.TOP_LEFT : boxAlignment.get();
 265     }
 266 
 267     public final ObjectProperty<Pos> boxAlignmentProperty() {
 268         if (boxAlignment == null) {
 269             boxAlignment = new StyleableObjectProperty<Pos>(Pos.TOP_LEFT) {
 270 
 271                 @Override
 272                 public void invalidated() {
 273                     final Pos value = get();
 274                     if (getSkinnable().getOrientation() == Orientation.VERTICAL) {
 275                         ((VBox)box).setAlignment(value);
 276                     } else {
 277                         ((HBox)box).setAlignment(value);
 278                     }
 279                 }
 280 
 281                 @Override
 282                 public Object getBean() {
 283                     return ToolBarSkin.this;
 284                 }
 285 
 286                 @Override
 287                 public String getName() {
 288                     return "boxAlignment";
 289                 }
 290 
 291                 @Override
 292                 public CssMetaData<ToolBar,Pos> getCssMetaData() {
 293                     return StyleableProperties.ALIGNMENT;
 294                 }
 295             };
 296         }
 297         return boxAlignment;
 298     }
 299 
 300     @Override protected void handleControlPropertyChanged(String property) {
 301         super.handleControlPropertyChanged(property);
 302         if ("ORIENTATION".equals(property)) {
 303             initialize();










 304         }
 305     }
 306 

 307     @Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 308         final ToolBar toolbar = getSkinnable();
 309         return toolbar.getOrientation() == Orientation.VERTICAL ?
 310             computePrefWidth(-1, topInset, rightInset, bottomInset, leftInset) :
 311             snapSize(overflowMenu.prefWidth(-1)) + leftInset + rightInset;
 312     }
 313 

 314     @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 315         final ToolBar toolbar = getSkinnable();
 316         return toolbar.getOrientation() == Orientation.VERTICAL?
 317             snapSize(overflowMenu.prefHeight(-1)) + topInset + bottomInset :
 318             computePrefHeight(-1, topInset, rightInset, bottomInset, leftInset);
 319     }
 320 

 321     @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 322         double prefWidth = 0;
 323         final ToolBar toolbar = getSkinnable();
 324 
 325         if (toolbar.getOrientation() == Orientation.HORIZONTAL) {
 326             for (Node node : toolbar.getItems()) {
 327                 prefWidth += snapSize(node.prefWidth(-1)) + getSpacing();
 328             }
 329             prefWidth -= getSpacing();
 330         } else {
 331             for (Node node : toolbar.getItems()) {
 332                 prefWidth = Math.max(prefWidth, snapSize(node.prefWidth(-1)));
 333             }
 334             if (toolbar.getItems().size() > 0) {
 335                 savedPrefWidth = prefWidth;
 336             } else {
 337                 prefWidth = savedPrefWidth;
 338             }
 339         }
 340         return leftInset + prefWidth + rightInset;
 341     }
 342 

 343     @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 344         double prefHeight = 0;
 345         final ToolBar toolbar = getSkinnable();
 346         
 347         if(toolbar.getOrientation() == Orientation.VERTICAL) {
 348             for (Node node: toolbar.getItems()) {
 349                 prefHeight += snapSize(node.prefHeight(-1)) + getSpacing();
 350             }
 351             prefHeight -= getSpacing();
 352         } else {
 353             for (Node node : toolbar.getItems()) {
 354                 prefHeight = Math.max(prefHeight, snapSize(node.prefHeight(-1)));
 355             }
 356             if (toolbar.getItems().size() > 0) {
 357                 savedPrefHeight = prefHeight;
 358             } else {
 359                 prefHeight = savedPrefHeight;
 360             }
 361         }
 362         return topInset + prefHeight + bottomInset;
 363     }
 364 

 365     @Override protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
 366         return getSkinnable().getOrientation() == Orientation.VERTICAL ?
 367                 snapSize(getSkinnable().prefWidth(-1)) : Double.MAX_VALUE;
 368     }
 369 

 370     @Override protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
 371         return getSkinnable().getOrientation() == Orientation.VERTICAL ?
 372                 Double.MAX_VALUE : snapSize(getSkinnable().prefHeight(-1));
 373     }
 374 

 375     @Override protected void layoutChildren(final double x,final double y,
 376             final double w, final double h) {
 377 //        super.layoutChildren();
 378         final ToolBar toolbar = getSkinnable();
 379 
 380         if (toolbar.getOrientation() == Orientation.VERTICAL) {
 381             if (snapSize(toolbar.getHeight()) != previousHeight || needsUpdate) {
 382                 ((VBox)box).setSpacing(getSpacing());
 383                 ((VBox)box).setAlignment(getBoxAlignment());
 384                 previousHeight = snapSize(toolbar.getHeight());
 385                 addNodesToToolBar();
 386             }
 387         } else {
 388             if (snapSize(toolbar.getWidth()) != previousWidth || needsUpdate) {
 389                 ((HBox)box).setSpacing(getSpacing());
 390                 ((HBox)box).setAlignment(getBoxAlignment());
 391                 previousWidth = snapSize(toolbar.getWidth());
 392                 addNodesToToolBar();
 393             }
 394         }


 438                     toolbarHeight = savedPrefHeight;
 439                 }
 440                 VPos pos = ((HBox)box).getAlignment().getVpos();
 441                 if (VPos.TOP.equals(pos)) {
 442                     overflowY = y +
 443                         Math.abs((toolbarHeight - overflowMenuHeight)/2);
 444                 } else if (VPos.BOTTOM.equals(pos)) {
 445                     overflowY = (snapSize(toolbar.getHeight()) - snappedBottomInset() - toolbarHeight) +
 446                         Math.abs((toolbarHeight - overflowMenuHeight)/2);
 447                 } else {
 448                     overflowY = y + Math.abs((toolbarHeight - overflowMenuHeight)/2);
 449                 }
 450                overflowX = snapSize(toolbar.getWidth()) - overflowMenuWidth - snappedRightInset();
 451             }
 452             overflowMenu.resize(overflowMenuWidth, overflowMenuHeight);
 453             positionInArea(overflowMenu, overflowX, overflowY, overflowMenuWidth, overflowMenuHeight, /*baseline ignored*/0,
 454                     HPos.CENTER, VPos.CENTER);
 455         }
 456     }
 457 








 458     private void initialize() {
 459         if (getSkinnable().getOrientation() == Orientation.VERTICAL) {
 460             box = new VBox();
 461         } else {
 462             box = new HBox();
 463         }
 464         box.getStyleClass().add("container");
 465         box.getChildren().addAll(getSkinnable().getItems());
 466         overflowMenu = new ToolBarOverflowMenu(overflowMenuItems);
 467         overflowMenu.setVisible(false);
 468         overflowMenu.setManaged(false);
 469 
 470         getChildren().clear();
 471         getChildren().add(box);
 472         getChildren().add(overflowMenu);
 473 
 474         previousWidth = 0;
 475         previousHeight = 0;
 476         savedPrefWidth = 0;
 477         savedPrefHeight = 0;


 579                             break;
 580                     }
 581 
 582                     overflowMenuItems.add(customMenuItem);
 583                 }
 584             }
 585         }
 586 
 587         // Check if we overflowed.
 588         overflow = overflowMenuItems.size() > 0;
 589         if (!overflow && overflowMenu.isFocused()) {
 590             Node last = engine.selectLast();
 591             if (last != null) {
 592                 last.requestFocus();
 593             }
 594         }
 595         overflowMenu.setVisible(overflow);
 596         overflowMenu.setManaged(overflow);
 597     }
 598 








 599     class ToolBarOverflowMenu extends StackPane {
 600         private StackPane downArrow;
 601         private ContextMenu popup;
 602         private ObservableList<MenuItem> menuItems;
 603 
 604         public ToolBarOverflowMenu(ObservableList<MenuItem> items) {
 605             getStyleClass().setAll("tool-bar-overflow-button");
 606             setAccessibleRole(AccessibleRole.BUTTON);
 607             setAccessibleText(getString("Accessibility.title.ToolBar.OverflowButton"));
 608             setFocusTraversable(true);
 609             this.menuItems = items;
 610             downArrow = new StackPane();
 611             downArrow.getStyleClass().setAll("arrow");
 612             downArrow.setOnMousePressed(me -> {
 613                 fire();
 614             });
 615 
 616             setOnKeyPressed(ke -> {
 617                 if (KeyCode.SPACE.equals(ke.getCode())) {
 618                     if (!popup.isShowing()) {


 741             final List<CssMetaData<? extends Styleable, ?>> styleables =
 742                 new ArrayList<CssMetaData<? extends Styleable, ?>>(SkinBase.getClassCssMetaData());
 743             
 744             // StackPane also has -fx-alignment. Replace it with 
 745             // ToolBarSkin's. 
 746             // TODO: Really should be able to reference StackPane.StyleableProperties.ALIGNMENT
 747             final String alignmentProperty = ALIGNMENT.getProperty();
 748             for (int n=0, nMax=styleables.size(); n<nMax; n++) {
 749                 final CssMetaData<?,?> prop = styleables.get(n);
 750                 if (alignmentProperty.equals(prop.getProperty())) styleables.remove(prop);
 751             }
 752             
 753             styleables.add(SPACING);
 754             styleables.add(ALIGNMENT);
 755             STYLEABLES = Collections.unmodifiableList(styleables);
 756 
 757          }
 758     }
 759 
 760     /**
 761      * @return The CssMetaData associated with this class, which may include the
 762      * CssMetaData of its super classes.
 763      */
 764     public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
 765         return StyleableProperties.STYLEABLES;
 766     }
 767 
 768     /**
 769      * {@inheritDoc}
 770      */
 771     @Override
 772     public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
 773         return getClassCssMetaData();
 774     }
 775 
 776     @Override
 777     protected Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
 778         switch (attribute) {
 779             case OVERFLOW_BUTTON: return overflowMenu;
 780             default: return super.queryAccessibleAttribute(attribute, parameters);
 781         }
   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) {


 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         }


 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;


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


 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         }