1 /* 2 * Copyright (c) 2010, 2013, 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.Node; 48 import javafx.scene.Parent; 49 import javafx.scene.accessibility.Action; 50 import javafx.scene.accessibility.Attribute; 51 import javafx.scene.accessibility.Role; 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 public class ToolBarSkin extends BehaviorSkinBase<ToolBar, ToolBarBehavior> { 77 78 private Pane box; 79 private ToolBarOverflowMenu overflowMenu; 80 private boolean overflow = false; 81 private double previousWidth = 0; 82 private double previousHeight = 0; 83 private double savedPrefWidth = 0; 84 private double savedPrefHeight = 0; 85 private ObservableList<MenuItem> overflowMenuItems; 86 private boolean needsUpdate = false; 87 private final ParentTraversalEngine engine; 88 89 public ToolBarSkin(ToolBar toolbar) { 90 super(toolbar, new ToolBarBehavior(toolbar)); 91 overflowMenuItems = FXCollections.observableArrayList(); 92 initialize(); 93 registerChangeListener(toolbar.orientationProperty(), "ORIENTATION"); 94 95 engine = new ParentTraversalEngine(getSkinnable(), new Algorithm() { 96 97 private Node selectPrev(int from, TraversalContext context) { 98 for (int i = from; i >= 0; --i) { 99 Node n = box.getChildren().get(i); 100 if (n.isDisabled() || !n.impl_isTreeVisible()) continue; 101 if (n instanceof Parent) { 102 Node selected = context.selectLastInParent((Parent)n); 103 if (selected != null) return selected; 104 } 105 if (n.isFocusTraversable() ) { 106 return n; 107 } 108 } 109 return null; 110 } 111 112 private Node selectNext(int from, TraversalContext context) { 113 for (int i = from, max = box.getChildren().size(); i < max; ++i) { 114 Node n = box.getChildren().get(i); 115 if (n.isDisabled() || !n.impl_isTreeVisible()) continue; 116 if (n.isFocusTraversable()) { 117 return n; 118 } 119 if (n instanceof Parent) { 120 Node selected = context.selectFirstInParent((Parent)n); 121 if (selected != null) return selected; 122 } 123 } 124 return null; 125 } 126 127 @Override 128 public Node select(Node owner, Direction dir, TraversalContext context) { 129 final ObservableList<Node> boxChildren = box.getChildren(); 130 if (owner == overflowMenu) { 131 if (dir.isForward()) { 132 return null; 133 } else { 134 Node selected = selectPrev(boxChildren.size() - 1, context); 135 if (selected != null) return selected; 136 } 137 } 138 139 int idx = boxChildren.indexOf(owner); 140 141 if (idx < 0) { 142 // The current focus owner is a child of some Toolbar's item 143 Parent item = owner.getParent(); 144 while (!boxChildren.contains(item)) { 145 item = item.getParent(); 146 } 147 Node selected = context.selectInSubtree(item, owner, dir); 148 if (selected != null) return selected; 149 idx = boxChildren.indexOf(owner); 150 if (dir == Direction.NEXT) dir = Direction.NEXT_IN_LINE; 151 } 152 153 if (idx >= 0) { 154 if (dir.isForward()) { 155 Node selected = selectNext(idx + 1, context); 156 if (selected != null) return selected; 157 if (overflow) { 158 overflowMenu.requestFocus(); 159 return overflowMenu; 160 } 161 } else { 162 Node selected = selectPrev(idx - 1, context); 163 if (selected != null) return selected; 164 } 165 } 166 return null; 167 } 168 169 @Override 170 public Node selectFirst(TraversalContext context) { 171 Node selected = selectNext(0, context); 172 if (selected != null) return selected; 173 if (overflow) { 174 return overflowMenu; 175 } 176 return null; 177 } 178 179 @Override 180 public Node selectLast(TraversalContext context) { 181 if (overflow) { 182 return overflowMenu; 183 } 184 return selectPrev(box.getChildren().size() - 1, context); 185 } 186 }); 187 getSkinnable().setImpl_traversalEngine(engine); 188 189 toolbar.focusedProperty().addListener((observable, oldValue, newValue) -> { 190 if (newValue) { 191 // TODO need to detect the focus direction 192 // to selected the first control in the toolbar when TAB is pressed 193 // or select the last control in the toolbar when SHIFT TAB is pressed. 194 if (!box.getChildren().isEmpty()) { 195 box.getChildren().get(0).requestFocus(); 196 } else { 197 overflowMenu.requestFocus(); 198 } 199 } 200 }); 201 202 toolbar.getItems().addListener((ListChangeListener<Node>) c -> { 203 while (c.next()) { 204 for (Node n: c.getRemoved()) { 205 box.getChildren().remove(n); 206 } 207 box.getChildren().addAll(c.getAddedSubList()); 208 } 209 needsUpdate = true; 210 getSkinnable().requestLayout(); 211 }); 212 } 213 214 private DoubleProperty spacing; 215 public final void setSpacing(double value) { 216 spacingProperty().set(snapSpace(value)); 217 } 218 219 public final double getSpacing() { 220 return spacing == null ? 0.0 : snapSpace(spacing.get()); 221 } 222 223 public final DoubleProperty spacingProperty() { 224 if (spacing == null) { 225 spacing = new StyleableDoubleProperty() { 226 227 @Override 228 protected void invalidated() { 229 final double value = get(); 230 if (getSkinnable().getOrientation() == Orientation.VERTICAL) { 231 ((VBox)box).setSpacing(value); 232 } else { 233 ((HBox)box).setSpacing(value); 234 } 235 } 236 237 @Override 238 public Object getBean() { 239 return ToolBarSkin.this; 240 } 241 242 @Override 243 public String getName() { 244 return "spacing"; 245 } 246 247 @Override 248 public CssMetaData<ToolBar,Number> getCssMetaData() { 249 return StyleableProperties.SPACING; 250 } 251 }; 252 } 253 return spacing; 254 } 255 256 private ObjectProperty<Pos> boxAlignment; 257 public final void setBoxAlignment(Pos value) { 258 boxAlignmentProperty().set(value); 259 } 260 261 public final Pos getBoxAlignment() { 262 return boxAlignment == null ? Pos.TOP_LEFT : boxAlignment.get(); 263 } 264 265 public final ObjectProperty<Pos> boxAlignmentProperty() { 266 if (boxAlignment == null) { 267 boxAlignment = new StyleableObjectProperty<Pos>(Pos.TOP_LEFT) { 268 269 @Override 270 public void invalidated() { 271 final Pos value = get(); 272 if (getSkinnable().getOrientation() == Orientation.VERTICAL) { 273 ((VBox)box).setAlignment(value); 274 } else { 275 ((HBox)box).setAlignment(value); 276 } 277 } 278 279 @Override 280 public Object getBean() { 281 return ToolBarSkin.this; 282 } 283 284 @Override 285 public String getName() { 286 return "boxAlignment"; 287 } 288 289 @Override 290 public CssMetaData<ToolBar,Pos> getCssMetaData() { 291 return StyleableProperties.ALIGNMENT; 292 } 293 }; 294 } 295 return boxAlignment; 296 } 297 298 @Override protected void handleControlPropertyChanged(String property) { 299 super.handleControlPropertyChanged(property); 300 if ("ORIENTATION".equals(property)) { 301 initialize(); 302 } 303 } 304 305 @Override protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { 306 final ToolBar toolbar = getSkinnable(); 307 return toolbar.getOrientation() == Orientation.VERTICAL ? 308 computePrefWidth(-1, topInset, rightInset, bottomInset, leftInset) : 309 snapSize(overflowMenu.prefWidth(-1)) + leftInset + rightInset; 310 } 311 312 @Override protected double computeMinHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { 313 final ToolBar toolbar = getSkinnable(); 314 return toolbar.getOrientation() == Orientation.VERTICAL? 315 snapSize(overflowMenu.prefHeight(-1)) + topInset + bottomInset : 316 computePrefHeight(-1, topInset, rightInset, bottomInset, leftInset); 317 } 318 319 @Override protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { 320 double prefWidth = 0; 321 final ToolBar toolbar = getSkinnable(); 322 323 if (toolbar.getOrientation() == Orientation.HORIZONTAL) { 324 for (Node node : toolbar.getItems()) { 325 prefWidth += snapSize(node.prefWidth(-1)) + getSpacing(); 326 } 327 prefWidth -= getSpacing(); 328 } else { 329 for (Node node : toolbar.getItems()) { 330 prefWidth = Math.max(prefWidth, snapSize(node.prefWidth(-1))); 331 } 332 if (toolbar.getItems().size() > 0) { 333 savedPrefWidth = prefWidth; 334 } else { 335 prefWidth = savedPrefWidth; 336 } 337 } 338 return leftInset + prefWidth + rightInset; 339 } 340 341 @Override protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { 342 double prefHeight = 0; 343 final ToolBar toolbar = getSkinnable(); 344 345 if(toolbar.getOrientation() == Orientation.VERTICAL) { 346 for (Node node: toolbar.getItems()) { 347 prefHeight += snapSize(node.prefHeight(-1)) + getSpacing(); 348 } 349 prefHeight -= getSpacing(); 350 } else { 351 for (Node node : toolbar.getItems()) { 352 prefHeight = Math.max(prefHeight, snapSize(node.prefHeight(-1))); 353 } 354 if (toolbar.getItems().size() > 0) { 355 savedPrefHeight = prefHeight; 356 } else { 357 prefHeight = savedPrefHeight; 358 } 359 } 360 return topInset + prefHeight + bottomInset; 361 } 362 363 @Override protected double computeMaxWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { 364 return getSkinnable().getOrientation() == Orientation.VERTICAL ? 365 snapSize(getSkinnable().prefWidth(-1)) : Double.MAX_VALUE; 366 } 367 368 @Override protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { 369 return getSkinnable().getOrientation() == Orientation.VERTICAL ? 370 Double.MAX_VALUE : snapSize(getSkinnable().prefHeight(-1)); 371 } 372 373 @Override protected void layoutChildren(final double x,final double y, 374 final double w, final double h) { 375 // super.layoutChildren(); 376 final ToolBar toolbar = getSkinnable(); 377 378 if (toolbar.getOrientation() == Orientation.VERTICAL) { 379 if (snapSize(toolbar.getHeight()) != previousHeight || needsUpdate) { 380 ((VBox)box).setSpacing(getSpacing()); 381 ((VBox)box).setAlignment(getBoxAlignment()); 382 previousHeight = snapSize(toolbar.getHeight()); 383 addNodesToToolBar(); 384 } 385 } else { 386 if (snapSize(toolbar.getWidth()) != previousWidth || needsUpdate) { 387 ((HBox)box).setSpacing(getSpacing()); 388 ((HBox)box).setAlignment(getBoxAlignment()); 389 previousWidth = snapSize(toolbar.getWidth()); 390 addNodesToToolBar(); 391 } 392 } 393 needsUpdate = false; 394 395 double toolbarWidth = w; 396 double toolbarHeight = h; 397 398 if (getSkinnable().getOrientation() == Orientation.VERTICAL) { 399 toolbarHeight -= (overflow ? snapSize(overflowMenu.prefHeight(-1)) : 0); 400 } else { 401 toolbarWidth -= (overflow ? snapSize(overflowMenu.prefWidth(-1)) : 0); 402 } 403 404 box.resize(toolbarWidth, toolbarHeight); 405 positionInArea(box, x, y, 406 toolbarWidth, toolbarHeight, /*baseline ignored*/0, HPos.CENTER, VPos.CENTER); 407 408 // If popup menu is not null show the overflowControl 409 if (overflow) { 410 double overflowMenuWidth = snapSize(overflowMenu.prefWidth(-1)); 411 double overflowMenuHeight = snapSize(overflowMenu.prefHeight(-1)); 412 double overflowX = x; 413 double overflowY = x; 414 if (getSkinnable().getOrientation() == Orientation.VERTICAL) { 415 // This is to prevent the overflow menu from moving when there 416 // are no items in the toolbar. 417 if (toolbarWidth == 0) { 418 toolbarWidth = savedPrefWidth; 419 } 420 HPos pos = ((VBox)box).getAlignment().getHpos(); 421 if (HPos.LEFT.equals(pos)) { 422 overflowX = x + Math.abs((toolbarWidth - overflowMenuWidth)/2); 423 } else if (HPos.RIGHT.equals(pos)) { 424 overflowX = (snapSize(toolbar.getWidth()) - snappedRightInset() - toolbarWidth) + 425 Math.abs((toolbarWidth - overflowMenuWidth)/2); 426 } else { 427 overflowX = x + 428 Math.abs((snapSize(toolbar.getWidth()) - (x) + 429 snappedRightInset() - overflowMenuWidth)/2); 430 } 431 overflowY = snapSize(toolbar.getHeight()) - overflowMenuHeight - y; 432 } else { 433 // This is to prevent the overflow menu from moving when there 434 // are no items in the toolbar. 435 if (toolbarHeight == 0) { 436 toolbarHeight = savedPrefHeight; 437 } 438 VPos pos = ((HBox)box).getAlignment().getVpos(); 439 if (VPos.TOP.equals(pos)) { 440 overflowY = y + 441 Math.abs((toolbarHeight - overflowMenuHeight)/2); 442 } else if (VPos.BOTTOM.equals(pos)) { 443 overflowY = (snapSize(toolbar.getHeight()) - snappedBottomInset() - toolbarHeight) + 444 Math.abs((toolbarHeight - overflowMenuHeight)/2); 445 } else { 446 overflowY = y + Math.abs((toolbarHeight - overflowMenuHeight)/2); 447 } 448 overflowX = snapSize(toolbar.getWidth()) - overflowMenuWidth - snappedRightInset(); 449 } 450 overflowMenu.resize(overflowMenuWidth, overflowMenuHeight); 451 positionInArea(overflowMenu, overflowX, overflowY, overflowMenuWidth, overflowMenuHeight, /*baseline ignored*/0, 452 HPos.CENTER, VPos.CENTER); 453 } 454 } 455 456 private void initialize() { 457 if (getSkinnable().getOrientation() == Orientation.VERTICAL) { 458 box = new VBox(); 459 } else { 460 box = new HBox(); 461 } 462 box.getStyleClass().add("container"); 463 box.getChildren().addAll(getSkinnable().getItems()); 464 overflowMenu = new ToolBarOverflowMenu(overflowMenuItems); 465 overflowMenu.setVisible(false); 466 overflowMenu.setManaged(false); 467 468 getChildren().clear(); 469 getChildren().add(box); 470 getChildren().add(overflowMenu); 471 472 previousWidth = 0; 473 previousHeight = 0; 474 savedPrefWidth = 0; 475 savedPrefHeight = 0; 476 needsUpdate = true; 477 getSkinnable().requestLayout(); 478 } 479 480 private void addNodesToToolBar() { 481 final ToolBar toolbar = getSkinnable(); 482 double length = 0; 483 if (getSkinnable().getOrientation() == Orientation.VERTICAL) { 484 length = snapSize(toolbar.getHeight()) - snappedTopInset() - snappedBottomInset() + getSpacing(); 485 } else { 486 length = snapSize(toolbar.getWidth()) - snappedLeftInset() - snappedRightInset() + getSpacing(); 487 } 488 489 // Is there overflow ? 490 double x = 0; 491 boolean hasOverflow = false; 492 for (Node node : getSkinnable().getItems()) { 493 if (getSkinnable().getOrientation() == Orientation.VERTICAL) { 494 x += snapSize(node.prefHeight(-1)) + getSpacing(); 495 } else { 496 x += snapSize(node.prefWidth(-1)) + getSpacing(); 497 } 498 if (x > length) { 499 hasOverflow = true; 500 break; 501 } 502 } 503 504 if (hasOverflow) { 505 if (getSkinnable().getOrientation() == Orientation.VERTICAL) { 506 length -= snapSize(overflowMenu.prefHeight(-1)); 507 } else { 508 length -= snapSize(overflowMenu.prefWidth(-1)); 509 } 510 length -= getSpacing(); 511 } 512 513 // Determine which node goes to the toolbar and which goes to the overflow. 514 x = 0; 515 overflowMenuItems.clear(); 516 box.getChildren().clear(); 517 for (Node node : getSkinnable().getItems()) { 518 node.getStyleClass().remove("menu-item"); 519 node.getStyleClass().remove("custom-menu-item"); 520 if (getSkinnable().getOrientation() == Orientation.VERTICAL) { 521 x += snapSize(node.prefHeight(-1)) + getSpacing(); 522 } else { 523 x += snapSize(node.prefWidth(-1)) + getSpacing(); 524 } 525 if (x <= length) { 526 box.getChildren().add(node); 527 } else { 528 if (node.isFocused()) { 529 if (!box.getChildren().isEmpty()) { 530 Node last = engine.selectLast(); 531 if (last != null) { 532 last.requestFocus(); 533 } 534 } else { 535 overflowMenu.requestFocus(); 536 } 537 } 538 if (node instanceof Separator) { 539 overflowMenuItems.add(new SeparatorMenuItem()); 540 } else { 541 CustomMenuItem customMenuItem = new CustomMenuItem(node); 542 543 // RT-36455: 544 // We can't be totally certain of all nodes, but for the 545 // most common nodes we can check to see whether we should 546 // hide the menu when the node is clicked on. The common 547 // case is for TextField or Slider. 548 // This list won't be exhaustive (there is no point really 549 // considering the ListView case), but it should try to 550 // include most common control types that find themselves 551 // placed in menus. 552 final String nodeType = node.getTypeSelector(); 553 switch (nodeType) { 554 case "Button": 555 case "Hyperlink": 556 case "Label": 557 customMenuItem.setHideOnClick(true); 558 break; 559 case "CheckBox": 560 case "ChoiceBox": 561 case "ColorPicker": 562 case "ComboBox": 563 case "DatePicker": 564 case "MenuButton": 565 case "PasswordField": 566 case "RadioButton": 567 case "ScrollBar": 568 case "ScrollPane": 569 case "Slider": 570 case "SplitMenuButton": 571 case "SplitPane": 572 case "TextArea": 573 case "TextField": 574 case "ToggleButton": 575 case "ToolBar": 576 customMenuItem.setHideOnClick(false); 577 break; 578 } 579 580 overflowMenuItems.add(customMenuItem); 581 } 582 } 583 } 584 585 // Check if we overflowed. 586 overflow = overflowMenuItems.size() > 0; 587 if (!overflow && overflowMenu.isFocused()) { 588 Node last = engine.selectLast(); 589 if (last != null) { 590 last.requestFocus(); 591 } 592 } 593 overflowMenu.setVisible(overflow); 594 overflowMenu.setManaged(overflow); 595 } 596 597 class ToolBarOverflowMenu extends StackPane { 598 private StackPane downArrow; 599 private ContextMenu popup; 600 private ObservableList<MenuItem> menuItems; 601 602 public ToolBarOverflowMenu(ObservableList<MenuItem> items) { 603 getStyleClass().setAll("tool-bar-overflow-button"); 604 setFocusTraversable(true); 605 this.menuItems = items; 606 downArrow = new StackPane(); 607 downArrow.getStyleClass().setAll("arrow"); 608 downArrow.setOnMousePressed(me -> { 609 fire(); 610 }); 611 612 setOnKeyPressed(ke -> { 613 if (KeyCode.SPACE.equals(ke.getCode())) { 614 if (!popup.isShowing()) { 615 popup.getItems().clear(); 616 popup.getItems().addAll(menuItems); 617 popup.show(downArrow, Side.BOTTOM, 0, 0); 618 } 619 ke.consume(); 620 } else if (KeyCode.ESCAPE.equals(ke.getCode())) { 621 if (popup.isShowing()) { 622 popup.hide(); 623 } 624 ke.consume(); 625 } else if (KeyCode.ENTER.equals(ke.getCode())) { 626 fire(); 627 ke.consume(); 628 } 629 }); 630 631 focusedProperty().addListener((observable, oldValue, newValue) -> { 632 if (newValue) { 633 if (!popup.isShowing()) { 634 popup.getItems().clear(); 635 popup.getItems().addAll(menuItems); 636 popup.show(downArrow, Side.BOTTOM, 0, 0); 637 } 638 } else { 639 if (popup.isShowing()) { 640 popup.hide(); 641 } 642 } 643 }); 644 645 visibleProperty().addListener((observable, oldValue, newValue) -> { 646 if (newValue) { 647 if (box.getChildren().isEmpty()) { 648 setFocusTraversable(true); 649 } 650 } 651 }); 652 popup = new ContextMenu(); 653 setVisible(false); 654 setManaged(false); 655 getChildren().add(downArrow); 656 } 657 658 private void fire() { 659 if (popup.isShowing()) { 660 popup.hide(); 661 } else { 662 popup.getItems().clear(); 663 popup.getItems().addAll(menuItems); 664 popup.show(downArrow, Side.BOTTOM, 0, 0); 665 } 666 } 667 668 @Override protected double computePrefWidth(double height) { 669 return snappedLeftInset() + snappedRightInset(); 670 } 671 672 @Override protected double computePrefHeight(double width) { 673 return snappedTopInset() + snappedBottomInset(); 674 } 675 676 @Override protected void layoutChildren() { 677 double w = snapSize(downArrow.prefWidth(-1)); 678 double h = snapSize(downArrow.prefHeight(-1)); 679 double x = (snapSize(getWidth()) - w)/2; 680 double y = (snapSize(getHeight()) - h)/2; 681 682 // TODO need to provide support for when the toolbar is on the right 683 // or bottom 684 if (getSkinnable().getOrientation() == Orientation.VERTICAL) { 685 downArrow.setRotate(0); 686 } 687 688 downArrow.resize(w, h); 689 positionInArea(downArrow, x, y, w, h, 690 /*baseline ignored*/0, HPos.CENTER, VPos.CENTER); 691 } 692 693 @Override public Object accGetAttribute(Attribute attribute, Object... parameters) { 694 switch (attribute) { 695 case ROLE: return Role.BUTTON; 696 case TITLE: return "Overflow button"; 697 default: return super.accGetAttribute(attribute, parameters); 698 } 699 } 700 701 @Override public void accExecuteAction(Action action, Object... parameters) { 702 switch (action) { 703 case FIRE: fire(); break; 704 default: super.accExecuteAction(action); break; 705 } 706 } 707 } 708 709 /*************************************************************************** 710 * * 711 * Stylesheet Handling * 712 * * 713 **************************************************************************/ 714 715 /** 716 * Super-lazy instantiation pattern from Bill Pugh. 717 * @treatAsPrivate implementation detail 718 */ 719 private static class StyleableProperties { 720 private static final CssMetaData<ToolBar,Number> SPACING = 721 new CssMetaData<ToolBar,Number>("-fx-spacing", 722 SizeConverter.getInstance(), 0.0) { 723 724 @Override 725 public boolean isSettable(ToolBar n) { 726 final ToolBarSkin skin = (ToolBarSkin) n.getSkin(); 727 return skin.spacing == null || !skin.spacing.isBound(); 728 } 729 730 @Override 731 public StyleableProperty<Number> getStyleableProperty(ToolBar n) { 732 final ToolBarSkin skin = (ToolBarSkin) n.getSkin(); 733 return (StyleableProperty<Number>)(WritableValue<Number>)skin.spacingProperty(); 734 } 735 }; 736 737 private static final CssMetaData<ToolBar,Pos>ALIGNMENT = 738 new CssMetaData<ToolBar,Pos>("-fx-alignment", 739 new EnumConverter<Pos>(Pos.class), Pos.TOP_LEFT ) { 740 741 @Override 742 public boolean isSettable(ToolBar n) { 743 final ToolBarSkin skin = (ToolBarSkin) n.getSkin(); 744 return skin.boxAlignment == null || !skin.boxAlignment.isBound(); 745 } 746 747 @Override 748 public StyleableProperty<Pos> getStyleableProperty(ToolBar n) { 749 final ToolBarSkin skin = (ToolBarSkin) n.getSkin(); 750 return (StyleableProperty<Pos>)(WritableValue<Pos>)skin.boxAlignmentProperty(); 751 } 752 }; 753 754 755 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 756 static { 757 758 final List<CssMetaData<? extends Styleable, ?>> styleables = 759 new ArrayList<CssMetaData<? extends Styleable, ?>>(SkinBase.getClassCssMetaData()); 760 761 // StackPane also has -fx-alignment. Replace it with 762 // ToolBarSkin's. 763 // TODO: Really should be able to reference StackPane.StyleableProperties.ALIGNMENT 764 final String alignmentProperty = ALIGNMENT.getProperty(); 765 for (int n=0, nMax=styleables.size(); n<nMax; n++) { 766 final CssMetaData<?,?> prop = styleables.get(n); 767 if (alignmentProperty.equals(prop.getProperty())) styleables.remove(prop); 768 } 769 770 styleables.add(SPACING); 771 styleables.add(ALIGNMENT); 772 STYLEABLES = Collections.unmodifiableList(styleables); 773 774 } 775 } 776 777 /** 778 * @return The CssMetaData associated with this class, which may include the 779 * CssMetaData of its super classes. 780 */ 781 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 782 return StyleableProperties.STYLEABLES; 783 } 784 785 /** 786 * {@inheritDoc} 787 */ 788 @Override 789 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { 790 return getClassCssMetaData(); 791 } 792 793 @Override protected Object accGetAttribute(Attribute attribute, Object... parameters) { 794 switch (attribute) { 795 case OVERFLOW_BUTTON: return overflowMenu; 796 default: return super.accGetAttribute(attribute, parameters); 797 } 798 } 799 800 @Override protected void accExecuteAction(Action action, Object... parameters) { 801 switch (action) { 802 case SHOW_MENU: 803 overflowMenu.fire(); 804 break; 805 default: super.accExecuteAction(action, parameters); 806 } 807 } 808 }