001package conexp.fx.gui.assistent;
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.Map;
026import java.util.concurrent.ConcurrentHashMap;
027
028import com.google.common.base.Function;
029
030import conexp.fx.gui.util.ColorScheme;
031import javafx.beans.binding.BooleanBinding;
032import javafx.beans.binding.ObjectBinding;
033import javafx.beans.binding.StringBinding;
034import javafx.beans.property.ListProperty;
035import javafx.beans.property.ObjectProperty;
036import javafx.beans.property.SimpleListProperty;
037import javafx.beans.property.SimpleObjectProperty;
038import javafx.beans.property.SimpleStringProperty;
039import javafx.beans.property.StringProperty;
040import javafx.collections.FXCollections;
041import javafx.event.ActionEvent;
042import javafx.event.EventHandler;
043import javafx.geometry.InsetsBuilder;
044import javafx.geometry.Pos;
045import javafx.scene.Node;
046import javafx.scene.SceneBuilder;
047import javafx.scene.control.Button;
048import javafx.scene.control.ButtonBuilder;
049import javafx.scene.input.KeyCode;
050import javafx.scene.input.KeyEvent;
051import javafx.scene.layout.BorderPane;
052import javafx.scene.layout.BorderPaneBuilder;
053import javafx.scene.layout.HBoxBuilder;
054import javafx.scene.layout.StackPaneBuilder;
055import javafx.scene.layout.VBoxBuilder;
056import javafx.scene.paint.Color;
057import javafx.scene.paint.CycleMethod;
058import javafx.scene.paint.LinearGradientBuilder;
059import javafx.scene.paint.StopBuilder;
060import javafx.scene.shape.Rectangle;
061import javafx.scene.shape.RectangleBuilder;
062import javafx.scene.text.FontBuilder;
063import javafx.scene.text.Text;
064import javafx.scene.text.TextBuilder;
065import javafx.stage.Modality;
066import javafx.stage.Stage;
067import javafx.stage.StageBuilder;
068import javafx.stage.StageStyle;
069
070@SuppressWarnings("deprecation")
071public abstract class Assistent<T> extends AssistentPage<T> {
072
073  protected final int                              width                     = 500;
074  protected final int                              height                    = 700;
075  protected final Stage                            stage                     = StageBuilder.create().build();
076  protected final BorderPane                       pane                      = BorderPaneBuilder.create().build();
077  protected final Text                             title                     = TextBuilder
078                                                                                 .create()
079                                                                                 .font(
080                                                                                     FontBuilder
081                                                                                         .create()
082                                                                                         .size(20)
083                                                                                         .build())
084                                                                                 .build();
085  protected final Text                             text                      = TextBuilder
086                                                                                 .create()
087                                                                                 .font(
088                                                                                     FontBuilder
089                                                                                         .create()
090                                                                                         .size(16)
091                                                                                         .build())
092                                                                                 .wrappingWidth(width - 50)
093                                                                                 .build();
094  protected final Map<String, AssistentPage<?>>    availablePages            =
095                                                                                 new ConcurrentHashMap<String, AssistentPage<?>>();
096  protected final ObjectProperty<AssistentPage<?>> currentPage               =
097                                                                                 new SimpleObjectProperty<AssistentPage<?>>(
098                                                                                     this);
099  protected final ObjectProperty<Object>           currentResultProperty     = new SimpleObjectProperty<Object>();
100  protected final StringProperty                   currentNextPageIdProperty = new SimpleStringProperty();
101  protected final ListProperty<AssistentPage<?>>   previousPages             =
102                                                                                 new SimpleListProperty<AssistentPage<?>>(
103                                                                                     FXCollections
104                                                                                         .<AssistentPage<?>> observableArrayList());
105  protected final Stage                            owner;
106
107  public Assistent(
108      Stage owner,
109      String stageTitle,
110      String title,
111      String text,
112      Node content,
113      Function<T, String> nextPageIdFunction) {
114    super(title, text, content, nextPageIdFunction);
115    this.owner = owner;
116    this.stage.setTitle(stageTitle);
117    this.stage.addEventHandler(KeyEvent.KEY_PRESSED, e -> {
118      if (e.getCode().equals(KeyCode.ESCAPE))
119        stage.close();
120    });
121  }
122
123  protected void initialize() {
124    contentProperty.set(createInitialNode());
125    if (owner != null)
126      stage.initOwner(owner);
127    stage.initStyle(StageStyle.UTILITY);
128    stage.initModality(Modality.WINDOW_MODAL);
129    stage.setScene(SceneBuilder.create().width(width).height(height).root(pane).build());
130    createPages();
131    createTop();
132    createBottom();
133    this.title.textProperty().bind(new StringBinding() {
134
135      {
136        super.bind(currentPage);
137      }
138
139      @Override
140      protected String computeValue() {
141        return currentPage.get().titleProperty.get();
142      }
143    });
144    this.text.textProperty().bind(new StringBinding() {
145
146      {
147        super.bind(currentPage);
148      }
149
150      @Override
151      protected String computeValue() {
152        return currentPage.get().textProperty.get();
153      }
154    });
155    pane.centerProperty().bind(new ObjectBinding<Node>() {
156
157      {
158        super.bind(currentPage);
159      }
160
161      @Override
162      protected Node computeValue() {
163        return currentPage.get().contentProperty.get();
164      }
165    });
166    currentPage.set(this);
167    currentResultProperty.bind(currentPage.get().resultProperty);
168    currentNextPageIdProperty.bind(currentPage.get().nextPageIdBinding);
169  }
170
171  public void showAndWait() {
172    stage.showAndWait();
173  }
174
175  protected abstract Node createInitialNode();
176
177  protected abstract void createPages();
178
179  protected final void next() {
180    currentPage.get().onNext();
181    previousPages.add(currentPage.get());
182    if (currentPage.get().nextPageIdBinding.get() != null) {
183      currentPage.set(availablePages.get(currentPage.get().nextPageIdBinding.get()));
184      currentResultProperty.bind(currentPage.get().resultProperty);
185      currentNextPageIdProperty.bind(currentPage.get().nextPageIdBinding);
186    } else
187      stage.close();
188  }
189
190  protected final void previous() {
191    currentPage.set(previousPages.remove(previousPages.size() - 1));
192    currentResultProperty.bind(currentPage.get().resultProperty);
193    currentNextPageIdProperty.bind(currentPage.get().nextPageIdBinding);
194  }
195
196  protected final void cancel() {
197    stage.close();
198  }
199
200  protected final void createTop() {
201    final Rectangle background =
202        RectangleBuilder
203            .create()
204            .fill(
205                LinearGradientBuilder
206                    .create()
207                    .startX(0)
208                    .startY(0)
209                    .endX(0)
210                    .endY(1)
211                    .cycleMethod(CycleMethod.NO_CYCLE)
212                    .proportional(true)
213                    .stops(
214                        StopBuilder.create().color(ColorScheme.JAVA_FX.getColor(4)).offset(0).build(),
215                        StopBuilder.create().color(Color.WHITE).offset(1).build())
216                    .build())
217            .height(80)
218            .width(width)
219            .build();
220    pane.setTop(StackPaneBuilder
221        .create()
222        .children(
223            background,
224            VBoxBuilder
225                .create()
226                .alignment(Pos.TOP_LEFT)
227                .padding(InsetsBuilder.create().top(10).left(10).build())
228                .children(title, text)
229                .build())
230        .build());
231  }
232
233  protected final void createBottom() {
234    final Rectangle background =
235        RectangleBuilder
236            .create()
237            .fill(
238                LinearGradientBuilder
239                    .create()
240                    .startX(0)
241                    .startY(0)
242                    .endX(0)
243                    .endY(1)
244                    .cycleMethod(CycleMethod.NO_CYCLE)
245                    .proportional(true)
246                    .stops(
247                        StopBuilder.create().color(Color.LIGHTGRAY).offset(1).build(),
248                        StopBuilder.create().color(Color.WHITE).offset(0).build())
249                    .build())
250            .height(50)
251            .width(width)
252            .build();
253    final Button cancel =
254        ButtonBuilder.create().text("Cancel").minHeight(20).minWidth(100).onAction(new EventHandler<ActionEvent>() {
255
256          @Override
257          public void handle(ActionEvent event) {
258            cancel();
259          }
260        }).build();
261    final Button previous =
262        ButtonBuilder.create().text("< Previous").minHeight(20).minWidth(100).onAction(new EventHandler<ActionEvent>() {
263
264          @Override
265          public void handle(ActionEvent event) {
266            previous();
267          }
268        }).build();
269    previous.disableProperty().bind(new BooleanBinding() {
270
271      {
272        super.bind(previousPages);
273      }
274
275      @Override
276      protected boolean computeValue() {
277        return previousPages.isEmpty();
278      }
279    });
280    final Button next =
281        ButtonBuilder.create().text("Next >").minHeight(20).minWidth(100).onAction(new EventHandler<ActionEvent>() {
282
283          @Override
284          public void handle(ActionEvent event) {
285            next();
286          }
287        }).build();
288    next.disableProperty().bind(new BooleanBinding() {
289
290      {
291        super.bind(currentResultProperty);
292      }
293
294      @Override
295      protected boolean computeValue() {
296        return currentResultProperty.isNull().get();
297      }
298    });
299    next.textProperty().bind(new StringBinding() {
300
301      {
302        super.bind(currentResultProperty, currentNextPageIdProperty);
303      }
304
305      @Override
306      protected String computeValue() {
307        if (currentResultProperty.isNotNull().get() && currentNextPageIdProperty.isNull().get())
308          return "Finish";
309        else
310          return "Next >";
311      }
312    });
313    pane.setBottom(StackPaneBuilder
314        .create()
315        .children(
316            background,
317            HBoxBuilder
318                .create()
319                .alignment(Pos.CENTER_RIGHT)
320                .spacing(10)
321                .padding(InsetsBuilder.create().right(30).build())
322                .children(cancel, previous, next)
323                .build())
324        .build());
325  }
326
327}