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