/** * 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.geom.Rectangle2D; import com.rapidminer.gui.flow.processrendering.model.ProcessRendererModel; import com.rapidminer.operator.ExecutionUnit; import com.rapidminer.operator.Operator; /** * Simple workflow annotation drag helper class. * * @author Marco Boeck * @since 6.4.0 * */ public final class AnnotationDragHelper { /** move an operator annotation at least that much to unsnap it */ private static final int OPERATOR_ANNOTATION_UNSNAP_DISTANCE = 75; /** the dragged annotation */ private final WorkflowAnnotation dragged; /** the process renderer model instance */ private final ProcessRendererModel model; /** the original location of the annotation before dragging started */ private final Point startingPoint; /** drag starting point */ private Point origin; /** operator under the mouse while dragging */ private Operator hoveredOperator; /** indicates if actual dragging has taken place */ private boolean dragStarted; /** indicates if an operator annotation was "unsnapped" from its operator */ private boolean unsnapped; /** * Creates a new drag helper which keeps track of the drag state. * * @param dragged * the annotation being dragged * @param origin * the location of the annotation before the drag * @param model * the process renderer model instance */ public AnnotationDragHelper(final WorkflowAnnotation dragged, final Point origin, final ProcessRendererModel model) { if (dragged == null) { throw new IllegalArgumentException("dragged must not be null!"); } if (origin == null) { throw new IllegalArgumentException("origin must not be null!"); } if (model == null) { throw new IllegalArgumentException("model must not be null!"); } this.dragged = dragged; this.origin = origin; this.startingPoint = new Point((int) dragged.getLocation().getX(), (int) dragged.getLocation().getY()); this.model = model; // process annotations are always unsnapped this.unsnapped = dragged instanceof ProcessAnnotation; } /** * Returns the last intermediate point of the drag. * * @return the last intermediate point, never {@code null} */ public Point getOrigin() { return origin; } /** * Returns the absolute starting point of the drag. * * @return the starting point, never {@code null} */ public Point getStartingPoint() { return startingPoint; } /** * Returns the operator the mouse is currently over while dragging. * * @return the operator or {@code null} */ public Operator getHoveredOperator() { return hoveredOperator; } /** * Updates the origin. * * @param origin * the new origin */ public void setOrigin(final Point origin) { this.origin = origin; } /** * Returns the dragged annotation. * * @return the annotation, never {@code null} */ public WorkflowAnnotation getDraggedAnnotation() { return dragged; } /** * Returns whether actual dragging has taken place. * * @return {@code true} if {@link #handleDragEvent(Point)} has been called at least once; * {@code false} otherwise */ public boolean isDragInProgress() { return dragStarted; } /** * Returns whether an operator annotation was unsnapped from its operator. * * @return {@code true} if it was; {@code false} otherwise */ public boolean isUnsnapped() { return unsnapped; } /** * Handles a drag event based on the given {@link Point}. * * @param point * the new point */ public void handleDragEvent(final Point point) { WorkflowAnnotation draggedAnno = getDraggedAnnotation(); double xOffset = point.getX() - getOrigin().getX(); double yOffset = point.getY() - getOrigin().getY(); // we set the drag flag even if not yet unsnapped if (xOffset != 0 || yOffset != 0) { dragStarted = true; } // operator annotations need to be dragged a certain amount before they come "loose" if (dragged instanceof OperatorAnnotation) { if (!unsnapped && Math.abs(xOffset) < OPERATOR_ANNOTATION_UNSNAP_DISTANCE && Math.abs(yOffset) < OPERATOR_ANNOTATION_UNSNAP_DISTANCE) { return; } else { unsnapped = true; } } double newX = draggedAnno.getLocation().getX() + xOffset; double newY = draggedAnno.getLocation().getY() + yOffset; double width = draggedAnno.getLocation().getWidth(); double height = draggedAnno.getLocation().getHeight(); // make sure dragging out of process is not allowed if (newX < WorkflowAnnotation.MIN_X) { newX = WorkflowAnnotation.MIN_X; } if (newY < WorkflowAnnotation.MIN_Y) { newY = WorkflowAnnotation.MIN_Y; } // check if we are hovering over an operator which does NOT have an annotation if (model.getHoveringProcessIndex() != -1) { ExecutionUnit process = model.getProcess(model.getHoveringProcessIndex()); if (process != null) { hoveredOperator = null; for (Operator op : process.getOperators()) { if (model.getOperatorRect(op) != null && model.getOperatorRect(op).contains(model.getMousePositionRelativeToProcess())) { if (model.getOperatorAnnotations(op) == null || model.getOperatorAnnotations(op).isEmpty()) { hoveredOperator = op; break; } else if (model.getOperatorAnnotations(op).getAnnotationsDrawOrder().contains(draggedAnno)) { // our own origin is a valid target as well hoveredOperator = op; break; } } } } } Rectangle2D newLoc = new Rectangle2D.Double(newX, newY, width, height); draggedAnno.setLocation(newLoc); setOrigin(point); } }