DialogBindings.java

  1. /*
  2.  * *************************************************************************************************************************************************************
  3.  *
  4.  * SteelBlue: DCI User Interfaces
  5.  * http://tidalwave.it/projects/steelblue
  6.  *
  7.  * Copyright (C) 2015 - 2025 by Tidalwave s.a.s. (http://tidalwave.it)
  8.  *
  9.  * *************************************************************************************************************************************************************
  10.  *
  11.  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
  12.  * You may obtain a copy of the License at
  13.  *
  14.  *     http://www.apache.org/licenses/LICENSE-2.0
  15.  *
  16.  * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
  17.  * CONDITIONS OF ANY KIND, either express or implied.  See the License for the specific language governing permissions and limitations under the License.
  18.  *
  19.  * *************************************************************************************************************************************************************
  20.  *
  21.  * git clone https://bitbucket.org/tidalwave/steelblue-src
  22.  * git clone https://github.com/tidalwave-it/steelblue-src
  23.  *
  24.  * *************************************************************************************************************************************************************
  25.  */
  26. package it.tidalwave.ui.javafx.impl.dialog;

  27. import jakarta.annotation.Nonnull;
  28. import java.util.Optional;
  29. import java.util.concurrent.Executor;
  30. import java.io.IOException;
  31. import java.net.URI;
  32. import java.net.URISyntaxException;
  33. import java.awt.Desktop;
  34. import javafx.beans.value.ChangeListener;
  35. import javafx.beans.value.ObservableValue;
  36. import javafx.scene.Node;
  37. import javafx.scene.control.Alert;
  38. import javafx.scene.control.ButtonType;
  39. import javafx.scene.control.Dialog;
  40. import javafx.scene.control.Label;
  41. import javafx.scene.paint.Color;
  42. import javafx.scene.web.WebView;
  43. import javafx.application.Platform;
  44. import it.tidalwave.util.BundleUtilities;
  45. import it.tidalwave.util.Callback;
  46. import it.tidalwave.ui.core.UserNotificationWithFeedback;
  47. import it.tidalwave.ui.javafx.impl.common.DelegateSupport;
  48. import lombok.RequiredArgsConstructor;
  49. import lombok.SneakyThrows;
  50. import lombok.extern.slf4j.Slf4j;

  51. /***************************************************************************************************************************************************************
  52.  *
  53.  * @author  Fabrizio Giudici
  54.  *
  55.  **************************************************************************************************************************************************************/
  56. @Slf4j
  57. public class DialogBindings extends DelegateSupport
  58.   {
  59.     @RequiredArgsConstructor
  60.     static class UrlOpener implements ChangeListener<String>
  61.       {
  62.         @Nonnull
  63.         private final WebView webView;

  64.         @Nonnull
  65.         private final String text;

  66.         @Override
  67.         public void changed (final ObservableValue<? extends String> observableValue, final String oldValue, final String newValue)
  68.           {
  69.             if (newValue != null && !newValue.isEmpty())
  70.               {
  71.                 Platform.runLater(() ->
  72.                   {
  73.                     try
  74.                       {
  75.                         log.debug("Opening {} ...", newValue);
  76.                         Desktop.getDesktop().browse(new URI(newValue));
  77.                       }
  78.                     catch (IOException | URISyntaxException e)
  79.                       {
  80.                         log.warn("", e);
  81.                       }
  82.                   });

  83.                 webView.getEngine().loadContent(text);
  84.               }
  85.           }
  86.       }

  87.     /***********************************************************************************************************************************************************
  88.      *
  89.      **********************************************************************************************************************************************************/
  90.     public DialogBindings (@Nonnull final Executor executor)
  91.       {
  92.         super(executor);
  93.       }

  94.     /***********************************************************************************************************************************************************
  95.      * {@inheritDoc}
  96.      **********************************************************************************************************************************************************/
  97.     public void showInModalDialog (@Nonnull final UserNotificationWithFeedback notification,
  98.                                    @Nonnull final Optional<Node> node)
  99.       {
  100.         // FIXME: should not be needed
  101.         Platform.runLater(() ->
  102.           {
  103.             log.debug("modalDialog({}, {})", node, notification);

  104. //                final Dialog<ButtonType> dialog = new Dialog<>();
  105.             final Dialog<ButtonType> dialog = new Alert(Alert.AlertType.NONE);
  106.             dialog.initOwner(mainWindow);
  107.             dialog.setTitle(notification.getCaption());
  108.             dialog.setResizable(false);
  109.             final var dialogPane = dialog.getDialogPane();
  110.             final var text = notification.getText();

  111.             if (!text.startsWith("<html>"))
  112.               {
  113.                 dialog.setContentText(text);
  114.                 node.ifPresent(n -> dialogPane.setContent(n));
  115.               }
  116.             else
  117.               {
  118.                 final var webView = new WebView();
  119.                 webView.setPrefSize(600, 300); // FIXME: proportional to screen
  120.                 webView.setContextMenuEnabled(false);
  121.                 final var label = new Label();
  122.                 dialogPane.setContent(label);
  123.                 final var fontFamily = label.getFont().getFamily();
  124.                 // System.err.println("FILL " + label.getTextFill());
  125.                 final var textColor = "white"; // FIXME
  126.                 var backgroundColor = "#252525"; // FIXME

  127.                 try
  128.                   {
  129.                     // Available in JDK 18+
  130.                     final var setPageFill = webView.getClass().getDeclaredMethod("setPageFill", Color.class);
  131.                     setPageFill.invoke(webView, Color.TRANSPARENT);
  132.                     backgroundColor = "transparent";
  133.                   }
  134.                 catch (Exception e)
  135.                   {
  136.                     log.trace("WebView.setPageFill() not available", e);
  137.                   }

  138.                 // FIXME
  139.                 final var text2 = BundleUtilities.getMessage(getClass(),"htmlTemplate", text, textColor, backgroundColor, fontFamily);
  140.                 // System.err.println(text2);
  141.                 webView.getEngine().loadContent(text2);
  142.                 webView.getEngine().locationProperty().addListener(new UrlOpener(webView, text2));
  143.                 dialogPane.setContent(webView);
  144.               }

  145.             final var feedback = notification.getFeedback();
  146.             final var hasOnCancel = feedback.canCancel();

  147.             final var buttonTypes = dialogPane.getButtonTypes();
  148.             buttonTypes.clear();
  149.             buttonTypes.add(ButtonType.OK);

  150.             if (hasOnCancel)
  151.               {
  152.                 buttonTypes.add(ButtonType.CANCEL);
  153.               }

  154. //                okButton.disableProperty().bind(new PropertyAdapter<>(valid)); // FIXME: doesn't work

  155.             final var result = dialog.showAndWait();

  156.             if (result.isEmpty())
  157.               {
  158.                 if (hasOnCancel)
  159.                   {
  160.                     wrap(notification::cancel);
  161.                   }
  162.                 else
  163.                   {
  164.                     wrap(notification::confirm);
  165.                   }
  166.               }
  167.             else
  168.               {
  169.                 if (result.get() == ButtonType.OK)
  170.                   {
  171.                     wrap(notification::confirm);
  172.                   }
  173.                 else if (result.get() == ButtonType.CANCEL)
  174.                   {
  175.                     wrap(notification::cancel);
  176.                   }
  177.                 else
  178.                   {
  179.                     throw new IllegalStateException("Unexpected button pressed: " + result.get());
  180.                   }
  181.               }
  182.           });
  183.       }

  184.     /***********************************************************************************************************************************************************
  185.      *
  186.      **********************************************************************************************************************************************************/
  187.     @SneakyThrows(Throwable.class)
  188.     private static void wrap (@Nonnull final Callback callback)
  189.       {
  190.         callback.call();
  191.       }
  192.   }