All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.controlsfx.dialog.Dialogs Maven / Gradle / Ivy

Go to download

High quality UI controls and other tools to complement the core JavaFX distribution

There is a newer version: 11.2.1
Show newest version
/**
 * Copyright (c) 2013, 2014 ControlsFX
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *     * Neither the name of ControlsFX, any associated website, nor the
 * names of its contributors may be used to endorse or promote products
 * derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.controlsfx.dialog;

import static impl.org.controlsfx.i18n.Localization.asKey;
import static impl.org.controlsfx.i18n.Localization.getString;
import static impl.org.controlsfx.i18n.Localization.localize;
import static org.controlsfx.dialog.Dialog.ACTION_CANCEL;
import static org.controlsfx.dialog.Dialog.ACTION_NO;
import static org.controlsfx.dialog.Dialog.ACTION_OK;
import static org.controlsfx.dialog.Dialog.ACTION_YES;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;

import javafx.application.Platform;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.transformation.FilteredList;
import javafx.concurrent.Worker;
import javafx.concurrent.Worker.State;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Control;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.PasswordField;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.SelectionModel;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.effect.Effect;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.RowConstraints;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;
import javafx.scene.text.FontPosture;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.stage.Window;
import javafx.util.Callback;
import javafx.util.Pair;

import org.controlsfx.control.ButtonBar;
import org.controlsfx.control.ButtonBar.ButtonType;
import org.controlsfx.control.action.Action;
import org.controlsfx.control.textfield.CustomPasswordField;
import org.controlsfx.control.textfield.CustomTextField;
import org.controlsfx.control.textfield.TextFields;
import org.controlsfx.validation.ValidationSupport;
import org.controlsfx.validation.Validator;


/**
 * A simple (yet flexible) API for showing the most common forms of (modal) UI 
 * dialogs. This class contains a fluent API to make building and customizing
 * the pre-built dialogs really easy, but for those developers who want complete
 * control, you may be interested in instead using the {@link Dialog} class
 * (which is what all of these pre-built dialogs use as well).
 * 
 * 

A dialog consists of a number of sections, and the pre-built dialogs in * this class modify these sections as required. Refer to the {@link Dialog} * class documentation for more detail, but a brief overview is provided in * the following section. * *

Anatomy of a Dialog

* *

A dialog consists of the following sections: * *

    *
  • Title, *
  • System buttons (min, max, close), *
  • Masthead, *
  • Content, *
  • Expandable content, *
  • Button bar *
* *

This is more easily demonstrated in the diagram shown below: * *
*

* *
* The system buttons are hidden, only "close" button is visible by default. * "Maximize" button is only available when dialog becomes resizable. * This happens for example with the Exception dialog when details are expanded. * *

Screenshots

*

To better explain the dialogs, here is a table showing the default look * of all available pre-built dialogs when run on Windows (the button placement * in dialogs uses the {@link ButtonBar} control, so the buttons vary in order * based on the operating system in which the dialog is shown): * *
*

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

Without Masthead

With Masthead

Information
Confirmation
Warning
Error
Exception
Exception (Expanded)
Exception (new window)
Text Input
Choice Input
(ChoiceBox/ComboBox)
Command Link
Font Selector
Progress
* * *

Styled Dialogs

* *

The ControlsFX dialogs API supports displaying dialogs with either a * consistent cross-platform titlebar area, or by using the titlebar of the users * operating system, or without a titlebar. All of the screenshots above are taken * using the cross-platform style, whereas the screenshots below are the * same dialog code being rendered using different dialog styles.

* *

* A dialog has one of the following styles: *

    *
  • {@link Dialog#STYLE_CLASS_CROSS_PLATFORM} - a dialog with a cross-platform title bar.
  • *
  • {@link Dialog#STYLE_CLASS_NATIVE} - a dialog with a native title bar.
  • *
  • {@link Dialog#STYLE_CLASS_UNDECORATED} - a dialog without a title bar.
  • *
*

*

If no style is specified, the dialogs will be rendered using the default * {@link Dialog#STYLE_CLASS_CROSS_PLATFORM} style.

* * To enable this in the Dialogs fluent API, simply call {@link #styleClass(String)} * when creating the dialog. * *

Here are the screenshots of dialogs using different dialog styles: * *
*

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

Platform

DialogStyle

Screenshot

Cross-Platform (default){@code DialogStyle.CROSS_PLATFORM_DARK}
Mac OS X{@code DialogStyle.NATIVE}
Windows 8{@code DialogStyle.NATIVE}
Linux (Ubuntu){@code DialogStyle.NATIVE}
Linux (Ubuntu){@code DialogStyle.UNDECORATED}
* *

Heavyweight vs Lightweight Dialogs

* *

The ControlsFX dialogs API supports a distinction between heavyweight and * lightweight dialogs. In short, a heavyweight dialog is rendered in its own * JavaFX window, allowing for it to appear outside the bounds of the application. * This is the most common style of dialog, and is therefore the default behavior * when creating dialogs in ControlsFX. However, in some case, lightweight dialogs * make more sense, so the following paragraphs will detail when you might want * to use lightweight dialogs. * *

Lightweight dialogs are rendered within the scenegraph (and can't leave * the window). Other than this limitation, lightweight dialogs otherwise render * exactly the same as heavyweight dialogs (except a lightweight dialog normally * inserts an opaque overlay into the scene so that the dialog sticks out * visually). Lightweight dialogs are commonly useful in environments where a * windowing system is unavailable (e.g. tablet devices), and also when you only * want to block execution (and access to) a portion of your user interface. For * example, you could create a lightweight dialog with an owner of a single * {@link Tab} in a {@link TabPane}, and this will only block on that one tab - * all other tabs will continue to be interactive and execute as per usual. * *

One limitation of lightweight dialogs is that it is not possible to use * the native titlebar feature. If you call both {@link #lightweight()} and * {@link #styleClass(String)} with {@link Dialog#STYLE_CLASS_NATIVE} option, * the call to enable lightweight takes precedence over the use of the * native style, so you will end up seeing what is shown in the screenshot * below (that is, a cross-platform-looking dialog that is lightweight). * *

To make a dialog lightweight, you simply call {@link #lightweight()} when * constructing the dialog using this Dialogs API. If you are using the * {@link Dialog} class instead, then the decision between heavyweight and * lightweight dialogs must be made in the * {@link Dialog#Dialog(Object, String, boolean)} constructor. * *

Shown below are screenshots of a lightweight dialog whose owner is the * Tab. This means that whilst the first tab is blocked for input until the * dialog is dismissed by the user, the rest of the UI (including going to other * tabs) remains interactive): * *

Lightweight dialog whose style is {@link Dialog#STYLE_CLASS_CROSS_PLATFORM}

*
*
* *

Lightweight dialog whose style is {@link Dialog#STYLE_CLASS_UNDECORATED}

*
*
* *

Java 8 API elements

* *

Many methods in this class are taking advantage of {@link Optional}. There are many benefits of this, but the main one * is that there no more checking for null and thus no possibility of NullPointerExceptions. Here some things you can do with * {@link Optional}s:
* *

{@code 
 *   optional.isPresent(value->{...}) //perform an operation only if there is a value 
 *   optional.orElse(default)         //get the value of optional with default if it does not exists
 *   optional.map( value -> {...} ).orElse(default) //convert to other type with default
 * }
* and many more - check {@link Optional} API.
* {@link Optional} can be thought of as a stream collection of one or none elements, allowing for functional approach - * chaining methods without keeping state. This especially beneficial in concurrent environments. * *

Code Examples

* *

The code below will setup and show a confirmation dialog: * *

{@code
 *  Action response = Dialogs.create()
 *      .owner( isOwnerSelected ? stage : null)
 *      .title("You do want dialogs right?")
 *      .masthead(isMastheadVisible() ? "Just Checkin'" : null)
 *      .message( "I was a bit worried that you might not want them, so I wanted to double check.")
 *      .showConfirm();}
* *

The most important point to note about the dialogs is that they are modal, * which means that they stop the application code from progressing until the * dialog is closed. Because of this, it is very easy to retrieve the users input: * when the user closes the dialog (e.g. by clicking on one of the buttons), the * dialog will be hidden, and their response will be returned from the * show method that was called to bring the dialog up in the * first place. In other words, following on from the code sample above, you * might do the following: * *

 * {@code 
 * if (response == Dialog.ACTION_YES) {
 *     // ... submit user input
 * } else {
 *     // ... user cancelled, reset form to default
 * }}
* *

The following code is an example of setting up a CommandLink dialog: * *

{@code 
 *   List links = Arrays.asList(
 *        new CommandLink("Add a network that is in the range of this computer", 
 *                        "This shows you a list of networks that are currently available and lets you connect to one."),
 *        new CommandLink("Manually create a network profile", 
 *                        "This creates a new network profile or locates an existing one and saves it on your computer"),
 *        new CommandLink("Create an ad hoc network", 
 *                "This creates a temporary network for sharing files or and Internet connection"));
 *   
 *   Action response = Dialogs.create()
 *           .owner(cbSetOwner.isSelected() ? stage : null)
 *           .title("Manually connect to wireless network")
 *           .masthead(isMastheadVisible() ? "Manually connect to wireless network": null)
 *           .message("How do you want to add a network?")
 *           .showCommandLinks( links.get(1), links );}
* * @see Dialog * @see Action * @see Action * @see Optional */ @Deprecated public final class Dialogs { /** * USE_DEFAULT can be passed in to {@link #title(String)} and {@link #masthead(String)} methods * to specify that the default text for the dialog should be used, where the default text is * specific to the type of dialog being shown. */ public static final String USE_DEFAULT = "$$$"; //$NON-NLS-1$ private Object owner; private String title = USE_DEFAULT; private Node graphic; private String message; private String masthead; private boolean lightweight; private Set actions = new LinkedHashSet<>(); private Effect backgroundEffect; private List styleClasses; /** * Creates the initial dialog * @return dialog instance */ public static Dialogs create() { return new Dialogs(); } private Dialogs() {} /** * Assigns the owner of the dialog. If an owner is specified, the dialog will * block input to the owner and all parent owners. If no owner is specified, * or if owner is null, the dialog will block input to the entire application. * * @param owner The dialog owner. * @return dialog instance. */ public Dialogs owner(final Object owner) { this.owner = owner; return this; } /** * Assigns dialog's title * @param title dialog title * @return dialog instance. */ public Dialogs title(final String title) { this.title = title; return this; } /** * Assigns dialog's graphic * @param graphic dialog graphic * @return dialog instance. */ public Dialogs graphic(final Node graphic) { this.graphic = graphic; return this; } /** * Assigns dialog's instructions * @param message dialog message * @return dialog instance. */ public Dialogs message(final String message) { this.message = message; return this; } /** * Assigns dialog's masthead * @param masthead dialog masthead * @return dialog instance. */ public Dialogs masthead(final String masthead) { this.masthead = masthead; return this; } /** * Completely replaces standard actions with provided ones. * @param actions new dialog actions * @return dialog instance. */ public Dialogs actions( Collection actions) { this.actions.clear(); this.actions.addAll(actions); return this; } /** * Completely replaces standard actions with provided ones. * @param actions new dialog actions * @return dialog instance. */ public Dialogs actions( Action... actions) { return actions( Arrays.asList(actions)); } /** * Sets the effect which should be applied to background components when dialog appears. * If not set or set to null, default semi-transparent panel will be used * Good example of effect to use is GaussianBlur * Currently works only with lightweight dialogs. * @param effect to be applied * @return dialog instance */ public Dialogs backgroundEffect(Effect effect) { this.backgroundEffect = effect; return this; } /** * Specifies that the dialog should become lightweight, which means it is * rendered within the scene graph (and can't leave the window). Lightweight * dialogs are commonly useful in environments where a windowing system is unavailable * (e.g. tablet devices), and also when you only want to block execution * (and access to) a portion of your user interface. For example, you could * create a lightweight dialog with an owner of a single {@link Tab} in a * {@link TabPane}, and this will only block on that one tab - all other * tabs will continue to be interactive and execute as per usual. * * @return dialog instance. */ public Dialogs lightweight() { this.lightweight = true; return this; } /** * Specifies that the dialog should use the given style class, which allows * for custom styling via CSS. There are three built-in style classes that * cover most use cases, these are: * *
    *
  • {@link Dialog#STYLE_CLASS_CROSS_PLATFORM}
  • *
  • {@link Dialog#STYLE_CLASS_NATIVE}
  • *
  • {@link Dialog#STYLE_CLASS_UNDECORATED}
  • *
* *

By default, dialogs use the cross-platform style class, to give a * consistent look across all operating systems. * *

This method can be called multiple times, with each style class being * added to the final dialog instance. * * @param styleClass The style class to add to the dialog. * @return dialog instance. */ public Dialogs styleClass(String styleClass) { if (styleClasses == null) { styleClasses = new ArrayList<>(); } styleClasses.add(styleClass); return this; } /** * Shows information dialog. */ public void showInformation() { showSimpleContentDialog(Type.INFORMATION); } /** * Shows confirmation dialog. * @return action used to close dialog. */ public Action showConfirm() { return showSimpleContentDialog(Type.CONFIRMATION); } /** * Shows warning dialog * @return action used to close dialog */ public Action showWarning() { return showSimpleContentDialog(Type.WARNING); } /** * Show error dialog * @return action used to close dialog */ public Action showError() { return showSimpleContentDialog(Type.ERROR); } /** * Shows exception dialog with expandable stack trace. * @param exception exception to present * @return action used to close dialog */ public Action showException(Throwable exception) { Dialog dlg = buildDialog(Type.ERROR); dlg.setContent(message != null && ! message.isEmpty() ? message : exception.getMessage()); dlg.setExpandableContent(buildExceptionDetails(exception)); if ( !actions.isEmpty()) { dlg.getActions().clear(); dlg.getActions().addAll(actions); } return dlg.show(); } /** * Shows exception dialog with a button to open the exception text in a * new window. * @param exception exception to present * @return action used to close dialog */ public Action showExceptionInNewWindow(final Throwable exception) { Dialog dlg = buildDialog(Type.ERROR); dlg.setContent(message != null && ! message.isEmpty() ? message : exception.getMessage()); dlg.getActions().clear(); Action openExceptionAction = new Action(localize(asKey("exception.button.label")), ae -> { //$NON-NLS-1$ StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); exception.printStackTrace(pw); String moreDetails = sw.toString(); new ExceptionDialogImpl((Window)owner, moreDetails).show(); }); ButtonBar.setType(openExceptionAction, ButtonType.HELP_2); dlg.getActions().add(openExceptionAction); if ( !actions.isEmpty()) { dlg.getActions().addAll(actions); } return dlg.show(); } /** * Shows dialog with one text field * @param defaultValue text field default value * @return Optional of text from input field if OK action is used otherwise {@link Optional#empty()} */ public Optional showTextInput(String defaultValue) { Dialog dlg = buildDialog(Type.INPUT); final TextField textField = new TextField(defaultValue); dlg.setContent(buildInputContent(textField)); return Optional.ofNullable( dlg.show() == ACTION_OK ? textField.getText() : null ); } /** * Shows dialog with one text field * @return Optional of text from input field or {@link Optional#empty()} if dialog is cancelled */ public Optional showTextInput() { return showTextInput(""); //$NON-NLS-1$ } /** * Show a dialog with one combobox filled with provided choices. The combobox selection * will be set to a default value if one is provided. * @param defaultChoice default combobox selection * @param choices dialog choices * @return Optional of selected choice or {@link Optional#empty()} if dialog is cancelled. */ public Optional showChoices(T defaultChoice, Collection choices) { Dialog dlg = buildDialog(Type.INPUT); final double MIN_WIDTH = 150; SelectionModel selectionModel=null; if (choices.size() > 10) { // use ComboBox ComboBox comboBox = new ComboBox<>(); comboBox.setMinWidth(MIN_WIDTH); comboBox.getItems().addAll(choices); selectionModel = comboBox.getSelectionModel(); dlg.setContent(buildInputContent(comboBox)); } else { // use ChoiceBox ChoiceBox choiceBox = new ChoiceBox<>(); choiceBox.setMinWidth(MIN_WIDTH); choiceBox.getItems().addAll(choices); selectionModel = choiceBox.getSelectionModel(); dlg.setContent(buildInputContent(choiceBox)); } if (defaultChoice==null) { selectionModel.selectFirst(); } else { selectionModel.select(defaultChoice); } return Optional.ofNullable( dlg.show() == ACTION_OK ? selectionModel.getSelectedItem() : null); } /** * Show a dialog with one combobox filled with provided choices * @param choices dialog choices * @return Optional of selected choice or Optinal.EMPTY if dialog is cancelled */ public Optional showChoices(Collection choices) { return showChoices(null, choices); } /** * Show a dialog with one combobox filled with provided choices * @param choices dialog choices * @return Optional of selected choice or {@link Optional#empty()} if dialog is cancelled */ public Optional showChoices(@SuppressWarnings("unchecked") T... choices) { return showChoices(Arrays.asList(choices)); } /** * Show a dialog filled with provided command links. Command links are just a {@link DialogAction}s and used instead of button bar and represent * a set of available 'radio' buttons. "Command link" action uses content of its 'text' property as a title of a command link and content of 'long text' * property as a description of the command link. * @param links list of command links presented in specified sequence * @return action used to close dialog (it is either one of command links or CANCEL) */ public Action showCommandLinks(List links) { final Dialog dlg = buildDialog(Type.INFORMATION); dlg.setContent(message); Node messageNode = dlg.getContent(); messageNode.getStyleClass().add("command-link-message"); //$NON-NLS-1$ final int gapSize = 10; final List