ComboBoxBindings.java

  1. /*
  2.  * *********************************************************************************************************************
  3.  *
  4.  * SteelBlue: DCI User Interfaces
  5.  * http://tidalwave.it/projects/steelblue
  6.  *
  7.  * Copyright (C) 2015 - 2023 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
  12.  * the License. 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
  17.  * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the
  18.  * specific language governing permissions and limitations under the License.
  19.  *
  20.  * *********************************************************************************************************************
  21.  *
  22.  * git clone https://bitbucket.org/tidalwave/steelblue-src
  23.  * git clone https://github.com/tidalwave-it/steelblue-src
  24.  *
  25.  * *********************************************************************************************************************
  26.  */
  27. package it.tidalwave.role.ui.javafx.impl.combobox;

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

  53. /***********************************************************************************************************************
  54.  *
  55.  * @author  Fabrizio Giudici
  56.  *
  57.  **********************************************************************************************************************/
  58. @Slf4j
  59. public class ComboBoxBindings extends DelegateSupport
  60.   {
  61.     @Nonnull
  62.     private final CellBinder cellBinder;

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

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

  65.     /*******************************************************************************************************************
  66.      *
  67.      * Event handler that executes the default action bound to the given combobox item.
  68.      *
  69.      ******************************************************************************************************************/
  70.     private final EventHandler<ActionEvent> eventHandler = event ->
  71.       {
  72.         final var comboBox = (ComboBox<PresentationModel>)event.getSource();
  73.         final var selectedPm = comboBox.getSelectionModel().getSelectedItem();
  74.         selectedPm.maybeAs(_UserActionProvider_)
  75.                   .flatMap(UserActionProvider::getOptionalDefaultAction)
  76.                   .ifPresent(UserAction::actionPerformed);
  77.       };

  78.     /*******************************************************************************************************************
  79.      *
  80.      *
  81.      *
  82.      ******************************************************************************************************************/
  83.     public ComboBoxBindings (@Nonnull final Executor executor, @Nonnull final CellBinder cellBinder)
  84.       {
  85.         super(executor);
  86.         this.cellBinder = cellBinder;
  87.         cellFactory = comboBox -> new AsObjectListCell<>(cellBinder);
  88.       }

  89.     /*******************************************************************************************************************
  90.      *
  91.      * {@inheritDoc}
  92.      *
  93.      ******************************************************************************************************************/
  94.     public void bind (@Nonnull final ComboBox<PresentationModel> comboBox,
  95.                       @Nonnull final PresentationModel pm,
  96.                       @Nonnull final Optional<Runnable> callback)
  97.       {
  98.         comboBox.setCellFactory(cellFactory);
  99.         comboBox.setButtonCell(new AsObjectListCell<>(cellBinder));
  100.         comboBox.setOnAction(eventHandler);

  101.         // FIXME: WEAK LISTENERS

  102.         // FIXME: this won't work with any external navigation system, such as CEC menus
  103.         // TODO: try by having CEC selection emulating RETURN and optionally accepting RETURN here
  104.         comboBox.setOnKeyPressed(event ->
  105.           {
  106.             if (List.of(SPACE, ENTER).contains(event.getCode()))
  107.               {
  108.                 comboBox.show();
  109.               }
  110.           });

  111.         final var selectedProperty = comboBox.getSelectionModel().selectedItemProperty();
  112.         selectedProperty.removeListener(changeListener);
  113.         JavaFXWorker.run(executor,
  114.                          () -> childrenPm(pm),
  115.                          items -> finalize(comboBox, items, selectedProperty, callback));
  116.       }

  117.     /*******************************************************************************************************************
  118.      *
  119.      ******************************************************************************************************************/
  120.     private void finalize (@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.   }