DefaultMainPanelPresentationControl.java

  1. /*
  2.  * *************************************************************************************************************************************************************
  3.  *
  4.  * SteelBlue: DCI User Interfaces
  5.  * http://tidalwave.it/projects/steelblue
  6.  *
  7.  * Copyright (C) 2015 - 2024 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.role.ui.example.presentation.impl;

  27. import javax.annotation.Nonnull;
  28. import javax.annotation.PostConstruct;
  29. import java.util.List;
  30. import java.nio.file.Path;
  31. import java.nio.file.Paths;
  32. import org.springframework.stereotype.Component;
  33. import it.tidalwave.message.PowerOnEvent;
  34. import it.tidalwave.util.annotation.VisibleForTesting;
  35. import it.tidalwave.role.Aggregate;
  36. import it.tidalwave.role.ui.BoundProperty;
  37. import it.tidalwave.role.ui.Displayable;
  38. import it.tidalwave.role.ui.MenuBarModel.MenuPlacement;
  39. import it.tidalwave.role.ui.PresentationModel;
  40. import it.tidalwave.role.ui.PresentationModelAggregate;
  41. import it.tidalwave.role.ui.Selectable;
  42. import it.tidalwave.role.ui.UserAction;
  43. import it.tidalwave.role.ui.UserActionProvider;
  44. import it.tidalwave.role.ui.Visible;
  45. import it.tidalwave.role.ui.example.model.Dao;
  46. import it.tidalwave.role.ui.example.model.SimpleDciEntity;
  47. import it.tidalwave.role.ui.example.model.SimpleEntity;
  48. import it.tidalwave.role.ui.example.presentation.MainPanelPresentation;
  49. import it.tidalwave.role.ui.example.presentation.MainPanelPresentation.Bindings;
  50. import it.tidalwave.role.ui.example.presentation.MainPanelPresentationControl;
  51. import it.tidalwave.messagebus.annotation.ListensTo;
  52. import lombok.Getter;
  53. import lombok.RequiredArgsConstructor;
  54. import static it.tidalwave.util.Parameters.r;
  55. import static it.tidalwave.util.ui.UserNotificationWithFeedback.*;
  56. import static it.tidalwave.role.ui.Presentable._Presentable_;
  57. import static it.tidalwave.role.ui.spi.PresentationModelCollectors.toCompositePresentationModel;

  58. /***************************************************************************************************************************************************************
  59.  *
  60.  * @stereotype  Control
  61.  *
  62.  * @author  Fabrizio Giudici
  63.  *
  64.  **************************************************************************************************************************************************************/
  65. @Component @RequiredArgsConstructor
  66. // @SimpleMessageSubscriber
  67. public class DefaultMainPanelPresentationControl implements MainPanelPresentationControl
  68.   {
  69.     private static final Path USER_HOME = Paths.get(System.getProperty("user.home"));

  70.     // START SNIPPET: injections
  71.     @Nonnull
  72.     private final Dao dao;

  73.     @Nonnull
  74.     private final MainPanelPresentation presentation;
  75.     // END SNIPPET: injections

  76.     // For each button on the presentation that can do something, a UserAction is provided.
  77.     // START SNIPPET: userActions
  78.     @Getter
  79.     private final UserAction actionButton = UserAction.of(this::onButtonPressed,
  80.                                                           Displayable.of("Press me"));

  81.     @Getter
  82.     private final UserAction actionDialogOk = UserAction.of(this::onButtonDialogOkPressed,
  83.                                                             Displayable.of("Dialog with ok"));

  84.     @Getter
  85.     private final UserAction actionDialogCancelOk = UserAction.of(this::onButtonDialogOkCancelPressed, List.of(
  86.                                                                   Displayable.of("Dialog with ok/cancel"),
  87.                                                                   MenuPlacement.under("Tools")));

  88.     @Getter
  89.     private final UserAction actionPickFile = UserAction.of(this::onButtonPickFilePressed, List.of(
  90.                                                             Displayable.of("Open file..."),
  91.                                                             MenuPlacement.under("File")));

  92.     @Getter
  93.     private final UserAction actionPickDirectory = UserAction.of(this::onButtonPickDirectoryPressed, List.of(
  94.                                                             Displayable.of("Open directory..."),
  95.                                                             MenuPlacement.under("File")));
  96.     // END SNIPPET: userActions
  97.     // START SNIPPET: bindings
  98.     private final Bindings bindings = Bindings.builder()
  99.                                               .actionButton(actionButton)
  100.                                               .actionDialogOk(actionDialogOk)
  101.                                               .actionDialogCancelOk(actionDialogCancelOk)
  102.                                               .actionPickFile(actionPickFile)
  103.                                               .actionPickDirectory(actionPickDirectory)
  104.                                               .build();
  105.     // END SNIPPET: bindings

  106.     // Then there can be a set of variables that represent the internal state of the control.
  107.     @VisibleForTesting int status = 1;

  108.     /***********************************************************************************************************************************************************
  109.      * At {@link PostConstruct} time the control just performs the binding to the presentation.
  110.      **********************************************************************************************************************************************************/
  111.     // START SNIPPET: initialization
  112.     @PostConstruct
  113.     @VisibleForTesting void initialize()
  114.       {
  115.         presentation.bind(bindings);
  116.       }
  117.     // END SNIPPET: initialization

  118.     /***********************************************************************************************************************************************************
  119.      * {@inheritDoc}
  120.      *
  121.      * This method demonstrates the typical idiom for populating model:
  122.      *
  123.      * 1. A dao is called to provide raw model - let's say in form of collections;
  124.      * 2. Objects in the collection are transformed into PresentationModels.
  125.      * 3. The PresentationModels are then passed to the presentation.
  126.      **********************************************************************************************************************************************************/
  127.     // START SNIPPET: populate
  128.     @Override
  129.     public void populate ()
  130.       {
  131.         final var entities1 = dao.getSimpleEntities();
  132.         final var entities2 = dao.getDciEntities();
  133.         final var files = dao.getFiles();
  134.         final var pmEnt1 = entities1.stream().map(this::pmFor).collect(toCompositePresentationModel());
  135.         final var pmEnt2 = entities2.stream().map(this::pmFor).collect(toCompositePresentationModel());
  136.         final var pmFiles = files.stream()
  137.                              .map(item -> item.as(_Presentable_).createPresentationModel())
  138.                              .collect(toCompositePresentationModel(r(Visible.INVISIBLE)));
  139.         presentation.populate(pmEnt1, pmEnt2, pmFiles);
  140.       }
  141.     // END SNIPPET: populate

  142.     /***********************************************************************************************************************************************************
  143.      * Alternatively to expose methods to a public interface, a pubsub facility can be used. This method is called back at application initialisation.
  144.      * @param   event   the 'power on' event
  145.      **********************************************************************************************************************************************************/
  146.     // START SNIPPET: onPowerOn
  147.     @VisibleForTesting void onPowerOn (@ListensTo final PowerOnEvent event)
  148.       {
  149.         presentation.bind(bindings);
  150.         populate();
  151.       }
  152.     // END SNIPPET: onPowerOn

  153.     /***********************************************************************************************************************************************************
  154.      * Factory method for the PresentationModel of SimpleEntity instances.
  155.      *
  156.      * It aggregates a few extra roles into the PresentationModel that are used by the control, such as callbacks
  157.      * for action associated to the context menu. Also a Displayable role is usually injected to control the rendering
  158.      * of entities.
  159.      **********************************************************************************************************************************************************/
  160.     // START SNIPPET: pmSimpleEntity
  161.     @Nonnull
  162.     private PresentationModel pmFor (@Nonnull final SimpleEntity entity)
  163.       {
  164.         final Selectable selectable = () -> onSelected(entity);
  165.         final var action1 = UserAction.of(() -> action1(entity), Displayable.of("Action 1"));
  166.         final var action2 = UserAction.of(() -> action2(entity), Displayable.of("Action 2"));
  167.         final var action3 = UserAction.of(() -> action3(entity), Displayable.of("Action 3"));
  168.         return PresentationModel.of(entity, r(Displayable.of("Item #" + entity.getName()),
  169.                                               selectable,
  170.                                               UserActionProvider.of(action1, action2, action3)));
  171.       }
  172.     // END SNIPPET: pmSimpleEntity

  173.     /***********************************************************************************************************************************************************
  174.      * Factory method for the PresentationModel of SimpleDciEntity instances.
  175.      **********************************************************************************************************************************************************/
  176.     // START SNIPPET: pmSimpleDciEntity
  177.     @Nonnull
  178.     private PresentationModel pmFor (@Nonnull final SimpleDciEntity entity)
  179.       {
  180.         // FIXME: column names
  181.         final Aggregate<PresentationModel> aggregate = PresentationModelAggregate.newInstance()
  182.              .withPmOf("C1", r(Displayable.of(entity.getName())))
  183.              .withPmOf("C2", r(Displayable.of("" + entity.getAttribute1())))
  184.              .withPmOf("C3", r(Displayable.of("" + entity.getAttribute2())));
  185.         final Selectable selectable = () -> onSelected(entity);
  186.         final var action1 = UserAction.of(() -> action1(entity), Displayable.of("Action 1"));
  187.         final var action2 = UserAction.of(() -> action2(entity), Displayable.of("Action 2"));
  188.         final var action3 = UserAction.of(() -> action3(entity), Displayable.of("Action 3"));
  189.         // No explicit Displayable here, as the one inside SimpleDciEntity is used.
  190.         return PresentationModel.of(entity, r(aggregate, selectable, UserActionProvider.of(action1, action2, action3)));
  191.       }
  192.     // END SNIPPET: pmSimpleDciEntity

  193.     // Below simple business methods, as per usual business.

  194.     /***********************************************************************************************************************************************************
  195.      *
  196.      **********************************************************************************************************************************************************/
  197.     // START SNIPPET: onButtonPressed
  198.     private void onButtonPressed()
  199.       {
  200.         presentation.notify("Button pressed");
  201.         status++;
  202.         bindings.textProperty.set(Integer.toString(status));
  203.       }
  204.     // END SNIPPET: onButtonPressed

  205.     /***********************************************************************************************************************************************************
  206.      *
  207.      **********************************************************************************************************************************************************/
  208.     // START SNIPPET: onButtonDialogOkPressed
  209.     private void onButtonDialogOkPressed()
  210.       {
  211.         presentation.notify(notificationWithFeedback()
  212.                 .withCaption("Notification")
  213.                 .withText("Now press the button")
  214.                 .withFeedback(feedback().withOnConfirm(() -> presentation.notify("Pressed ok"))));
  215.       }
  216.     // END SNIPPET: onButtonDialogOkPressed

  217.     /***********************************************************************************************************************************************************
  218.      *
  219.      **********************************************************************************************************************************************************/
  220.     // START SNIPPET: onButtonDialogOkCancelPressed
  221.     private void onButtonDialogOkCancelPressed()
  222.       {
  223.         presentation.notify(notificationWithFeedback()
  224.                 .withCaption("Notification")
  225.                 .withText("Now press the button")
  226.                 .withFeedback(feedback().withOnConfirm(() -> presentation.notify("Pressed ok"))
  227.                                         .withOnCancel(() -> presentation.notify("Pressed cancel"))));
  228.       }
  229.     // END SNIPPET: onButtonDialogOkCancelPressed

  230.     /***********************************************************************************************************************************************************
  231.      * This method demonstrates how to pick a file name by using the proper UI dialog.
  232.      **********************************************************************************************************************************************************/
  233.     // START SNIPPET: onButtonPickFilePressed
  234.     private void onButtonPickFilePressed()
  235.       {
  236.         final var selectedFile = new BoundProperty<>(USER_HOME);
  237.         presentation.pickFile(selectedFile,
  238.             notificationWithFeedback()
  239.                 .withCaption("Pick a file")
  240.                 .withFeedback(feedback().withOnConfirm(() -> presentation.notify("Selected file: " + selectedFile.get()))
  241.                                         .withOnCancel(() -> presentation.notify("Selection cancelled"))));
  242.       }
  243.     // END SNIPPET: onButtonPickFilePressed

  244.     /***********************************************************************************************************************************************************
  245.      * This method demonstrates how to pick a directory name by using the proper UI dialog.
  246.      **********************************************************************************************************************************************************/
  247.     // START SNIPPET: onButtonPickDirectoryPressed
  248.     private void onButtonPickDirectoryPressed()
  249.       {
  250.         final var selectedFolder = new BoundProperty<>(USER_HOME);
  251.         presentation.pickDirectory(selectedFolder,
  252.             notificationWithFeedback()
  253.                 .withCaption("Pick a directory")
  254.                 .withFeedback(feedback().withOnConfirm(() -> presentation.notify("Selected directory: " + selectedFolder.get()))
  255.                                         .withOnCancel(() -> presentation.notify("Selection cancelled"))));
  256.       }
  257.     // END SNIPPET: onButtonPickDirectoryPressed

  258.     /***********************************************************************************************************************************************************
  259.      *
  260.      **********************************************************************************************************************************************************/
  261.     // START SNIPPET: onSelected
  262.     private void onSelected (@Nonnull final Object object)
  263.       {
  264.         presentation.notify("Selected " + object);
  265.       }
  266.     // END SNIPPET: onSelected

  267.     /***********************************************************************************************************************************************************
  268.      *
  269.      **********************************************************************************************************************************************************/
  270.     // START SNIPPET: action1
  271.     private void action1 (@Nonnull final Object object)
  272.       {
  273.         presentation.notify("Action 1 on " + object);
  274.       }
  275.     // END SNIPPET: action1

  276.     /***********************************************************************************************************************************************************
  277.      *
  278.      **********************************************************************************************************************************************************/
  279.     private void action2 (@Nonnull final Object object)
  280.       {
  281.         presentation.notify("Action 2 on " + object);
  282.       }

  283.     /***********************************************************************************************************************************************************
  284.      *
  285.      **********************************************************************************************************************************************************/
  286.     private void action3 (@Nonnull final Object object)
  287.       {
  288.         presentation.notify("Action 3 on " + object);
  289.       }
  290.   }