/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.kie.workbench.common.stunner.core.client.canvas.controls.toolbox;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.enterprise.event.Observes;
import javax.inject.Inject;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.Widget;
import org.jboss.errai.ioc.client.api.ManagedInstance;
import org.kie.workbench.common.stunner.core.client.canvas.AbstractCanvasHandler;
import org.kie.workbench.common.stunner.core.client.canvas.controls.AbstractCanvasHandlerRegistrationControl;
import org.kie.workbench.common.stunner.core.client.canvas.controls.toolbox.command.AbstractContext;
import org.kie.workbench.common.stunner.core.client.canvas.controls.toolbox.command.Context;
import org.kie.workbench.common.stunner.core.client.canvas.controls.toolbox.command.ToolboxCommand;
import org.kie.workbench.common.stunner.core.client.canvas.event.selection.CanvasClearSelectionEvent;
import org.kie.workbench.common.stunner.core.client.canvas.event.selection.CanvasElementSelectedEvent;
import org.kie.workbench.common.stunner.core.client.command.CanvasCommandManager;
import org.kie.workbench.common.stunner.core.client.components.toolbox.Toolbox;
import org.kie.workbench.common.stunner.core.client.components.toolbox.ToolboxButton;
import org.kie.workbench.common.stunner.core.client.components.toolbox.ToolboxButtonGrid;
import org.kie.workbench.common.stunner.core.client.components.toolbox.ToolboxFactory;
import org.kie.workbench.common.stunner.core.client.components.toolbox.builder.ToolboxBuilder;
import org.kie.workbench.common.stunner.core.client.components.toolbox.builder.ToolboxButtonBuilder;
import org.kie.workbench.common.stunner.core.client.components.toolbox.event.ToolboxButtonEvent;
import org.kie.workbench.common.stunner.core.client.shape.NodeShape;
import org.kie.workbench.common.stunner.core.client.shape.Shape;
import org.kie.workbench.common.stunner.core.client.shape.view.HasEventHandlers;
import org.kie.workbench.common.stunner.core.client.shape.view.event.DragEvent;
import org.kie.workbench.common.stunner.core.client.shape.view.event.DragHandler;
import org.kie.workbench.common.stunner.core.client.shape.view.event.ViewEventType;
import org.kie.workbench.common.stunner.core.graph.Element;
import static org.uberfire.commons.validation.PortablePreconditions.checkNotNull;
// TODO: Update / rebuild toolbox/buttons after the source element has been updated.
// TODO: Destroy toolbox command instances as well once removing the toolbox for a given element (missing call to ToolboxCommand::destroy method).
public class CanvasToolboxControl extends AbstractCanvasHandlerRegistrationControl<AbstractCanvasHandler>
implements ToolboxControl<AbstractCanvasHandler, Element>,
IsWidget {
public interface View extends IsWidget {
View addWidget(final IsWidget widget);
View clear();
}
private final ManagedInstance<ToolboxControlProvider> controlProviders;
private final ToolboxFactory toolboxFactory;
private final View view;
private final Map<String, List<Toolbox>> toolboxMap = new HashMap<>();
private String currentToolboxUUID;
private CommandManagerProvider<AbstractCanvasHandler> commandManagerProvider;
@Inject
public CanvasToolboxControl(final ManagedInstance<ToolboxControlProvider> controlProviders,
final ToolboxFactory toolboxFactory,
final View view) {
this.controlProviders = controlProviders;
this.toolboxFactory = toolboxFactory;
this.view = view;
this.currentToolboxUUID = null;
}
@Override
public void setCommandManagerProvider(final CommandManagerProvider<AbstractCanvasHandler> provider) {
this.commandManagerProvider = provider;
}
@Override
public void enable(final AbstractCanvasHandler canvasHandler) {
super.enable(canvasHandler);
// Add the control view widget into the canvas.
canvasHandler.getAbstractCanvas().addControl(CanvasToolboxControl.this.asWidget());
}
@Override
@SuppressWarnings("unchecked")
public void register(final Element element) {
if (checkNotRegistered(element) && hasToolboxControls(element)) {
// Register the element's identifier for further toolbox lazy loading.
toolboxMap.put(element.getUUID(),
new LinkedList<>());
// If shape view can be drag, hide the shape's toolbox/es when drag starts.
final Shape shape = canvasHandler.getCanvas().getShape(element.getUUID());
if (shape instanceof NodeShape) {
final HasEventHandlers hasEventHandlers = (HasEventHandlers) shape.getShapeView();
if (hasEventHandlers.supports(ViewEventType.DRAG)) {
final DragHandler handler = new DragHandler() {
@Override
public void handle(final DragEvent event) {
}
@Override
public void start(final DragEvent event) {
hideToolboxes(element);
}
@Override
public void end(final DragEvent event) {
}
};
hasEventHandlers.addHandler(ViewEventType.DRAG,
handler);
registerHandler(element.getUUID(),
handler);
}
}
}
}
/**
* Once an element has been updated, the toolbox/es should be re-built, as
* rule evaluations have to be evaluated against latest status
* and latest graph structure.
* <p/>
* TODO:
* - bug -> applies the new toolbox buttons after any further op with the node, but not the 1st time.
* - improve by not recreating instances, just adding/removing buttons.
*/
@Override
public void update(Element element) {
super.update(element);
this.deregister(element);
this.register(element);
canvasHandler.getCanvas().draw();
}
protected boolean hasToolboxControls(final Element element) {
final List<ToolboxControlProvider<AbstractCanvasHandler, Element>> toolboxControlProviders = getToolboxProviders(element);
return null != toolboxControlProviders && !toolboxControlProviders.isEmpty();
}
protected List<Toolbox> lazyLoad(final String uuid) {
final List<Toolbox> t = toolboxMap.get(uuid);
if (null != t && t.isEmpty()) {
// Lazy loading.
load(canvasHandler.getGraphIndex().get(uuid));
}
return toolboxMap.get(uuid);
}
@SuppressWarnings("unchecked")
protected void load(final Element element) {
final Shape shape = canvasHandler.getCanvas().getShape(element.getUUID());
if (shape instanceof NodeShape) {
final List<ToolboxControlProvider<AbstractCanvasHandler, Element>> toolboxControlProviders = getToolboxProviders(element);
if (null != toolboxControlProviders && !toolboxControlProviders.isEmpty()) {
for (final ToolboxControlProvider<AbstractCanvasHandler, Element> toolboxControlProvider : toolboxControlProviders) {
final List<ToolboxCommand<AbstractCanvasHandler, ?>> commands = toolboxControlProvider.getCommands(canvasHandler,
element);
if (null != commands && !commands.isEmpty()) {
final ToolboxBuilder<?, ToolboxButtonGrid, ?> toolboxBuilder = (ToolboxBuilder<?, ToolboxButtonGrid, ?>) toolboxFactory.toolboxBuilder();
final ToolboxButtonGrid grid = toolboxControlProvider.getGrid(canvasHandler,
element);
toolboxBuilder.forLayer(canvasHandler.getCanvas().getLayer());
toolboxBuilder.forView(shape.getShapeView());
toolboxBuilder.direction(toolboxControlProvider.getOn(),
toolboxControlProvider.getTowards());
toolboxBuilder.grid(grid);
final ToolboxButtonBuilder<Object> buttonBuilder = (ToolboxButtonBuilder<Object>) toolboxFactory.toolboxButtonBuilder();
for (final ToolboxCommand<AbstractCanvasHandler, ?> command : commands) {
// TODO: Use command title (tooltip).
final ToolboxButton button = buttonBuilder.setIcon(command.getIcon(canvasHandler,
grid.getButtonSize(),
grid.getButtonSize()))
.setClickHandler(event -> fireCommandExecutionAndHideToolbox(element,
command,
event,
Context.EventType.CLICK))
.setMouseEnterHandler(event -> fireCommandExecution(element,
command,
event,
Context.EventType.MOUSE_ENTER))
.setMouseExitHandler(event -> fireCommandExecution(element,
command,
event,
Context.EventType.MOUSE_EXIT))
.setMouseDownHandler(event -> fireCommandExecutionAndHideToolbox(element,
command,
event,
Context.EventType.MOUSE_DOWN))
.build();
toolboxBuilder.add(button);
}
final Toolbox toolbox = toolboxBuilder.build();
addToolbox(element.getUUID(),
toolbox);
}
}
}
}
}
@SuppressWarnings("unchecked")
protected List<ToolboxControlProvider<AbstractCanvasHandler, Element>> getToolboxProviders(final Element element) {
if (element.getContent() instanceof org.kie.workbench.common.stunner.core.graph.content.view.View) {
final org.kie.workbench.common.stunner.core.graph.content.view.View viewContent = (org.kie.workbench.common.stunner.core.graph.content.view.View) element.getContent();
final Object definition = viewContent.getDefinition();
final List<ToolboxControlProvider<AbstractCanvasHandler, Element>> result = new LinkedList<>();
// Create a command provider instance for each type available and load the provided commands.
controlProviders.forEach(c -> {
if (c.supports(definition)) {
result.add(c);
}
});
return result;
}
return null;
}
private void addToolbox(final String uuid,
final Toolbox toolbox) {
if (null != uuid && null != toolbox) {
List<Toolbox> toolboxes = toolboxMap.get(uuid);
if (null == toolboxes) {
toolboxes = new LinkedList<>();
toolboxMap.put(uuid,
toolboxes);
}
toolboxes.add(toolbox);
}
}
@SuppressWarnings("unchecked")
private void fireCommandExecution(final Element element,
final ToolboxCommand command,
final ToolboxButtonEvent event,
final Context.EventType eventTypeType) {
final Context _context = new AbstractContext(canvasHandler,
eventTypeType,
event.getX(),
event.getY(),
event.getAbsoluteX(),
event.getAbsoluteY(),
event.getClientX(),
event.getClientY()) {
@Override
public CanvasCommandManager getCommandManager() {
return CanvasToolboxControl.this.commandManagerProvider.getCommandManager();
}
};
setCommandView(command).execute(_context,
element);
}
private void fireCommandExecutionAndHideToolbox(final Element element,
final ToolboxCommand command,
final ToolboxButtonEvent event,
final Context.EventType eventTypeType) {
fireCommandExecution(element,
command,
event,
eventTypeType);
hideToolboxes(element);
}
@Override
protected void doDisable() {
super.doDisable();
// Delete the control view.
canvasHandler.getAbstractCanvas().deleteControl(CanvasToolboxControl.this.asWidget());
}
@Override
public void deregisterAll() {
super.deregisterAll();
new HashSet<>(toolboxMap.keySet())
.stream()
.forEach(this::deregister);
toolboxMap.clear();
}
@Override
public void deregister(final String uuid) {
super.deregister(uuid);
final List<Toolbox> toolboxes = getToolboxes(uuid);
if (null != toolboxes) {
toolboxes
.stream()
.forEach(Toolbox::remove);
toolboxMap.remove(uuid);
}
}
private ToolboxCommand setCommandView(final ToolboxCommand command) {
view.clear();
if (command instanceof IsWidget) {
view.addWidget(((IsWidget) command).asWidget());
}
return command;
}
@Override
public Widget asWidget() {
return view.asWidget();
}
void onCanvasElementSelectedEvent(final @Observes CanvasElementSelectedEvent event) {
checkNotNull("event",
event);
if (checkEventContext(event)) {
final String uuid = event.getElementUUID();
if (null != uuid) {
switchVisibility(uuid);
}
}
}
void CanvasClearSelectionEvent(final @Observes CanvasClearSelectionEvent event) {
checkNotNull("event",
event);
if (null != this.currentToolboxUUID) {
setVisible(this.currentToolboxUUID,
false);
this.currentToolboxUUID = null;
}
}
private boolean isVisible(final String uuid) {
return currentToolboxUUID != null && currentToolboxUUID.equals(uuid);
}
private void switchVisibility(final String uuid) {
if (isVisible(uuid)) {
setVisible(this.currentToolboxUUID,
false);
this.currentToolboxUUID = null;
} else {
if (null != this.currentToolboxUUID) {
setVisible(this.currentToolboxUUID,
false);
}
setVisible(uuid,
true);
this.currentToolboxUUID = uuid;
}
}
private void setVisible(final String uuid,
final boolean visible) {
final List<Toolbox> toolboxes = loadToolboxes(uuid);
if (null != toolboxes) {
for (final Toolbox toolbox : toolboxes) {
if (visible) {
toolbox.show();
} else {
toolbox.hide();
}
}
}
}
private void hideToolboxes(final Element<?> element) {
final List<Toolbox> toolboxes = getToolboxes(element);
if (null != toolboxes) {
toolboxes.stream().forEach(Toolbox::hide);
}
}
private List<Toolbox> getToolboxes(final Element<?> element) {
final String uuid = null != element ? element.getUUID() : null;
return null != uuid ? getToolboxes(uuid) : null;
}
private List<Toolbox> loadToolboxes(final String uuid) {
return lazyLoad(uuid);
}
private List<Toolbox> getToolboxes(final String uuid) {
return toolboxMap.get(uuid);
}
}