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 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) { 116 Node n = box.getChildren().get(i); 117 if (n.isDisabled() || !n.impl_isTreeVisible()) continue; 118 if (n.isFocusTraversable()) { 119 return n; 120 } 121 if (n instanceof Parent) { 122 Node selected = context.selectFirstInParent((Parent)n); 123 if (selected != null) return selected; 124 } 125 } 126 return null; 127 } 128 129 @Override 130 public Node select(Node owner, Direction dir, TraversalContext context) { 131 final ObservableList<Node> boxChildren = box.getChildren(); 132 if (owner == overflowMenu) { 133 if (dir.isForward()) { 134 return null; 135 } else { 136 Node selected = selectPrev(boxChildren.size() - 1, context); 137 if (selected != null) return selected; 138 } 139 } 140 141 int idx = boxChildren.indexOf(owner); 142 143 if (idx < 0) { 144 // The current focus owner is a child of some Toolbar's item 145 Parent item = owner.getParent(); 146 while (!boxChildren.contains(item)) { 147 item = item.getParent(); 148 } 149 Node selected = context.selectInSubtree(item, owner, dir); 150 if (selected != null) return selected; 151 idx = boxChildren.indexOf(owner); 152 if (dir == Direction.NEXT) dir = Direction.NEXT_IN_LINE; 153 } 154 155 if (idx >= 0) { 156 if (dir.isForward()) { 157 Node selected = selectNext(idx + 1, context); 158 if (selected != null) return selected; 159 if (overflow) { 160 overflowMenu.requestFocus(); 161 return overflowMenu; 162 } 163 } else { 164 Node selected = selectPrev(idx - 1, context); 165 if (selected != null) return selected; 166 } 167 } 168 return null; 169 } 170 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 } 395 needsUpdate = false; 396 397 double toolbarWidth = w; 398 double toolbarHeight = h; 399 400 if (getSkinnable().getOrientation() == Orientation.VERTICAL) { 401 toolbarHeight -= (overflow ? snapSize(overflowMenu.prefHeight(-1)) : 0); 402 } else { 403 toolbarWidth -= (overflow ? snapSize(overflowMenu.prefWidth(-1)) : 0); 404 } 405 406 box.resize(toolbarWidth, toolbarHeight); 407 positionInArea(box, x, y, 408 toolbarWidth, toolbarHeight, /*baseline ignored*/0, HPos.CENTER, VPos.CENTER); 409 410 // If popup menu is not null show the overflowControl 411 if (overflow) { 412 double overflowMenuWidth = snapSize(overflowMenu.prefWidth(-1)); 413 double overflowMenuHeight = snapSize(overflowMenu.prefHeight(-1)); 414 double overflowX = x; 415 double overflowY = x; 416 if (getSkinnable().getOrientation() == Orientation.VERTICAL) { 417 // This is to prevent the overflow menu from moving when there 418 // are no items in the toolbar. 419 if (toolbarWidth == 0) { 420 toolbarWidth = savedPrefWidth; 421 } 422 HPos pos = ((VBox)box).getAlignment().getHpos(); 423 if (HPos.LEFT.equals(pos)) { 424 overflowX = x + Math.abs((toolbarWidth - overflowMenuWidth)/2); 425 } else if (HPos.RIGHT.equals(pos)) { 426 overflowX = (snapSize(toolbar.getWidth()) - snappedRightInset() - toolbarWidth) + 427 Math.abs((toolbarWidth - overflowMenuWidth)/2); 428 } else { 429 overflowX = x + 430 Math.abs((snapSize(toolbar.getWidth()) - (x) + 431 snappedRightInset() - overflowMenuWidth)/2); 432 } 433 overflowY = snapSize(toolbar.getHeight()) - overflowMenuHeight - y; 434 } else { 435 // This is to prevent the overflow menu from moving when there 436 // are no items in the toolbar. 437 if (toolbarHeight == 0) { 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; 478 needsUpdate = true; 479 getSkinnable().requestLayout(); 480 } 481 482 private void addNodesToToolBar() { 483 final ToolBar toolbar = getSkinnable(); 484 double length = 0; 485 if (getSkinnable().getOrientation() == Orientation.VERTICAL) { 486 length = snapSize(toolbar.getHeight()) - snappedTopInset() - snappedBottomInset() + getSpacing(); 487 } else { 488 length = snapSize(toolbar.getWidth()) - snappedLeftInset() - snappedRightInset() + getSpacing(); 489 } 490 491 // Is there overflow ? 492 double x = 0; 493 boolean hasOverflow = false; 494 for (Node node : getSkinnable().getItems()) { 495 if (getSkinnable().getOrientation() == Orientation.VERTICAL) { 496 x += snapSize(node.prefHeight(-1)) + getSpacing(); 497 } else { 498 x += snapSize(node.prefWidth(-1)) + getSpacing(); 499 } 500 if (x > length) { 501 hasOverflow = true; 502 break; 503 } 504 } 505 506 if (hasOverflow) { 507 if (getSkinnable().getOrientation() == Orientation.VERTICAL) { 508 length -= snapSize(overflowMenu.prefHeight(-1)); 509 } else { 510 length -= snapSize(overflowMenu.prefWidth(-1)); 511 } 512 length -= getSpacing(); 513 } 514 515 // Determine which node goes to the toolbar and which goes to the overflow. 516 x = 0; 517 overflowMenuItems.clear(); 518 box.getChildren().clear(); 519 for (Node node : getSkinnable().getItems()) { 520 node.getStyleClass().remove("menu-item"); 521 node.getStyleClass().remove("custom-menu-item"); 522 if (getSkinnable().getOrientation() == Orientation.VERTICAL) { 523 x += snapSize(node.prefHeight(-1)) + getSpacing(); 524 } else { 525 x += snapSize(node.prefWidth(-1)) + getSpacing(); 526 } 527 if (x <= length) { 528 box.getChildren().add(node); 529 } else { 530 if (node.isFocused()) { 531 if (!box.getChildren().isEmpty()) { 532 Node last = engine.selectLast(); 533 if (last != null) { 534 last.requestFocus(); 535 } 536 } else { 537 overflowMenu.requestFocus(); 538 } 539 } 540 if (node instanceof Separator) { 541 overflowMenuItems.add(new SeparatorMenuItem()); 542 } else { 543 CustomMenuItem customMenuItem = new CustomMenuItem(node); 544 545 // RT-36455: 546 // We can't be totally certain of all nodes, but for the 547 // most common nodes we can check to see whether we should 548 // hide the menu when the node is clicked on. The common 549 // case is for TextField or Slider. 550 // This list won't be exhaustive (there is no point really 551 // considering the ListView case), but it should try to 552 // include most common control types that find themselves 553 // placed in menus. 554 final String nodeType = node.getTypeSelector(); 555 switch (nodeType) { 556 case "Button": 557 case "Hyperlink": 558 case "Label": 559 customMenuItem.setHideOnClick(true); 560 break; 561 case "CheckBox": 562 case "ChoiceBox": 563 case "ColorPicker": 564 case "ComboBox": 565 case "DatePicker": 566 case "MenuButton": 567 case "PasswordField": 568 case "RadioButton": 569 case "ScrollBar": 570 case "ScrollPane": 571 case "Slider": 572 case "SplitMenuButton": 573 case "SplitPane": 574 case "TextArea": 575 case "TextField": 576 case "ToggleButton": 577 case "ToolBar": 578 customMenuItem.setHideOnClick(false); 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 setFocusTraversable(true); 607 this.menuItems = items; 608 downArrow = new StackPane(); 609 downArrow.getStyleClass().setAll("arrow"); 610 downArrow.setOnMousePressed(me -> { 611 fire(); 612 }); 613 614 setOnKeyPressed(ke -> { 615 if (KeyCode.SPACE.equals(ke.getCode())) { 616 if (!popup.isShowing()) { 617 popup.getItems().clear(); 618 popup.getItems().addAll(menuItems); 619 popup.show(downArrow, Side.BOTTOM, 0, 0); 620 } 621 ke.consume(); 622 } else if (KeyCode.ESCAPE.equals(ke.getCode())) { 623 if (popup.isShowing()) { 624 popup.hide(); 625 } 626 ke.consume(); 627 } else if (KeyCode.ENTER.equals(ke.getCode())) { 628 fire(); 629 ke.consume(); 630 } 631 }); 632 633 focusedProperty().addListener((observable, oldValue, newValue) -> { 634 if (newValue) { 635 if (!popup.isShowing()) { 636 popup.getItems().clear(); 637 popup.getItems().addAll(menuItems); 638 popup.show(downArrow, Side.BOTTOM, 0, 0); 639 } 640 } else { 641 if (popup.isShowing()) { 642 popup.hide(); 643 } 644 } 645 }); 646 647 visibleProperty().addListener((observable, oldValue, newValue) -> { 648 if (newValue) { 649 if (box.getChildren().isEmpty()) { 650 setFocusTraversable(true); 651 } 652 } 653 }); 654 popup = new ContextMenu(); 655 setVisible(false); 656 setManaged(false); 657 getChildren().add(downArrow); 658 } 659 660 private void fire() { 661 if (popup.isShowing()) { 662 popup.hide(); 663 } else { 664 popup.getItems().clear(); 665 popup.getItems().addAll(menuItems); 666 popup.show(downArrow, Side.BOTTOM, 0, 0); 667 } 668 } 669 670 @Override protected double computePrefWidth(double height) { 671 return snappedLeftInset() + snappedRightInset(); 672 } 673 674 @Override protected double computePrefHeight(double width) { 675 return snappedTopInset() + snappedBottomInset(); 676 } 677 678 @Override protected void layoutChildren() { 679 double w = snapSize(downArrow.prefWidth(-1)); 680 double h = snapSize(downArrow.prefHeight(-1)); 681 double x = (snapSize(getWidth()) - w)/2; 682 double y = (snapSize(getHeight()) - h)/2; 683 684 // TODO need to provide support for when the toolbar is on the right 685 // or bottom 686 if (getSkinnable().getOrientation() == Orientation.VERTICAL) { 687 downArrow.setRotate(0); 688 } 689 690 downArrow.resize(w, h); 691 positionInArea(downArrow, x, y, w, h, 692 /*baseline ignored*/0, HPos.CENTER, VPos.CENTER); 693 } 694 695 @Override public Object accGetAttribute(Attribute attribute, Object... parameters) { 696 switch (attribute) { 697 case ROLE: return Role.BUTTON; 698 case TITLE: return getString("Accessibility.title.ToolBar.OverflowButton"); 699 default: return super.accGetAttribute(attribute, parameters); 700 } 701 } 702 703 @Override public void accExecuteAction(Action action, Object... parameters) { 704 switch (action) { 705 case FIRE: fire(); break; 706 default: super.accExecuteAction(action); break; 707 } 708 } 709 } 710 711 /*************************************************************************** 712 * * 713 * Stylesheet Handling * 714 * * 715 **************************************************************************/ 716 717 /** 718 * Super-lazy instantiation pattern from Bill Pugh. 719 * @treatAsPrivate implementation detail 720 */ 721 private static class StyleableProperties { 722 private static final CssMetaData<ToolBar,Number> SPACING = 723 new CssMetaData<ToolBar,Number>("-fx-spacing", 724 SizeConverter.getInstance(), 0.0) { 725 726 @Override 727 public boolean isSettable(ToolBar n) { 728 final ToolBarSkin skin = (ToolBarSkin) n.getSkin(); 729 return skin.spacing == null || !skin.spacing.isBound(); 730 } 731 732 @Override 733 public StyleableProperty<Number> getStyleableProperty(ToolBar n) { 734 final ToolBarSkin skin = (ToolBarSkin) n.getSkin(); 735 return (StyleableProperty<Number>)(WritableValue<Number>)skin.spacingProperty(); 736 } 737 }; 738 739 private static final CssMetaData<ToolBar,Pos>ALIGNMENT = 740 new CssMetaData<ToolBar,Pos>("-fx-alignment", 741 new EnumConverter<Pos>(Pos.class), Pos.TOP_LEFT ) { 742 743 @Override 744 public boolean isSettable(ToolBar n) { 745 final ToolBarSkin skin = (ToolBarSkin) n.getSkin(); 746 return skin.boxAlignment == null || !skin.boxAlignment.isBound(); 747 } 748 749 @Override 750 public StyleableProperty<Pos> getStyleableProperty(ToolBar n) { 751 final ToolBarSkin skin = (ToolBarSkin) n.getSkin(); 752 return (StyleableProperty<Pos>)(WritableValue<Pos>)skin.boxAlignmentProperty(); 753 } 754 }; 755 756 757 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 758 static { 759 760 final List<CssMetaData<? extends Styleable, ?>> styleables = 761 new ArrayList<CssMetaData<? extends Styleable, ?>>(SkinBase.getClassCssMetaData()); 762 763 // StackPane also has -fx-alignment. Replace it with 764 // ToolBarSkin's. 765 // TODO: Really should be able to reference StackPane.StyleableProperties.ALIGNMENT 766 final String alignmentProperty = ALIGNMENT.getProperty(); 767 for (int n=0, nMax=styleables.size(); n<nMax; n++) { 768 final CssMetaData<?,?> prop = styleables.get(n); 769 if (alignmentProperty.equals(prop.getProperty())) styleables.remove(prop); 770 } 771 772 styleables.add(SPACING); 773 styleables.add(ALIGNMENT); 774 STYLEABLES = Collections.unmodifiableList(styleables); 775 776 } 777 } 778 779 /** 780 * @return The CssMetaData associated with this class, which may include the 781 * CssMetaData of its super classes. 782 */ 783 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 784 return StyleableProperties.STYLEABLES; 785 } 786 787 /** 788 * {@inheritDoc} 789 */ 790 @Override 791 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { 792 return getClassCssMetaData(); 793 } 794 795 @Override protected Object accGetAttribute(Attribute attribute, Object... parameters) { 796 switch (attribute) { 797 case OVERFLOW_BUTTON: return overflowMenu; 798 default: return super.accGetAttribute(attribute, parameters); 799 } 800 } 801 802 @Override protected void accExecuteAction(Action action, Object... parameters) { 803 switch (action) { 804 case SHOW_MENU: 805 overflowMenu.fire(); 806 break; 807 default: super.accExecuteAction(action, parameters); 808 } 809 } 810 }