/* * 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.client.lienzo.canvas.controls.resize; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.enterprise.context.Dependent; import javax.inject.Inject; import org.kie.workbench.common.stunner.core.client.canvas.AbstractCanvas; import org.kie.workbench.common.stunner.core.client.canvas.AbstractCanvasHandler; import org.kie.workbench.common.stunner.core.client.canvas.Point2D; import org.kie.workbench.common.stunner.core.client.canvas.controls.AbstractCanvasHandlerRegistrationControl; import org.kie.workbench.common.stunner.core.client.canvas.controls.resize.ResizeControl; import org.kie.workbench.common.stunner.core.client.command.CanvasCommandFactory; import org.kie.workbench.common.stunner.core.client.command.CanvasCommandManager; import org.kie.workbench.common.stunner.core.client.command.CanvasViolation; import org.kie.workbench.common.stunner.core.client.command.CanvasViolationImpl; import org.kie.workbench.common.stunner.core.client.command.RequiresCommandManager; import org.kie.workbench.common.stunner.core.client.shape.Shape; import org.kie.workbench.common.stunner.core.client.shape.view.HasControlPoints; import org.kie.workbench.common.stunner.core.client.shape.view.HasEventHandlers; import org.kie.workbench.common.stunner.core.client.shape.view.ShapeView; import org.kie.workbench.common.stunner.core.client.shape.view.event.MouseClickEvent; import org.kie.workbench.common.stunner.core.client.shape.view.event.MouseClickHandler; import org.kie.workbench.common.stunner.core.client.shape.view.event.ResizeEvent; import org.kie.workbench.common.stunner.core.client.shape.view.event.ResizeHandler; import org.kie.workbench.common.stunner.core.client.shape.view.event.ViewEventType; import org.kie.workbench.common.stunner.core.command.Command; import org.kie.workbench.common.stunner.core.command.CommandResult; import org.kie.workbench.common.stunner.core.command.impl.CommandResultImpl; import org.kie.workbench.common.stunner.core.command.impl.CompositeCommandImpl; import org.kie.workbench.common.stunner.core.command.util.CommandUtils; import org.kie.workbench.common.stunner.core.definition.adapter.DefinitionAdapter; import org.kie.workbench.common.stunner.core.definition.property.PropertyMetaTypes; import org.kie.workbench.common.stunner.core.graph.Edge; import org.kie.workbench.common.stunner.core.graph.Element; import org.kie.workbench.common.stunner.core.graph.Node; import org.kie.workbench.common.stunner.core.graph.content.definition.Definition; import org.kie.workbench.common.stunner.core.graph.content.view.BoundImpl; import org.kie.workbench.common.stunner.core.graph.content.view.BoundsImpl; import org.kie.workbench.common.stunner.core.graph.content.view.View; import org.kie.workbench.common.stunner.core.graph.util.GraphUtils; import org.kie.workbench.common.stunner.core.rule.violations.BoundsExceededViolation; @Dependent public class ResizeControlImpl extends AbstractCanvasHandlerRegistrationControl<AbstractCanvasHandler> implements ResizeControl<AbstractCanvasHandler, Element> { private static Logger LOGGER = Logger.getLogger(ResizeControlImpl.class.getName()); private final CanvasCommandFactory<AbstractCanvasHandler> canvasCommandFactory; private RequiresCommandManager.CommandManagerProvider<AbstractCanvasHandler> commandManagerProvider; protected ResizeControlImpl() { this(null); } @Inject public ResizeControlImpl(final CanvasCommandFactory<AbstractCanvasHandler> canvasCommandFactory) { this.canvasCommandFactory = canvasCommandFactory; } @Override @SuppressWarnings("unchecked") public void register(final Element element) { if (checkNotRegistered(element)) { final AbstractCanvas<?> canvas = canvasHandler.getAbstractCanvas(); final Shape<?> shape = canvas.getShape(element.getUUID()); if (supportsResize(shape)) { registerCPHandlers(element, shape.getShapeView()); registerResizeHandlers(element, shape); } } } @Override public CommandResult<CanvasViolation> resize(final Element element, final double width, final double height) { return doResize(element, null, null, width, height); } @Override public CommandResult<CanvasViolation> resize(final Element element, final double x, final double y, final double width, final double height) { return doResize(element, x, y, width, height); } @Override public void setCommandManagerProvider(final RequiresCommandManager.CommandManagerProvider<AbstractCanvasHandler> provider) { this.commandManagerProvider = provider; } @Override protected void doDisable() { super.doDisable(); this.commandManagerProvider = null; } /** * To enable the resize control next bullets must be met: * - shape view must support resize event - for capturing user resize events * - shape view must support mouse click event - for enabling resize control points on mouse click * - shape view must support control points as well */ private boolean supportsResize(final Shape<?> shape) { final ShapeView<?> view = shape.getShapeView(); final boolean supportsResize = (view instanceof HasEventHandlers) && (((HasEventHandlers) view).supports(ViewEventType.RESIZE)) && (((HasEventHandlers) view).supports(ViewEventType.MOUSE_CLICK)); final boolean supportsCtrlPoints = (view instanceof HasControlPoints); return supportsResize && supportsCtrlPoints; } /** * In order to show the shape's control points on mouse click + shift key down. */ private void registerCPHandlers(final Element element, final ShapeView<?> shapeView) { final HasEventHandlers hasEventHandlers = (HasEventHandlers) shapeView; final HasControlPoints hasControlPoints = (HasControlPoints) shapeView; if (hasEventHandlers.supports(ViewEventType.MOUSE_CLICK)) { final MouseClickHandler clickHandler = new MouseClickHandler() { @Override public void handle(final MouseClickEvent event) { if (event.isButtonLeft() && event.isShiftKeyDown() && !hasControlPoints.areControlsVisible()) { hasControlPoints.showControlPoints(HasControlPoints.ControlPointType.RESIZE); } else { hasControlPoints.hideControlPoints(); } canvasHandler.getCanvas().getLayer().draw(); } }; hasEventHandlers.addHandler(ViewEventType.MOUSE_CLICK, clickHandler); registerHandler(element.getUUID(), clickHandler); } } @SuppressWarnings("unchecked") private void registerResizeHandlers(final Element element, final Shape<?> shape) { if (shape.getShapeView() instanceof HasEventHandlers) { final HasEventHandlers hasEventHandlers = (HasEventHandlers) shape.getShapeView(); final ResizeHandler resizeHandler = new ResizeHandler() { @Override public void start(final ResizeEvent event) { } @Override public void handle(final ResizeEvent event) { } @Override public void end(final ResizeEvent event) { LOGGER.log(Level.FINE, "Shape [" + element.getUUID() + "] resized to size {" + event.getWidth() + ", " + event.getHeight() + "] " + "& Coordinates [" + event.getX() + ", " + event.getY() + "]"); final Shape shape = canvasHandler.getCanvas().getShape(element.getUUID()); final double x = shape.getShapeView().getShapeX(); final double y = shape.getShapeView().getShapeY(); final CommandResult<CanvasViolation> result = doResize(element, shape, x + event.getX(), y + event.getY(), event.getWidth(), event.getHeight()); if (CommandUtils.isError(result)) { LOGGER.log(Level.WARNING, "Command failed at resize end [result=" + result + "]"); } } }; hasEventHandlers.addHandler(ViewEventType.RESIZE, resizeHandler); registerHandler(element.getUUID(), resizeHandler); } } private CommandResult<CanvasViolation> doResize(final Element<? extends View<?>> element, final Double x, final Double y, final double w, final double h) { final Shape shape = canvasHandler.getCanvas().getShape(element.getUUID()); return doResize(element, shape, x, y, w, h); } @SuppressWarnings("unchecked") private CommandResult<CanvasViolation> doResize(final Element<? extends View<?>> element, final Shape shape, final Double x, final Double y, final double w, final double h) { // Calculate the new graph element's bounds. final Point2D current = (null != x && null != y) ? new Point2D(x, y) : GraphUtils.getPosition(element.getContent()); final BoundsImpl newBounds = new BoundsImpl( new BoundImpl(current.getX(), current.getY()), new BoundImpl(current.getX() + w, current.getY() + h) ); // Check the new bound values that come from the user's action do not exceed graph ones. if (!GraphUtils.checkBoundsExceeded(canvasHandler.getDiagram().getGraph(), newBounds)) { final CanvasViolation cv = CanvasViolationImpl.Builder.build(new BoundsExceededViolation(newBounds) .setUUID(canvasHandler.getUuid())); return new CommandResultImpl<CanvasViolation>( CommandResult.Type.ERROR, Collections.singleton(cv) ); } // Execute the update position and update property/ies command/s on the bean instance to achieve the new bounds. final List<Command<AbstractCanvasHandler, CanvasViolation>> commands = getResizeCommands(element, shape, w, h); final CompositeCommandImpl.CompositeCommandBuilder<AbstractCanvasHandler, CanvasViolation> commandBuilder = new CompositeCommandImpl.CompositeCommandBuilder<AbstractCanvasHandler, CanvasViolation>(); if (null != commands) { if (null != x && null != y) { commandBuilder .addCommand(canvasCommandFactory.updatePosition((Node<View<?>, Edge>) element, x, y)); } commands.stream().forEach(commandBuilder::addCommand); } final CommandResult<CanvasViolation> resizeResults = getCommandManager().execute(canvasHandler, commandBuilder.build()); // Update the view bounds on the node content after successful resize. if (!CommandUtils.isError(resizeResults)) { element.getContent().setBounds(newBounds); } return resizeResults; } /** * It provides the necessary canvas commands in order to update the domain model with new values that will met * the new bounding box size. * It always updates the element's position, as resize can update it, and it updates as well some of the bean's properties. */ private List<Command<AbstractCanvasHandler, CanvasViolation>> getResizeCommands(final Element<? extends Definition<?>> element, final Shape shape, final double w, final double h) { final Definition content = (Definition) element.getContent(); final Object def = content.getDefinition(); final DefinitionAdapter<Object> adapter = canvasHandler.getDefinitionManager() .adapters().registry().getDefinitionAdapter(def.getClass()); final List<Command<AbstractCanvasHandler, CanvasViolation>> result = new LinkedList<>(); final Object width = adapter.getMetaProperty(PropertyMetaTypes.WIDTH, def); if (null != width) { appendCommandForModelProperty(element, width, w, result); } final Object height = adapter.getMetaProperty(PropertyMetaTypes.HEIGHT, def); if (null != height) { appendCommandForModelProperty(element, height, h, result); } final Object radius = adapter.getMetaProperty(PropertyMetaTypes.RADIUS, def); if (null != radius) { final double r = w > h ? (h / 2) : (w / 2); appendCommandForModelProperty(element, radius, r, result); } return result; } private void appendCommandForModelProperty(final Element<? extends Definition<?>> element, final Object property, final Object value, final List<Command<AbstractCanvasHandler, CanvasViolation>> result) { final String id = canvasHandler.getDefinitionManager().adapters().forProperty().getId(property); result.add(canvasCommandFactory.updatePropertyValue(element, id, value)); } private CanvasCommandManager<AbstractCanvasHandler> getCommandManager() { return commandManagerProvider.getCommandManager(); } }