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><br/><img src="doc-files/buttonBar-windows.png" /><br> 73 * <strong>Mac OS:</strong><br/><img src="doc-files/buttonBar-mac.png" /><br> 74 * <strong>Linux:</strong><br/><img src="doc-files/buttonBar-linux.png" /><br> 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"> 106 * <tr> 107 * <td width="75"><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 */ 321 public String getTypeCode() { 322 return typeCode; 323 } 324 325 /** 326 * Indicates whether buttons created from the ButtonData enumeration 327 * should be the 'cancel' button in the user interface. This typically 328 * means that the button will respond to the escape key press, even if 329 * the button does not have focus. 330 * 331 * <p>ButtonData enumeration values that can be the cancel button have a 332 * comment stating this in their javadoc. 333 */ 334 public final boolean isCancelButton() { 335 return cancelButton; 336 } 337 338 /** 339 * Indicates whether buttons created from the ButtonData enumeration 340 * should be the 'default' button in the user interface. This typically 341 * means that the button will respond to enter key presses, even if the 342 * button does not have focus. 343 * 344 * <p>ButtonData enumeration values that can be the default button have 345 * a comment stating this in their javadoc. 346 */ 347 public final boolean isDefaultButton() { 348 return defaultButton; 349 } 350 } 351 352 353 /** 354 * Sets the given ButtonData on the given button. If this button is 355 * subsequently placed in a {@link ButtonBar} it will be placed in the 356 * correct position relative to all other buttons in the bar. 357 * 358 * @param button The button to annotate with the given {@link ButtonData} value. 359 * @param buttonData The ButtonData to designate the button as. 360 */ 361 public static void setButtonData(Node button, ButtonData buttonData) { 362 final Map<Object,Object> properties = button.getProperties(); 363 final ObjectProperty<ButtonData> property = 364 (ObjectProperty<ButtonData>) properties.getOrDefault( 365 Properties.BUTTON_DATA_PROPERTY, 366 new SimpleObjectProperty<>(button, "buttonData", buttonData)); 367 368 property.set(buttonData); 369 properties.putIfAbsent(Properties.BUTTON_DATA_PROPERTY, property); 370 } 371 372 /** 373 * Returns the previously set ButtonData property on the given button. If this 374 * was never set, this method will return null. 375 * 376 * @param button The button to return the previously set ButtonData for. 377 */ 378 public static ButtonData getButtonData(Node button) { 379 final Map<Object,Object> properties = button.getProperties(); 380 if (properties.containsKey(Properties.BUTTON_DATA_PROPERTY)) { 381 ObjectProperty<ButtonData> property = (ObjectProperty<ButtonData>) properties.get(Properties.BUTTON_DATA_PROPERTY); 382 return property == null ? null : property.get(); 383 } 384 return null; 385 } 386 387 /** 388 * By default all buttons are uniformly sized in a ButtonBar, meaning that all 389 * buttons take the width of the widest button. It is possible to opt-out of this 390 * on a per-button basis, but calling the setButtonUniformSize method with 391 * a boolean value of false. 392 * 393 * <p>If a button is excluded from uniform sizing, it is both excluded from 394 * being resized away from its preferred size, and also excluded from the 395 * measuring process, so its size will not influence the maximum size calculated 396 * for all buttons in the ButtonBar. 397 * 398 * @param button The button to include / exclude from uniform sizing. 399 * @param uniformSize Boolean true to force uniform sizing on the button, 400 * false to exclude the button from uniform sizing. 401 */ 402 public static void setButtonUniformSize(Node button, boolean uniformSize) { 403 // we store the false, but remove the true (as the isButtonUniformSize 404 // method returns true by default) 405 if (uniformSize) { 406 button.getProperties().remove(Properties.BUTTON_SIZE_INDEPENDENCE); 407 } else { 408 button.getProperties().put(Properties.BUTTON_SIZE_INDEPENDENCE, uniformSize); 409 } 410 } 411 412 /** 413 * Returns whether the given node is part of the uniform sizing calculations 414 * or not. By default all nodes that have not opted out (via 415 * {@link #setButtonUniformSize(Node, boolean)}) will return true here. 416 */ 417 public static boolean isButtonUniformSize(Node button) { 418 return (boolean) button.getProperties().getOrDefault(Properties.BUTTON_SIZE_INDEPENDENCE, true); 419 } 420 421 422 423 /************************************************************************** 424 * 425 * Private fields 426 * 427 **************************************************************************/ 428 429 private ObservableList<Node> buttons = FXCollections.<Node>observableArrayList(); 430 431 432 433 /************************************************************************** 434 * 435 * Constructors 436 * 437 **************************************************************************/ 438 439 /** 440 * Creates a default ButtonBar instance using the default properties for 441 * the users operating system. 442 */ 443 public ButtonBar() { 444 this(null); 445 } 446 447 /** 448 * Creates a ButtonBar with the given button order (refer to 449 * {@link #buttonOrderProperty()} for more information). 450 * 451 * @param buttonOrder The button order to use in this button bar instance. 452 */ 453 public ButtonBar(final String buttonOrder) { 454 getStyleClass().add("button-bar"); //$NON-NLS-1$ 455 456 // we allow for the buttons inside the ButtonBar to be focus traversable, 457 // but the ButtonBar itself is not. 458 // focusTraversable is styleable through css. Calling setFocusTraversable 459 // makes it look to css like the user set the value and css will not 460 // override. Initializing focusTraversable by calling set on the 461 // CssMetaData ensures that css will be able to override the value. 462 ((StyleableProperty<Boolean>)(WritableValue<Boolean>)focusTraversableProperty()).applyStyle(null, Boolean.FALSE); 463 464 final boolean buttonOrderEmpty = buttonOrder == null || buttonOrder.isEmpty(); 465 466 if (Utils.isMac()) { 467 setButtonOrder(buttonOrderEmpty ? BUTTON_ORDER_MAC_OS : buttonOrder); 468 setButtonMinWidth(70); 469 } else if (Utils.isUnix()) { 470 setButtonOrder(buttonOrderEmpty ? BUTTON_ORDER_LINUX : buttonOrder); 471 setButtonMinWidth(85); 472 } else { 473 // windows by default 474 setButtonOrder(buttonOrderEmpty ? BUTTON_ORDER_WINDOWS : buttonOrder); 475 setButtonMinWidth(75); 476 } 477 } 478 479 480 481 /************************************************************************** 482 * 483 * Public API 484 * 485 **************************************************************************/ 486 487 /** 488 * {@inheritDoc} 489 */ 490 @Override protected Skin<?> createDefaultSkin() { 491 return new ButtonBarSkin(this); 492 } 493 494 /** 495 * Placing buttons inside this ObservableList will instruct the ButtonBar 496 * to position them relative to each other based on their specified 497 * {@link ButtonData}. To set the ButtonData for a button, simply call 498 * {@link ButtonBar#setButtonData(Node, ButtonData)}, passing in the 499 * relevant ButtonData. 500 * 501 * @return A list containing all buttons currently in the button bar, and 502 * allowing for further buttons to be added or removed. 503 */ 504 public final ObservableList<Node> getButtons() { 505 return buttons; 506 } 507 508 509 510 /************************************************************************** 511 * 512 * Properties 513 * 514 **************************************************************************/ 515 516 // --- Button order 517 /** 518 * The order for the typical buttons in a standard button bar. It is 519 * one letter per {@link ButtonData} enumeration value. Default button orders 520 * for operating systems are also available: {@link #BUTTON_ORDER_WINDOWS}, 521 * {@link #BUTTON_ORDER_MAC_OS}, and {@link #BUTTON_ORDER_LINUX}. 522 */ 523 public final StringProperty buttonOrderProperty() { 524 return buttonOrderProperty; 525 } 526 private final StringProperty buttonOrderProperty = 527 new SimpleStringProperty(this, "buttonOrder"); //$NON-NLS-1$ 528 529 /** 530 * Sets the {@link #buttonOrderProperty() button order} 531 * @param buttonOrder The currently set button order, which by default will 532 * be the OS-specific button order. 533 */ 534 public final void setButtonOrder(String buttonOrder) { 535 buttonOrderProperty.set(buttonOrder); 536 } 537 538 /** 539 * Returns the current {@link #buttonOrderProperty() button order}. 540 * @return The current {@link #buttonOrderProperty() button order}. 541 */ 542 public final String getButtonOrder() { 543 return buttonOrderProperty.get(); 544 } 545 546 547 // --- button min width 548 /** 549 * Specifies the minimum width of all buttons placed in this button bar. 550 */ 551 public final DoubleProperty buttonMinWidthProperty() { 552 return buttonMinWidthProperty; 553 } 554 private final DoubleProperty buttonMinWidthProperty = 555 new SimpleDoubleProperty(this, "buttonMinWidthProperty"); //$NON-NLS-1$ 556 557 /** 558 * Sets the minimum width of all buttons placed in this button bar. 559 */ 560 public final void setButtonMinWidth(double value) { 561 buttonMinWidthProperty.set(value); 562 } 563 564 /** 565 * Returns the minimum width of all buttons placed in this button bar. 566 */ 567 public final double getButtonMinWidth() { 568 return buttonMinWidthProperty.get(); 569 } 570 571 572 573 /************************************************************************** 574 * 575 * Implementation 576 * 577 **************************************************************************/ 578 579 /** 580 * Returns the initial focus traversable state of this control, for use 581 * by the JavaFX CSS engine to correctly set its initial value. This method 582 * is overridden as by default UI controls have focus traversable set to true, 583 * but that is not appropriate for this control. 584 * 585 * @since 9 586 */ 587 @Override protected Boolean getInitialFocusTraversable() { 588 return Boolean.FALSE; 589 } 590 591 592 593 /************************************************************************** 594 * 595 * Support classes / enums 596 * 597 **************************************************************************/ 598 599 }