/* * RapidMiner * * Copyright (C) 2001-2011 by Rapid-I and the contributors * * Complete list of developers available at our web site: * * http://rapid-i.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.dnd; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.List; import javax.swing.Timer; import javax.swing.tree.TreePath; import com.rapidminer.Process; import com.rapidminer.gui.RapidMinerGUI; import com.rapidminer.gui.operatortree.OperatorTree; import com.rapidminer.gui.operatortree.ProcessTreeModel; import com.rapidminer.operator.ExecutionUnit; import com.rapidminer.operator.Operator; import com.rapidminer.operator.OperatorChain; import com.rapidminer.operator.ports.metadata.CompatibilityLevel; /** Transfer handler for an OperatorTree. * * @author Simon Fischer * */ public class OperatorTreeTransferHandler extends ReceivingOperatorTransferHandler { private static final long serialVersionUID = -3039947430247192040L; private final OperatorTree operatorTree; /** A timer needed for automatic node expansion */ private final Timer nodeExpandDelay = new Timer(1500, new ActionListener() { public void actionPerformed(ActionEvent e) { if (operatorTree.isRootVisible() && operatorTree.getRowForPath(currentPath) == 0) { return; } else { if (operatorTree.isExpanded(currentPath)) { operatorTree.collapsePath(currentPath); } else { operatorTree.expandPath(currentPath); } } } }) { private static final long serialVersionUID = 5805944782054617670L; { setRepeats(false); } }; public enum Position { ABOVE, BELOW, INTO, UNMARKED }; private static class DnDMarker { private final ExecutionUnit markedUnit; private final Operator markedOperator; private final Position markerPosition; public DnDMarker(Operator operator, Position position) { this.markedOperator = operator; this.markerPosition = position; this.markedUnit = null; } private DnDMarker(ExecutionUnit unit, Position position) { this.markedUnit = unit; this.markerPosition = position; this.markedOperator = null; } @Override public boolean equals(Object o) { if (!(o instanceof DnDMarker)) { return false; } DnDMarker marker = (DnDMarker) o; return marker != null && marker.markedOperator == this.markedOperator && marker.markerPosition == this.markerPosition && marker.markedUnit == this.markedUnit; } public boolean canDrop(List<Operator> operators) { // avoid adding operator as a child of its own. Operator target = markedOperator; if ((target.getExecutionUnit() == null) && (this.markerPosition != Position.INTO)) { return false; } for (Operator operator : operators) { if (operator instanceof OperatorChain) { if (target == operator) { return false; } if (markedUnit != null) { target = markedUnit.getEnclosingOperator(); } if ((target == operator) || ((OperatorChain)operator).getAllInnerOperators().contains(target)) { return false; } } } return true; } public void drop(List<Operator> operators) { if (markedOperator != null) { if (markerPosition == Position.INTO) { // this is only possible for operator chains with only one execution unit. for (Operator operator : operators) { ((OperatorChain)markedOperator).getSubprocess(0).addOperator(operator); } } else { // add as sibling of operator ExecutionUnit executionUnit = markedOperator.getExecutionUnit(); if (executionUnit == null) { return; } int newIndex = executionUnit.getOperators().indexOf(markedOperator); if (markerPosition == Position.BELOW) { newIndex++; } int i = 0; for (Operator operator : operators) { executionUnit.addOperator(operator, newIndex+i); i++; } } } else { // add to execution unit switch (markerPosition) { case ABOVE: int i = 0; for (Operator operator : operators) { markedUnit.addOperator(operator, i); i++; } break; case BELOW: for (Operator operator : operators) { markedUnit.addOperator(operator); } break; } } for (Operator operator : operators) { if (RapidMinerGUI.getMainFrame().VALIDATE_AUTOMATICALLY_ACTION.isSelected()) { operator.getExecutionUnit().autoWireSingle(operator, CompatibilityLevel.VERSION_5, RapidMinerGUI.getMainFrame().getNewOperatorEditor().shouldAutoConnectNewOperatorsInputs(), RapidMinerGUI.getMainFrame().getNewOperatorEditor().shouldAutoConnectNewOperatorsOutputs()); } } } } private DnDMarker currentDnDMarker = null; private TreePath currentPath; public OperatorTreeTransferHandler(OperatorTree operatorTree) { this.operatorTree = operatorTree; } @Override protected boolean dropNow(List<Operator> droppedOperators, Point loc) { if ((loc != null) || (currentDnDMarker != null)) { currentDnDMarker.drop(droppedOperators); } else { RapidMinerGUI.getMainFrame().getActions().insert(droppedOperators); } return true; } @Override protected void dropEnds() { currentPath = null; nodeExpandDelay.stop(); currentDnDMarker = null; } @Override protected boolean isDropLocationOk(List<Operator> newOperators, Point loc) { return currentDnDMarker.canDrop(newOperators); } @Override protected void markDropOver(Point currentCursorLocation) { currentPath = operatorTree.getClosestPathForLocation(currentCursorLocation.x, currentCursorLocation.y); Rectangle dropActionTriggerArea = operatorTree.getPathBounds(currentPath); if (dropActionTriggerArea != null) { Object currentDropZone = currentPath.getLastPathComponent(); if (currentDropZone != null) { // start the computation of the dropline position int operatorPosition = operatorTree.getPathBounds(currentPath).getLocation().y; double operatorHeight = operatorTree.getPathBounds(currentPath).getHeight(); Position position; if ((currentPath.getLastPathComponent() instanceof OperatorChain) && (((OperatorChain)currentPath.getLastPathComponent()).getNumberOfSubprocesses() == 1)) { if (currentCursorLocation.y < operatorPosition + operatorHeight/3) { position = Position.ABOVE; } else if (currentCursorLocation.y > operatorPosition + operatorHeight*2/3) { position = Position.BELOW; } else { position = Position.INTO; } } else { if (currentCursorLocation.y < operatorPosition + operatorHeight/2) { position = Position.ABOVE; } else { position = Position.BELOW; } } if (currentDropZone instanceof ExecutionUnit) { currentDnDMarker = new DnDMarker((ExecutionUnit)currentDropZone, position); nodeExpandDelay.restart(); } else if (currentDropZone instanceof Operator) { // if (((Operator)currentDropZone).getExecutionUnit() == null) { // currentDnDMarker = null; // } else { if (((Operator)currentDropZone).getExecutionUnit() == null) { position = Position.INTO; } currentDnDMarker = new DnDMarker((Operator)currentDropZone, position); if (currentDropZone instanceof OperatorChain) { nodeExpandDelay.restart(); } // } } else { throw new IllegalArgumentException("Nodes of tree must be Operators or ExecutionUnits."); } } } // autoscroll to possible dropzone Insets insets = new Insets(40, 40, 40, 40); Rectangle currentlyVisible = operatorTree.getVisibleRect(); Rectangle validCursorArea = new Rectangle(currentlyVisible.x + insets.left, currentlyVisible.y + insets.top, currentlyVisible.width - (insets.left + insets.right), currentlyVisible.height - (insets.top + insets.bottom)); if (!validCursorArea.contains(currentCursorLocation)) { Rectangle updatedArea = new Rectangle(currentCursorLocation.x - insets.left, currentCursorLocation.y - insets.top, insets.left + insets.right, insets.top + insets.bottom); operatorTree.scrollRectToVisible(updatedArea); } } @Override protected List<Operator> getDraggedOperators() { List<Operator> ops = operatorTree.getSelectedOperators(); if (ops.isEmpty()) { return null; } else { return ops; } } public Position getMarkerPosition(Operator operator) { if (currentDnDMarker == null) { return Position.UNMARKED; } else if (currentDnDMarker.markedOperator == operator) { return currentDnDMarker.markerPosition; } else { return Position.UNMARKED; } } public Position getMarkerPosition(ExecutionUnit unit) { if (currentDnDMarker == null) { return Position.UNMARKED; } else if (currentDnDMarker.markedUnit == unit) { return currentDnDMarker.markerPosition; } else { return Position.UNMARKED; } } @Override protected Process getProcess() { return ((Operator)((ProcessTreeModel)(operatorTree.getModel())).getRoot()).getProcess(); } }