001package conexp.fx.gui.dialog;
002
003/*
004 * #%L
005 * Concept Explorer FX
006 * %%
007 * Copyright (C) 2010 - 2023 Francesco Kriegel
008 * %%
009 * This program is free software: you can redistribute it and/or modify
010 * it under the terms of the GNU General Public License as
011 * published by the Free Software Foundation, either version 3 of the
012 * License, or (at your option) any later version.
013 * 
014 * This program is distributed in the hope that it will be useful,
015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
017 * GNU General Public License for more details.
018 * 
019 * You should have received a copy of the GNU General Public
020 * License along with this program.  If not, see
021 * <http://www.gnu.org/licenses/gpl-3.0.html>.
022 * #L%
023 */
024
025import java.util.LinkedList;
026import java.util.List;
027
028import javafx.beans.binding.DoubleBinding;
029import javafx.event.ActionEvent;
030import javafx.event.EventHandler;
031import javafx.geometry.InsetsBuilder;
032import javafx.geometry.Pos;
033import javafx.scene.Node;
034import javafx.scene.SceneBuilder;
035import javafx.scene.control.Button;
036import javafx.scene.control.ButtonBuilder;
037import javafx.scene.effect.BlurType;
038import javafx.scene.effect.DropShadow;
039import javafx.scene.effect.DropShadowBuilder;
040import javafx.scene.layout.BorderPane;
041import javafx.scene.layout.BorderPaneBuilder;
042import javafx.scene.layout.HBoxBuilder;
043import javafx.scene.layout.Pane;
044import javafx.scene.layout.StackPane;
045import javafx.scene.layout.StackPaneBuilder;
046import javafx.scene.layout.VBoxBuilder;
047import javafx.scene.paint.Color;
048import javafx.scene.paint.CycleMethod;
049import javafx.scene.paint.LinearGradientBuilder;
050import javafx.scene.paint.StopBuilder;
051import javafx.scene.shape.Rectangle;
052import javafx.scene.shape.RectangleBuilder;
053import javafx.scene.text.FontBuilder;
054import javafx.scene.text.Text;
055import javafx.scene.text.TextBuilder;
056import javafx.stage.Modality;
057import javafx.stage.Stage;
058import javafx.stage.StageBuilder;
059import javafx.stage.StageStyle;
060
061public class FXDialog<T> {
062
063  public enum Answer {
064
065    OK,
066    CANCEL,
067    YES,
068    NO,
069    UNKNOWN;
070
071  }
072
073  public enum Style {
074
075    INFO,
076    WARN,
077    ERROR,
078    QUESTION;
079
080  }
081
082  private final int            width;
083  private final Stage          stage        = StageBuilder.create().build();
084  protected final BorderPane   pane         = BorderPaneBuilder.create().build();
085  private final Text           text;
086  private final FXDialog.Style style;
087  private Answer               result       = Answer.UNKNOWN;
088  protected T                  value        = null;
089  private StackPane            topPane;
090  private StackPane            bottomPane;
091  private Rectangle            topBackground;
092  private Rectangle            bottomBackground;
093  private final Button         okButton     = ButtonBuilder
094      .create()
095      .text("OK")
096      .minHeight(20)
097      .minWidth(100)
098      .effect(new DropShadow())
099      .onAction(new EventHandler<ActionEvent>() {
100
101                                                  @Override
102                                                  public void handle(ActionEvent event) {
103                                                    result = Answer.OK;
104                                                    stage.close();
105                                                  }
106                                                })
107      .build();
108  private final Button         cancelButton = ButtonBuilder
109      .create()
110      .text("Cancel")
111      .minHeight(20)
112      .minWidth(100)
113      .effect(new DropShadow())
114      .onAction(new EventHandler<ActionEvent>() {
115
116                                                  @Override
117                                                  public void handle(ActionEvent event) {
118                                                    result = Answer.CANCEL;
119                                                    stage.close();
120                                                  }
121                                                })
122      .build();
123  private final Button         yesButton    = ButtonBuilder
124      .create()
125      .text("Yes")
126      .minHeight(20)
127      .minWidth(100)
128      .effect(new DropShadow())
129      .onAction(new EventHandler<ActionEvent>() {
130
131                                                  @Override
132                                                  public void handle(ActionEvent event) {
133                                                    result = Answer.YES;
134                                                    stage.close();
135                                                  }
136                                                })
137      .build();
138  private final Button         noButton     = ButtonBuilder
139      .create()
140      .text("No")
141      .minHeight(20)
142      .minWidth(100)
143      .effect(new DropShadow())
144      .onAction(new EventHandler<ActionEvent>() {
145
146                                                  @Override
147                                                  public void handle(ActionEvent event) {
148                                                    result = Answer.NO;
149                                                    stage.close();
150                                                  }
151                                                })
152      .build();
153
154  public FXDialog(
155      final Stage primaryStage,
156      final FXDialog.Style style,
157      final String title,
158      final String message,
159      final Node optionalCenterNode) {
160    this(primaryStage, style, title, message, optionalCenterNode, 500);
161  }
162
163  public FXDialog(
164      final Stage primaryStage,
165      final FXDialog.Style style,
166      final String title,
167      final String message,
168      final Node optionalCenterNode,
169      final int width) {
170    super();
171    this.width = width;
172    this.style = style;
173    this.text = TextBuilder
174        .create()
175        .effect(
176            DropShadowBuilder.create().radius(1).blurType(BlurType.GAUSSIAN).color(Color.LIGHTGREY).spread(1).build())
177        .font(FontBuilder.create().size(16).build())
178        .wrappingWidth(width - 50)
179        .build();
180    stage.initOwner(primaryStage);
181    stage.initStyle(StageStyle.UTILITY);
182    stage.initModality(Modality.WINDOW_MODAL);
183    stage.setTitle(title);
184    stage.setResizable(false);
185    stage.setScene(SceneBuilder.create().width(width).root(pane).build());
186    text.setText(message);
187    createTop();
188    if (optionalCenterNode != null)
189      pane.setCenter(optionalCenterNode);
190    createBottom();
191  }
192
193  public final void setCenterNode(final Node centerNode) {
194    pane.setCenter(centerNode);
195  }
196
197  public final static class Return<T> {
198
199    private final Answer answer;
200    private final T      value;
201
202    private Return(final Answer answer, final T value) {
203      super();
204      this.answer = answer;
205      this.value = value;
206    }
207
208    public final Answer result() {
209      return answer;
210    }
211
212    public final T value() {
213      return value;
214    }
215  }
216
217  public final Return<T> showAndWait() {
218    stage.showAndWait();
219    bindHeight();
220    return new Return<T>(result, value);
221  }
222
223  private final void bindHeight() {
224    topBackground.heightProperty().bind(new DoubleBinding() {
225
226      {
227        super.bind(text.layoutBoundsProperty());
228      }
229
230      @Override
231      protected double computeValue() {
232        return text.getLayoutBounds().getHeight() + 20;
233      }
234    });
235    final DoubleBinding height = new DoubleBinding() {
236
237      {
238        super.bind(topBackground.heightProperty(), bottomBackground.heightProperty());
239        if (pane.getCenter() != null && pane.getCenter() instanceof Pane) {
240          super.bind(((Pane) pane.getCenter()).minHeightProperty());
241        }
242      }
243
244      @Override
245      protected double computeValue() {
246        return topBackground.heightProperty().get() + bottomBackground.heightProperty().get()
247            + ((pane.getCenter() != null && pane.getCenter() instanceof Pane)
248                ? ((Pane) pane.getCenter()).minHeightProperty().get() : 0d);
249      }
250    };
251    pane.minHeightProperty().bind(height);
252    pane.maxHeightProperty().bind(height);
253    stage.minHeightProperty().bind(pane.heightProperty());
254    stage.maxHeightProperty().bind(pane.heightProperty());
255  }
256
257  private final void createTop() {
258    topBackground = RectangleBuilder.create().fill(Color.WHITE).width(width).build();
259    topPane = StackPaneBuilder
260        .create()
261        .children(
262            topBackground,
263            VBoxBuilder
264                .create()
265                .alignment(Pos.TOP_LEFT)
266                .spacing(10)
267                .padding(InsetsBuilder.create().left(10).top(10).right(10).bottom(10).build())
268                .children(text)
269                .build())
270        .build();
271    if (text.getText().trim() != "")
272      pane.setTop(topPane);
273  }
274
275  private final void createBottom() {
276    bottomBackground = RectangleBuilder
277        .create()
278        .fill(
279            LinearGradientBuilder
280                .create()
281                .startX(0)
282                .startY(0)
283                .endX(0)
284                .endY(1)
285                .cycleMethod(CycleMethod.NO_CYCLE)
286                .proportional(true)
287                .stops(
288                    StopBuilder.create().color(Color.LIGHTGRAY).offset(1).build(),
289                    StopBuilder.create().color(Color.WHITE).offset(0).build())
290                .build())
291        .height(50)
292        .width(width)
293        .build();
294    final List<Button> buttons = new LinkedList<Button>();
295    switch (style) {
296    case INFO:
297    case ERROR:
298      buttons.add(okButton);
299      break;
300    case WARN:
301      buttons.add(okButton);
302      buttons.add(cancelButton);
303      break;
304    case QUESTION:
305      buttons.add(yesButton);
306      buttons.add(noButton);
307      buttons.add(cancelButton);
308      break;
309    }
310    bottomPane = StackPaneBuilder
311        .create()
312        .children(
313            bottomBackground,
314            HBoxBuilder
315                .create()
316                .alignment(Pos.CENTER_RIGHT)
317                .spacing(10)
318                .padding(InsetsBuilder.create().right(30).build())
319                .children(buttons)
320                .build())
321        .build();
322    pane.setBottom(bottomPane);
323  }
324
325}