MenuBarControlSupport.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.core.spi;

  27. import jakarta.annotation.Nonnull;
  28. import java.util.Collection;
  29. import java.util.Comparator;
  30. import java.util.HashMap;
  31. import java.util.Map;
  32. import java.util.function.Supplier;
  33. import it.tidalwave.ui.core.MenuBarControl;
  34. import it.tidalwave.ui.core.role.UserAction;
  35. import it.tidalwave.util.As;
  36. import it.tidalwave.util.Pair;
  37. import lombok.AccessLevel;
  38. import lombok.RequiredArgsConstructor;
  39. import lombok.experimental.Delegate;
  40. import lombok.extern.slf4j.Slf4j;
  41. import static it.tidalwave.ui.core.MenuBarControl.MenuPlacement._MenuItemPlacement_;
  42. import static it.tidalwave.ui.core.role.UserActionProvider._UserActionProvider_;
  43. import static java.util.stream.Collectors.*;

  44. /***************************************************************************************************************************************************************
  45.  *
  46.  * A support implementation for {@link MenuBarControl}.
  47.  *
  48.  * @param   <B>               the type of the binder
  49.  * @param   <MB>              the type of the menubar
  50.  * @param   <M>               the type of the menu
  51.  * @since   1.1-ALPHA-6
  52.  * @author  Fabrizio Giudici
  53.  *
  54.  **************************************************************************************************************************************************************/
  55. @RequiredArgsConstructor(access = AccessLevel.PROTECTED) @Slf4j @SuppressWarnings("this-escape")
  56. public abstract class MenuBarControlSupport<B, MB, M> implements MenuBarControl<B, MB>
  57.   {
  58.     @Delegate
  59.     private final As as = As.forObject(this);

  60.     /** The default supplier of {@link UserAction}s, can be injected for testing. */
  61.     @Nonnull
  62.     protected final Supplier<Collection<? extends UserAction>> userActionsSupplier;

  63.     /***********************************************************************************************************************************************************
  64.      * Default constructor.
  65.      **********************************************************************************************************************************************************/
  66.     protected MenuBarControlSupport ()
  67.       {
  68.         userActionsSupplier = () -> asMany(_UserActionProvider_).stream().flatMap(ap -> ap.getActions().stream()).collect(toList());
  69.       }

  70.     /***********************************************************************************************************************************************************
  71.      * {@inheritDoc}
  72.      **********************************************************************************************************************************************************/
  73.     @Override
  74.     public void populate (@Nonnull final B binder, @Nonnull final MB menuBar)
  75.       {
  76.         final var menuMapByLabel = new HashMap<String, M>();
  77.         final var actions = userActionsSupplier.get();
  78.         log.info("Menu bar user actions: {}", actions);
  79.         actions.stream().map(a -> Pair.of(a, a.maybeAs(_MenuItemPlacement_)))
  80.                         .filter(p -> p.b.isPresent())
  81.                         .forEach(p ->
  82.           {
  83.             final var menuPath = p.b.map(MenuPlacement::getPath).orElseThrow();
  84.             final var menu = menuMapByLabel.computeIfAbsent(menuPath, this::createMenu);
  85.             log.debug("Binding {} to menu item {}", p.a, menuPath);
  86.             addMenuItemToMenu(menu, binder, p.a);
  87.           });

  88.         menuMapByLabel.entrySet().stream().sorted(menuComparator()).forEach(e -> addMenuToMenuBar(menuBar, e.getValue()));
  89.       }

  90.     /***********************************************************************************************************************************************************
  91.      * {@return a new menu with the given label}.
  92.      * @param   label     the label
  93.      **********************************************************************************************************************************************************/
  94.     @Nonnull
  95.     protected abstract M createMenu (@Nonnull final String label);

  96.     /***********************************************************************************************************************************************************
  97.      * Adds a menu to the menu bar.
  98.      * @param   menuBar   the menu bar
  99.      * @param   menu      the menu
  100.      **********************************************************************************************************************************************************/
  101.     protected abstract void addMenuToMenuBar (@Nonnull final MB menuBar, @Nonnull final M menu);

  102.     /***********************************************************************************************************************************************************
  103.      * Adds to the given menu a new item bound to the given {@link UserAction}.
  104.      * @param   menu      the menu
  105.      * @param   binder    the binder
  106.      * @param   action    the user action
  107.      **********************************************************************************************************************************************************/
  108.     protected abstract void addMenuItemToMenu (@Nonnull final M menu, @Nonnull final B binder, @Nonnull final UserAction action);

  109.     /***********************************************************************************************************************************************************
  110.      * {@return a {@link Comparator } for menus}
  111.      **********************************************************************************************************************************************************/
  112.     @Nonnull
  113.     private static Comparator<Map.Entry<String, ?>> menuComparator ()
  114.       {
  115.         return (e1, e2) ->
  116.           {
  117.             final var i1 = MenuIndex.findPosition(e1.getKey());
  118.             final var i2 = MenuIndex.findPosition(e2.getKey());
  119.             return (i1 >= 0 && i2 >= 0) ? i1 - i2 : e1.getKey().compareTo(e2.getKey());
  120.           };
  121.       }
  122.   }