1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 package it.tidalwave.ui.javafx.impl.dialog;
27
28 import jakarta.annotation.Nonnull;
29 import java.util.Optional;
30 import java.util.concurrent.Executor;
31 import java.util.function.Supplier;
32 import java.io.IOException;
33 import java.net.URI;
34 import java.net.URISyntaxException;
35 import java.awt.Desktop;
36 import javafx.beans.value.ChangeListener;
37 import javafx.beans.value.ObservableValue;
38 import javafx.scene.Node;
39 import javafx.scene.control.Alert;
40 import javafx.scene.control.ButtonType;
41 import javafx.scene.control.Dialog;
42 import javafx.scene.control.Label;
43 import javafx.scene.paint.Color;
44 import javafx.scene.web.WebView;
45 import javafx.stage.Window;
46 import javafx.application.Platform;
47 import it.tidalwave.ui.core.UserNotificationWithFeedback;
48 import it.tidalwave.ui.javafx.impl.common.DelegateSupport;
49 import it.tidalwave.util.BundleUtilities;
50 import it.tidalwave.util.Callback;
51 import lombok.RequiredArgsConstructor;
52 import lombok.SneakyThrows;
53 import lombok.extern.slf4j.Slf4j;
54
55
56
57
58
59
60 @Slf4j
61 public class DialogBindings extends DelegateSupport
62 {
63 @RequiredArgsConstructor
64 private static class UrlOpener implements ChangeListener<String>
65 {
66 @Nonnull
67 private final WebView webView;
68
69 @Nonnull
70 private final String text;
71
72 @Override
73 public void changed (final ObservableValue<? extends String> observableValue, final String oldValue, final String newValue)
74 {
75 if (newValue != null && !newValue.isEmpty())
76 {
77 Platform.runLater(() ->
78 {
79 try
80 {
81 log.debug("Opening {} ...", newValue);
82 Desktop.getDesktop().browse(new URI(newValue));
83 }
84 catch (IOException | URISyntaxException e)
85 {
86 log.warn("", e);
87 }
88 });
89
90 webView.getEngine().loadContent(text);
91 }
92 }
93 }
94
95
96
97
98 public DialogBindings (@Nonnull final Executor executor, @Nonnull final Supplier<Window> mainWindow)
99 {
100 super(executor, mainWindow);
101 }
102
103
104
105
106 public void showInModalDialog (@Nonnull final UserNotificationWithFeedback notification,
107 @Nonnull final Optional<Node> node)
108 {
109
110 Platform.runLater(() ->
111 {
112 log.debug("modalDialog({}, {})", node, notification);
113
114
115 final Dialog<ButtonType> dialog = new Alert(Alert.AlertType.NONE);
116 dialog.initOwner(mainWindow.get());
117 dialog.setTitle(notification.getCaption());
118 dialog.setResizable(false);
119 final var dialogPane = dialog.getDialogPane();
120 final var text = notification.getText();
121
122 if (!text.startsWith("<html>"))
123 {
124 dialog.setContentText(text);
125 node.ifPresent(dialogPane::setContent);
126 }
127 else
128 {
129 final var webView = new WebView();
130 webView.setPrefSize(600, 300);
131 webView.setContextMenuEnabled(false);
132 final var label = new Label();
133 dialogPane.setContent(label);
134 final var fontFamily = label.getFont().getFamily();
135
136 final var textColor = "white";
137 var backgroundColor = "#252525";
138
139 try
140 {
141
142 final var setPageFill = webView.getClass().getDeclaredMethod("setPageFill", Color.class);
143 setPageFill.invoke(webView, Color.TRANSPARENT);
144 backgroundColor = "transparent";
145 }
146 catch (Exception e)
147 {
148 log.trace("WebView.setPageFill() not available", e);
149 }
150
151
152 final var text2 = BundleUtilities.getMessage(getClass(),"htmlTemplate", text, textColor, backgroundColor, fontFamily);
153
154 webView.getEngine().loadContent(text2);
155 webView.getEngine().locationProperty().addListener(new UrlOpener(webView, text2));
156 dialogPane.setContent(webView);
157 }
158
159 final var feedback = notification.getFeedback();
160 final var hasOnCancel = feedback.canCancel();
161
162 final var buttonTypes = dialogPane.getButtonTypes();
163 buttonTypes.clear();
164 buttonTypes.add(ButtonType.OK);
165
166 if (hasOnCancel)
167 {
168 buttonTypes.add(ButtonType.CANCEL);
169 }
170
171
172
173 final var result = dialog.showAndWait();
174
175 if (result.isEmpty())
176 {
177 if (hasOnCancel)
178 {
179 wrap(notification::cancel);
180 }
181 else
182 {
183 wrap(notification::confirm);
184 }
185 }
186 else
187 {
188 if (result.get() == ButtonType.OK)
189 {
190 wrap(notification::confirm);
191 }
192 else if (result.get() == ButtonType.CANCEL)
193 {
194 wrap(notification::cancel);
195 }
196 else
197 {
198 throw new IllegalStateException("Unexpected button pressed: " + result.get());
199 }
200 }
201 });
202 }
203
204
205
206
207 @SneakyThrows(Throwable.class)
208 private static void wrap (@Nonnull final Callback callback)
209 {
210 callback.call();
211 }
212 }