1 /* 2 * Copyright (c) 2014, 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 package javafx.scene.control; 26 27 import javafx.beans.InvalidationListener; 28 import javafx.beans.Observable; 29 import javafx.beans.property.DoubleProperty; 30 import javafx.beans.property.ObjectProperty; 31 import javafx.beans.property.SimpleDoubleProperty; 32 import javafx.beans.property.SimpleObjectProperty; 33 import javafx.beans.property.SimpleStringProperty; 34 import javafx.beans.property.StringProperty; 35 import javafx.collections.FXCollections; 36 import javafx.collections.ObservableList; 37 import javafx.scene.Node; 38 import javafx.scene.layout.HBox; 39 40 import com.sun.javafx.util.Utils; 41 import com.sun.javafx.scene.control.skin.ButtonBarSkin; 42 import com.sun.javafx.scene.traversal.Algorithm; 43 import com.sun.javafx.scene.traversal.Direction; 44 import com.sun.javafx.scene.traversal.ParentTraversalEngine; 45 import com.sun.javafx.scene.traversal.TraversalContext; 46 import javafx.beans.value.WritableValue; 47 import javafx.css.StyleableProperty; 48 49 import java.util.Map; 50 51 /** 52 * A ButtonBar is essentially a {@link HBox}, with the additional functionality 53 * for operating system specific button placement. In other words, any Node may 54 * be annotated (via the {@link ButtonBar#setButtonData(Node, ButtonData)} 55 * method, placed inside a ButtonBar (via the {@link #getButtons()} list), and will 56 * then be positioned relative to all other nodes in the button list based on their 57 * annotations, as well as the overarching 58 * {@link #buttonOrderProperty() button order} specified for the ButtonBar. 59 * 60 * <strong>Uniform button sizing</strong> 61 * <p>By default all buttons are uniformly sized in a ButtonBar, meaning that all 62 * buttons take the width of the widest button. It is possible to opt-out of this 63 * on a per-button basis, but calling the {@link #setButtonUniformSize(Node, boolean)} method with 64 * a boolean value of false. 65 * 66 * <p>If a button is excluded from uniform sizing, it is both excluded from 67 * being resized away from its preferred size, and also excluded from the 68 * measuring process, so its size will not influence the maximum size calculated 69 * for all buttons in the ButtonBar. 70 * 71 * <h3>Screenshots</h3> 72 * <p>Because a ButtonBar comes with built-in support for Windows, Mac OS 73 * and Linux, there are three screenshots shown below, with the same buttons 74 * laid out on each of the three operating systems. 75 * 76 * <p> 77 * <strong>Windows:</strong><br/><img src="doc-files/buttonBar-windows.png" /><br> 78 * <strong>Mac OS:</strong><br/><img src="doc-files/buttonBar-mac.png" /><br> 79 * <strong>Linux:</strong><br/><img src="doc-files/buttonBar-linux.png" /><br> 80 * 81 * <h3>Code Samples</h3> 82 * <p>Instantiating and using the ButtonBar is simple, simply do the following: 83 * 84 * <pre> 85 * {@code 86 * // Create the ButtonBar instance 87 * ButtonBar buttonBar = new ButtonBar(); 88 * 89 * // Create the buttons to go into the ButtonBar 90 * Button yesButton = new Button("Yes"); 91 * ButtonBar.setButtonData(yesButton, ButtonData.YES); 92 * 93 * Button noButton = new Button("No"); 94 * ButtonBar.setButtonData(noButton, ButtonData.NO); 95 * 96 * // Add buttons to the ButtonBar 97 * buttonBar.getButtons().addAll(yesButton, noButton); 98 * }</pre> 99 * 100 * <p>The code sample above will position the Yes and No buttons relative to the 101 * users operating system. This means that on Windows and Linux the Yes button 102 * will come before the No button, whereas on Mac OS it'll be No and then Yes. 103 * 104 * <p>In most cases the OS-specific layout is the best choice, but in cases 105 * where you want a custom layout, this is achieved be modifying the 106 * {@link #buttonOrderProperty() button order property}. These are cryptic-looking 107 * strings that are shorthand representations for the button order. The built-in 108 * orders for Windows, Mac OS and Linux are: 109 * 110 * <table border="0"> 111 * <tr> 112 * <td width="75"><strong>Windows:</strong></td> 113 * <td>L_E+U+FBXI_YNOCAH_R</td> 114 * </tr> 115 * <tr> 116 * <td><strong>Mac OS:</strong></td> 117 * <td>L_HE+U+FBIX_NCYOA_R</td> 118 * </tr> 119 * <tr> 120 * <td><strong>Linux:</strong></td> 121 * <td>L_HE+UNYACBXIO_R</td> 122 * </tr> 123 * </table> 124 * 125 * <p>You should refer to the {@link ButtonData} enumeration for a description of 126 * what each of these characters mean. However, if your ButtonBar only consisted 127 * of {@link ButtonData#YES} and {@link ButtonData#NO} buttons, you always 128 * wanted the yes buttons before the no buttons, and you wanted the buttons to 129 * be {@link ButtonData#BIG_GAP right-aligned}, you could do the following: 130 * 131 * <pre> 132 * {@code 133 * // Create the ButtonBar instance 134 * ButtonBar buttonBar = new ButtonBar(); 135 * 136 * // Set the custom button order 137 * buttonBar.setButtonOrder("+YN"); 138 * }</pre> 139 * 140 * @see ButtonData 141 * @since JavaFX 8u40 142 */ 143 public class ButtonBar extends Control { 144 145 // TODO add support for BUTTON_ORDER_NONE 146 // TODO test and document what happens with unexpected button order strings 147 148 /************************************************************************** 149 * 150 * Static fields 151 * 152 **************************************************************************/ 153 154 /** 155 * The default button ordering on Windows. 156 */ 157 public static final String BUTTON_ORDER_WINDOWS = "L_E+U+FBXI_YNOCAH_R"; //$NON-NLS-1$ 158 159 /** 160 * The default button ordering on Mac OS. 161 */ 162 public static final String BUTTON_ORDER_MAC_OS = "L_HE+U+FBIX_NCYOA_R"; //$NON-NLS-1$ 163 164 /** 165 * The default button ordering on Linux (specifically, GNOME). 166 */ 167 public static final String BUTTON_ORDER_LINUX = "L_HE+UNYACBXIO_R"; //$NON-NLS-1$ 168 169 /** 170 * A button ordering string that specifies there is no button ordering. In 171 * other words, buttons will be placed in the order that exist in the 172 * {@link #getButtons()} list. The only aspect of layout that makes this 173 * different than using an HBox is that the buttons are right-aligned. 174 */ 175 public static final String BUTTON_ORDER_NONE = ""; //$NON-NLS-1$ 176 177 178 179 /************************************************************************** 180 * 181 * Static enumerations 182 * 183 **************************************************************************/ 184 185 /** 186 * An enumeration of all available button data annotations. By annotating 187 * every button in a {@link ButtonBar} with one of these annotations, the 188 * buttons will be appropriately positioned relative to all other buttons in 189 * the ButtonBar. 190 * 191 * <p>For details on the button order code for each ButtonData, refer to 192 * the javadoc comment. 193 * 194 * @since JavaFX 8u40 195 */ 196 public static enum ButtonData { 197 /** 198 * Buttons with this style tag will statically end up on the left end of the bar. 199 * 200 * <p><strong>Button order code:</strong> L 201 */ 202 LEFT("L",false,false), //$NON-NLS-1$ 203 204 /** 205 * Buttons with this style tag will statically end up on the right end of the bar. 206 * 207 * <p><strong>Button order code:</strong> R 208 */ 209 RIGHT("R", false, false), //$NON-NLS-1$ 210 211 /** 212 * A tag for the "help" button that normally is supposed to be on the right. 213 * 214 * <p><strong>Button order code:</strong> H 215 */ 216 HELP("H", false, false ), //$NON-NLS-1$ 217 218 /** 219 * A tag for the "help2" button that normally is supposed to be on the left. 220 * 221 * <p><strong>Button order code:</strong> E 222 */ 223 HELP_2("E", false, false), //$NON-NLS-1$ 224 225 /** 226 * A tag for the "yes" button. 227 * 228 * <p><strong>Is default button:</strong> True 229 * <p><strong>Button order code:</strong> Y 230 */ 231 YES("Y", false, true), //$NON-NLS-1$ 232 233 /** 234 * A tag for the "no" button. 235 * 236 * <p><strong>Is cancel button:</strong> True 237 * <p><strong>Button order code:</strong> N 238 */ 239 NO("N", true, false), //$NON-NLS-1$ 240 241 /** 242 * A tag for the "next" or "forward" button. 243 * 244 * <p><strong>Is default button:</strong> True 245 * <p><strong>Button order code:</strong> X 246 */ 247 NEXT_FORWARD("X", false, true), //$NON-NLS-1$ 248 249 /** 250 * A tag for the "back" or "previous" button. 251 * 252 * <p><strong>Button order code:</strong> B 253 */ 254 BACK_PREVIOUS("B", false, false), //$NON-NLS-1$ 255 256 /** 257 * A tag for the "finish". 258 * 259 * <p><strong>Is default button:</strong> True 260 * <p><strong>Button order code:</strong> I 261 */ 262 FINISH("I", false, true), //$NON-NLS-1$ 263 264 /** 265 * A tag for the "apply" button. 266 * 267 * <p><strong>Button order code:</strong> A 268 */ 269 APPLY("A", false, false), //$NON-NLS-1$ 270 271 /** 272 * A tag for the "cancel" or "close" button. 273 * 274 * <p><strong>Is cancel button:</strong> True 275 * <p><strong>Button order code:</strong> C 276 */ 277 CANCEL_CLOSE("C", true, false), //$NON-NLS-1$ 278 279 /** 280 * A tag for the "ok" or "done" button. 281 * 282 * <p><strong>Is default button:</strong> True 283 * <p><strong>Button order code:</strong> O 284 */ 285 OK_DONE("O", false, true), //$NON-NLS-1$ 286 287 /** 288 * All Uncategorized, Other, or "Unknown" buttons. Tag will be "other". 289 * 290 * <p><strong>Button order code:</strong> U 291 */ 292 OTHER("U", false, false), //$NON-NLS-1$ 293 294 295 /** 296 * A glue push gap that will take as much space as it can and at least 297 * an "unrelated" gap. (Platform dependent) 298 * 299 * <p><strong>Button order code:</strong> + 300 */ 301 BIG_GAP("+", false, false), //$NON-NLS-1$ 302 303 /** 304 * An "unrelated" gap. (Platform dependent) 305 * 306 * <p><strong>Button order code:</strong> _ (underscore) 307 */ 308 SMALL_GAP("_", false, false); //$NON-NLS-1$ 309 310 private final String typeCode; 311 312 private final boolean cancelButton; 313 private final boolean defaultButton; 314 315 private ButtonData(String type, boolean cancelButton, boolean defaultButton) { 316 this.typeCode = type; 317 this.cancelButton = cancelButton; 318 this.defaultButton = defaultButton; 319 } 320 321 /** 322 * Returns the single character code used to represent the ButtonData 323 * annotation in the {@link ButtonBar#buttonOrderProperty() button order} 324 * string. 325 */ 326 public String getTypeCode() { 327 return typeCode; 328 } 329 330 /** 331 * Indicates whether buttons created from the ButtonData enumeration 332 * should be the 'cancel' button in the user interface. This typically 333 * means that the button will respond to the escape key press, even if 334 * the button does not have focus. 335 * 336 * <p>ButtonData enumeration values that can be the cancel button have a 337 * comment stating this in their javadoc. 338 */ 339 public final boolean isCancelButton() { 340 return cancelButton; 341 } 342 343 /** 344 * Indicates whether buttons created from the ButtonData enumeration 345 * should be the 'default' button in the user interface. This typically 346 * means that the button will respond to enter key presses, even if the 347 * button does not have focus. 348 * 349 * <p>ButtonData enumeration values that can be the default button have 350 * a comment stating this in their javadoc. 351 */ 352 public final boolean isDefaultButton() { 353 return defaultButton; 354 } 355 } 356 357 358 /** 359 * Sets the given ButtonData on the given button. If this button is 360 * subsequently placed in a {@link ButtonBar} it will be placed in the 361 * correct position relative to all other buttons in the bar. 362 * 363 * @param button The button to annotate with the given {@link ButtonData} value. 364 * @param buttonData The ButtonData to designate the button as. 365 */ 366 public static void setButtonData(Node button, ButtonData buttonData) { 367 final Map<Object,Object> properties = button.getProperties(); 368 final ObjectProperty<ButtonData> property = 369 (ObjectProperty<ButtonData>) properties.getOrDefault( 370 ButtonBarSkin.BUTTON_DATA_PROPERTY, 371 new SimpleObjectProperty<>(button, "buttonData", buttonData)); 372 373 property.set(buttonData); 374 properties.putIfAbsent(ButtonBarSkin.BUTTON_DATA_PROPERTY, property); 375 } 376 377 /** 378 * Returns the previously set ButtonData property on the given button. If this 379 * was never set, this method will return null. 380 * 381 * @param button The button to return the previously set ButtonData for. 382 */ 383 public static ButtonData getButtonData(Node button) { 384 final Map<Object,Object> properties = button.getProperties(); 385 if (properties.containsKey(ButtonBarSkin.BUTTON_DATA_PROPERTY)) { 386 ObjectProperty<ButtonData> property = (ObjectProperty<ButtonData>) properties.get(ButtonBarSkin.BUTTON_DATA_PROPERTY); 387 return property == null ? null : property.get(); 388 } 389 return null; 390 } 391 392 /** 393 * By default all buttons are uniformly sized in a ButtonBar, meaning that all 394 * buttons take the width of the widest button. It is possible to opt-out of this 395 * on a per-button basis, but calling the setButtonUniformSize method with 396 * a boolean value of false. 397 * 398 * <p>If a button is excluded from uniform sizing, it is both excluded from 399 * being resized away from its preferred size, and also excluded from the 400 * measuring process, so its size will not influence the maximum size calculated 401 * for all buttons in the ButtonBar. 402 * 403 * @param button The button to include / exclude from uniform sizing. 404 * @param uniformSize Boolean true to force uniform sizing on the button, 405 * false to exclude the button from uniform sizing. 406 */ 407 public static void setButtonUniformSize(Node button, boolean uniformSize) { 408 // we store the false, but remove the true (as the isButtonUniformSize 409 // method returns true by default) 410 if (uniformSize) { 411 button.getProperties().remove(ButtonBarSkin.BUTTON_SIZE_INDEPENDENCE); 412 } else { 413 button.getProperties().put(ButtonBarSkin.BUTTON_SIZE_INDEPENDENCE, uniformSize); 414 } 415 } 416 417 /** 418 * Returns whether the given node is part of the uniform sizing calculations 419 * or not. By default all nodes that have not opted out (via 420 * {@link #setButtonUniformSize(Node, boolean)}) will return true here. 421 */ 422 public static boolean isButtonUniformSize(Node button) { 423 return (boolean) button.getProperties().getOrDefault(ButtonBarSkin.BUTTON_SIZE_INDEPENDENCE, true); 424 } 425 426 427 428 /************************************************************************** 429 * 430 * Private fields 431 * 432 **************************************************************************/ 433 434 private ObservableList<Node> buttons = FXCollections.<Node>observableArrayList(); 435 436 437 438 /************************************************************************** 439 * 440 * Constructors 441 * 442 **************************************************************************/ 443 444 /** 445 * Creates a default ButtonBar instance using the default properties for 446 * the users operating system. 447 */ 448 public ButtonBar() { 449 this(null); 450 } 451 452 /** 453 * Creates a ButtonBar with the given button order (refer to 454 * {@link #buttonOrderProperty()} for more information). 455 * 456 * @param buttonOrder The button order to use in this button bar instance. 457 */ 458 public ButtonBar(final String buttonOrder) { 459 getStyleClass().add("button-bar"); //$NON-NLS-1$ 460 461 // we allow for the buttons inside the ButtonBar to be focus traversable, 462 // but the ButtonBar itself is not. 463 // focusTraversable is styleable through css. Calling setFocusTraversable 464 // makes it look to css like the user set the value and css will not 465 // override. Initializing focusTraversable by calling set on the 466 // CssMetaData ensures that css will be able to override the value. 467 ((StyleableProperty<Boolean>)(WritableValue<Boolean>)focusTraversableProperty()).applyStyle(null, Boolean.FALSE); 468 469 final boolean buttonOrderEmpty = buttonOrder == null || buttonOrder.isEmpty(); 470 471 if (Utils.isMac()) { 472 setButtonOrder(buttonOrderEmpty ? BUTTON_ORDER_MAC_OS : buttonOrder); 473 setButtonMinWidth(70); 474 } else if (Utils.isUnix()) { 475 setButtonOrder(buttonOrderEmpty ? BUTTON_ORDER_LINUX : buttonOrder); 476 setButtonMinWidth(85); 477 } else { 478 // windows by default 479 setButtonOrder(buttonOrderEmpty ? BUTTON_ORDER_WINDOWS : buttonOrder); 480 setButtonMinWidth(75); 481 } 482 } 483 484 485 486 /************************************************************************** 487 * 488 * Public API 489 * 490 **************************************************************************/ 491 492 /** 493 * {@inheritDoc} 494 */ 495 @Override protected Skin<?> createDefaultSkin() { 496 return new ButtonBarSkin(this); 497 } 498 499 /** 500 * Placing buttons inside this ObservableList will instruct the ButtonBar 501 * to position them relative to each other based on their specified 502 * {@link ButtonData}. To set the ButtonData for a button, simply call 503 * {@link ButtonBar#setButtonData(Node, ButtonData)}, passing in the 504 * relevant ButtonData. 505 * 506 * @return A list containing all buttons currently in the button bar, and 507 * allowing for further buttons to be added or removed. 508 */ 509 public final ObservableList<Node> getButtons() { 510 return buttons; 511 } 512 513 514 515 /************************************************************************** 516 * 517 * Properties 518 * 519 **************************************************************************/ 520 521 // --- Button order 522 /** 523 * The order for the typical buttons in a standard button bar. It is 524 * one letter per {@link ButtonData} enumeration value. Default button orders 525 * for operating systems are also available: {@link #BUTTON_ORDER_WINDOWS}, 526 * {@link #BUTTON_ORDER_MAC_OS}, and {@link #BUTTON_ORDER_LINUX}. 527 */ 528 public final StringProperty buttonOrderProperty() { 529 return buttonOrderProperty; 530 } 531 private final StringProperty buttonOrderProperty = 532 new SimpleStringProperty(this, "buttonOrder"); //$NON-NLS-1$ 533 534 /** 535 * Sets the {@link #buttonOrderProperty() button order} 536 * @param buttonOrder The currently set button order, which by default will 537 * be the OS-specific button order. 538 */ 539 public final void setButtonOrder(String buttonOrder) { 540 buttonOrderProperty.set(buttonOrder); 541 } 542 543 /** 544 * Returns the current {@link #buttonOrderProperty() button order}. 545 * @return The current {@link #buttonOrderProperty() button order}. 546 */ 547 public final String getButtonOrder() { 548 return buttonOrderProperty.get(); 549 } 550 551 552 // --- button min width 553 /** 554 * Specifies the minimum width of all buttons placed in this button bar. 555 */ 556 public final DoubleProperty buttonMinWidthProperty() { 557 return buttonMinWidthProperty; 558 } 559 private final DoubleProperty buttonMinWidthProperty = 560 new SimpleDoubleProperty(this, "buttonMinWidthProperty"); //$NON-NLS-1$ 561 562 /** 563 * Sets the minimum width of all buttons placed in this button bar. 564 */ 565 public final void setButtonMinWidth(double value) { 566 buttonMinWidthProperty.set(value); 567 } 568 569 /** 570 * Returns the minimum width of all buttons placed in this button bar. 571 */ 572 public final double getButtonMinWidth() { 573 return buttonMinWidthProperty.get(); 574 } 575 576 577 578 /************************************************************************** 579 * 580 * Implementation 581 * 582 **************************************************************************/ 583 584 /** 585 * Most Controls return true for focusTraversable, so Control overrides 586 * this method to return true, but ButtonBar returns false for 587 * focusTraversable's initial value; hence the override of the override. 588 * This method is called from CSS code to get the correct initial value. 589 * @treatAsPrivate implementation detail 590 */ 591 @Deprecated @Override 592 protected /*do not make final*/ Boolean impl_cssGetFocusTraversableInitialValue() { 593 return Boolean.FALSE; 594 } 595 596 597 598 /************************************************************************** 599 * 600 * Support classes / enums 601 * 602 **************************************************************************/ 603 604 }