/**
* Copyright (C) 2001-2017 by RapidMiner and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapidminer.com
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.gui.flow.processrendering.annotations.model;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Shape;
import java.awt.geom.Rectangle2D;
import com.rapidminer.gui.flow.processrendering.annotations.AnnotationDrawUtils;
/**
* Simple workflow annotation resize helper class.
*
* @author Marco Boeck
* @since 6.4.0
*
*/
public final class AnnotationResizeHelper {
/**
* The direction in which a resize is happening for a process annotation.
*/
public static enum ResizeDirection {
/** started in top left corner */
TOP_LEFT,
/** started in top right corner */
TOP_RIGHT,
/** started in bottom left corner */
BOTTOM_LEFT,
/** started in bottom right corner */
BOTTOM_RIGHT;
}
/** the resized annotation */
private final WorkflowAnnotation resized;
/** the direction in which to resize */
private final ResizeDirection direction;
/** resize starting point */
private Point origin;
/** indicates if actual resizing has taken place */
private boolean resizeStarted;
/**
* Creates a new resize helper which keeps track of the resizing state.
*
* @param resized
* the annotation being resized
* @param direction
* the resize direction
* @param origin
* the location of the annotation before the resize
*/
public AnnotationResizeHelper(final WorkflowAnnotation resized, final ResizeDirection direction, final Point origin) {
if (resized == null) {
throw new IllegalArgumentException("resized must not be null!");
}
if (origin == null) {
throw new IllegalArgumentException("origin must not be null!");
}
this.resized = resized;
this.direction = direction;
this.origin = origin;
}
/**
* Returns the origin point of the resize.
*
* @return the starting origin, never {@code null}
*/
public Point getOrigin() {
return origin;
}
/**
* Updates the origin.
*
* @param origin
* the new origin
*/
public void setOrigin(final Point origin) {
this.origin = origin;
}
/**
* Returns the resized annotation.
*
* @return the annotation, never {@code null}
*/
public WorkflowAnnotation getResized() {
return resized;
}
/**
* Returns the direction in which to resize.
*
* @return the direction, never {@code null}
*/
public ResizeDirection getDirection() {
return direction;
}
/**
* Returns whether actual resizing has taken place.
*
* @return {@code true} if {@link #handleResizeEvent(Point)} has been called at least once;
* {@code false} otherwise
*/
public boolean isResizeInProgress() {
return resizeStarted;
}
/**
* Handles a resize event based on the given {@link Point}.
*
* @param point
* the new point
*/
public void handleResizeEvent(final Point point) {
WorkflowAnnotation resizedAnno = getResized();
if (resizedAnno instanceof OperatorAnnotation) {
// should not happen
return;
}
Rectangle2D startFrame = resizedAnno.getLocation();
double xOffset = point.getX() - getOrigin().getX();
double yOffset = point.getY() - getOrigin().getY();
double xIncrease = 0;
double yIncrease = 0;
double widthIncrease = 0;
double heightIncrease = 0;
// calculate new x,y, width, height while keeping min/max width/height in mind
if (resizedAnno instanceof ProcessAnnotation) {
switch (getDirection()) {
case BOTTOM_LEFT:
if (xOffset >= 0) {
double newWidth = Math.max(startFrame.getWidth() - xOffset, ProcessAnnotation.MIN_WIDTH);
widthIncrease = newWidth - startFrame.getWidth();
xIncrease = -widthIncrease;
} else {
double newWidth = Math.min(startFrame.getWidth() - xOffset, ProcessAnnotation.MAX_WIDTH);
widthIncrease = newWidth - startFrame.getWidth();
xIncrease = -widthIncrease;
}
if (yOffset >= 0) {
double newHeight = Math.min(startFrame.getHeight() + yOffset, ProcessAnnotation.MAX_HEIGHT);
heightIncrease = newHeight - startFrame.getHeight();
} else {
double newHeight = Math.max(startFrame.getHeight() + yOffset, ProcessAnnotation.MIN_HEIGHT);
heightIncrease = newHeight - startFrame.getHeight();
}
break;
case BOTTOM_RIGHT:
if (xOffset >= 0) {
double newWidth = Math.min(startFrame.getWidth() + xOffset, ProcessAnnotation.MAX_WIDTH);
widthIncrease = newWidth - startFrame.getWidth();
} else {
double newWidth = Math.max(startFrame.getWidth() + xOffset, ProcessAnnotation.MIN_WIDTH);
widthIncrease = newWidth - startFrame.getWidth();
}
if (yOffset >= 0) {
double newHeight = Math.min(startFrame.getHeight() + yOffset, ProcessAnnotation.MAX_HEIGHT);
heightIncrease = newHeight - startFrame.getHeight();
} else {
double newHeight = Math.max(startFrame.getHeight() + yOffset, ProcessAnnotation.MIN_HEIGHT);
heightIncrease = newHeight - startFrame.getHeight();
}
break;
case TOP_LEFT:
if (xOffset >= 0) {
double newWidth = Math.max(startFrame.getWidth() - xOffset, ProcessAnnotation.MIN_WIDTH);
widthIncrease = newWidth - startFrame.getWidth();
xIncrease = -widthIncrease;
} else {
double newWidth = Math.min(startFrame.getWidth() - xOffset, ProcessAnnotation.MAX_WIDTH);
widthIncrease = newWidth - startFrame.getWidth();
xIncrease = -widthIncrease;
}
if (yOffset >= 0) {
double newHeight = Math.max(startFrame.getHeight() - yOffset, ProcessAnnotation.MIN_HEIGHT);
heightIncrease = newHeight - startFrame.getHeight();
yIncrease = -heightIncrease;
} else {
double newHeight = Math.min(startFrame.getHeight() - yOffset, ProcessAnnotation.MAX_HEIGHT);
heightIncrease = newHeight - startFrame.getHeight();
yIncrease = -heightIncrease;
}
break;
case TOP_RIGHT:
if (xOffset >= 0) {
double newWidth = Math.min(startFrame.getWidth() + xOffset, ProcessAnnotation.MAX_WIDTH);
widthIncrease = newWidth - startFrame.getWidth();
} else {
double newWidth = Math.max(startFrame.getWidth() + xOffset, ProcessAnnotation.MIN_WIDTH);
widthIncrease = newWidth - startFrame.getWidth();
}
if (yOffset >= 0) {
double newHeight = Math.max(startFrame.getHeight() - yOffset, ProcessAnnotation.MIN_HEIGHT);
heightIncrease = newHeight - startFrame.getHeight();
yIncrease = -heightIncrease;
} else {
double newHeight = Math.min(startFrame.getHeight() - yOffset, ProcessAnnotation.MAX_HEIGHT);
heightIncrease = newHeight - startFrame.getHeight();
yIncrease = -heightIncrease;
}
break;
// $CASES-OMITTED$
default:
break;
}
}
double newX = startFrame.getX() + xIncrease;
double newY = startFrame.getY() + yIncrease;
double newWidth = startFrame.getWidth() + widthIncrease;
double newHeight = startFrame.getHeight() + heightIncrease;
if (resizedAnno instanceof OperatorAnnotation) {
newHeight = Math.min(AnnotationDrawUtils.getContentHeight(
AnnotationDrawUtils.createStyledCommentString(resizedAnno.getComment(), resizedAnno.getStyle()),
(int) newWidth), OperatorAnnotation.MAX_HEIGHT);
}
// cannot relocate annotation to less than min x/y values
if (newX < WorkflowAnnotation.MIN_X) {
// prevent width increase if resize x was less than min value
if (resizedAnno instanceof ProcessAnnotation) {
newWidth = newWidth - (WorkflowAnnotation.MIN_X - newX);
newX = WorkflowAnnotation.MIN_X;
}
}
if (newY < WorkflowAnnotation.MIN_Y) {
// prevent height increase if resize x was less than min value
newHeight = newHeight - (WorkflowAnnotation.MIN_Y - newY);
newY = WorkflowAnnotation.MIN_Y;
}
Rectangle2D newLoc = new Rectangle2D.Double(newX, newY, newWidth, newHeight);
resizedAnno.setLocation(newLoc);
setOrigin(point);
resizeStarted = true;
resizedAnno.setResized();
}
/**
* Determines if the origin point is in one of resize starting areas, e.g. the corners.
*
* @param anno
* the annotation which should be checked
* @param point
* the location of the mouse
* @return the direction or {@code null} if the point was in none of the resize starting areas
*/
public static ResizeDirection getResizeDirectionOrNull(final WorkflowAnnotation anno, final Point point) {
if (anno == null) {
throw new IllegalArgumentException("anno must not be null!");
}
if (point == null) {
throw new IllegalArgumentException("point must not be null!");
}
int x = (int) anno.getLocation().getX();
int y = (int) anno.getLocation().getY();
int maxX = (int) anno.getLocation().getMaxX();
int maxY = (int) anno.getLocation().getMaxY();
if (anno instanceof ProcessAnnotation) {
Shape triangle = new Polygon(new int[] { x, x, x + 20 }, new int[] { y + 20, y, y }, 3);
if (triangle.contains(point)) {
return ResizeDirection.TOP_LEFT;
}
triangle = new Polygon(new int[] { maxX - 20, maxX, maxX }, new int[] { y, y, y + 20 }, 3);
if (triangle.contains(point)) {
return ResizeDirection.TOP_RIGHT;
}
triangle = new Polygon(new int[] { x, x, x + 20 }, new int[] { maxY, maxY - 20, maxY }, 3);
if (triangle.contains(point)) {
return ResizeDirection.BOTTOM_LEFT;
}
triangle = new Polygon(new int[] { maxX, maxX, maxX - 20 }, new int[] { maxY - 20, maxY, maxY }, 3);
if (triangle.contains(point)) {
return ResizeDirection.BOTTOM_RIGHT;
}
}
return null;
}
}