Java tutorial
/* * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package javafx.scene.control; import com.sun.javafx.scene.control.Properties; import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.Node; import javafx.scene.layout.HBox; import com.sun.javafx.util.Utils; import javafx.scene.control.skin.ButtonBarSkin; import javafx.beans.value.WritableValue; import javafx.css.StyleableProperty; import java.util.Map; /** * A ButtonBar is essentially a {@link HBox}, with the additional functionality * for operating system specific button placement. In other words, any Node may * be annotated (via the {@link ButtonBar#setButtonData(Node, ButtonData)} * method, placed inside a ButtonBar (via the {@link #getButtons()} list), and will * then be positioned relative to all other nodes in the button list based on their * annotations, as well as the overarching * {@link #buttonOrderProperty() button order} specified for the ButtonBar. * * <h3>Uniform button sizing</h3> * <p>By default all buttons are uniformly sized in a ButtonBar, meaning that all * buttons take the width of the widest button. It is possible to opt-out of this * on a per-button basis, but calling the {@link #setButtonUniformSize(Node, boolean)} method with * a boolean value of false. * * <p>If a button is excluded from uniform sizing, it is both excluded from * being resized away from its preferred size, and also excluded from the * measuring process, so its size will not influence the maximum size calculated * for all buttons in the ButtonBar. * * <h3>Screenshots</h3> * <p>Because a ButtonBar comes with built-in support for Windows, Mac OS * and Linux, there are three screenshots shown below, with the same buttons * laid out on each of the three operating systems. * * <p> * <strong>Windows:</strong><p><img src="doc-files/buttonBar-windows.png" alt=""></p> * <strong>Mac OS:</strong><p><img src="doc-files/buttonBar-mac.png" alt=""></p> * <strong>Linux:</strong><p><img src="doc-files/buttonBar-linux.png" alt=""></p> * * <h3>Code Samples</h3> * <p>Instantiating and using the ButtonBar is simple, simply do the following: * * <pre> * {@code * // Create the ButtonBar instance * ButtonBar buttonBar = new ButtonBar(); * * // Create the buttons to go into the ButtonBar * Button yesButton = new Button("Yes"); * ButtonBar.setButtonData(yesButton, ButtonData.YES); * * Button noButton = new Button("No"); * ButtonBar.setButtonData(noButton, ButtonData.NO); * * // Add buttons to the ButtonBar * buttonBar.getButtons().addAll(yesButton, noButton); * }</pre> * * <p>The code sample above will position the Yes and No buttons relative to the * users operating system. This means that on Windows and Linux the Yes button * will come before the No button, whereas on Mac OS it'll be No and then Yes. * * <p>In most cases the OS-specific layout is the best choice, but in cases * where you want a custom layout, this is achieved be modifying the * {@link #buttonOrderProperty() button order property}. These are cryptic-looking * strings that are shorthand representations for the button order. The built-in * orders for Windows, Mac OS and Linux are: * * <table> * <caption>ButtonBar Layout Table</caption> * <tr> * <th scope="col">Operating System</th> * <th scope="col">Button Order</th> * </tr> * <tr> * <th scope="row">Windows</th> * <td>L_E+U+FBXI_YNOCAH_R</td> * </tr> * <tr> * <th scope="row">Mac OS</th> * <td>L_HE+U+FBIX_NCYOA_R</td> * </tr> * <tr> * <th scope="row">Linux</th> * <td>L_HE+UNYACBXIO_R</td> * </tr> * </table> * * <p>You should refer to the {@link ButtonData} enumeration for a description of * what each of these characters mean. However, if your ButtonBar only consisted * of {@link ButtonData#YES} and {@link ButtonData#NO} buttons, you always * wanted the yes buttons before the no buttons, and you wanted the buttons to * be {@link ButtonData#BIG_GAP right-aligned}, you could do the following: * * <pre> * {@code * // Create the ButtonBar instance * ButtonBar buttonBar = new ButtonBar(); * * // Set the custom button order * buttonBar.setButtonOrder("+YN"); * }</pre> * * @see ButtonData * @since JavaFX 8u40 */ public class ButtonBar extends Control { // TODO add support for BUTTON_ORDER_NONE // TODO test and document what happens with unexpected button order strings /************************************************************************** * * Static fields * **************************************************************************/ /** * The default button ordering on Windows. */ public static final String BUTTON_ORDER_WINDOWS = "L_E+U+FBXI_YNOCAH_R"; //$NON-NLS-1$ /** * The default button ordering on Mac OS. */ public static final String BUTTON_ORDER_MAC_OS = "L_HE+U+FBIX_NCYOA_R"; //$NON-NLS-1$ /** * The default button ordering on Linux (specifically, GNOME). */ public static final String BUTTON_ORDER_LINUX = "L_HE+UNYACBXIO_R"; //$NON-NLS-1$ /** * A button ordering string that specifies there is no button ordering. In * other words, buttons will be placed in the order that exist in the * {@link #getButtons()} list. The only aspect of layout that makes this * different than using an HBox is that the buttons are right-aligned. */ public static final String BUTTON_ORDER_NONE = ""; //$NON-NLS-1$ /************************************************************************** * * Static enumerations * **************************************************************************/ /** * An enumeration of all available button data annotations. By annotating * every button in a {@link ButtonBar} with one of these annotations, the * buttons will be appropriately positioned relative to all other buttons in * the ButtonBar. * * <p>For details on the button order code for each ButtonData, refer to * the javadoc comment. * * @since JavaFX 8u40 */ public static enum ButtonData { /** * Buttons with this style tag will statically end up on the left end of the bar. * * <p><strong>Button order code:</strong> L */ LEFT("L", false, false), //$NON-NLS-1$ /** * Buttons with this style tag will statically end up on the right end of the bar. * * <p><strong>Button order code:</strong> R */ RIGHT("R", false, false), //$NON-NLS-1$ /** * A tag for the "help" button that normally is supposed to be on the right. * * <p><strong>Button order code:</strong> H */ HELP("H", false, false), //$NON-NLS-1$ /** * A tag for the "help2" button that normally is supposed to be on the left. * * <p><strong>Button order code:</strong> E */ HELP_2("E", false, false), //$NON-NLS-1$ /** * A tag for the "yes" button. * * <p><strong>Is default button:</strong> True * <p><strong>Button order code:</strong> Y */ YES("Y", false, true), //$NON-NLS-1$ /** * A tag for the "no" button. * * <p><strong>Is cancel button:</strong> True * <p><strong>Button order code:</strong> N */ NO("N", true, false), //$NON-NLS-1$ /** * A tag for the "next" or "forward" button. * * <p><strong>Is default button:</strong> True * <p><strong>Button order code:</strong> X */ NEXT_FORWARD("X", false, true), //$NON-NLS-1$ /** * A tag for the "back" or "previous" button. * * <p><strong>Button order code:</strong> B */ BACK_PREVIOUS("B", false, false), //$NON-NLS-1$ /** * A tag for the "finish". * * <p><strong>Is default button:</strong> True * <p><strong>Button order code:</strong> I */ FINISH("I", false, true), //$NON-NLS-1$ /** * A tag for the "apply" button. * * <p><strong>Button order code:</strong> A */ APPLY("A", false, false), //$NON-NLS-1$ /** * A tag for the "cancel" or "close" button. * * <p><strong>Is cancel button:</strong> True * <p><strong>Button order code:</strong> C */ CANCEL_CLOSE("C", true, false), //$NON-NLS-1$ /** * A tag for the "ok" or "done" button. * * <p><strong>Is default button:</strong> True * <p><strong>Button order code:</strong> O */ OK_DONE("O", false, true), //$NON-NLS-1$ /** * All Uncategorized, Other, or "Unknown" buttons. Tag will be "other". * * <p><strong>Button order code:</strong> U */ OTHER("U", false, false), //$NON-NLS-1$ /** * A glue push gap that will take as much space as it can and at least * an "unrelated" gap. (Platform dependent) * * <p><strong>Button order code:</strong> + */ BIG_GAP("+", false, false), //$NON-NLS-1$ /** * An "unrelated" gap. (Platform dependent) * * <p><strong>Button order code:</strong> _ (underscore) */ SMALL_GAP("_", false, false); //$NON-NLS-1$ private final String typeCode; private final boolean cancelButton; private final boolean defaultButton; private ButtonData(String type, boolean cancelButton, boolean defaultButton) { this.typeCode = type; this.cancelButton = cancelButton; this.defaultButton = defaultButton; } /** * Returns the single character code used to represent the ButtonData * annotation in the {@link ButtonBar#buttonOrderProperty() button order} * string. * @return the single character code used to represent the ButtonData * annotation */ public String getTypeCode() { return typeCode; } /** * Indicates whether buttons created from the ButtonData enumeration * should be the 'cancel' button in the user interface. This typically * means that the button will respond to the escape key press, even if * the button does not have focus. * * <p>ButtonData enumeration values that can be the cancel button have a * comment stating this in their javadoc. * @return true if this is a 'cancel' button */ public final boolean isCancelButton() { return cancelButton; } /** * Indicates whether buttons created from the ButtonData enumeration * should be the 'default' button in the user interface. This typically * means that the button will respond to enter key presses, even if the * button does not have focus. * * <p>ButtonData enumeration values that can be the default button have * a comment stating this in their javadoc. * @return true if this is a 'default' button */ public final boolean isDefaultButton() { return defaultButton; } } /** * Sets the given ButtonData on the given button. If this button is * subsequently placed in a {@link ButtonBar} it will be placed in the * correct position relative to all other buttons in the bar. * * @param button The button to annotate with the given {@link ButtonData} value. * @param buttonData The ButtonData to designate the button as. */ public static void setButtonData(Node button, ButtonData buttonData) { final Map<Object, Object> properties = button.getProperties(); final ObjectProperty<ButtonData> property = (ObjectProperty<ButtonData>) properties.getOrDefault( Properties.BUTTON_DATA_PROPERTY, new SimpleObjectProperty<>(button, "buttonData", buttonData)); property.set(buttonData); properties.putIfAbsent(Properties.BUTTON_DATA_PROPERTY, property); } /** * Returns the previously set ButtonData property on the given button. If this * was never set, this method will return null. * * @param button The button to return the previously set ButtonData for. * @return the previously set ButtonData property on the given button */ public static ButtonData getButtonData(Node button) { final Map<Object, Object> properties = button.getProperties(); if (properties.containsKey(Properties.BUTTON_DATA_PROPERTY)) { ObjectProperty<ButtonData> property = (ObjectProperty<ButtonData>) properties .get(Properties.BUTTON_DATA_PROPERTY); return property == null ? null : property.get(); } return null; } /** * By default all buttons are uniformly sized in a ButtonBar, meaning that all * buttons take the width of the widest button. It is possible to opt-out of this * on a per-button basis, but calling the setButtonUniformSize method with * a boolean value of false. * * <p>If a button is excluded from uniform sizing, it is both excluded from * being resized away from its preferred size, and also excluded from the * measuring process, so its size will not influence the maximum size calculated * for all buttons in the ButtonBar. * * @param button The button to include / exclude from uniform sizing. * @param uniformSize Boolean true to force uniform sizing on the button, * false to exclude the button from uniform sizing. */ public static void setButtonUniformSize(Node button, boolean uniformSize) { // we store the false, but remove the true (as the isButtonUniformSize // method returns true by default) if (uniformSize) { button.getProperties().remove(Properties.BUTTON_SIZE_INDEPENDENCE); } else { button.getProperties().put(Properties.BUTTON_SIZE_INDEPENDENCE, uniformSize); } } /** * Returns whether the given node is part of the uniform sizing calculations * or not. By default all nodes that have not opted out (via * {@link #setButtonUniformSize(Node, boolean)}) will return true here. * @param button the button * @return true if button is part of the uniform sizing calculations */ public static boolean isButtonUniformSize(Node button) { return (boolean) button.getProperties().getOrDefault(Properties.BUTTON_SIZE_INDEPENDENCE, true); } /************************************************************************** * * Private fields * **************************************************************************/ private ObservableList<Node> buttons = FXCollections.<Node>observableArrayList(); /************************************************************************** * * Constructors * **************************************************************************/ /** * Creates a default ButtonBar instance using the default properties for * the users operating system. */ public ButtonBar() { this(null); } /** * Creates a ButtonBar with the given button order (refer to * {@link #buttonOrderProperty()} for more information). * * @param buttonOrder The button order to use in this button bar instance. */ public ButtonBar(final String buttonOrder) { getStyleClass().add("button-bar"); //$NON-NLS-1$ // we allow for the buttons inside the ButtonBar to be focus traversable, // but the ButtonBar itself is not. // focusTraversable is styleable through css. Calling setFocusTraversable // makes it look to css like the user set the value and css will not // override. Initializing focusTraversable by calling set on the // CssMetaData ensures that css will be able to override the value. ((StyleableProperty<Boolean>) (WritableValue<Boolean>) focusTraversableProperty()).applyStyle(null, Boolean.FALSE); final boolean buttonOrderEmpty = buttonOrder == null || buttonOrder.isEmpty(); if (Utils.isMac()) { setButtonOrder(buttonOrderEmpty ? BUTTON_ORDER_MAC_OS : buttonOrder); setButtonMinWidth(70); } else if (Utils.isUnix()) { setButtonOrder(buttonOrderEmpty ? BUTTON_ORDER_LINUX : buttonOrder); setButtonMinWidth(85); } else { // windows by default setButtonOrder(buttonOrderEmpty ? BUTTON_ORDER_WINDOWS : buttonOrder); setButtonMinWidth(75); } } /************************************************************************** * * Public API * **************************************************************************/ /** * {@inheritDoc} */ @Override protected Skin<?> createDefaultSkin() { return new ButtonBarSkin(this); } /** * Placing buttons inside this ObservableList will instruct the ButtonBar * to position them relative to each other based on their specified * {@link ButtonData}. To set the ButtonData for a button, simply call * {@link ButtonBar#setButtonData(Node, ButtonData)}, passing in the * relevant ButtonData. * * @return A list containing all buttons currently in the button bar, and * allowing for further buttons to be added or removed. */ public final ObservableList<Node> getButtons() { return buttons; } /************************************************************************** * * Properties * **************************************************************************/ // --- Button order /** * The order for the typical buttons in a standard button bar. It is * one letter per {@link ButtonData} enumeration value. Default button orders * for operating systems are also available: {@link #BUTTON_ORDER_WINDOWS}, * {@link #BUTTON_ORDER_MAC_OS}, and {@link #BUTTON_ORDER_LINUX}. * @return the button order property */ public final StringProperty buttonOrderProperty() { return buttonOrderProperty; } private final StringProperty buttonOrderProperty = new SimpleStringProperty(this, "buttonOrder"); //$NON-NLS-1$ /** * Sets the {@link #buttonOrderProperty() button order} * @param buttonOrder The currently set button order, which by default will * be the OS-specific button order. */ public final void setButtonOrder(String buttonOrder) { buttonOrderProperty.set(buttonOrder); } /** * Returns the current {@link #buttonOrderProperty() button order}. * @return The current {@link #buttonOrderProperty() button order}. */ public final String getButtonOrder() { return buttonOrderProperty.get(); } // --- button min width /** * Specifies the minimum width of all buttons placed in this button bar. * @return the minimum width property */ public final DoubleProperty buttonMinWidthProperty() { return buttonMinWidthProperty; } private final DoubleProperty buttonMinWidthProperty = new SimpleDoubleProperty(this, "buttonMinWidthProperty"); //$NON-NLS-1$ /** * Sets the minimum width of all buttons placed in this button bar. * @param value the minimum width value */ public final void setButtonMinWidth(double value) { buttonMinWidthProperty.set(value); } /** * Returns the minimum width of all buttons placed in this button bar. * @return the minimum width value */ public final double getButtonMinWidth() { return buttonMinWidthProperty.get(); } /************************************************************************** * * Implementation * **************************************************************************/ /** * Returns the initial focus traversable state of this control, for use * by the JavaFX CSS engine to correctly set its initial value. This method * is overridden as by default UI controls have focus traversable set to true, * but that is not appropriate for this control. * * @since 9 */ @Override protected Boolean getInitialFocusTraversable() { return Boolean.FALSE; } /************************************************************************** * * Support classes / enums * **************************************************************************/ }