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}