View Javadoc
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.spi;
27  
28  import javax.annotation.Nonnull;
29  import java.util.Collection;
30  import java.util.Comparator;
31  import java.util.HashMap;
32  import java.util.Map;
33  import java.util.function.Supplier;
34  import it.tidalwave.util.As;
35  import it.tidalwave.util.Pair;
36  import it.tidalwave.role.ui.MenuBarModel;
37  import it.tidalwave.role.ui.UserAction;
38  import it.tidalwave.role.ui.UserActionProvider;
39  import lombok.AccessLevel;
40  import lombok.RequiredArgsConstructor;
41  import lombok.experimental.Delegate;
42  import lombok.extern.slf4j.Slf4j;
43  import static java.util.Collections.emptyList;
44  import static it.tidalwave.role.ui.MenuBarModel.MenuPlacement._MenuItemPlacement_;
45  import static it.tidalwave.role.ui.UserActionProvider._UserActionProvider_;
46  
47  /***************************************************************************************************************************************************************
48   *
49   * A support implementation for {@link MenuBarModel}.
50   *
51   * @param   <B>               the type of the binder
52   * @param   <MB>              the type of the menubar
53   * @param   <M>               the type of the menu
54   * @param   <MI>              the type of the menu item
55   * @since   1.1-ALPHA-6
56   * @author  Fabrizio Giudici
57   *
58   **************************************************************************************************************************************************************/
59  @RequiredArgsConstructor(access = AccessLevel.PROTECTED) @Slf4j
60  public abstract class MenuBarModelSupport<B, MB, M, MI> implements MenuBarModel
61    {
62      @Delegate
63      private final As as = As.forObject(this);
64  
65      /** The default supplier of {@link UserAction}s, can be injected for testing. */
66      @Nonnull
67      protected final Supplier<Collection<? extends UserAction>> userActionsSupplier;
68  
69      /***********************************************************************************************************************************************************
70       * Default constructor.
71       **********************************************************************************************************************************************************/
72      protected MenuBarModelSupport()
73        {
74          userActionsSupplier = () -> maybeAs(_UserActionProvider_).map(UserActionProvider::getActions).orElse(emptyList());
75        }
76  
77      /***********************************************************************************************************************************************************
78       * {@inheritDoc}
79       **********************************************************************************************************************************************************/
80      @SuppressWarnings("unchecked")
81      public final void populate (@Nonnull final Object binder, @Nonnull final Object menuBar)
82        {
83          populateImpl((B)binder, (MB)menuBar);
84        }
85  
86      /***********************************************************************************************************************************************************
87       * Populates the menu bar.
88       * @param   binder    the binder
89       * @param   menuBar   the menu bar to populate
90       **********************************************************************************************************************************************************/
91      protected void populateImpl (@Nonnull final B binder, @Nonnull final MB menuBar)
92        {
93          final var menuMapByLabel = new HashMap<String, M>();
94          final var actions = userActionsSupplier.get();
95          log.info("Menu bar user actions: {}", actions);
96          actions.stream().map(a -> Pair.of(a, a.maybeAs(_MenuItemPlacement_)))
97                          .filter(p -> p.b.isPresent())
98                          .forEach(p ->
99            {
100             final var menuPath = p.b.map(MenuPlacement::getPath).orElseThrow();
101             final var menu = menuMapByLabel.computeIfAbsent(menuPath, this::createMenu);
102             log.debug("Binding {} to menu item {}", p.a, menuPath);
103             addMenuItemToMenu(menu, binder, p.a);
104           });
105 
106         menuMapByLabel.entrySet().stream().sorted(menuComparator()).forEach(e -> addMenuToMenuBar(menuBar, e.getValue()));
107       }
108 
109     /***********************************************************************************************************************************************************
110      * {@return a new menu with the given label}.
111      * @param   label     the label
112      **********************************************************************************************************************************************************/
113     @Nonnull
114     protected abstract M createMenu (@Nonnull final String label);
115 
116     /***********************************************************************************************************************************************************
117      * Adds a menu to the menu bar.
118      * @param   menuBar   the menu bar
119      * @param   menu      the menu
120      **********************************************************************************************************************************************************/
121     protected abstract void addMenuToMenuBar (@Nonnull final MB menuBar, @Nonnull final M menu);
122 
123     /***********************************************************************************************************************************************************
124      * Adds to the given menu a new item bound to the given {@link UserAction}.
125      * @param   menu      the menu
126      * @param   binder    the binder
127      * @param   action    the user action
128      **********************************************************************************************************************************************************/
129     protected abstract void addMenuItemToMenu (@Nonnull final M menu, @Nonnull final B binder, @Nonnull final UserAction action);
130 
131     /***********************************************************************************************************************************************************
132      * {@return a {@link Comparator } for menus}
133      **********************************************************************************************************************************************************/
134     @Nonnull
135     private final Comparator<Map.Entry<String, ?>> menuComparator()
136       {
137         return (e1, e2) ->
138           {
139             final var i1 = MenuIndex.findPosition(e1.getKey());
140             final var i2 = MenuIndex.findPosition(e2.getKey());
141             return (i1 >= 0 && i2 >= 0) ? i1 - i2 : e1.getKey().compareTo(e2.getKey());
142           };
143       }
144   }