/*
* Copyright 2016 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.util;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Logger;
import javax.enterprise.context.Dependent;
import org.kie.workbench.common.stunner.core.client.canvas.CanvasHandler;
import org.kie.workbench.common.stunner.core.diagram.Diagram;
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.Graph;
import org.kie.workbench.common.stunner.core.graph.Node;
import org.kie.workbench.common.stunner.core.graph.content.Bounds;
import org.kie.workbench.common.stunner.core.graph.content.definition.DefinitionSet;
import org.kie.workbench.common.stunner.core.graph.content.relationship.Child;
import org.kie.workbench.common.stunner.core.graph.content.view.View;
import org.kie.workbench.common.stunner.core.graph.util.GraphUtils;
import static org.uberfire.commons.validation.PortablePreconditions.checkNotNull;
/**
* This class is a basic implementation for achieving a simple layout mechanism.
* It finds an empty area on the canvas where new elements could be place as:
* - Calling <code>getNextLayoutPosition( CanvasHandler canvasHandler )</code> returns the
* cartesian coordinates for an empty area found in the diagram's graph that is being managed by the
* canvas handler instance.
* - Calling <code>getNextLayoutPosition( CanvasHandler canvasHandler, Element<View<?>> source )</code> returns the
* cartesian coordinates for an empty area found in the diagram's graph but relative to the given <code>source</code>
* argument and its parent, if any.
* In both cases the resulting coordinates are given from the coordinates of the visible element in the graph, which
* is position is on bottom right rather than the others, plus a given <code>PADDING</code> anb some
* error margin given by the <code>MARGIN</code> floating point.
* <p/>
* TODO: This has to be refactored by the use of a good impl that achieve good dynamic layouts. Probably each
* Definition Set / Diagram will require a different layout manager as well.
*/
@Dependent
public class CanvasLayoutUtils {
private static Logger LOGGER = Logger.getLogger(CanvasLayoutUtils.class.getName());
private static final int PADDING = 50;
private static final float MARGIN = 0.2f;
public class LayoutBoundExceededException extends RuntimeException {
private final double x;
private final double y;
private final double maxX;
private final double maxY;
public LayoutBoundExceededException(final double x,
final double y,
final double maxX,
final double maxY) {
this.x = x;
this.y = y;
this.maxX = maxX;
this.maxY = maxY;
}
}
public static boolean isCanvasRoot(final Diagram diagram,
final Element parent) {
return null != parent && isCanvasRoot(diagram,
parent.getUUID());
}
public static boolean isCanvasRoot(final Diagram diagram,
final String pUUID) {
final String canvasRoot = diagram.getMetadata().getCanvasRootUUID();
return (null != canvasRoot && null != pUUID && canvasRoot.equals(pUUID));
}
@SuppressWarnings("unchecked")
public double[] getNext(final CanvasHandler canvasHandler,
final double width,
final double height) {
checkNotNull("canvasHandler",
canvasHandler);
final Bounds bounds = getGraphBounds(canvasHandler);
final Bounds.Bound ul = bounds.getUpperLeft();
final String ruuid = canvasHandler.getDiagram().getMetadata().getCanvasRootUUID();
if (null != ruuid) {
Node root = canvasHandler.getDiagram().getGraph().getNode(ruuid);
return getNext(canvasHandler,
root,
width,
height,
ul.getX(),
ul.getY());
}
final Iterable<Node> nodes = canvasHandler.getDiagram().getGraph().nodes();
if (null != nodes) {
final Bounds.Bound lr = bounds.getLowerRight();
final List<Node<View<?>, Edge>> nodeList = new LinkedList<>();
nodes.forEach(nodeList::add);
return getNext(canvasHandler,
nodeList,
width,
height,
ul.getX(),
ul.getY(),
lr.getX() - PADDING,
lr.getY() - PADDING);
}
return new double[]{ul.getX(), ul.getY()};
}
@SuppressWarnings("unchecked")
public double[] getNext(final CanvasHandler canvasHandler,
final double w,
final double h,
final double minX,
final double minY) {
checkNotNull("canvasHandler",
canvasHandler);
final String ruuid = canvasHandler.getDiagram().getMetadata().getCanvasRootUUID();
if (null != ruuid) {
Node root = canvasHandler.getDiagram().getGraph().getNode(ruuid);
return getNext(canvasHandler,
root,
w,
h,
minX,
minY);
}
final Bounds bounds = getGraphBounds(canvasHandler);
final Bounds.Bound lr = bounds.getLowerRight();
final Iterable<Node> nodes = canvasHandler.getDiagram().getGraph().nodes();
if (null != nodes) {
final List<Node<View<?>, Edge>> nodeList = new LinkedList<>();
nodes.forEach(nodeList::add);
return getNext(canvasHandler,
nodeList,
w,
h,
minX,
minY,
lr.getX() - PADDING,
lr.getY() - PADDING);
}
return new double[]{minX, minY};
}
@SuppressWarnings("unchecked")
public double[] getNext(final CanvasHandler canvasHandler,
final Node<View<?>, Edge> root) {
final double[] rootBounds = getBoundCoordinates(root.getContent());
final double[] size = GraphUtils.getNodeSize(root.getContent());
return getNext(canvasHandler,
root,
size[0],
size[1],
rootBounds[0],
rootBounds[1]);
}
@SuppressWarnings("unchecked")
public double[] getNext(final CanvasHandler canvasHandler,
final Node<View<?>, Edge> root,
final double w,
final double h,
final double minX,
final double minY) {
checkNotNull("canvasHandler",
canvasHandler);
checkNotNull("root",
root);
final List<Edge> outEdges = root.getOutEdges();
if (null != outEdges) {
final List<Node<View<?>, Edge>> nodes = new LinkedList<>();
outEdges.stream().forEach(edge -> {
if (edge instanceof Child
&& edge.getTargetNode().getContent() instanceof View) {
nodes.add(edge.getTargetNode());
}
});
if (!nodes.isEmpty()) {
final double[] rootBounds = getBoundCoordinates(root.getContent());
final double[] n = getNext(canvasHandler,
nodes,
w,
h,
minX,
minY,
rootBounds[0] - PADDING,
rootBounds[1] - PADDING);
return new double[]{n[0] + PADDING, n[1]};
}
}
final Bounds bounds = getGraphBounds(canvasHandler);
final Bounds.Bound lr = bounds.getLowerRight();
return check(minX,
minY,
w,
h,
minX,
minY,
lr.getX() - PADDING,
lr.getY() - PADDING);
}
private double[] getNext(final CanvasHandler canvasHandler,
final List<Node<View<?>, Edge>> nodes,
final double width,
final double height,
final double minX,
final double minY,
final double maxX,
final double maxY) {
checkNotNull("canvasHandler",
canvasHandler);
checkNotNull("nodes",
nodes);
final double[] result = new double[]{minX, minY};
nodes.stream().forEach(node -> {
final double[] coordinates = getAbsolute(node);
result[0] = coordinates[0] >= result[0] ? coordinates[0] : result[0];
result[1] = coordinates[1] >= result[1] ? coordinates[1] : result[1];
final double[] r = check(coordinates[0],
coordinates[1],
width,
height,
minX,
minY,
maxX,
maxY);
if ((coordinates[0] + width) >= maxX) {
result[0] = r[0];
result[1] = r[1];
}
if ((result[1] + height) > maxX) {
throw new LayoutBoundExceededException(result[0],
result[1],
maxX,
maxY);
}
});
return result;
}
private double[] check(final double x,
final double y,
final double w,
final double h,
final double lx,
final double ly,
final double ux,
final double uy) {
final double[] result = new double[]{x, y};
if ((x + w) >= ux) {
result[0] = lx;
result[1] += y + PADDING;
}
if ((y + h) > uy) {
throw new LayoutBoundExceededException(result[0],
result[1],
ux,
uy);
}
return new double[]{result[0] + PADDING, result[1]};
}
@SuppressWarnings("unchecked")
private double[] getAbsolute(final Node<View<?>, Edge> root) {
final double[] pos = getBoundCoordinates(root.getContent());
return getAbsolute(root,
pos[0],
pos[1]);
}
@SuppressWarnings("unchecked")
private double[] getAbsolute(final Node<View<?>, Edge> root,
final double x,
final double y) {
Element parent = GraphUtils.getParent(root);
if (null != parent
&& parent instanceof Node
&& parent.getContent() instanceof View) {
final double[] pos = getBoundCoordinates((View) parent.getContent());
return getAbsolute((Node<View<?>, Edge>) parent,
x + pos[0],
y + pos[1]);
}
return new double[]{x, y};
}
private double[] getBoundCoordinates(final View view) {
final Bounds bounds = view.getBounds();
final Bounds.Bound ulBound = bounds.getUpperLeft();
final Bounds.Bound lrBound = bounds.getLowerRight();
final double lrX = lrBound.getX();
final double lrY = ulBound.getY();
return new double[]{lrX, lrY};
}
@SuppressWarnings("unchecked")
private Bounds getGraphBounds(final CanvasHandler canvasHandler) {
final Graph<DefinitionSet, ?> graph = canvasHandler.getDiagram().getGraph();
return graph.getContent().getBounds();
}
}