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}