Skip to content

Method: lambda$bind$0(As)

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.javafx.impl.common;
27:
28: import javax.annotation.Nonnull;
29: import javax.annotation.Nullable;
30: import java.util.List;
31: import java.util.concurrent.Executor;
32: import javafx.collections.ObservableList;
33: import javafx.scene.control.Cell;
34: import javafx.scene.control.ContextMenu;
35: import javafx.scene.control.MenuItem;
36: import javafx.scene.input.KeyCode;
37: import it.tidalwave.ui.role.javafx.CustomGraphicProvider;
38: import it.tidalwave.util.As;
39: import it.tidalwave.util.annotation.VisibleForTesting;
40: import it.tidalwave.role.ui.Displayable;
41: import it.tidalwave.role.ui.UserAction;
42: import it.tidalwave.role.ui.UserActionProvider;
43: import lombok.RequiredArgsConstructor;
44: import lombok.extern.slf4j.Slf4j;
45: import static it.tidalwave.ui.role.javafx.CustomGraphicProvider._CustomGraphicProvider_;
46: import static java.util.stream.Collectors.*;
47: import static it.tidalwave.role.ui.Displayable._Displayable_;
48: import static it.tidalwave.role.ui.Styleable._Styleable_;
49: import static it.tidalwave.role.ui.UserActionProvider._UserActionProvider_;
50:
51: /***************************************************************************************************************************************************************
52: *
53: * An implementation of {@link CellBinder} that extracts information from a {@link UserActionProvider}.
54: *
55: * @author Fabrizio Giudici
56: *
57: **************************************************************************************************************************************************************/
58: @RequiredArgsConstructor @Slf4j
59: public class DefaultCellBinder implements CellBinder
60: {
61: /** Roles to preload, so they are computed in the background thread. */
62: private static final List<Class<?>> PRELOADING_ROLE_TYPES = List.of(
63: _Displayable_, _UserActionProvider_, _Styleable_, _CustomGraphicProvider_);
64:
65: private static final String ROLE_STYLE_PREFIX = "-rs-";
66:
67: @Nonnull
68: private final Executor executor;
69:
70: /***********************************************************************************************************************************************************
71: * {@inheritDoc}
72: **********************************************************************************************************************************************************/
73: @Override
74: public void bind (@Nonnull final Cell<?> cell, @Nullable final As item, final boolean empty)
75: {
76: log.trace("bind({}, {}, {})", cell, item, empty);
77: clearBindings(cell);
78:
79: if (!empty && (item != null))
80: {
81: JavaFXWorker.run(executor,
82: () -> new RoleBag(item, PRELOADING_ROLE_TYPES),
83: roles -> bindAll(cell, roles));
84: }
85: }
86:
87: /***********************************************************************************************************************************************************
88: * Binds everything provided by the given {@link RoleBag} to the given {@link Cell}.
89: *
90: * @param cell the {@code Cell}
91: * @param roles the role bag
92: **********************************************************************************************************************************************************/
93: private void bindAll (@Nonnull final Cell<?> cell, @Nonnull final RoleBag roles)
94: {
95: bindTextAndGraphic(cell, roles);
96: bindDefaultAction(cell, roles);
97: bindContextMenu(cell, roles);
98: bindStyles(cell.getStyleClass(), roles);
99: }
100:
101: /***********************************************************************************************************************************************************
102: * Binds the text and eventual custom {@link javafx.scene.Node} provided by the given {@link RoleBag} to the given
103: * {@link Cell}.
104: *
105: * @param cell the {@code Cell}
106: * @param roles the role bag
107: **********************************************************************************************************************************************************/
108: private void bindTextAndGraphic (@Nonnull final Cell<?> cell, @Nonnull final RoleBag roles)
109: {
110: final var cgp = roles.get(_CustomGraphicProvider_);
111: cell.setGraphic(cgp.map(CustomGraphicProvider::getGraphic).orElse(null));
112: cell.setText(cgp.map(c -> "").orElse(roles.get(_Displayable_).map(Displayable::getDisplayName).orElse("")));
113: }
114:
115: /***********************************************************************************************************************************************************
116: * Binds the default {@link UserAction}s provided by the given {@link RoleBag} as the default action of the given
117: * {@link Cell} (activated by double click or key pressure).
118: *
119: * @param cell the {@code Cell}
120: * @param roles the role bag
121: **********************************************************************************************************************************************************/
122: private void bindDefaultAction (@Nonnull final Cell<?> cell, @Nonnull final RoleBag roles)
123: {
124: roles.getDefaultUserAction().ifPresent(defaultAction ->
125: {
126: // FIXME: doesn't work - keyevents are probably handled by ListView
127: cell.setOnKeyPressed(event ->
128: {
129: log.debug("onKeyPressed: {}", event);
130:
131: if (event.getCode().equals(KeyCode.SPACE))
132: {
133: executor.execute(defaultAction::actionPerformed);
134: }
135: });
136:
137: // FIXME: depends on mouse click, won't handle keyboard
138: cell.setOnMouseClicked(event ->
139: {
140: if (event.getClickCount() == 2)
141: {
142: executor.execute(defaultAction::actionPerformed);
143: }
144: });
145: });
146: }
147:
148: /***********************************************************************************************************************************************************
149: * Binds the {@link UserAction}s provided by the given {@link RoleBag} as items of the contextual menu of a
150: * {@link Cell}.
151: *
152: * @param cell the {@code Cell}
153: * @param roles the role bag
154: **********************************************************************************************************************************************************/
155: private void bindContextMenu (@Nonnull final Cell<?> cell, @Nonnull final RoleBag roles)
156: {
157: final var menuItems = createMenuItems(roles);
158: cell.setContextMenu(menuItems.isEmpty() ? null : new ContextMenu(menuItems.toArray(new MenuItem[0])));
159: }
160:
161: /***********************************************************************************************************************************************************
162: * Adds all the styles provided by the given {@link RoleBag} to a {@link ObservableList} of styles.
163: *
164: * @param styleClasses the destination where to add styles
165: * @param roles the role bag
166: **********************************************************************************************************************************************************/
167: @Nonnull
168: private void bindStyles (@Nonnull final ObservableList<String> styleClasses, @Nonnull final RoleBag roles)
169: {
170: final var styles = styleClasses.stream()
171: .filter(s -> !s.startsWith(ROLE_STYLE_PREFIX))
172: .collect(toList());
173: // FIXME: shouldn't reset them? In case of cell reuse, they get accumulated
174: styles.addAll(roles.getMany(_Styleable_)
175: .stream()
176: .flatMap(styleable -> styleable.getStyles().stream())
177: .map(s -> ROLE_STYLE_PREFIX + s)
178: .collect(toList()));
179: styleClasses.setAll(styles);
180: }
181:
182: /***********************************************************************************************************************************************************
183: * Create a list of {@link MenuItem}s for each action provided by the given {@link RoleBag}.
184: * Don't directly return a ContextMenu otherwise it will be untestable.
185: *
186: * @param roles the role bag
187: * @return the list of {@MenuItem}s
188: **********************************************************************************************************************************************************/
189: @Nonnull
190: @VisibleForTesting public List<MenuItem> createMenuItems (@Nonnull final RoleBag roles)
191: {
192: return roles.getMany(_UserActionProvider_).stream()
193: .flatMap(uap -> uap.getActions().stream())
194: .map(this::createMenuItem)
195: .collect(toList());
196: }
197:
198: /***********************************************************************************************************************************************************
199: *
200: **********************************************************************************************************************************************************/
201: private void clearBindings (@Nonnull final Cell<?> cell)
202: {
203: cell.setText("");
204: cell.setGraphic(null);
205: cell.setContextMenu(null);
206: cell.setOnKeyPressed(null);
207: cell.setOnMouseClicked(null);
208: }
209:
210: /***********************************************************************************************************************************************************
211: * Creates a {@link MenuItem} bound to the given action.
212: *
213: * @param action the action
214: * @return the bound {@code MenuItem}
215: **********************************************************************************************************************************************************/
216: @Nonnull
217: private MenuItem createMenuItem (@Nonnull final UserAction action)
218: {
219: final var menuItem = new MenuItem(action.as(_Displayable_).getDisplayName());
220: menuItem.setOnAction(new EventHandlerUserActionAdapter(executor, action));
221: return menuItem;
222: }
223: }