1 /* 2 * Copyright (c) 2014, 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 package javafx.scene.control; 26 27 import java.lang.ref.WeakReference; 28 import java.util.Optional; 29 30 import com.sun.javafx.scene.control.skin.AccordionSkin; 31 import com.sun.javafx.scene.control.skin.resources.ControlResources; 32 import javafx.beans.InvalidationListener; 33 import javafx.beans.NamedArg; 34 import javafx.beans.property.ObjectProperty; 35 import javafx.beans.property.SimpleObjectProperty; 36 import javafx.collections.ListChangeListener; 37 import javafx.collections.ObservableList; 38 import javafx.scene.Node; 39 import javafx.scene.image.Image; 40 import javafx.scene.image.ImageView; 41 42 /** 43 * The Alert class subclasses the {@link Dialog} class, and provides support for a number 44 * of pre-built dialog types that can be easily shown to users to prompt for a 45 * response. Therefore, for many users, the Alert class is the most suited class 46 * for their needs (as opposed to using {@link Dialog} directly). Alternatively, 47 * users who want to prompt a user for text input or to make a choice from a list 48 * of options would be better served by using {@link TextInputDialog} and 49 * {@link ChoiceDialog}, respectively. 50 * 51 * <p>When creating an Alert instance, users must pass in an {@link AlertType} 52 * enumeration value. It is by passing in this value that the Alert instance will 53 * configure itself appropriately (by setting default values for many of the 54 * {@link Dialog} properties, including {@link #titleProperty() title}, 55 * {@link #headerTextProperty() header}, and {@link #graphicProperty() graphic}, 56 * as well as the default {@link #getButtonTypes() buttons} that are expected in 57 * a dialog of the given type. 58 * 59 * <p>To instantiate (but not yet show) an Alert, simply use code such as the following: 60 * {@code Alert alert = new Alert(AlertType.CONFIRMATION, "Are you sure you want to format your system?");} 61 * 62 * <p>Once an Alert is instantiated, we must show it. More often than not, alerts 63 * (and dialogs in general) are shown in a modal and blocking fashion. 'Modal' 64 * means that the dialog prevents user interaction with the owning application 65 * whilst it is showing, and 'blocking' means that code execution stops at the 66 * point in which the dialog is shown. This means that you can show a dialog, 67 * await the user response, and then continue running the code that directly 68 * follows the show call, giving developers the ability to immediately deal with 69 * the user input from the dialog (if relevant). 70 * 71 * <p>JavaFX dialogs are modal by default (you can change this via the 72 * {@link #initModality(javafx.stage.Modality)} API). To specify whether you want 73 * blocking or non-blocking dialogs, developers simply choose to call 74 * {@link #showAndWait()} or {@link #show()} (respectively). By default most 75 * developers should choose to use {@link #showAndWait()}, given the ease of 76 * coding in these situations. Shown below is three code snippets, showing three 77 * equally valid ways of showing the Alert dialog that was specified above: 78 * 79 * <p><strong>Option 1: The 'traditional' approach</strong> 80 * <pre>{@code Optional<ButtonType> result = alert.showAndWait(); 81 * if (result.get() == ButtonType.OK) { 82 * formatSystem(); 83 * }}</pre> 84 * 85 * <p><strong>Option 2: The traditional + Optional approach</strong> 86 * <pre>{@code alert.showAndWait().ifPresent(response -> { 87 * if (response == ButtonType.OK) { 88 * formatSystem(); 89 * } 90 * });}</pre> 91 * 92 * <p><strong>Option 3: The fully lambda approach</strong> 93 * <pre>{@code alert.showAndWait() 94 * .filter(response -> response == ButtonType.OK) 95 * .ifPresent(response -> formatSystem()); 96 * }</pre> 97 * 98 * <p>There is no better or worse option of the three listed above, so developers 99 * are encouraged to work to their own style preferences. The purpose of showing 100 * the above is to help introduce developers to the {@link Optional} API, which 101 * is new in Java 8 and may be foreign to many developers. 102 * 103 * @see Dialog 104 * @see AlertType 105 * @see TextInputDialog 106 * @see ChoiceDialog 107 * @since JavaFX 8u40 108 */ 109 public class Alert extends Dialog<ButtonType> { 110 111 /************************************************************************** 112 * 113 * Static enums 114 * 115 **************************************************************************/ 116 117 /** 118 * An enumeration containing the available, pre-built alert types that 119 * the {@link Alert} class can use to pre-populate various properties. 120 */ 121 public static enum AlertType { 122 /** 123 * The NONE alert type has the effect of not setting any default properties 124 * in the Alert. 125 */ 126 NONE, 127 128 /** 129 * The INFORMATION alert type configures the Alert dialog to appear in a 130 * way that suggests the content of the dialog is informing the user of 131 * a piece of information. This includes an 'information' image, an 132 * appropriate title and header, and just an OK button for the user to 133 * click on to dismiss the dialog. 134 */ 135 INFORMATION, 136 137 /** 138 * The WARNING alert type configures the Alert dialog to appear in a 139 * way that suggests the content of the dialog is warning the user about 140 * some fact or action. This includes a 'warning' image, an 141 * appropriate title and header, and just an OK button for the user to 142 * click on to dismiss the dialog. 143 */ 144 WARNING, 145 146 /** 147 * The CONFIRMATION alert type configures the Alert dialog to appear in a 148 * way that suggests the content of the dialog is seeking confirmation from 149 * the user. This includes a 'confirmation' image, an 150 * appropriate title and header, and both OK and Cancel buttons for the 151 * user to click on to dismiss the dialog. 152 */ 153 CONFIRMATION, 154 155 /** 156 * The ERROR alert type configures the Alert dialog to appear in a 157 * way that suggests that something has gone wrong. This includes an 158 * 'error' image, an appropriate title and header, and just an OK button 159 * for the user to click on to dismiss the dialog. 160 */ 161 ERROR 162 } 163 164 165 166 /************************************************************************** 167 * 168 * Fields 169 * 170 **************************************************************************/ 171 172 private WeakReference<DialogPane> dialogPaneRef; 173 174 private boolean installingDefaults = false; 175 private boolean hasCustomButtons = false; 176 private boolean hasCustomTitle = false; 177 private boolean hasCustomGraphic = false; 178 private boolean hasCustomHeaderText = false; 179 180 private final InvalidationListener graphicListener = o -> { 181 if (!installingDefaults) hasCustomGraphic = true; 182 }; 183 184 private final InvalidationListener headerTextListener = o -> { 185 if (!installingDefaults) hasCustomHeaderText = true; 186 }; 187 188 private final InvalidationListener titleListener = o -> { 189 if (!installingDefaults) hasCustomTitle = true; 190 }; 191 192 private final ListChangeListener<ButtonType> buttonsListener = change -> { 193 if (!installingDefaults) hasCustomButtons = true; 194 }; 195 196 197 198 /************************************************************************** 199 * 200 * Constructors 201 * 202 **************************************************************************/ 203 204 /** 205 * Creates an alert with the given AlertType (refer to the {@link AlertType} 206 * documentation for clarification over which one is most appropriate). 207 * 208 * <p>By passing in an AlertType, default values for the 209 * {@link #titleProperty() title}, {@link #headerTextProperty() headerText}, 210 * and {@link #graphicProperty() graphic} properties are set, as well as the 211 * relevant {@link #getButtonTypes() buttons} being installed. Once the Alert 212 * is instantiated, developers are able to modify the values of the alert as 213 * desired. 214 * 215 * <p>It is important to note that the one property that does not have a 216 * default value set, and which therefore the developer must set, is the 217 * {@link #contentTextProperty() content text} property (or alternatively, 218 * the developer may call {@code alert.getDialogPane().setContent(Node)} if 219 * they want a more complex alert). If the contentText (or content) properties 220 * are not set, there is no useful information presented to end users. 221 */ 222 public Alert(@NamedArg("AlertType") AlertType alertType) { 223 this(alertType, ""); 224 } 225 226 /** 227 * Creates an alert with the given contentText and AlertType (refer to the 228 * {@link AlertType} documentation for clarification over which one is most 229 * appropriate). 230 * 231 * <p>By passing in an AlertType, default values for the 232 * {@link #titleProperty() title}, {@link #headerTextProperty() headerText}, 233 * and {@link #graphicProperty() graphic} properties are set, as well as the 234 * relevant {@link #getButtonTypes() buttons} being installed. Once the Alert 235 * is instantiated, developers are able to modify the values of the alert as 236 * desired. 237 */ 238 public Alert(@NamedArg("AlertType") AlertType alertType, 239 @NamedArg("contentText") String contentText) { 240 this(alertType, contentText, (ButtonType[])null); 241 } 242 243 /** 244 * Creates an alert with the given contentText, ButtonTypes, and AlertType 245 * (refer to the {@link AlertType} documentation for clarification over which 246 * one is most appropriate). 247 * 248 * <p>By passing in a variable number of ButtonType arguments, the developer 249 * is directly overriding the default buttons that will be displayed in the 250 * dialog, replacing the pre-defined buttons with whatever is specified in the 251 * varargs array. 252 * 253 * <p>By passing in an AlertType, default values for the 254 * {@link #titleProperty() title}, {@link #headerTextProperty() headerText}, 255 * and {@link #graphicProperty() graphic} properties are set. Once the Alert 256 * is instantiated, developers are able to modify the values of the alert as 257 * desired. 258 */ 259 public Alert(@NamedArg("AlertType") AlertType alertType, 260 @NamedArg("contentText") String contentText, 261 ButtonType... buttons) { 262 super(); 263 final DialogPane dialogPane = getDialogPane(); 264 dialogPane.setContentText(contentText); 265 266 dialogPaneRef = new WeakReference<>(dialogPane); 267 268 hasCustomButtons = buttons != null && buttons.length > 0; 269 if (hasCustomButtons) { 270 for (ButtonType btnType : buttons) { 271 dialogPane.getButtonTypes().addAll(btnType); 272 } 273 } 274 275 setAlertType(alertType); 276 277 // listening to property changes on Dialog and DialogPane 278 dialogPaneProperty().addListener(o -> updateListeners()); 279 titleProperty().addListener(titleListener); 280 updateListeners(); 281 } 282 283 284 285 /************************************************************************** 286 * 287 * Properties 288 * 289 **************************************************************************/ 290 291 /** 292 * When creating an Alert instance, users must pass in an {@link AlertType} 293 * enumeration value. It is by passing in this value that the Alert instance will 294 * configure itself appropriately (by setting default values for many of the 295 * {@link Dialog} properties, including {@link #titleProperty() title}, 296 * {@link #headerTextProperty() header}, and {@link #graphicProperty() graphic}, 297 * as well as the default {@link #getButtonTypes() buttons} that are expected in 298 * a dialog of the given type. 299 */ 300 // --- alertType 301 private final ObjectProperty<AlertType> alertType = new SimpleObjectProperty<AlertType>(null) { 302 protected void invalidated() { 303 String newTitle = ""; 304 String newHeader = ""; 305 Node newGraphic = null; 306 ButtonType[] newButtons = new ButtonType[] { ButtonType.OK }; 307 switch (getAlertType()) { 308 case NONE: { 309 newButtons = new ButtonType[] { }; 310 break; 311 } 312 case INFORMATION: { 313 newTitle = ControlResources.getString("Dialog.info.title"); 314 newHeader = ControlResources.getString("Dialog.info.header"); 315 316 // TODO extract out to CSS 317 newGraphic = new ImageView(new Image(AccordionSkin.class.getResource("modena/dialog-information.png").toExternalForm())); 318 break; 319 } 320 case WARNING: { 321 newTitle = ControlResources.getString("Dialog.warning.title"); 322 newHeader = ControlResources.getString("Dialog.warning.header"); 323 324 // TODO extract out to CSS 325 newGraphic = new ImageView(new Image(AccordionSkin.class.getResource("modena/dialog-warning.png").toExternalForm())); 326 break; 327 } 328 case ERROR: { 329 newTitle = ControlResources.getString("Dialog.error.title"); 330 newHeader = ControlResources.getString("Dialog.error.header"); 331 332 // TODO extract out to CSS 333 newGraphic = new ImageView(new Image(AccordionSkin.class.getResource("modena/dialog-error.png").toExternalForm())); 334 break; 335 } 336 case CONFIRMATION: { 337 newTitle = ControlResources.getString("Dialog.confirm.title"); 338 newHeader = ControlResources.getString("Dialog.confirm.header"); 339 340 // TODO extract out to CSS 341 newGraphic = new ImageView(new Image(AccordionSkin.class.getResource("modena/dialog-confirm.png").toExternalForm())); 342 newButtons = new ButtonType[] { ButtonType.OK, ButtonType.CANCEL }; 343 break; 344 } 345 } 346 347 installingDefaults = true; 348 if (!hasCustomTitle) setTitle(newTitle); 349 if (!hasCustomHeaderText) setHeaderText(newHeader); 350 if (!hasCustomGraphic) setGraphic(newGraphic); 351 if (!hasCustomButtons) getButtonTypes().setAll(newButtons); 352 installingDefaults = false; 353 } 354 }; 355 356 public final AlertType getAlertType() { 357 return alertType.get(); 358 } 359 360 public final void setAlertType(AlertType alertType) { 361 this.alertType.setValue(alertType); 362 } 363 364 public final ObjectProperty<AlertType> alertTypeProperty() { 365 return alertType; 366 } 367 368 369 /** 370 * Returns an {@link ObservableList} of all {@link ButtonType} instances that 371 * are currently set inside this Alert instance. A ButtonType may either be one 372 * of the pre-defined types (e.g. {@link ButtonType#OK}), or it may be a 373 * custom type (created via the {@link ButtonType#ButtonType(String)} or 374 * {@link ButtonType#ButtonType(String, javafx.scene.control.ButtonBar.ButtonData)} 375 * constructors. 376 * 377 * <p>Readers should refer to the {@link ButtonType} class documentation for more details, 378 * but at a high level, each ButtonType instance is converted to 379 * a Node (although most commonly a {@link Button}) via the (overridable) 380 * {@link DialogPane#createButton(ButtonType)} method on {@link DialogPane}. 381 */ 382 // --- buttonTypes 383 public final ObservableList<ButtonType> getButtonTypes() { 384 return getDialogPane().getButtonTypes(); 385 } 386 387 388 389 /************************************************************************** 390 * 391 * Private Implementation 392 * 393 **************************************************************************/ 394 395 private void updateListeners() { 396 DialogPane oldPane = dialogPaneRef.get(); 397 398 if (oldPane != null) { 399 oldPane.graphicProperty().removeListener(graphicListener); 400 oldPane.headerTextProperty().removeListener(headerTextListener); 401 oldPane.getButtonTypes().removeListener(buttonsListener); 402 } 403 404 // listen to changes to properties that would be changed by alertType being 405 // changed, so that we only change values that are still at their default 406 // value (i.e. the user hasn't changed them, so we are free to set them 407 // to a new default value when the alertType changes). 408 409 DialogPane newPane = getDialogPane(); 410 if (newPane != null) { 411 newPane.graphicProperty().addListener(graphicListener); 412 newPane.headerTextProperty().addListener(headerTextListener); 413 newPane.getButtonTypes().addListener(buttonsListener); 414 } 415 416 dialogPaneRef = new WeakReference<DialogPane>(newPane); 417 } 418 }