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