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 }