1 /*
   2  * Copyright (c) 2010, 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 
  26 package javafx.scene.control;
  27 
  28 import javafx.collections.MapChangeListener;
  29 import javafx.css.PseudoClass;
  30 import javafx.beans.property.*;
  31 import javafx.event.ActionEvent;
  32 import javafx.event.Event;
  33 import javafx.event.EventHandler;
  34 import javafx.event.EventType;
  35 import javafx.scene.AccessibleAction;
  36 import javafx.scene.AccessibleAttribute;
  37 
  38 /**
  39  * Abstract base class for ComboBox-like controls. A ComboBox typically has
  40  * a button that, when clicked, will pop up some means of allowing a user
  41  * to select one or more values (depending on the implementation). This base
  42  * class makes no assumptions about what happens when the {@link #show()} and
  43  * {@link #hide()} methods are called, however commonly this results in either
  44  * a popup or dialog appearing that allows for the user to provide the
  45  * required information.
  46  *
  47  * <p>A ComboBox has a {@link #valueProperty() value} property that represents
  48  * the current user input. This may be based on a selection from a drop-down list,
  49  * or it may be from user input when the ComboBox is
  50  * {@link #editableProperty() editable}.
  51  *
  52  * <p>An {@link #editableProperty() editable} ComboBox is one which provides some
  53  * means for an end-user to provide input for values that are not otherwise
  54  * options available to them. For example, in the {@link ComboBox} implementation,
  55  * an editable ComboBox provides a {@link TextField} that may be typed into.
  56  * As mentioned above, when the user commits textual input into the textfield
  57  * (commonly by pressing the Enter keyboard key), the
  58  * {@link #valueProperty() value} property will be updated.
  59  *
  60  * <p>The purpose of the separation between this class and, say, {@link ComboBox}
  61  * is to allow for ComboBox-like controls that do not necessarily pop up a list
  62  * of items. Examples of other implementations include color pickers, calendar
  63  * pickers, etc. The  {@link ComboBox} class provides the default, and most commonly
  64  * expected implementation. Refer to that classes javadoc for more information.
  65  *
  66  * @see ComboBox
  67  * @param <T> The type of the value that has been selected or otherwise
  68  *      entered in to this ComboBox.
  69  * @since JavaFX 2.1
  70  */
  71 public abstract class ComboBoxBase<T> extends Control {
  72 
  73 
  74     /***************************************************************************
  75      *                                                                         *
  76      * Static properties and methods                                           *
  77      *                                                                         *
  78      **************************************************************************/
  79 
  80     /**
  81      * <p>Called prior to the ComboBox showing its popup/display after the user
  82      * has clicked or otherwise interacted with the ComboBox.
  83      * @since JavaFX 2.2
  84      */
  85     public static final EventType<Event> ON_SHOWING =
  86             new EventType<Event>(Event.ANY, "COMBO_BOX_BASE_ON_SHOWING");
  87 
  88     /**
  89      * <p>Called after the ComboBox has shown its popup/display.
  90      * @since JavaFX 2.2
  91      */
  92     public static final EventType<Event> ON_SHOWN =
  93             new EventType<Event>(Event.ANY, "COMBO_BOX_BASE_ON_SHOWN");
  94 
  95     /**
  96      * <p>Called when the ComboBox popup/display <b>will</b> be hidden.
  97      * @since JavaFX 2.2
  98      */
  99     public static final EventType<Event> ON_HIDING =
 100             new EventType<Event>(Event.ANY, "COMBO_BOX_BASE_ON_HIDING");
 101 
 102     /**
 103      * <p>Called when the ComboBox popup/display has been hidden.
 104      * @since JavaFX 2.2
 105      */
 106     public static final EventType<Event> ON_HIDDEN =
 107             new EventType<Event>(Event.ANY, "COMBO_BOX_BASE_ON_HIDDEN");
 108 
 109 
 110 
 111     /***************************************************************************
 112      *                                                                         *
 113      * Constructors                                                            *
 114      *                                                                         *
 115      **************************************************************************/
 116 
 117     /**
 118      * Creates a default ComboBoxBase instance.
 119      */
 120     public ComboBoxBase() {
 121         getStyleClass().add(DEFAULT_STYLE_CLASS);
 122 
 123         // Fix for RT-29885
 124         getProperties().addListener((MapChangeListener<Object, Object>) change -> {
 125             if (change.wasAdded()) {
 126                 if (change.getKey() == "FOCUSED") {
 127                     setFocused((Boolean)change.getValueAdded());
 128                     getProperties().remove("FOCUSED");
 129                 }
 130             }
 131         });
 132         // End of fix for RT-29885
 133     }
 134 
 135     /***************************************************************************
 136      *                                                                         *
 137      * Properties                                                              *
 138      *                                                                         *
 139      **************************************************************************/
 140 
 141     // --- value
 142     /**
 143      * The value of this ComboBox is defined as the selected item if the input
 144      * is not editable, or if it is editable, the most recent user action:
 145      * either the value input by the user, or the last selected item.
 146      */
 147     public ObjectProperty<T> valueProperty() { return value; }
 148     private ObjectProperty<T> value = new SimpleObjectProperty<T>(this, "value");
 149 
 150     public final void setValue(T value) { valueProperty().set(value); }
 151     public final T getValue() { return valueProperty().get(); }
 152 
 153 
 154     // --- editable
 155     /**
 156      * Specifies whether the ComboBox allows for user input. When editable is
 157      * true, the ComboBox has a text input area that a user may type in to. This
 158      * input is then available via the {@link #valueProperty() value} property.
 159      *
 160      * <p>Note that when the editable property changes, the value property is
 161      * reset, along with any other relevant state.
 162      */
 163     public BooleanProperty editableProperty() { return editable; }
 164     public final void setEditable(boolean value) { editableProperty().set(value); }
 165     public final boolean isEditable() { return editableProperty().get(); }
 166     private BooleanProperty editable = new SimpleBooleanProperty(this, "editable", false) {
 167         @Override protected void invalidated() {
 168             pseudoClassStateChanged(PSEUDO_CLASS_EDITABLE, get());
 169         }
 170     };
 171 
 172 
 173     // --- showing
 174     /**
 175      * Represents the current state of the ComboBox popup, and whether it is
 176      * currently visible on screen (although it may be hidden behind other windows).
 177      */
 178     private ReadOnlyBooleanWrapper showing;
 179     public ReadOnlyBooleanProperty showingProperty() { return showingPropertyImpl().getReadOnlyProperty(); }
 180     public final boolean isShowing() { return showingPropertyImpl().get(); }
 181     private void setShowing(boolean value) {
 182         // these events will not fire if the showing property is bound
 183         Event.fireEvent(this, value ? new Event(ComboBoxBase.ON_SHOWING) :
 184             new Event(ComboBoxBase.ON_HIDING));
 185         showingPropertyImpl().set(value);
 186         Event.fireEvent(this, value ? new Event(ComboBoxBase.ON_SHOWN) :
 187             new Event(ComboBoxBase.ON_HIDDEN));
 188     }
 189     private ReadOnlyBooleanWrapper showingPropertyImpl() {
 190         if (showing == null) {
 191             showing = new ReadOnlyBooleanWrapper(false) {
 192                 @Override protected void invalidated() {
 193                     pseudoClassStateChanged(PSEUDO_CLASS_SHOWING, get());
 194                     notifyAccessibleAttributeChanged(AccessibleAttribute.EXPANDED);
 195                 }
 196 
 197                 @Override
 198                 public Object getBean() {
 199                     return ComboBoxBase.this;
 200                 }
 201 
 202                 @Override
 203                 public String getName() {
 204                     return "showing";
 205                 }
 206             };
 207         }
 208         return showing;
 209     }
 210 
 211 
 212     // --- prompt text
 213     /**
 214      * The {@code ComboBox} prompt text to display, or <tt>null</tt> if no
 215      * prompt text is displayed. Prompt text is not displayed in all circumstances,
 216      * it is dependent upon the subclasses of ComboBoxBase to clarify when
 217      * promptText will be shown. For example, in most cases prompt text will never be
 218      * shown when a combo box is non-editable (that is, prompt text is only shown
 219      * when user input is allowed via text input).
 220      */
 221     private StringProperty promptText = new SimpleStringProperty(this, "promptText", null) {
 222         @Override protected void invalidated() {
 223             // Strip out newlines
 224             String txt = get();
 225             if (txt != null && txt.contains("\n")) {
 226                 txt = txt.replace("\n", "");
 227                 set(txt);
 228             }
 229         }
 230     };
 231     public final StringProperty promptTextProperty() { return promptText; }
 232     public final String getPromptText() { return promptText.get(); }
 233     public final void setPromptText(String value) { promptText.set(value); }
 234 
 235 
 236     // --- armed
 237     /**
 238      * Indicates that the ComboBox has been "armed" such that a mouse release
 239      * will cause the ComboBox {@link #show()} method to be invoked. This is
 240      * subtly different from pressed. Pressed indicates that the mouse has been
 241      * pressed on a Node and has not yet been released. {@code arm} however
 242      * also takes into account whether the mouse is actually over the
 243      * ComboBox and pressed.
 244      */
 245     public BooleanProperty armedProperty() { return armed; }
 246     private final void setArmed(boolean value) { armedProperty().set(value); }
 247     public final boolean isArmed() { return armedProperty().get(); }
 248     private BooleanProperty armed = new SimpleBooleanProperty(this, "armed", false) {
 249         @Override protected void invalidated() {
 250             pseudoClassStateChanged(PSEUDO_CLASS_ARMED, get());
 251         }
 252     };
 253 
 254 
 255     // --- On Action
 256     /**
 257      * The ComboBox action, which is invoked whenever the ComboBox
 258      * {@link #valueProperty() value} property is changed. This
 259      * may be due to the value property being programmatically changed, when the
 260      * user selects an item in a popup list or dialog, or, in the case of
 261      * {@link #editableProperty() editable} ComboBoxes, it may be when the user
 262      * provides their own input (be that via a {@link TextField} or some other
 263      * input mechanism.
 264      */
 265     public final ObjectProperty<EventHandler<ActionEvent>> onActionProperty() { return onAction; }
 266     public final void setOnAction(EventHandler<ActionEvent> value) { onActionProperty().set(value); }
 267     public final EventHandler<ActionEvent> getOnAction() { return onActionProperty().get(); }
 268     private ObjectProperty<EventHandler<ActionEvent>> onAction = new ObjectPropertyBase<EventHandler<ActionEvent>>() {
 269         @Override protected void invalidated() {
 270             setEventHandler(ActionEvent.ACTION, get());
 271         }
 272 
 273         @Override
 274         public Object getBean() {
 275             return ComboBoxBase.this;
 276         }
 277 
 278         @Override
 279         public String getName() {
 280             return "onAction";
 281         }
 282     };
 283 
 284 
 285     // --- On Showing
 286     public final ObjectProperty<EventHandler<Event>> onShowingProperty() { return onShowing; }
 287     public final void setOnShowing(EventHandler<Event> value) { onShowingProperty().set(value); }
 288     public final EventHandler<Event> getOnShowing() { return onShowingProperty().get(); }
 289     /**
 290      * Called just prior to the {@code ComboBoxBase} popup/display being shown.
 291      * @since JavaFX 2.2
 292      */
 293     private ObjectProperty<EventHandler<Event>> onShowing = new ObjectPropertyBase<EventHandler<Event>>() {
 294         @Override protected void invalidated() {
 295             setEventHandler(ON_SHOWING, get());
 296         }
 297 
 298         @Override public Object getBean() {
 299             return ComboBoxBase.this;
 300         }
 301 
 302         @Override public String getName() {
 303             return "onShowing";
 304         }
 305     };
 306 
 307 
 308     // -- On Shown
 309     public final ObjectProperty<EventHandler<Event>> onShownProperty() { return onShown; }
 310     public final void setOnShown(EventHandler<Event> value) { onShownProperty().set(value); }
 311     public final EventHandler<Event> getOnShown() { return onShownProperty().get(); }
 312     /**
 313      * Called just after the {@link ComboBoxBase} popup/display is shown.
 314      * @since JavaFX 2.2
 315      */
 316     private ObjectProperty<EventHandler<Event>> onShown = new ObjectPropertyBase<EventHandler<Event>>() {
 317         @Override protected void invalidated() {
 318             setEventHandler(ON_SHOWN, get());
 319         }
 320 
 321         @Override public Object getBean() {
 322             return ComboBoxBase.this;
 323         }
 324 
 325         @Override public String getName() {
 326             return "onShown";
 327         }
 328     };
 329 
 330 
 331     // --- On Hiding
 332     public final ObjectProperty<EventHandler<Event>> onHidingProperty() { return onHiding; }
 333     public final void setOnHiding(EventHandler<Event> value) { onHidingProperty().set(value); }
 334     public final EventHandler<Event> getOnHiding() { return onHidingProperty().get(); }
 335     /**
 336      * Called just prior to the {@link ComboBox} popup/display being hidden.
 337      * @since JavaFX 2.2
 338      */
 339     private ObjectProperty<EventHandler<Event>> onHiding = new ObjectPropertyBase<EventHandler<Event>>() {
 340         @Override protected void invalidated() {
 341             setEventHandler(ON_HIDING, get());
 342         }
 343 
 344         @Override public Object getBean() {
 345             return ComboBoxBase.this;
 346         }
 347 
 348         @Override public String getName() {
 349             return "onHiding";
 350         }
 351     };
 352 
 353 
 354     // --- On Hidden
 355     public final ObjectProperty<EventHandler<Event>> onHiddenProperty() { return onHidden; }
 356     public final void setOnHidden(EventHandler<Event> value) { onHiddenProperty().set(value); }
 357     public final EventHandler<Event> getOnHidden() { return onHiddenProperty().get(); }
 358     /**
 359      * Called just after the {@link ComboBoxBase} popup/display has been hidden.
 360      * @since JavaFX 2.2
 361      */
 362     private ObjectProperty<EventHandler<Event>> onHidden = new ObjectPropertyBase<EventHandler<Event>>() {
 363         @Override protected void invalidated() {
 364             setEventHandler(ON_HIDDEN, get());
 365         }
 366 
 367         @Override public Object getBean() {
 368             return ComboBoxBase.this;
 369         }
 370 
 371         @Override public String getName() {
 372             return "onHidden";
 373         }
 374     };
 375 
 376 
 377     /***************************************************************************
 378      *                                                                         *
 379      * Methods                                                                 *
 380      *                                                                         *
 381      **************************************************************************/
 382 
 383     /**
 384      * Requests that the ComboBox display the popup aspect of the user interface.
 385      * As mentioned in the {@link ComboBoxBase} class javadoc, what is actually
 386      * shown when this method is called is undefined, but commonly it is some
 387      * form of popup or dialog window.
 388      */
 389     public void show() {
 390         if (!isDisabled()) {
 391             setShowing(true);
 392         }
 393     }
 394 
 395     /**
 396      * Closes the popup / dialog that was shown when {@link #show()} was called.
 397      */
 398     public void hide() {
 399         if (isShowing()) {
 400             setShowing(false);
 401         }
 402     }
 403 
 404     /**
 405      * Arms the ComboBox. An armed ComboBox will show a popup list on the next
 406      * expected UI gesture.
 407      *
 408      * @expert This function is intended to be used by experts, primarily
 409      *         by those implementing new Skins or Behaviors. It is not common
 410      *         for developers or designers to access this function directly.
 411      */
 412     public void arm() {
 413         if (! armedProperty().isBound()) {
 414             setArmed(true);
 415         }
 416     }
 417 
 418     /**
 419      * Disarms the ComboBox. See {@link #arm()}.
 420      *
 421      * @expert This function is intended to be used by experts, primarily
 422      *         by those implementing new Skins or Behaviors. It is not common
 423      *         for developers or designers to access this function directly.
 424      */
 425     public void disarm() {
 426         if (! armedProperty().isBound()) {
 427             setArmed(false);
 428         }
 429     }
 430 
 431 
 432     /***************************************************************************
 433      *                                                                         *
 434      * Stylesheet Handling                                                     *
 435      *                                                                         *
 436      **************************************************************************/
 437 
 438     private static final String DEFAULT_STYLE_CLASS = "combo-box-base";
 439 
 440     private static final PseudoClass PSEUDO_CLASS_EDITABLE =
 441             PseudoClass.getPseudoClass("editable");
 442     private static final PseudoClass PSEUDO_CLASS_SHOWING =
 443             PseudoClass.getPseudoClass("showing");
 444     private static final PseudoClass PSEUDO_CLASS_ARMED =
 445             PseudoClass.getPseudoClass("armed");
 446 
 447 
 448     /***************************************************************************
 449      *                                                                         *
 450      * Accessibility handling                                                  *
 451      *                                                                         *
 452      **************************************************************************/
 453 
 454     @Override
 455     public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
 456         switch (attribute) {
 457             case EXPANDED: return isShowing();
 458             case EDITABLE: return isEditable();
 459             default: return super.queryAccessibleAttribute(attribute, parameters);
 460         }
 461     }
 462 
 463     @Override
 464     public void executeAccessibleAction(AccessibleAction action, Object... parameters) {
 465         switch (action) {
 466             case EXPAND: show(); break;
 467             case COLLAPSE: hide(); break;
 468             default: super.executeAccessibleAction(action); break;
 469         }
 470     }
 471 }
 472