/**
* 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.actions;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import javax.swing.Action;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import com.rapidminer.BreakpointListener;
import com.rapidminer.Process;
import com.rapidminer.ProcessListener;
import com.rapidminer.gui.ConditionalAction;
import com.rapidminer.gui.MainFrame;
import com.rapidminer.gui.actions.OperatorActionFactory.ResourceEntry;
import com.rapidminer.gui.dnd.OperatorTransferHandler;
import com.rapidminer.gui.flow.AutoWireThread;
import com.rapidminer.gui.operatormenu.OperatorMenu;
import com.rapidminer.gui.operatortree.actions.DeleteOperatorAction;
import com.rapidminer.gui.operatortree.actions.InfoOperatorAction;
import com.rapidminer.gui.operatortree.actions.ToggleActivationItem;
import com.rapidminer.gui.operatortree.actions.ToggleAllBreakpointsItem;
import com.rapidminer.gui.operatortree.actions.ToggleBreakpointItem;
import com.rapidminer.gui.processeditor.ProcessEditor;
import com.rapidminer.gui.tools.EditBlockingProgressThread;
import com.rapidminer.gui.tools.ResourceAction;
import com.rapidminer.gui.tools.SwingTools;
import com.rapidminer.operator.ExecutionUnit;
import com.rapidminer.operator.IOContainer;
import com.rapidminer.operator.Operator;
import com.rapidminer.operator.OperatorChain;
import com.rapidminer.operator.ProcessRootOperator;
/**
* A process editor that enables/disables actions depending on the selection of operators.
*
* @author Simon Fischer, Tobias Malbrecht
*/
public class Actions implements ProcessEditor {
public final Action INFO_OPERATOR_ACTION = new InfoOperatorAction() {
private static final long serialVersionUID = 6758272768665592429L;
@Override
protected Operator getOperator() {
return getFirstSelectedOperator();
}
};
public final ToggleActivationItem TOGGLE_ACTIVATION_ITEM = new ToggleActivationItem(this);
public final Action RENAME_OPERATOR_ACTION = new ResourceAction(true, "rename_in_processrenderer") {
{
setCondition(OPERATOR_SELECTED, MANDATORY);
}
private static final long serialVersionUID = -3104160320178045540L;
@Override
public void actionPerformed(ActionEvent e) {
mainFrame.getProcessPanel().getProcessRenderer().rename(getFirstSelectedOperator());
}
};
public final Action DELETE_OPERATOR_ACTION = new DeleteOperatorAction();
public final ToggleBreakpointItem TOGGLE_BREAKPOINT[] = {
new ToggleBreakpointItem(this, BreakpointListener.BREAKPOINT_BEFORE),
new ToggleBreakpointItem(this, BreakpointListener.BREAKPOINT_AFTER) };
public transient final ToggleAllBreakpointsItem TOGGLE_ALL_BREAKPOINTS = new ToggleAllBreakpointsItem(this);
public transient final Action MAKE_DIRTY_ACTION = new ResourceAction(true, "make_dirty") {
private static final long serialVersionUID = -1260942717363137733L;
{
setCondition(OPERATOR_SELECTED, MANDATORY);
}
@Override
public void actionPerformed(ActionEvent e) {
for (Operator selectedOperator : new LinkedList<Operator>(getSelectedOperators())) {
selectedOperator.makeDirty();
}
}
};
private transient final Action SHOW_PROBLEM_ACTION = new ResourceAction(true, "show_potential_problem") {
private static final long serialVersionUID = -1260942717363137733L;
{
setCondition(OPERATOR_SELECTED, MANDATORY);
}
@Override
public void actionPerformed(ActionEvent e) {
mainFrame.getProcessPanel().getOperatorWarningHandler().showOperatorWarning(getFirstSelectedOperator());
}
};
private List<Operator> selection;
private Process process;
private final List<OperatorActionFactory> factories = new ArrayList<>();
private final OperatorActionContext context = new OperatorActionContext() {
@Override
public List<Operator> getOperators() {
return Collections.unmodifiableList(getSelectedOperators());
}
@Override
public Operator getDisplayedChain() {
return mainFrame.getProcessPanel().getProcessRenderer().getModel().getDisplayedChain();
}
};
private final MainFrame mainFrame;
private final BreakpointListener breakpointListener = new BreakpointListener() {
@Override
public void breakpointReached(Process process, Operator op, IOContainer iocontainer, int location) {
enableActions();
}
@Override
public void resume() {
enableActions();
}
};
private final ProcessListener processListener = new ProcessListener() {
@Override
public void processEnded(Process process) {
enableActions();
mainFrame.RUN_ACTION.setState(process.getProcessState());
}
@Override
public void processFinishedOperator(Process process, Operator op) {}
@Override
public void processStartedOperator(Process process, Operator op) {}
@Override
public void processStarts(Process process) {
enableActions();
}
};
public Actions(MainFrame mainFrame) {
this.mainFrame = mainFrame;
}
/** Creates a new popup menu for the selected operator. */
public void addToOperatorPopupMenu(JPopupMenu menu, Action renameAction, Action... furtherActions) {
final Operator op = getFirstSelectedOperator();
final boolean singleSelection = getSelectedOperators().size() == 1;
if (op != null && !singleSelection) {
if (!(op instanceof ProcessRootOperator) && op.getParent() != null) {
// enable / disable operator
menu.add(TOGGLE_ACTIVATION_ITEM.createMultipleActivationItem());
}
}
if (op != null && singleSelection) {
if (mainFrame.getProcessPanel().getProcessRenderer().getModel().getDisplayedChain() != op) {
menu.add(INFO_OPERATOR_ACTION);
menu.add(TOGGLE_ACTIVATION_ITEM.createMenuItem());
if (renameAction != null) {
menu.add(renameAction);
} else {
menu.add(RENAME_OPERATOR_ACTION);
}
if (!op.getErrorList().isEmpty()) {
menu.add(SHOW_PROBLEM_ACTION);
}
menu.addSeparator();
if (op instanceof OperatorChain && ((OperatorChain) op).getAllInnerOperators().size() > 0) {
menu.add(OperatorMenu.REPLACE_OPERATORCHAIN_MENU);
} else {
menu.add(OperatorMenu.REPLACE_OPERATOR_MENU);
}
}
}
// add new operator and building block menu
if (mainFrame.getProcessPanel().getProcessRenderer().getModel().getDisplayedChain() == op) {
menu.add(OperatorMenu.NEW_OPERATOR_MENU);
}
// populate menu with registered operator actions (if any)
synchronized (factories) {
for (OperatorActionFactory factory : factories) {
List<ResourceEntry> entries = factory.create(context);
if (!entries.isEmpty()) {
menu.addSeparator();
}
for (ResourceEntry entry : entries) {
if (entry.isMenu()) {
menu.add(entry.getMenu());
} else {
menu.add(entry.getAction());
}
}
}
}
menu.addSeparator();
boolean enableCutCopy = mainFrame.getProcessPanel().getProcessRenderer().getModel().getDisplayedChain() != op;
OperatorTransferHandler.installMenuItems(menu, enableCutCopy);
// add further actions here
if (furtherActions.length > 0) {
menu.addSeparator();
for (Action a : furtherActions) {
if (a == null) {
continue;
}
if (a instanceof ToggleAction) {
menu.add(((ToggleAction) a).createMenuItem());
} else {
menu.add(a);
}
}
}
if (op != null && !(op instanceof ProcessRootOperator) && singleSelection) {
menu.addSeparator();
for (int i = 0; i < TOGGLE_BREAKPOINT.length; i++) {
JMenuItem item = TOGGLE_BREAKPOINT[i].createMenuItem();
menu.add(item);
}
}
}
public Operator getFirstSelectedOperator() {
if (selection != null && !selection.isEmpty()) {
return selection.get(0);
} else {
return null;
}
}
public List<Operator> getSelectedOperators() {
return selection;
}
/**
* Enables and disables all actions according to the current state (process running, operator
* selected...
*/
public void enableActions() {
synchronized (process) {
SwingTools.invokeLater(new Runnable() {
@Override
public void run() {
enableActionsNow();
}
});
}
updateCheckboxStates();
}
private void enableActionsNow() {
boolean[] currentStates = new boolean[ConditionalAction.NUMBER_OF_CONDITIONS];
Operator op = getFirstSelectedOperator();
if (op != null) {
currentStates[ConditionalAction.OPERATOR_SELECTED] = true;
if (op instanceof OperatorChain) {
currentStates[ConditionalAction.OPERATOR_CHAIN_SELECTED] = true;
}
if (op.getParent() == null) {
currentStates[ConditionalAction.ROOT_SELECTED] = true;
} else {
currentStates[ConditionalAction.PARENT_ENABLED] = op.getParent().isEnabled();
if (op.getExecutionUnit().getNumberOfOperators() > 1) {
currentStates[ConditionalAction.SIBLINGS_EXIST] = true;
}
}
}
int processState = process.getProcessState();
currentStates[ConditionalAction.PROCESS_STOPPED] = processState == Process.PROCESS_STATE_STOPPED;
currentStates[ConditionalAction.PROCESS_PAUSED] = processState == Process.PROCESS_STATE_PAUSED;
currentStates[ConditionalAction.PROCESS_RUNNING] = processState == Process.PROCESS_STATE_RUNNING;
currentStates[ConditionalAction.EDIT_IN_PROGRESS] = EditBlockingProgressThread.isEditing();
currentStates[ConditionalAction.PROCESS_SAVED] = process.hasSaveDestination();
currentStates[ConditionalAction.PROCESS_RENDERER_IS_VISIBLE] = mainFrame.getProcessPanel().getProcessRenderer()
.isShowing();
currentStates[ConditionalAction.PROCESS_RENDERER_HAS_UNDO_STEPS] = mainFrame.hasUndoSteps();
currentStates[ConditionalAction.PROCESS_RENDERER_HAS_REDO_STEPS] = mainFrame.hasRedoSteps();
ConditionalAction.updateAll(currentStates);
updateCheckboxStates();
}
/** The currently selected operator will be deleted. */
public void delete() {
Operator parent = null;
for (Operator selectedOperator : new LinkedList<Operator>(getSelectedOperators())) {
if (parent == null) {
parent = selectedOperator.getParent();
}
if (selectedOperator instanceof ProcessRootOperator) {
return;
}
selectedOperator.remove();
}
mainFrame.selectOperator(parent);
}
/**
* The given operators will be inserted at the last position of the currently selected operator
* chain.
*/
public void insert(List<Operator> newOperators) {
Object selectedNode = getSelectedOperator();
if (selectedNode == null) {
SwingTools.showVerySimpleErrorMessage("cannot_insert_operator");
return;
} else if (mainFrame.getProcessPanel().getProcessRenderer().getModel().getDisplayedChain() == selectedNode) {
for (Operator newOperator : newOperators) {
int index = mainFrame.getProcessPanel().getProcessRenderer().getProcessIndexUnder(
mainFrame.getProcessPanel().getProcessRenderer().getModel().getCurrentMousePosition());
if (index == -1) {
index = 0;
}
((OperatorChain) selectedNode).getSubprocess(index).addOperator(newOperator);
}
} else {
int i = 0;
Operator selectedOperator = (Operator) selectedNode;
ExecutionUnit process = selectedOperator.getExecutionUnit();
int parentIndex = process.getOperators().indexOf(selectedOperator) + 1;
for (Operator newOperator : newOperators) {
process.addOperator(newOperator, parentIndex + i);
i++;
}
}
AutoWireThread.autoWireInBackground(newOperators, true);
mainFrame.selectOperators(newOperators);
}
public Operator getSelectedOperator() {
return getFirstSelectedOperator();
}
public Operator getRootOperator() {
if (process != null) {
return process.getRootOperator();
}
return null;
}
public Process getProcess() {
return process;
}
@Override
public void processChanged(Process process) {
if (this.process != process) {
if (this.process != null) {
this.process.removeBreakpointListener(breakpointListener);
this.process.getRootOperator().removeProcessListener(processListener);
}
this.process = process;
enableActions();
if (this.process != null) {
this.process.addBreakpointListener(breakpointListener);
this.process.getRootOperator().addProcessListener(processListener);
}
}
}
@Override
public void processUpdated(Process process) {
enableActions();
}
/**
* Registers an {@link OperatorActionFactory} to be included when compiling {@link Operator}
* specific {@link Action}s.
*
* @param factory
* the factory
* @since 6.5
*/
public void register(OperatorActionFactory factory) {
if (factory == null) {
throw new IllegalArgumentException("factory must not be null");
}
synchronized (factories) {
factories.add(factory);
}
}
@Override
public void setSelection(List<Operator> selection) {
this.selection = selection;
enableActions();
}
private void updateCheckboxStates() {
Operator op = getSelectedOperator();
if (op != null) {
for (int pos = 0; pos < TOGGLE_BREAKPOINT.length; pos++) {
TOGGLE_BREAKPOINT[pos].setSelected(op.hasBreakpoint(pos));
}
TOGGLE_ACTIVATION_ITEM.setSelected(op.isEnabled());
}
}
}