/*
* 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;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import org.kie.workbench.common.stunner.core.api.DefinitionManager;
import org.kie.workbench.common.stunner.core.client.canvas.listener.CanvasElementListener;
import org.kie.workbench.common.stunner.core.client.canvas.listener.HasCanvasListeners;
import org.kie.workbench.common.stunner.core.client.canvas.util.CanvasLayoutUtils;
import org.kie.workbench.common.stunner.core.client.shape.MutationContext;
import org.kie.workbench.common.stunner.core.client.shape.Shape;
import org.kie.workbench.common.stunner.core.client.shape.factory.ShapeFactory;
import org.kie.workbench.common.stunner.core.diagram.Diagram;
import org.kie.workbench.common.stunner.core.graph.Element;
import org.kie.workbench.common.stunner.core.graph.command.GraphCommandExecutionContext;
import org.kie.workbench.common.stunner.core.graph.content.view.View;
import org.kie.workbench.common.stunner.core.graph.processing.index.Index;
import org.kie.workbench.common.stunner.core.rule.RuleManager;
import org.kie.workbench.common.stunner.core.rule.RuleSet;
import org.kie.workbench.common.stunner.core.util.UUID;
/**
* This type is the canvas handler type used as default by Stunner, most of the component implementations
* rely on this public API handler type.
* If you need to provide a custom canvas handler for you Definition Set, is a good idea to create the handler
* as a subtype for <code>AbstractCanvasHandler</code>, so most of the different IOC resolutions that Stunner requires
* on other areas will be, at least, resolved with the default implementations that rely on subtypes
* for <code>AbstractCanvasHandler</code>.
* @param <D> The diagram type.
* @param <C> The handled canvas type.
*/
public abstract class AbstractCanvasHandler<D extends Diagram, C extends AbstractCanvas>
implements CanvasHandler<D, C>,
HasCanvasListeners<CanvasElementListener> {
private final String uuid;
private final List<CanvasElementListener> listeners = new LinkedList<>();
public AbstractCanvasHandler() {
this.uuid = UUID.uuid();
}
/**
* Provides a definition manager instance in this context.
*/
public abstract DefinitionManager getDefinitionManager();
/**
* Provides the rule manager instance.
*/
public abstract RuleManager getRuleManager();
/**
* Provides the ruleSet instance for this handler.
*/
public abstract RuleSet getRuleSet();
/**
* Returns the graph index instance to perform lookups over the graph structure
* foe this canvas handler's diagram instance loaded.
* Implementation can provide custom graph index types, if necessary targeted
* and optimized for a concrete graph structure.
*/
public abstract Index<?, ?> getGraphIndex();
/**
* Should return a graph execution context to perform the model updates applied by the graph command executions.
* If the implementation is not going to perform model updates, the graph execution context can be
* either <code>null</code> or an empty context type.
*/
public abstract GraphCommandExecutionContext getGraphExecutionContext();
/**
* This method sets the given <code>child</code> instance as children for the given
* given <code>parent</code> instance.
* It sets the shape for the <code>child</code> instance as child shape for the
* <code>parent</code> instance's shape.
* @param parent The parent graph element.
* @param child The graph element to set as child.
*/
public abstract void addChild(final Element parent,
final Element child);
/**
* This method sets the given <code>child</code> instance as children for the given
* given <code>parent</code> instance at the given <code>index</code>. The default
* implementation adds the child to the parent and index is unused. The Child's
* Shape is also set as a sibling of the Parent Shape.
* @param parent The parent graph element.
* @param child The graph element to set as child.
* @param index The index of the child in the parent.
*/
public abstract void addChild(final Element parent,
final Element child,
final int index);
/**
* This method removes the given <code>child</code> instance as children for the given
* given <code>parent</code> instance.
* It removes the shape for the <code>child</code> instance as child shape for the
* <code>parent</code> instance's shape.
* @param parent The parent graph element.
* @param child The element to remove as a child from the parent.
*/
public abstract void removeChild(final Element parent,
final Element child);
/**
* Gets the Element at the specified Canvas coordinates
* @param x The X canvas coordinate
* @param y The Y canvas coordinate
* @return Element at the coordinate
*/
public abstract Optional<Element> getElementAt(final double x,
final double y);
/**
* This method sets the given <code>child</code> instance as docked child for the given
* given <code>parent</code> instance.
* It sets the shape for the <code>child</code> instance as a docked child shape for the
* <code>parent</code> instance's shape.
* @param parent The parent graph element.
* @param child The graph element to set as a docked child.
*/
public abstract void dock(final Element parent,
final Element child);
/**
* This method removes the given <code>child</code> instance as docked child for the given
* given <code>parent</code> instance.
* It removes the shape for the <code>child</code> instance as a docked child shape for the
* <code>parent</code> instance's shape.
* @param parent The parent graph element.
* @param child The element to remove as a docked child from the parent.
*/
public abstract void undock(final Element parent,
final Element child);
/**
* Subtypes must clear this instance's state here.
*/
public abstract CanvasHandler<D, C> doClear();
/**
* Subtypes must destroy this instance's state here.
*/
public abstract void doDestroy();
public abstract void register(final Shape shape,
final Element<View<?>> candidate,
final boolean fireEvents);
public abstract void deregister(final Shape shape,
final Element element,
final boolean fireEvents);
public abstract void applyElementMutation(final Shape shape,
final Element candidate,
final boolean applyPosition,
final boolean applyProperties,
final MutationContext mutationContext);
public abstract ShapeFactory<Object, AbstractCanvasHandler, Shape> getShapeFactory(final String shapeSetId);
/**
* It does:
* - Registers a new graph element into the structure
* - Creates the shape for the element to register, using the shape factory provided
* for the given <code>shapeSetId</code> value.
* @param shapeSetId The identifier for the ShapeSet to use.
* @param candidate The graph element to register.
*/
@SuppressWarnings("unchecked")
public void register(final String shapeSetId,
final Element<View<?>> candidate) {
final ShapeFactory<Object, AbstractCanvasHandler, Shape> factory = getShapeFactory(shapeSetId);
register(factory,
candidate,
true);
}
/**
* It does:
* - Registers a new graph element into the structure
* - Creates the shape for the element to register, using the given shape factory.
* @param factory The shape factory to use.
* @param candidate The graph element to register.
* @param fireEvents If canvas and canvas handled registration events must be fired.
*/
@SuppressWarnings("unchecked")
public void register(final ShapeFactory<Object, AbstractCanvasHandler, Shape> factory,
final Element<View<?>> candidate,
final boolean fireEvents) {
assert factory != null && candidate != null;
final Shape shape = factory.build(candidate.getContent().getDefinition(),
AbstractCanvasHandler.this);
// Set the same identifier as the graph element's one.
if (null == shape.getUUID()) {
shape.setUUID(candidate.getUUID());
}
register(shape,
candidate,
fireEvents);
}
/**
* Deregisters an element from the graph structure and from the canvas.
* @param element The element to deregister and remove from the canvas.
*/
public void deregister(final Element element) {
deregister(element,
true);
}
/**
* De-registers an element from the graph structure and from the canvas.
* @param element The element to de-register and remove from the canvas.
* @param fireEvents If canvas and canvas handled registration events must be fired.
*/
public void deregister(final Element element,
final boolean fireEvents) {
final Shape shape = getCanvas().getShape(element.getUUID());
deregister(shape,
element,
fireEvents);
}
/**
* When an element has been changed, this method produces to update the handler and the canvas
* and mutates the shape bind to the given element using new properties and/or values.
* This method checks all available element properties and can potentially change
* the shape coordinates, size,or whatever property that produces any visual effect on the canvas.
* @param element The element that has been updated.
* @param mutationContext The context for the shape mutations.
*/
public void applyElementMutation(final Element element,
final MutationContext mutationContext) {
applyElementMutation(element,
true,
true,
mutationContext);
}
/**
* When an element has been changed, this method produces to update the coordinates for
* the shape bind to the given element.
* @param element The element that has been updated.
* @param mutationContext The context for the shape mutations.
*/
public void updateElementPosition(final Element element,
final MutationContext mutationContext) {
applyElementMutation(element,
true,
false,
mutationContext);
}
/**
* When an element has been changed, this method produces to update the handler and the canvas
* and mutates the shape bind to the given element using new properties and/or values.
* This method checks all available element properties and can potentially change whatever property
* that produces any visual effect on the canvas, but no produces coordinates or bounds size updates.
* @param element The element that has been updated.
* @param mutationContext The context for the shape mutations.
*/
public void updateElementProperties(final Element element,
final MutationContext mutationContext) {
applyElementMutation(element,
false,
true,
mutationContext);
}
@SuppressWarnings("unchecked")
public void applyElementMutation(final Element candidate,
final boolean applyPosition,
final boolean applyProperties,
final MutationContext mutationContext) {
if (null != candidate && !isCanvasRoot(candidate)) {
final Shape shape = getCanvas().getShape(candidate.getUUID());
applyElementMutation(shape,
candidate,
applyPosition,
applyProperties,
mutationContext);
}
}
/**
* Adds a listener instance in order to be notified about graph element structure updates.
* @param instance The listener instance.
*/
@Override
public HasCanvasListeners<CanvasElementListener> addRegistrationListener(final CanvasElementListener instance) {
listeners.add(instance);
return this;
}
/**
* Removes a previously register listener instance.
* @param instance The listener instance.
*/
@Override
public HasCanvasListeners<CanvasElementListener> removeRegistrationListener(final CanvasElementListener instance) {
listeners.remove(instance);
return this;
}
/**
* Clears all the registered listeners.
*/
@Override
public HasCanvasListeners<CanvasElementListener> clearRegistrationListeners() {
listeners.clear();
return this;
}
/**
* Notifies an element removed to the listeners.
*/
public void notifyCanvasElementRemoved(final Element candidate) {
for (final CanvasElementListener instance : listeners) {
instance.deregister(candidate);
}
}
/**
* Notifies an element added to the listeners.
*/
public void notifyCanvasElementAdded(final Element candidate) {
for (final CanvasElementListener instance : listeners) {
instance.register(candidate);
}
}
/**
* Notifies an element updated to the listeners.
*/
public void notifyCanvasElementUpdated(final Element candidate) {
for (final CanvasElementListener instance : listeners) {
instance.update(candidate);
}
}
/**
* Notifies a clean canvas to the listeners.
*/
public void notifyCanvasClear() {
for (final CanvasElementListener instance : listeners) {
instance.clear();
}
}
public void clearCanvas() {
if (null != getCanvas()) {
notifyCanvasClear();
getCanvas().clear();
getCanvas().draw();
}
}
/**
* Clears this handler state and the handled canvas instance.
* Other further diagrams can be loaded and displayed using this handler.
*/
@Override
public CanvasHandler<D, C> clear() {
if (null != getCanvas()) {
getCanvas().clear();
}
doClear();
return this;
}
/**
* Destroys this handler state and the handled canvas instance.
* This instance cannot be longer used and should be eligible by the garbage collector.
*/
@Override
public void destroy() {
if (null != getCanvas()) {
getCanvas().destroy();
}
doDestroy();
listeners.clear();
}
/**
* Used to avoid forcing specifying the generic for the canvas type
* from other beans.
*/
public AbstractCanvas getAbstractCanvas() {
return getCanvas();
}
public boolean isCanvasRoot(final Element parent) {
return CanvasLayoutUtils.isCanvasRoot(getDiagram(),
parent);
}
public String getUuid() {
return uuid;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (!(o instanceof AbstractCanvasHandler)) {
return false;
}
AbstractCanvasHandler that = (AbstractCanvasHandler) o;
return getUuid().equals(that.getUuid());
}
@Override
public int hashCode() {
return getUuid() == null ? 0 : ~~getUuid().hashCode();
}
@Override
public String toString() {
return this.getClass().getName() + " [" + getUuid() + "]";
}
}