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 }