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