/*
* 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.operatortree;
import java.util.List;
import javax.swing.JOptionPane;
import javax.swing.event.EventListenerList;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import com.rapidminer.ProcessSetupListener;
import com.rapidminer.gui.RapidMinerGUI;
import com.rapidminer.operator.ExecutionUnit;
import com.rapidminer.operator.Operator;
import com.rapidminer.operator.OperatorChain;
import com.rapidminer.tools.LogService;
/** A model view of a process that displays processes as individual nodes.
* Rewrite of the original OperatorTreeModel.
*
* Nodes of this model can be {@link Operator}s and {@link ExecutionUnit}s. The latter
* are hidden from the model if an {@link OperatorChain} contains only one {@link ExecutionUnit}.
*
* @author Simon Fischer
*
*/
public class ProcessTreeModel implements TreeModel {
private final EventListenerList listenerList = new EventListenerList();
/** Defines whether or not disabled operators are shown by this model. */
private boolean showDisabled = true;
/** The root of the operator tree. */
private final Operator root;
private final ProcessSetupListener delegatingListener = new ProcessSetupListener() {
@Override
public void operatorAdded(Operator operator) {
fireTreeNodesInserted(operator);
}
@Override
public void operatorChanged(Operator operator) {
if (operator.getProcess().getProcessState() != com.rapidminer.Process.PROCESS_STATE_RUNNING) {
fireTreeNodesChanged(operator);
}
}
@Override
public void operatorRemoved(Operator operator, int oldIndex, int oldIndexAmongEnabled) {
fireTreeNodesRemoved(operator, showDisabled ? oldIndex : oldIndexAmongEnabled);
}
@Override
public void executionOrderChanged(ExecutionUnit unit) {
fireTreeStructureChanged(unit);
}
};
public ProcessTreeModel(Operator root) {
this.root = root;
root.getProcess().addProcessSetupListener(delegatingListener);
}
/** Returns either the list of enabled schildren of the given process
* or the list of all children, depending on {@link #showDisabled}. */
private List<Operator> getChildren(ExecutionUnit process) {
if (showDisabled) {
return process.getOperators();
} else {
return process.getEnabledOperators();
}
}
@Override
public void addTreeModelListener(TreeModelListener l) {
listenerList.add(TreeModelListener.class, l);
}
@Override
public void removeTreeModelListener(TreeModelListener l) {
listenerList.remove(TreeModelListener.class, l);
}
@Override
public Object getChild(Object parent, int index) {
if (index == -1) {
throw new IllegalArgumentException("Index -1 not allowed.");
}
if (parent instanceof OperatorChain) {
OperatorChain chain = (OperatorChain)parent;
if (chain.getNumberOfSubprocesses() == 1) {
return getChildren(chain.getSubprocess(0)).get(index);
} else {
return ((OperatorChain)parent).getSubprocess(index);
}
} else if (parent instanceof ExecutionUnit) {
return getChildren((ExecutionUnit)parent).get(index);
} else {
throw new IllegalArgumentException("Illegal tree node: "+parent);
}
}
@Override
public int getChildCount(Object parent) {
if (parent instanceof OperatorChain) {
OperatorChain chain = (OperatorChain)parent;
if (chain.getNumberOfSubprocesses() == 1) {
return getChildren(chain.getSubprocess(0)).size();
} else {
return ((OperatorChain)parent).getNumberOfSubprocesses();
}
} else if (parent instanceof ExecutionUnit) {
return getChildren((ExecutionUnit)parent).size();
} else {
return 0;
}
}
@Override
public int getIndexOfChild(Object parent, Object child) {
if (parent instanceof OperatorChain) {
OperatorChain chain = (OperatorChain)parent;
if (chain.getNumberOfSubprocesses() == 1) {
return getChildren(chain.getSubprocess(0)).indexOf(child);
} else {
return ((OperatorChain)parent).getSubprocesses().indexOf(child);
}
} else if (parent instanceof ExecutionUnit) {
return getChildren((ExecutionUnit)parent).indexOf(child);
} else {
LogService.getRoot().warning(child + " is no child of " + parent);
return -1;
}
}
@Override
public Object getRoot() {
return root;
}
@Override
public boolean isLeaf(Object node) {
if (node instanceof OperatorChain) {
return ((OperatorChain)node).getNumberOfSubprocesses() == 0;
} else if (node instanceof ExecutionUnit) {
return ((ExecutionUnit)node).getNumberOfOperators() == 0;
} else {
return true;
}
}
@Override
public void valueForPathChanged(TreePath path, Object newValue) {
Object leaf = path.getLastPathComponent();
if (leaf instanceof Operator) {
Operator op = (Operator) leaf;
String desiredName = ((String)newValue).trim();
if (desiredName.length() > 0) {
if (desiredName.indexOf('.') >= 0) {
JOptionPane.showMessageDialog(RapidMinerGUI.getMainFrame(), "Renaming not possible: operator names are now allowed to contain the character '.'", "Renaming failed", JOptionPane.WARNING_MESSAGE);
} else {
op.rename(desiredName);
}
}
}
}
/** Creates TreePath leading to the specified ExecutionUnit. */
TreePath getPathTo(ExecutionUnit process) {
return getPathTo(process.getEnclosingOperator()).pathByAddingChild(process);
}
/** Creates TreePath leading to the specified operator. */
public TreePath getPathTo(Operator operator) {
if (operator.getParent() == null) {
return new TreePath(operator);
} else {
TreePath pathToParent;
if (operator.getParent().getNumberOfSubprocesses() == 1) {
pathToParent = getPathTo(operator.getParent());
} else {
pathToParent = getPathTo(operator.getExecutionUnit());
}
return pathToParent.pathByAddingChild(operator);
}
}
/** Creates an event that points to changes in the given operator. */
private TreeModelEvent makeChangeEvent(Operator operator) {
ExecutionUnit parent = operator.getExecutionUnit();
if (parent != null) {
// NOTE: In the tree model, the parent may be an ExecutionUnit or an OperatorChain (if it has only one subprocess)
// In both cases, the index of the operator is the same, so we don't have to treat these cases separately.
TreePath path = getPathTo(operator).getParentPath();
int index = getChildren(operator.getExecutionUnit()).indexOf(operator);
return new TreeModelEvent(this, path, new int[] { index }, new Object[] { operator });
} else {
return new TreeModelEvent(this, (TreePath)null, null, null);
}
}
private void fireTreeNodesChanged(Operator operator) {
TreeModelEvent e = makeChangeEvent(operator);
if ((e.getChildIndices() != null) && (e.getChildIndices()[0] != -1)) { // otherwise the operator is in the state of being removed and has triggered an update while dying.
for (TreeModelListener l : listenerList.getListeners(TreeModelListener.class)) {
l.treeNodesChanged(e);
}
}
}
private void fireTreeNodesInserted(Operator operator) {
TreeModelEvent e = makeChangeEvent(operator);
for (TreeModelListener l : listenerList.getListeners(TreeModelListener.class)) {
l.treeNodesInserted(e);
}
}
private void fireTreeNodesRemoved(Operator operator, int oldIndex) {
TreePath path = getPathTo(operator).getParentPath();
TreeModelEvent e = new TreeModelEvent(this, path, new int[] { oldIndex }, new Object[] { operator });
for (TreeModelListener l : listenerList.getListeners(TreeModelListener.class)) {
l.treeNodesRemoved(e);
}
}
private void fireTreeStructureChanged(ExecutionUnit unit) {
TreePath path = getPathTo(unit).getParentPath();
TreeModelEvent e = new TreeModelEvent(this, path);
for (TreeModelListener l : listenerList.getListeners(TreeModelListener.class)) {
l.treeStructureChanged(e);
}
}
public void setShowDisabledOperators(boolean showDisabled) {
this.showDisabled = showDisabled;
}
public boolean showDisabledOperators() {
return showDisabled;
}
}