/*
* 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.dialog;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Vector;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import com.rapidminer.gui.actions.Actions;
import com.rapidminer.gui.tools.CamelCaseFilter;
import com.rapidminer.gui.tools.ExtendedJScrollPane;
import com.rapidminer.gui.tools.OperatorList;
import com.rapidminer.gui.tools.ResourceAction;
import com.rapidminer.gui.tools.ResourceLabel;
import com.rapidminer.gui.tools.SwingTools;
import com.rapidminer.gui.tools.UpdateQueue;
import com.rapidminer.gui.tools.dialogs.ButtonDialog;
import com.rapidminer.operator.IOObject;
import com.rapidminer.operator.Operator;
import com.rapidminer.operator.OperatorCapability;
import com.rapidminer.operator.OperatorCreationException;
import com.rapidminer.operator.OperatorDescription;
import com.rapidminer.operator.learner.CapabilityProvider;
import com.rapidminer.tools.GroupTree;
import com.rapidminer.tools.I18N;
import com.rapidminer.tools.OperatorService;
/**
* A dialog for adding new operators to the currently selected operator chain of
* the operator tree. The new operator can be searched by name, by groups, by
* input types, and by output types (or combinations). A short description of
* the operator is also shown. Therefore this dialog might be useful for less
* experienced RapidMiner users. A shorter way for adding operators is to use the
* context menu in the tree view or the new operator tab.
*
* @author Ingo Mierswa, Helge Homburg, Tobias Malbrecht
*/
public final class NewOperatorDialog extends ButtonDialog {
private static final long serialVersionUID = 390653805759799295L;
private transient OperatorDescription description = null;
private final JPanel mainPanel = new JPanel();
private OperatorInfoPanel operatorInfo = null;
private final JList operatorList = new OperatorList();
private String searchText = "";
private boolean isFullText;
private Class<? extends IOObject> inputClass = null;
private Class<? extends IOObject> outputClass = null;
private String group = null;
private transient OperatorCapability firstCapability = null;
private transient OperatorCapability secondCapability = null;
private final Actions actions;
private final JCheckBox fullTextCheckBox;
private final UpdateQueue updateQueue = new UpdateQueue("operator-filter");
public NewOperatorDialog(Actions actions) {
this(actions, null, null, null, null, false);
}
public NewOperatorDialog(Actions actions, final Class<? extends IOObject> inputClass, final Class<? extends IOObject> outputClass, final OperatorCapability firstCapability, final OperatorCapability secondCapability, boolean modal) {
super("new_operator", modal);
this.actions = actions;
this.inputClass = inputClass;
this.outputClass = outputClass;
this.firstCapability = firstCapability;
this.secondCapability = secondCapability;
// main
mainPanel.setLayout(new BorderLayout(GAP, 0));
// search panel
int rows = 2;
if (inputClass == null) {
rows++;
}
if (outputClass == null) {
rows++;
}
if (firstCapability == null) {
rows++;
}
if (secondCapability == null){
rows++;
}
JPanel searchPanel = new JPanel(new GridLayout(rows + 1, 2));
searchPanel.setBorder(createTitledBorder(I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.new_op_dialog.search_constraints")));
mainPanel.add(searchPanel, BorderLayout.NORTH);
// input objects (for in- and output classes)
final String[] ioObjects = convertSet2Strings(OperatorService.getIOObjectsNames());
String[] inputObjects = new String[ioObjects.length + 1];
inputObjects[0] = "Any";
System.arraycopy(ioObjects, 0, inputObjects, 1, ioObjects.length);
// search text
final JPanel searchFieldPanel = new JPanel(new BorderLayout());
final JTextField searchField = new JTextField(searchText);
searchField.addKeyListener(new KeyListener() {
@Override
public void keyTyped(KeyEvent e) {}
@Override
public void keyPressed(KeyEvent e) {}
@Override
public void keyReleased(KeyEvent e) {
searchText = searchField.getText().trim();
updateOperatorList();
}
});
searchFieldPanel.add(searchField, BorderLayout.CENTER);
JButton clearSearchFieldButton = new JButton(new ResourceAction(true, "new_op_dialog.clear_search_field") {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
searchText = "";
searchField.setText(searchText);
searchField.requestFocusInWindow();
updateOperatorList();
}
});
searchFieldPanel.add(clearSearchFieldButton, BorderLayout.EAST);
ResourceLabel textLabel = new ResourceLabel("new_op_dialog.search_text");
textLabel.setLabelFor(searchField);
searchPanel.add(textLabel);
searchPanel.add(searchFieldPanel);
searchPanel.add(new JPanel());
fullTextCheckBox = new JCheckBox(new ResourceAction("new_op_dialog.full_text_search") {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
isFullText = fullTextCheckBox.isSelected();
}
});
isFullText = fullTextCheckBox.isSelected();
searchPanel.add(fullTextCheckBox);
// groups
java.util.List<String> allGroups = new LinkedList<String>();
allGroups.add("Any");
GroupTree groupTree = OperatorService.getGroups();
addGroups(groupTree, null, allGroups);
Collections.sort(allGroups);
final String[] groupArray = new String[allGroups.size()];
allGroups.toArray(groupArray);
final JComboBox groupComboBox = new JComboBox(groupArray);
groupComboBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int selectedIndex = groupComboBox.getSelectedIndex();
if (selectedIndex <= 0) {
group = null;
} else {
group = groupArray[selectedIndex];
}
updateOperatorList();
}
});
ResourceLabel groupLabel = new ResourceLabel("new_op_dialog.operator_group");
searchPanel.add(groupLabel);
groupLabel.setLabelFor(groupComboBox);
searchPanel.add(groupComboBox);
// input
if (inputClass == null) {
final JComboBox inputType = new JComboBox(inputObjects);
inputType.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int selectedIndex = inputType.getSelectedIndex();
if (selectedIndex <= 0) {
NewOperatorDialog.this.inputClass = null;
} else {
NewOperatorDialog.this.inputClass = OperatorService.getIOObjectClass(ioObjects[selectedIndex - 1]);
}
updateOperatorList();
}
});
ResourceLabel inputLabel = new ResourceLabel("new_op_dialog.required_input");
searchPanel.add(inputLabel);
inputLabel.setLabelFor(inputType);
searchPanel.add(inputType);
}
// output
if (outputClass == null) {
final JComboBox outputType = new JComboBox(inputObjects);
outputType.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int selectedIndex = outputType.getSelectedIndex();
if (selectedIndex <= 0) {
NewOperatorDialog.this.outputClass = null;
} else {
NewOperatorDialog.this.outputClass = OperatorService.getIOObjectClass(ioObjects[selectedIndex - 1]);
}
updateOperatorList();
}
});
ResourceLabel outputLabel = new ResourceLabel("new_op_dialog.delivered_output");
searchPanel.add(outputLabel);
outputLabel.setLabelFor(outputType);
searchPanel.add(outputType);
}
// capabilities
OperatorCapability[] caps = OperatorCapability.values();
String[] capabilities = new String[caps.length + 1];
capabilities[0] = "Any";
int k = 1;
for (OperatorCapability capability : caps) {
capabilities[k++] = capability.getDescription();
}
if (firstCapability == null) {
final JComboBox firstCapabilityType = new JComboBox(capabilities);
firstCapabilityType.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int selectedIndex = firstCapabilityType.getSelectedIndex();
if (selectedIndex <= 0) {
NewOperatorDialog.this.firstCapability = null;
} else {
NewOperatorDialog.this.firstCapability = OperatorCapability.values()[selectedIndex - 1];
}
updateOperatorList();
}
});
ResourceLabel firstCapabilityLabel = new ResourceLabel("new_op_dialog.first_capability");
searchPanel.add(firstCapabilityLabel);
firstCapabilityLabel.setLabelFor(firstCapabilityType);
searchPanel.add(firstCapabilityType);
}
if (secondCapability == null) {
final JComboBox secondCapabilityType = new JComboBox(capabilities);
secondCapabilityType.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int selectedIndex = secondCapabilityType.getSelectedIndex();
if (selectedIndex <= 0) {
NewOperatorDialog.this.secondCapability = null;
} else {
NewOperatorDialog.this.secondCapability = OperatorCapability.values()[selectedIndex - 1];
}
updateOperatorList();
}
});
ResourceLabel secondCapabilityLabel = new ResourceLabel("new_op_dialog.second_capability");
searchPanel.add(secondCapabilityLabel);
secondCapabilityLabel.setLabelFor(secondCapabilityType);
searchPanel.add(secondCapabilityType);
}
// list panel
operatorList.addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
if (!e.getValueIsAdjusting()) {
OperatorDescription selection = (OperatorDescription) operatorList.getSelectedValue();
setSelectedOperator(selection);
}
}
});
operatorList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
JScrollPane listScrollPane = new ExtendedJScrollPane(operatorList);
listScrollPane.setPreferredSize(new Dimension((250), 50));
GridBagLayout layout = new GridBagLayout();
JPanel listPanel = new JPanel(layout);
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.BOTH;
c.gridwidth = GridBagConstraints.REMAINDER;
c.weightx = 1;
c.weighty = 1;
listPanel.setBorder(createTitledBorder(I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.new_op_dialog.matching_operators")));
layout.setConstraints(listScrollPane, c);
listPanel.add(listScrollPane);
mainPanel.add(listPanel, BorderLayout.WEST);
updateOperatorListNow();
updateQueue.start();
if (!modal) {
JButton addButton = new JButton(new ResourceAction("add_operator_now") {
private static final long serialVersionUID = -6725386765826715152L;
@Override
public void actionPerformed(ActionEvent e) {
add();
}
});
layoutDefault(mainPanel, LARGE, addButton, makeCloseButton());
getRootPane().setDefaultButton(addButton);
} else {
JButton okButton = makeOkButton();
layoutDefault(mainPanel, LARGE, okButton, makeCancelButton());
getRootPane().setDefaultButton(okButton);
}
}
private void updateOperatorList() {
updateQueue.execute(new Runnable() {
@Override
public void run() {
updateOperatorListNow();
}
});
}
private void updateOperatorListNow() {
CamelCaseFilter filter = null;
if (searchText != null) {
filter = new CamelCaseFilter(searchText.trim());
}
final Vector<OperatorDescription> operators = new Vector<OperatorDescription>();
for (String key: OperatorService.getOperatorKeys()) {
OperatorDescription description = OperatorService.getOperatorDescription(key);
if ((searchText != null) && (searchText.length() > 0)) {
if (isFullText) {
if (!description.getLongDescriptionHTML().toLowerCase().contains(searchText.toLowerCase())) {
continue;
}
} else {
if (!filter.matches(description.getName())) {
continue;
}
}
}
if ((group != null) && (group.length() > 0) && (description.getGroup().indexOf(group) < 0))
continue;
try {
Operator operator = description.createOperatorInstance();
if ((inputClass != null) && !operator.acceptsInput(inputClass))
continue;
if ((outputClass != null) && !operator.producesOutput(outputClass))
continue;
if (firstCapability != null) {
if ((!(operator instanceof CapabilityProvider)) ||
(!((CapabilityProvider)operator).supportsCapability(firstCapability)))
continue;
}
if (secondCapability != null) {
if ((!(operator instanceof CapabilityProvider)) ||
(!((CapabilityProvider)operator).supportsCapability(secondCapability)))
continue;
}
} catch (Exception e) {}
operators.add(description);
}
Collections.sort(operators);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
operatorList.removeAll();
operatorList.setListData(operators);
if (operators.size() > 0)
operatorList.setSelectedIndex(0);
}
});
}
private void addGroups(GroupTree tree, String parentName, Collection<String> names) {
Iterator<? extends GroupTree> i = tree.getSubGroups().iterator();
while (i.hasNext()) {
GroupTree subGroup = i.next();
String name = parentName == null ? subGroup.getName() : parentName + "." + subGroup.getName();
names.add(name);
addGroups(subGroup, name, names);
}
}
private void setSelectedOperator(OperatorDescription descriptionName) {
if (operatorInfo != null)
mainPanel.remove(operatorInfo);
if (descriptionName != null) {
this.description = descriptionName;
operatorInfo = new OperatorInfoPanel(this.description);
} else {
operatorInfo = new OperatorInfoPanel(null);
}
operatorInfo.setBorder(createTitledBorder(I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.new_op_dialog.operator_info")));
mainPanel.add(operatorInfo, BorderLayout.CENTER);
operatorInfo.revalidate();
}
private String[] convertSet2Strings(Collection<String> ioObjects) {
String[] objectArray = new String[ioObjects.size()];
Iterator<String> i = ioObjects.iterator();
int index = 0;
while (i.hasNext()) {
objectArray[index++] = i.next();
}
return objectArray;
}
private Operator getOperator() throws OperatorCreationException {
if (description != null) {
Operator operator = OperatorService.createOperator(description);
return operator;
} else {
return null;
}
}
private void add() {
try {
Operator operator = getOperator();
if (operator != null) {
actions.insert(Collections.singletonList(operator));
}
} catch (Exception ex) {
ex.printStackTrace();
SwingTools.showSimpleErrorMessage("cannot_create_operator", ex);
}
}
/**
* This method opens a dialog for selecting one operator fulfilling the given conditions.
* Conditions not needed must be null.
* @throws OperatorCreationException
*/
public static Operator selectMatchingOperator(Actions actions, Class<? extends IOObject> inputClass, Class<? extends IOObject> outputClass, OperatorCapability firstCapability, OperatorCapability secondCapability) throws OperatorCreationException {
NewOperatorDialog dialog = new NewOperatorDialog(actions, inputClass, outputClass, firstCapability, secondCapability, true);
dialog.setVisible(true);
if (dialog.wasConfirmed()) {
return dialog.getOperator();
}
return null;
}
}