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      * @return the value property
 147      */
 148     public ObjectProperty<T> valueProperty() { return value; }
 149     private ObjectProperty<T> value = new SimpleObjectProperty<T>(this, "value");
 150 
 151     public final void setValue(T value) { valueProperty().set(value); }
 152     public final T getValue() { return valueProperty().get(); }
 153 
 154 
 155     // --- editable
 156     /**
 157      * Specifies whether the ComboBox allows for user input. When editable is
 158      * true, the ComboBox has a text input area that a user may type in to. This
 159      * input is then available via the {@link #valueProperty() value} property.
 160      *
 161      * <p>Note that when the editable property changes, the value property is
 162      * reset, along with any other relevant state.
 163      * @return the editable property
 164      */
 165     public BooleanProperty editableProperty() { return editable; }
 166     public final void setEditable(boolean value) { editableProperty().set(value); }
 167     public final boolean isEditable() { return editableProperty().get(); }
 168     private BooleanProperty editable = new SimpleBooleanProperty(this, "editable", false) {
 169         @Override protected void invalidated() {
 170             pseudoClassStateChanged(PSEUDO_CLASS_EDITABLE, get());
 171         }
 172     };
 173 
 174 
 175     // --- showing
 176     /**
 177      * Represents the current state of the ComboBox popup, and whether it is
 178      * currently visible on screen (although it may be hidden behind other windows).
 179      */
 180     private ReadOnlyBooleanWrapper showing;
 181     public ReadOnlyBooleanProperty showingProperty() { return showingPropertyImpl().getReadOnlyProperty(); }
 182     public final boolean isShowing() { return showingPropertyImpl().get(); }
 183     private void setShowing(boolean value) {
 184         // these events will not fire if the showing property is bound
 185         Event.fireEvent(this, value ? new Event(ComboBoxBase.ON_SHOWING) :
 186             new Event(ComboBoxBase.ON_HIDING));
 187         showingPropertyImpl().set(value);
 188         Event.fireEvent(this, value ? new Event(ComboBoxBase.ON_SHOWN) :
 189             new Event(ComboBoxBase.ON_HIDDEN));
 190     }
 191     private ReadOnlyBooleanWrapper showingPropertyImpl() {
 192         if (showing == null) {
 193             showing = new ReadOnlyBooleanWrapper(false) {
 194                 @Override protected void invalidated() {
 195                     pseudoClassStateChanged(PSEUDO_CLASS_SHOWING, get());
 196                     notifyAccessibleAttributeChanged(AccessibleAttribute.EXPANDED);
 197                 }
 198 
 199                 @Override
 200                 public Object getBean() {
 201                     return ComboBoxBase.this;
 202                 }
 203 
 204                 @Override
 205                 public String getName() {
 206                     return "showing";
 207                 }
 208             };
 209         }
 210         return showing;
 211     }
 212 
 213 
 214     // --- prompt text
 215     /**
 216      * The {@code ComboBox} prompt text to display, or <tt>null</tt> if no
 217      * prompt text is displayed. Prompt text is not displayed in all circumstances,
 218      * it is dependent upon the subclasses of ComboBoxBase to clarify when
 219      * promptText will be shown. For example, in most cases prompt text will never be
 220      * shown when a combo box is non-editable (that is, prompt text is only shown
 221      * when user input is allowed via text input).
 222      */
 223     private StringProperty promptText = new SimpleStringProperty(this, "promptText", null) {
 224         @Override protected void invalidated() {
 225             // Strip out newlines
 226             String txt = get();
 227             if (txt != null && txt.contains("\n")) {
 228                 txt = txt.replace("\n", "");
 229                 set(txt);
 230             }
 231         }
 232     };
 233     public final StringProperty promptTextProperty() { return promptText; }
 234     public final String getPromptText() { return promptText.get(); }
 235     public final void setPromptText(String value) { promptText.set(value); }
 236 
 237 
 238     // --- armed
 239     /**
 240      * Indicates that the ComboBox has been "armed" such that a mouse release
 241      * will cause the ComboBox {@link #show()} method to be invoked. This is
 242      * subtly different from pressed. Pressed indicates that the mouse has been
 243      * pressed on a Node and has not yet been released. {@code arm} however
 244      * also takes into account whether the mouse is actually over the
 245      * ComboBox and pressed.
 246      * @return the armed property
 247      */
 248     public BooleanProperty armedProperty() { return armed; }
 249     private final void setArmed(boolean value) { armedProperty().set(value); }
 250     public final boolean isArmed() { return armedProperty().get(); }
 251     private BooleanProperty armed = new SimpleBooleanProperty(this, "armed", false) {
 252         @Override protected void invalidated() {
 253             pseudoClassStateChanged(PSEUDO_CLASS_ARMED, get());
 254         }
 255     };
 256 
 257 
 258     // --- On Action
 259     /**
 260      * The ComboBox action, which is invoked whenever the ComboBox
 261      * {@link #valueProperty() value} property is changed. This
 262      * may be due to the value property being programmatically changed, when the
 263      * user selects an item in a popup list or dialog, or, in the case of
 264      * {@link #editableProperty() editable} ComboBoxes, it may be when the user
 265      * provides their own input (be that via a {@link TextField} or some other
 266      * input mechanism.
 267      * @return the on action property
 268      */
 269     public final ObjectProperty<EventHandler<ActionEvent>> onActionProperty() { return onAction; }
 270     public final void setOnAction(EventHandler<ActionEvent> value) { onActionProperty().set(value); }
 271     public final EventHandler<ActionEvent> getOnAction() { return onActionProperty().get(); }
 272     private ObjectProperty<EventHandler<ActionEvent>> onAction = new ObjectPropertyBase<EventHandler<ActionEvent>>() {
 273         @Override protected void invalidated() {
 274             setEventHandler(ActionEvent.ACTION, get());
 275         }
 276 
 277         @Override
 278         public Object getBean() {
 279             return ComboBoxBase.this;
 280         }
 281 
 282         @Override
 283         public String getName() {
 284             return "onAction";
 285         }
 286     };
 287 
 288 
 289     // --- On Showing
 290     public final ObjectProperty<EventHandler<Event>> onShowingProperty() { return onShowing; }
 291     public final void setOnShowing(EventHandler<Event> value) { onShowingProperty().set(value); }
 292     public final EventHandler<Event> getOnShowing() { return onShowingProperty().get(); }
 293     /**
 294      * Called just prior to the {@code ComboBoxBase} popup/display being shown.
 295      * @since JavaFX 2.2
 296      */
 297     private ObjectProperty<EventHandler<Event>> onShowing = new ObjectPropertyBase<EventHandler<Event>>() {
 298         @Override protected void invalidated() {
 299             setEventHandler(ON_SHOWING, get());
 300         }
 301 
 302         @Override public Object getBean() {
 303             return ComboBoxBase.this;
 304         }
 305 
 306         @Override public String getName() {
 307             return "onShowing";
 308         }
 309     };
 310 
 311 
 312     // -- On Shown
 313     public final ObjectProperty<EventHandler<Event>> onShownProperty() { return onShown; }
 314     public final void setOnShown(EventHandler<Event> value) { onShownProperty().set(value); }
 315     public final EventHandler<Event> getOnShown() { return onShownProperty().get(); }
 316     /**
 317      * Called just after the {@link ComboBoxBase} popup/display is shown.
 318      * @since JavaFX 2.2
 319      */
 320     private ObjectProperty<EventHandler<Event>> onShown = new ObjectPropertyBase<EventHandler<Event>>() {
 321         @Override protected void invalidated() {
 322             setEventHandler(ON_SHOWN, get());
 323         }
 324 
 325         @Override public Object getBean() {
 326             return ComboBoxBase.this;
 327         }
 328 
 329         @Override public String getName() {
 330             return "onShown";
 331         }
 332     };
 333 
 334 
 335     // --- On Hiding
 336     public final ObjectProperty<EventHandler<Event>> onHidingProperty() { return onHiding; }
 337     public final void setOnHiding(EventHandler<Event> value) { onHidingProperty().set(value); }
 338     public final EventHandler<Event> getOnHiding() { return onHidingProperty().get(); }
 339     /**
 340      * Called just prior to the {@link ComboBox} popup/display being hidden.
 341      * @since JavaFX 2.2
 342      */
 343     private ObjectProperty<EventHandler<Event>> onHiding = new ObjectPropertyBase<EventHandler<Event>>() {
 344         @Override protected void invalidated() {
 345             setEventHandler(ON_HIDING, get());
 346         }
 347 
 348         @Override public Object getBean() {
 349             return ComboBoxBase.this;
 350         }
 351 
 352         @Override public String getName() {
 353             return "onHiding";
 354         }
 355     };
 356 
 357 
 358     // --- On Hidden
 359     public final ObjectProperty<EventHandler<Event>> onHiddenProperty() { return onHidden; }
 360     public final void setOnHidden(EventHandler<Event> value) { onHiddenProperty().set(value); }
 361     public final EventHandler<Event> getOnHidden() { return onHiddenProperty().get(); }
 362     /**
 363      * Called just after the {@link ComboBoxBase} popup/display has been hidden.
 364      * @since JavaFX 2.2
 365      */
 366     private ObjectProperty<EventHandler<Event>> onHidden = new ObjectPropertyBase<EventHandler<Event>>() {
 367         @Override protected void invalidated() {
 368             setEventHandler(ON_HIDDEN, get());
 369         }
 370 
 371         @Override public Object getBean() {
 372             return ComboBoxBase.this;
 373         }
 374 
 375         @Override public String getName() {
 376             return "onHidden";
 377         }
 378     };
 379 
 380 
 381     /***************************************************************************
 382      *                                                                         *
 383      * Methods                                                                 *
 384      *                                                                         *
 385      **************************************************************************/
 386 
 387     /**
 388      * Requests that the ComboBox display the popup aspect of the user interface.
 389      * As mentioned in the {@link ComboBoxBase} class javadoc, what is actually
 390      * shown when this method is called is undefined, but commonly it is some
 391      * form of popup or dialog window.
 392      */
 393     public void show() {
 394         if (!isDisabled()) {
 395             setShowing(true);
 396         }
 397     }
 398 
 399     /**
 400      * Closes the popup / dialog that was shown when {@link #show()} was called.
 401      */
 402     public void hide() {
 403         if (isShowing()) {
 404             setShowing(false);
 405         }
 406     }
 407 
 408     /**
 409      * Arms the ComboBox. An armed ComboBox will show a popup list on the next
 410      * expected UI gesture.
 411      *
 412      * Note: This function is intended to be used by experts, primarily
 413      *       by those implementing new Skins or Behaviors. It is not common
 414      *       for developers or designers to access this function directly.
 415      */
 416     public void arm() {
 417         if (! armedProperty().isBound()) {
 418             setArmed(true);
 419         }
 420     }
 421 
 422     /**
 423      * Disarms the ComboBox. See {@link #arm()}.
 424      *
 425      * Note: This function is intended to be used by experts, primarily
 426      *       by those implementing new Skins or Behaviors. It is not common
 427      *       for developers or designers to access this function directly.
 428      */
 429     public void disarm() {
 430         if (! armedProperty().isBound()) {
 431             setArmed(false);
 432         }
 433     }
 434 
 435 
 436     /***************************************************************************
 437      *                                                                         *
 438      * Stylesheet Handling                                                     *
 439      *                                                                         *
 440      **************************************************************************/
 441 
 442     private static final String DEFAULT_STYLE_CLASS = "combo-box-base";
 443 
 444     private static final PseudoClass PSEUDO_CLASS_EDITABLE =
 445             PseudoClass.getPseudoClass("editable");
 446     private static final PseudoClass PSEUDO_CLASS_SHOWING =
 447             PseudoClass.getPseudoClass("showing");
 448     private static final PseudoClass PSEUDO_CLASS_ARMED =
 449             PseudoClass.getPseudoClass("armed");
 450 
 451 
 452     /***************************************************************************
 453      *                                                                         *
 454      * Accessibility handling                                                  *
 455      *                                                                         *
 456      **************************************************************************/
 457 
 458     @Override
 459     public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
 460         switch (attribute) {
 461             case EXPANDED: return isShowing();
 462             case EDITABLE: return isEditable();
 463             default: return super.queryAccessibleAttribute(attribute, parameters);
 464         }
 465     }
 466 
 467     @Override
 468     public void executeAccessibleAction(AccessibleAction action, Object... parameters) {
 469         switch (action) {
 470             case EXPAND: show(); break;
 471             case COLLAPSE: hide(); break;
 472             default: super.executeAccessibleAction(action); break;
 473         }
 474     }
 475 }
 476