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 }