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.util.Arrays;
  28 import java.util.Collection;
  29 
  30 import com.sun.javafx.scene.control.skin.AccordionSkin;
  31 import com.sun.javafx.scene.control.skin.resources.ControlResources;
  32 import javafx.application.Platform;
  33 import javafx.beans.property.ReadOnlyObjectProperty;
  34 import javafx.collections.ObservableList;
  35 import javafx.scene.image.Image;
  36 import javafx.scene.image.ImageView;
  37 import javafx.scene.layout.GridPane;
  38 import javafx.scene.layout.Priority;
  39 import javafx.scene.layout.Region;
  40 
  41 /**
  42  * A dialog that shows a list of choices to the user, from which they can pick
  43  * one item at most. 
  44  * 
  45  * @see Dialog
  46  * @param <T> The type of the items to show to the user, and the type that is returned
  47  *            via {@link #getResult()} when the dialog is dismissed.
  48  * @since JavaFX 8u40
  49  */
  50 public class ChoiceDialog<T> extends Dialog<T> {
  51     
  52     /**************************************************************************
  53      * 
  54      * Fields
  55      * 
  56      **************************************************************************/
  57     
  58     private final GridPane grid;
  59     private final Label label;
  60     private final ComboBox<T> comboBox;
  61     private final T defaultChoice;
  62     
  63     
  64     
  65     /**************************************************************************
  66      * 
  67      * Constructors
  68      * 
  69      **************************************************************************/
  70     
  71     /**
  72      * Creates a default, empty instance of ChoiceDialog with no set items and a
  73      * null default choice. Users of this constructor will subsequently need to
  74      * call {@link #getItems()} to specify which items to show to the user.
  75      */
  76     public ChoiceDialog() {
  77         this((T)null, (T[])null);
  78     }
  79     
  80     /**
  81      * Creates a new ChoiceDialog instance with the first argument specifying the
  82      * default choice that should be shown to the user, and all following arguments
  83      * considered a varargs array of all available choices for the user. It is 
  84      * expected that the defaultChoice be one of the elements in the choices varargs
  85      * array. If this is not true, then defaultChoice will be set to null and the
  86      * dialog will show with the initial choice set to the first item in the list
  87      * of choices.
  88      * 
  89      * @param defaultChoice The item to display as the pre-selected choice in the dialog.
  90      *        This item must be contained within the choices varargs array.
  91      * @param choices All possible choices to present to the user.
  92      */
  93     public ChoiceDialog(T defaultChoice,  @SuppressWarnings("unchecked") T... choices) {
  94         this(defaultChoice, Arrays.asList(choices));
  95     }
  96 
  97     /**
  98      * Creates a new ChoiceDialog instance with the first argument specifying the
  99      * default choice that should be shown to the user, and the second argument
 100      * specifying a collection of all available choices for the user. It is 
 101      * expected that the defaultChoice be one of the elements in the choices 
 102      * collection. If this is not true, then defaultChoice will be set to null and the
 103      * dialog will show with the initial choice set to the first item in the list
 104      * of choices.
 105      * 
 106      * @param defaultChoice The item to display as the pre-selected choice in the dialog.
 107      *        This item must be contained within the choices varargs array.
 108      * @param choices All possible choices to present to the user.
 109      */
 110     public ChoiceDialog(T defaultChoice, Collection<T> choices) {
 111         final DialogPane dialogPane = getDialogPane();
 112         
 113         // -- grid
 114         this.grid = new GridPane();
 115         this.grid.setHgap(10);
 116         this.grid.setMaxWidth(Double.MAX_VALUE);
 117         
 118         // -- label
 119         label = DialogPane.createContentLabel(dialogPane.getContentText());
 120         label.setPrefWidth(Region.USE_COMPUTED_SIZE);
 121         label.textProperty().bind(dialogPane.contentTextProperty());
 122         
 123         dialogPane.contentTextProperty().addListener(o -> updateGrid());
 124         
 125         setTitle(ControlResources.getString("Dialog.confirm.title"));
 126         dialogPane.setHeaderText(ControlResources.getString("Dialog.confirm.header"));
 127 
 128         // TODO extract out to CSS
 129         dialogPane.setGraphic(new ImageView(new Image(AccordionSkin.class.getResource("modena/dialog-confirm.png").toExternalForm())));
 130         dialogPane.getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
 131         
 132         final double MIN_WIDTH = 150;
 133         
 134         comboBox = new ComboBox<T>();
 135         comboBox.setMinWidth(MIN_WIDTH);
 136         if (choices != null) {
 137             comboBox.getItems().addAll(choices);
 138         }
 139         comboBox.setMaxWidth(Double.MAX_VALUE);
 140         GridPane.setHgrow(comboBox, Priority.ALWAYS);
 141         GridPane.setFillWidth(comboBox, true);
 142 
 143         this.defaultChoice = comboBox.getItems().contains(defaultChoice) ? defaultChoice : null;
 144         
 145         if (defaultChoice == null) {
 146             comboBox.getSelectionModel().selectFirst();
 147         } else {
 148             comboBox.getSelectionModel().select(defaultChoice);
 149         }
 150         
 151         updateGrid();
 152         
 153         setResultConverter(dialogButton -> dialogButton == ButtonType.OK ? getSelectedItem() : null);
 154     }
 155     
 156     
 157     
 158     /**************************************************************************
 159      * 
 160      * Public API
 161      * 
 162      **************************************************************************/
 163     
 164     /**
 165      * Returns the currently selected item in the dialog.
 166      */
 167     public final T getSelectedItem() {
 168         return comboBox.getSelectionModel().getSelectedItem();
 169     }
 170     
 171     /**
 172      * Returns the property representing the currently selected item in the dialog.
 173      */
 174     public final ReadOnlyObjectProperty<T> selectedItemProperty() {
 175         return comboBox.getSelectionModel().selectedItemProperty();
 176     }
 177     
 178     /**
 179      * Sets the currently selected item in the dialog.
 180      * @param item The item to select in the dialog.
 181      */
 182     public final void setSelectedItem(T item) {
 183         comboBox.getSelectionModel().select(item);
 184     }
 185     
 186     /**
 187      * Returns the list of all items that will be displayed to users. This list
 188      * can be modified by the developer to add, remove, or reorder the items
 189      * to present to the user.
 190      */
 191     public final ObservableList<T> getItems() {
 192         return comboBox.getItems();
 193     }
 194 
 195     /**
 196      * Returns the default choice that was specified in the constructor.
 197      */
 198     public final T getDefaultChoice() {
 199         return defaultChoice;
 200     }
 201     
 202     
 203     
 204     /**************************************************************************
 205      * 
 206      * Private Implementation
 207      * 
 208      **************************************************************************/
 209     
 210     private void updateGrid() {
 211         grid.getChildren().clear();
 212         
 213         grid.add(label, 0, 0);
 214         grid.add(comboBox, 1, 0);
 215         getDialogPane().setContent(grid);
 216         
 217         Platform.runLater(() -> comboBox.requestFocus());
 218     }
 219 }