ComboBoxBindings.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.combobox;

  27. import jakarta.annotation.Nonnull;
  28. import java.util.List;
  29. import java.util.Optional;
  30. import java.util.concurrent.Executor;
  31. import javafx.beans.property.ReadOnlyObjectProperty;
  32. import javafx.beans.value.ChangeListener;
  33. import javafx.collections.ObservableList;
  34. import javafx.event.ActionEvent;
  35. import javafx.scene.control.ComboBox;
  36. import javafx.scene.control.ListCell;
  37. import javafx.scene.control.ListView;
  38. import javafx.util.Callback;
  39. import it.tidalwave.ui.core.role.PresentationModel;
  40. import it.tidalwave.ui.core.role.UserAction;
  41. import it.tidalwave.ui.core.role.UserActionProvider;
  42. import it.tidalwave.ui.javafx.impl.common.CellBinder;
  43. import it.tidalwave.ui.javafx.impl.common.ChangeListenerSelectableAdapter;
  44. import it.tidalwave.ui.javafx.impl.common.DelegateSupport;
  45. import it.tidalwave.ui.javafx.impl.common.JavaFXWorker;
  46. import it.tidalwave.ui.javafx.impl.common.AsObjectListCell;
  47. import lombok.extern.slf4j.Slf4j;
  48. import static javafx.scene.input.KeyCode.*;
  49. import static it.tidalwave.ui.core.role.UserActionProvider._UserActionProvider_;
  50. import static it.tidalwave.ui.javafx.impl.DefaultJavaFXBinder.enforceFxApplicationThread;
  51. import static it.tidalwave.ui.javafx.impl.common.JavaFXWorker.childrenPm;

  52. /***************************************************************************************************************************************************************
  53.  *
  54.  * This class takes care of bindings related to {@link ComboBox}.
  55.  *
  56.  * @author  Fabrizio Giudici
  57.  *
  58.  **************************************************************************************************************************************************************/
  59. @Slf4j
  60. public class ComboBoxBindings extends DelegateSupport
  61.   {
  62.     @Nonnull
  63.     private final CellBinder cellBinder;

  64.     private final Callback<ListView<PresentationModel>, ListCell<PresentationModel>> cellFactory;

  65.     private final ChangeListener<PresentationModel> changeListener = new ChangeListenerSelectableAdapter(executor);

  66.     /***********************************************************************************************************************************************************
  67.      *
  68.      **********************************************************************************************************************************************************/
  69.     public ComboBoxBindings (@Nonnull final Executor executor, @Nonnull final CellBinder cellBinder)
  70.       {
  71.         super(executor);
  72.         this.cellBinder = cellBinder;
  73.         cellFactory = comboBox -> new AsObjectListCell<>(cellBinder);
  74.       }

  75.     /***********************************************************************************************************************************************************
  76.      * Binds a combobox.
  77.      * @param   comboBox            the {@link ComboBox}
  78.      * @param   pm                  the {@link PresentationModel}
  79.      * @param   callback            an optional callback to invoke at the end of the binding
  80.      **********************************************************************************************************************************************************/
  81.     public void bind (@Nonnull final ComboBox<PresentationModel> comboBox, @Nonnull final PresentationModel pm, @Nonnull final Optional<Runnable> callback)
  82.       {
  83.         enforceFxApplicationThread();
  84.         log.debug("bind({}, {}, {})", comboBox, pm, callback);
  85.         comboBox.setCellFactory(cellFactory);
  86.         comboBox.setButtonCell(new AsObjectListCell<>(cellBinder));
  87.         comboBox.setOnAction(this::onActionEvent);
  88.         comboBox.setOnKeyPressed(event ->
  89.           {
  90.             if (List.of(SPACE, ENTER).contains(event.getCode()))
  91.               {
  92.                 comboBox.show();
  93.               }
  94.           });

  95.         final var selectedProperty = comboBox.getSelectionModel().selectedItemProperty();
  96.         selectedProperty.removeListener(changeListener);
  97.         JavaFXWorker.run(executor,
  98.                          () -> childrenPm(pm),
  99.                          items -> finalizeBinding(comboBox, items, selectedProperty, callback));
  100.       }

  101.     /***********************************************************************************************************************************************************
  102.      * Event handler that calls back the default action bound to the given combobox item.
  103.      **********************************************************************************************************************************************************/
  104.     @SuppressWarnings("unchecked")
  105.     private void onActionEvent (@Nonnull final ActionEvent event)
  106.       {
  107.         final var comboBox = (ComboBox<PresentationModel>)event.getSource();
  108.         final var selectedPm = comboBox.getSelectionModel().getSelectedItem();
  109.         selectedPm.maybeAs(_UserActionProvider_)
  110.                   .flatMap(UserActionProvider::getOptionalDefaultAction)
  111.                   .ifPresent(UserAction::actionPerformed);
  112.       }

  113.     /***********************************************************************************************************************************************************
  114.      * Finalizes binding in the JavaFX thread and eventually invokes a callback.
  115.      * @param   comboBox            the {@link ComboBox}
  116.      * @param   items               the items of the combo box
  117.      * @param   selectedProperty    the 'selected' property
  118.      * @param   callback            an optional callback to invoke at the end
  119.      **********************************************************************************************************************************************************/
  120.     private void finalizeBinding (@Nonnull final ComboBox<PresentationModel> comboBox,
  121.                                   @Nonnull final ObservableList<PresentationModel> items,
  122.                                   @Nonnull final ReadOnlyObjectProperty<PresentationModel> selectedProperty,
  123.                                   @Nonnull final Optional<Runnable> callback)
  124.       {
  125.         comboBox.setItems(items);

  126.         if (!items.isEmpty())
  127.           {
  128.             comboBox.getSelectionModel().select(items.get(0));
  129.           }

  130.         selectedProperty.addListener(changeListener);
  131.         callback.ifPresent(Runnable::run);
  132.       }
  133.   }