/* * 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.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.enterprise.event.Event; import com.google.gwt.logging.client.LogConfiguration; import com.google.gwt.user.client.ui.IsWidget; import org.kie.workbench.common.stunner.core.client.canvas.event.CanvasClearEvent; import org.kie.workbench.common.stunner.core.client.canvas.event.CanvasDrawnEvent; import org.kie.workbench.common.stunner.core.client.canvas.event.CanvasFocusedEvent; import org.kie.workbench.common.stunner.core.client.canvas.event.registration.CanvasShapeAddedEvent; import org.kie.workbench.common.stunner.core.client.canvas.event.registration.CanvasShapeRemovedEvent; import org.kie.workbench.common.stunner.core.client.canvas.listener.CanvasShapeListener; import org.kie.workbench.common.stunner.core.client.canvas.listener.HasCanvasListeners; import org.kie.workbench.common.stunner.core.client.canvas.util.CanvasLoadingObserver; import org.kie.workbench.common.stunner.core.client.shape.Shape; import org.kie.workbench.common.stunner.core.client.shape.view.ShapeView; import org.kie.workbench.common.stunner.core.util.UUID; /** * For Lienzo's based Canvas. */ public abstract class AbstractCanvas<V extends AbstractCanvas.View> implements Canvas<Shape>, HasCanvasListeners<CanvasShapeListener> { private static Logger LOGGER = Logger.getLogger(AbstractCanvas.class.getName()); public enum Cursors { AUTO, MOVE, POINTER, TEXT, NOT_ALLOWED, WAIT, CROSSHAIR; } public interface View<P> extends IsWidget { View show(final P panel, final int width, final int height, final Layer layer); View add(final IsWidget widget); View remove(final IsWidget widget); View addShape(final ShapeView<?> shapeView); View removeShape(final ShapeView<?> shapeView); View addChildShape(final ShapeView<?> parent, final ShapeView<?> child); View removeChildShape(final ShapeView<?> parent, final ShapeView<?> child); View dock(final ShapeView<?> parent, final ShapeView<?> child); View undock(final ShapeView<?> targetDockShape, final ShapeView<?> childParent, final ShapeView<?> child); double getAbsoluteX(); double getAbsoluteY(); int getWidth(); int getHeight(); View setGrid(final CanvasGrid grid); View setCursor(final Cursors cursor); View setDecoratorStrokeWidth(final double width); View setDecoratorStrokeAlpha(final double alpha); View setDecoratorStrokeColor(final String color); Layer getLayer(); View clear(); void destroy(); } protected Layer layer; protected V view; protected CanvasGrid grid; protected Event<CanvasClearEvent> canvasClearEvent; protected Event<CanvasShapeAddedEvent> canvasShapeAddedEvent; protected Event<CanvasShapeRemovedEvent> canvasShapeRemovedEvent; protected Event<CanvasDrawnEvent> canvasDrawnEvent; protected Event<CanvasFocusedEvent> canvasFocusedEvent; protected final List<Shape> shapes = new ArrayList<Shape>(); protected final List<CanvasShapeListener> listeners = new LinkedList<>(); private final CanvasLoadingObserver loadingObserver = new CanvasLoadingObserver(); private final String uuid; protected AbstractCanvas(final Event<CanvasClearEvent> canvasClearEvent, final Event<CanvasShapeAddedEvent> canvasShapeAddedEvent, final Event<CanvasShapeRemovedEvent> canvasShapeRemovedEvent, final Event<CanvasDrawnEvent> canvasDrawnEvent, final Event<CanvasFocusedEvent> canvasFocusedEvent, final Layer layer, final V view) { this.canvasClearEvent = canvasClearEvent; this.canvasShapeAddedEvent = canvasShapeAddedEvent; this.canvasShapeRemovedEvent = canvasShapeRemovedEvent; this.canvasDrawnEvent = canvasDrawnEvent; this.canvasFocusedEvent = canvasFocusedEvent; this.layer = layer; this.view = view; this.uuid = UUID.uuid(); } @SuppressWarnings("unchecked") protected <P> void show(final P panel, final int width, final int height, final Layer layer) { // Show the canvas layer on using the given panel instance. view.show(panel, width, height, layer); // TODO: Review this. // If adding this handler, the SelectionControl for this layer never fires, // so it seems it's not registering fine more than one click event handler. /*final MouseClickHandler clickHandler = new MouseClickHandler() { @Override public void handle( final MouseClickEvent event ) { canvasFocusedEvent.fire( new CanvasFocusedEvent( AbstractCanvas.this ) ); } }; layer.addHandler( ViewEventType.MOUSE_CLICK, clickHandler );*/ } public abstract void addControl(final IsWidget controlView); public abstract void deleteControl(final IsWidget controlView); @Override public List<Shape> getShapes() { return shapes; } public Shape getShape(final String uuid) { if (null != shapes) { for (final Shape shape : shapes) { if (shape.getUUID().equals(uuid)) { return shape; } } } return null; } @SuppressWarnings("unchecked") public Canvas addChildShape(final Shape parent, final Shape child) { getView().addChildShape(parent.getShapeView(), child.getShapeView()); log(Level.FINE, "Adding child [" + child.getUUID() + "] into parent [" + parent.getUUID() + "]"); return this; } @SuppressWarnings("unchecked") public Canvas deleteChildShape(final Shape parent, final Shape child) { getView().removeChildShape(parent.getShapeView(), child.getShapeView()); log(Level.FINE, "Deleting child [" + child.getUUID() + "] from parent [" + parent.getUUID() + "]"); return this; } @SuppressWarnings("unchecked") public Canvas dock(final Shape parent, final Shape child) { getView().dock(parent.getShapeView(), child.getShapeView()); log(Level.FINE, "Docking child [" + child.getUUID() + "] into parent [" + parent.getUUID() + "]"); return this; } @SuppressWarnings("unchecked") public Canvas undock(final Shape targetDockShape, final Shape childParent, final Shape child) { final ShapeView parentView = null != childParent ? childParent.getShapeView() : null; getView().undock(targetDockShape.getShapeView(), parentView, child.getShapeView()); log(Level.FINE, "Undocking child [" + child.getUUID() + "] from parent [" + targetDockShape.getUUID() + "]"); return this; } @Override public Canvas addShape(final Shape shape) { if (!shapes.contains(shape)) { addTransientShape(shape); shapes.add(shape); fireCanvasShapeAdded(shape); canvasShapeAddedEvent.fire(new CanvasShapeAddedEvent(this, shape)); } return this; } @SuppressWarnings("unchecked") public Canvas addTransientShape(final Shape shape) { if (shape.getUUID() == null) { shape.setUUID(UUID.uuid()); } shape.getShapeView().setUUID(shape.getUUID()); view.addShape(shape.getShapeView()); return this; } public double getAbsoluteX() { return view.getAbsoluteX(); } public double getAbsoluteY() { return view.getAbsoluteY(); } public Canvas setGrid(final CanvasGrid grid) { this.grid = grid; view.setGrid(grid); return this; } public CanvasGrid getGrid() { return grid; } @Override public Layer getLayer() { return layer; } @Override public Canvas deleteShape(final Shape shape) { deleteTransientShape(shape); fireCanvasShapeRemoved(shape); shapes.remove(shape); canvasShapeRemovedEvent.fire(new CanvasShapeRemovedEvent(this, shape)); return this; } @SuppressWarnings("unchecked") public Canvas deleteTransientShape(final Shape shape) { view.removeShape(shape.getShapeView()); shape.destroy(); return this; } @Override public AbstractCanvas draw() { view.getLayer().draw(); return this; } public AbstractCanvas clear() { return clear(true); } private AbstractCanvas clear(final boolean fireEvents) { if (!shapes.isEmpty()) { new LinkedList<>(shapes).stream().forEach(this::deleteShape); shapes.clear(); } fireCanvasClear(); if (fireEvents) { canvasClearEvent.fire(new CanvasClearEvent(this)); } view.clear(); return this; } @Override public void destroy() { clear(false); listeners.clear(); view.destroy(); layer.destroy(); layer = null; view = null; } @Override public HasCanvasListeners<CanvasShapeListener> addRegistrationListener(final CanvasShapeListener instance) { listeners.add(instance); return this; } @Override public HasCanvasListeners<CanvasShapeListener> removeRegistrationListener(final CanvasShapeListener instance) { listeners.remove(instance); return this; } @Override public HasCanvasListeners<CanvasShapeListener> clearRegistrationListeners() { listeners.clear(); return this; } protected void fireCanvasShapeAdded(final Shape shape) { for (final CanvasShapeListener instance : listeners) { instance.register(shape); } } protected void fireCanvasShapeRemoved(final Shape shape) { for (final CanvasShapeListener instance : listeners) { instance.deregister(shape); } } protected void fireCanvasClear() { for (final CanvasShapeListener instance : listeners) { instance.clear(); } } protected void afterDrawCanvas() { canvasDrawnEvent.fire(new CanvasDrawnEvent(this)); } @Override public int getWidth() { return view.getWidth(); } @Override public int getHeight() { return view.getHeight(); } public V getView() { return view; } @Override public boolean equals(final Object o) { if (this == o) { return true; } if (!(o instanceof AbstractCanvas)) { return false; } AbstractCanvas that = (AbstractCanvas) o; return uuid.equals(that.uuid); } @Override public int hashCode() { return uuid == null ? 0 : ~~uuid.hashCode(); } public void setLoadingObserverCallback(final CanvasLoadingObserver.Callback loadingObserverCallback) { this.loadingObserver.setLoadingObserverCallback(loadingObserverCallback); } public void loadingStarted() { this.loadingObserver.loadingStarted(); } public void loadingCompleted() { this.loadingObserver.loadingCompleted(); } private void log(final Level level, final String message) { if (LogConfiguration.loggingIsEnabled()) { LOGGER.log(level, message); } } }