/*
* ------------------------------------------------------------------------
*
* Copyright (C) 2003 - 2013
* University of Konstanz, Germany and
* KNIME GmbH, Konstanz, Germany
* Website: http://www.knime.org; Email: contact@knime.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, Version 3, as
* published by the Free Software Foundation.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses>.
*
* Additional permission under GNU GPL version 3 section 7:
*
* KNIME interoperates with ECLIPSE solely via ECLIPSE's plug-in APIs.
* Hence, KNIME and ECLIPSE are both independent programs and are not
* derived from each other. Should, however, the interpretation of the
* GNU GPL Version 3 ("License") under any applicable laws result in
* KNIME and ECLIPSE being a combined program, KNIME GMBH herewith grants
* you the additional permission to use and propagate KNIME together with
* ECLIPSE with only the license terms in place for ECLIPSE applying to
* ECLIPSE and the GNU GPL Version 3 applying for KNIME, provided the
* license terms of ECLIPSE themselves allow for the respective use and
* propagation of ECLIPSE together with KNIME.
*
* Additional permission relating to nodes for KNIME that extend the Node
* Extension (and in particular that are based on subclasses of NodeModel,
* NodeDialog, and NodeView) and that only interoperate with KNIME through
* standard APIs ("Nodes"):
* Nodes are deemed to be separate and independent programs and to not be
* covered works. Notwithstanding anything to the contrary in the
* License, the License does not apply to Nodes, you are not required to
* license Nodes under the License, and you are granted a license to
* prepare and propagate Nodes, in each case even if such Nodes are
* propagated with or for interoperation with KNIME. The owner of a Node
* may freely choose the license terms applicable to such Node, including
* when such Node is propagated with or for interoperation with KNIME.
* --------------------------------------------------------------------- *
*
*/
package org.knime.knip.base.nodes.proc.imgjep;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultListModel;
import javax.swing.JEditorPane;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.ScrollPaneConstants;
import javax.swing.border.Border;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import org.knime.core.data.DataColumnSpec;
import org.knime.core.data.DataTableSpec;
import org.knime.core.data.DataValue;
import org.knime.core.data.DoubleValue;
import org.knime.core.data.IntValue;
import org.knime.core.node.InvalidSettingsException;
import org.knime.core.node.NodeDialogPane;
import org.knime.core.node.NodeSettingsRO;
import org.knime.core.node.NodeSettingsWO;
import org.knime.core.node.NotConfigurableException;
import org.knime.core.node.defaultnodesettings.DialogComponent;
import org.knime.core.node.defaultnodesettings.DialogComponentBoolean;
import org.knime.core.node.defaultnodesettings.DialogComponentColumnNameSelection;
import org.knime.core.node.defaultnodesettings.DialogComponentStringSelection;
import org.knime.core.node.defaultnodesettings.SettingsModelBoolean;
import org.knime.core.node.defaultnodesettings.SettingsModelString;
import org.knime.core.node.util.ColumnSelectionPanel;
import org.knime.core.node.util.DataColumnSpecListCellRenderer;
import org.knime.knip.base.data.img.ImgPlusValue;
import org.knime.knip.core.types.NativeTypes;
import org.knime.knip.core.util.EnumUtils;
/**
* Dialog for Mathematical Formula node. Shows expression text field, and three list containing variables, functions,
* and constants.
*
* @author <a href="mailto:dietzc85@googlemail.com">Christian Dietz</a>
* @author <a href="mailto:horn_martin@gmx.de">Martin Horn</a>
* @author <a href="mailto:michael.zinsmaier@googlemail.com">Michael Zinsmaier</a>
* @author Bernd Wiswedel, University of Konstanz
*/
public class ImgJEPNodeDialogPane extends NodeDialogPane {
private static class ConstantListRenderer extends DefaultListCellRenderer {
/**
*
*/
private static final long serialVersionUID = 1L;
/** {@inheritDoc} */
@Override
public Component getListCellRendererComponent(final JList list, final Object value, final int index,
final boolean isSelected, final boolean cellHasFocus) {
Object v;
if (value instanceof ImgJEPConstant) {
final ImgJEPConstant j = (ImgJEPConstant)value;
v = j.getFunctionFullName();
setToolTipText(j.getDescription());
} else {
v = null;
setToolTipText(null);
}
return super.getListCellRendererComponent(list, v, index, isSelected, cellHasFocus);
}
}
private static class FunctionListRenderer extends DefaultListCellRenderer {
/**
*
*/
private static final long serialVersionUID = 1L;
/** {@inheritDoc} */
@Override
public Component getListCellRendererComponent(final JList list, final Object value, final int index,
final boolean isSelected, final boolean cellHasFocus) {
Object v;
if (value instanceof ImgJEPFunction) {
final ImgJEPFunction j = (ImgJEPFunction)value;
v = j.getFunctionFullName();
setToolTipText(j.getDescription());
} else {
v = null;
setToolTipText(null);
}
return super.getListCellRendererComponent(list, v, index, isSelected, cellHasFocus);
}
}
/**
* Renderer that will display the rowindex and rowkey with different background.
*/
private static class ListRenderer extends DataColumnSpecListCellRenderer {
/**
*
*/
private static final long serialVersionUID = 1L;
/** {@inheritDoc} */
@Override
public Component getListCellRendererComponent(final JList list, final Object value, final int index,
final boolean isSelected, final boolean cellHasFocus) {
final Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (value instanceof String) {
c.setFont(list.getFont().deriveFont(Font.ITALIC));
}
return c;
}
}
private final DialogComponent m_adjustImgDim;
private final JRadioButton m_appendRadio;
private final JList m_constantList;
private DataTableSpec m_currenteSpec = null;
private final JEditorPane m_expEdit;
private final JList m_functionList;
private final JTextField m_newColNameField;
private final JTextField m_newTableColNameField;
private final JRadioButton m_newTableRadio;
private final DialogComponentColumnNameSelection m_refColumnSelection;
private final ColumnSelectionPanel m_replaceCombo;
private final JRadioButton m_replaceRadio;
private final DialogComponent m_resType;
private final JList m_varList;
/** Inits GUI. */
@SuppressWarnings("unchecked")
public ImgJEPNodeDialogPane() {
m_varList = new JList(new DefaultListModel());
m_varList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
m_varList.addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(final ListSelectionEvent e) {
final Object selected = m_varList.getSelectedValue();
if (selected != null) {
final DataColumnSpec colSpec = (DataColumnSpec)selected;
final String enter = "$" + colSpec.getName().toString() + "$";
m_expEdit.replaceSelection(enter);
m_varList.clearSelection();
m_expEdit.requestFocus();
}
}
});
m_varList.setCellRenderer(new ListRenderer());
m_functionList = new JList(ImgJEPFunction.values());
m_functionList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
m_functionList.addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(final ListSelectionEvent e) {
final ImgJEPFunction selected = (ImgJEPFunction)m_functionList.getSelectedValue();
if (selected != null) {
final String selectedString = m_expEdit.getSelectedText();
final StringBuilder newStr = new StringBuilder(selected.getFunctionName());
if (!selected.isInfixOperation()) {
newStr.append('(');
for (int i = 0; i < selected.getNrArgs(); i++) {
newStr.append(i > 0 ? ", " : "");
if ((i == 0) && (selectedString != null)) {
newStr.append(selectedString);
}
}
newStr.append(')');
}
m_expEdit.replaceSelection(newStr.toString());
if ((selected.getNrArgs() > 0) && (selectedString == null)) {
final int caretPos = m_expEdit.getCaretPosition();
m_expEdit.setCaretPosition(1 + m_expEdit.getText()
.indexOf('(', caretPos - newStr.toString().length()));
}
m_functionList.clearSelection();
m_expEdit.requestFocus();
}
}
});
m_functionList.setCellRenderer(new FunctionListRenderer());
m_constantList = new JList(ImgJEPConstant.values());
m_constantList.addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(final ListSelectionEvent e) {
final ImgJEPConstant selected = (ImgJEPConstant)m_constantList.getSelectedValue();
if (selected != null) {
final boolean needsArgument = selected.getNrArgs() > 0;
final String selectedString = m_expEdit.getSelectedText();
final StringBuilder newStr = new StringBuilder();
newStr.append(selected.getFunctionName());
newStr.append(needsArgument ? "(" : "");
for (int i = 0; i < selected.getNrArgs(); i++) {
newStr.append(i > 0 ? ", " : "");
if ((i == 0) && (selectedString != null)) {
newStr.append(selectedString);
}
}
newStr.append(needsArgument ? ")" : "");
m_expEdit.replaceSelection(newStr.toString());
if (needsArgument && (selectedString == null)) {
final int caretPos = m_expEdit.getCaretPosition();
final int newCaret = m_expEdit.getText().indexOf(')', caretPos - newStr.toString().length());
if (newCaret > 0) {
m_expEdit.setCaretPosition(newCaret);
}
}
m_constantList.clearSelection();
m_expEdit.requestFocus();
}
}
});
m_constantList.setCellRenderer(new ConstantListRenderer());
m_expEdit = new JEditorPane();
m_expEdit.setFont(Font.getFont("Monospaced"));
m_newColNameField = new JTextField(10);
m_newTableColNameField = new JTextField(10);
m_appendRadio = new JRadioButton("Append Column ");
m_appendRadio.setToolTipText("Appends a new column to the input " + "table with a given name and type.");
m_replaceRadio = new JRadioButton("Replace Column: ");
m_replaceRadio.setToolTipText("Replaces the column and changes " + "the column type accordingly");
m_newTableRadio = new JRadioButton("New Table: ");
m_newTableRadio.setToolTipText("Creates a new table " + "only keeping the result column");
// show all columns
m_replaceCombo = new ColumnSelectionPanel((Border)null, DataValue.class);
final ButtonGroup buttonGroup = new ButtonGroup();
buttonGroup.add(m_appendRadio);
buttonGroup.add(m_replaceRadio);
buttonGroup.add(m_newTableRadio);
final ActionListener actionListener = new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
m_replaceCombo.setEnabled(m_replaceRadio.isSelected());
m_newColNameField.setEnabled(m_appendRadio.isSelected());
m_newTableColNameField.setEnabled(m_newTableRadio.isSelected());
}
};
m_appendRadio.addActionListener(actionListener);
m_replaceRadio.addActionListener(actionListener);
m_newTableRadio.addActionListener(actionListener);
final JPanel finalPanel = new JPanel(new BorderLayout());
finalPanel.add(createPanel(), BorderLayout.CENTER);
finalPanel.setPreferredSize(new Dimension(300,350));
final JPanel additionalOptions = new JPanel();
additionalOptions.setLayout(new BoxLayout(additionalOptions, BoxLayout.Y_AXIS));
finalPanel.add(additionalOptions, BorderLayout.SOUTH);
// result pixel type panel
m_resType =
new DialogComponentStringSelection(new SettingsModelString(ImgJEPNodeModel.CFG_RESULT_TYPE,
NativeTypes.FLOATTYPE.toString()), "Result pixel type",
EnumUtils.getStringListFromName(NativeTypes.values()));
additionalOptions.add(m_resType.getComponentPanel(), BorderLayout.SOUTH);
addTab("Math Expression", finalPanel);
// reference image selection
final JPanel settingsPanel = new JPanel(new BorderLayout());
final JPanel referenceSettigns = new JPanel(new BorderLayout());
m_refColumnSelection =
new DialogComponentColumnNameSelection(new SettingsModelString(ImgJEPNodeModel.CFG_REF_COLUMN, ""),
"Reference Image", 0, false, true, ImgPlusValue.class);
final JLabel description = new JLabel("(Auto guess of reference if <none> is selected.)");
referenceSettigns.add(description, BorderLayout.NORTH);
referenceSettigns.add(m_refColumnSelection.getComponentPanel(), BorderLayout.CENTER);
settingsPanel.add(referenceSettigns, BorderLayout.NORTH);
// option to extend the image
m_adjustImgDim =
new DialogComponentBoolean(new SettingsModelBoolean(ImgJEPNodeModel.CFG_ADJUST_IMG_DIM, false),
"Adjust image dimensions if not compatible");
settingsPanel.add(m_adjustImgDim.getComponentPanel(), BorderLayout.CENTER);
addTab("Advanced Settings", settingsPanel);
}
private JPanel createPanel() {
final JPanel finalPanel = new JPanel(new GridBagLayout());
final GridBagConstraints gdb = new GridBagConstraints();
gdb.insets = new Insets(15, 15, 0, 15);
gdb.anchor = GridBagConstraints.SOUTH;
gdb.weighty = 0.0;
gdb.weightx = 0.0;
gdb.gridx = 0;
gdb.gridy = 0;
finalPanel.add(new JLabel("Column List"), gdb);
gdb.gridx = 1;
finalPanel.add(new JLabel("Mathematical Function"), gdb);
gdb.gridx = 2;
finalPanel.add(new JLabel("Constants"), gdb);
gdb.gridx = 0;
gdb.gridy++;
gdb.insets.bottom = 15;
gdb.insets.top = 0;
gdb.weighty = 2.0;
gdb.weightx = 1.0;
gdb.anchor = GridBagConstraints.CENTER;
gdb.fill = GridBagConstraints.BOTH;
finalPanel.add(new JScrollPane(m_varList), gdb);
gdb.gridx = 1;
finalPanel.add(new JScrollPane(m_functionList), gdb);
gdb.gridx = 2;
finalPanel.add(new JScrollPane(m_constantList), gdb);
gdb.insets.top = 10;
gdb.insets.bottom = 0;
gdb.gridwidth = 3;
gdb.gridy++;
gdb.gridx = 0;
gdb.weighty = 0.0;
gdb.anchor = GridBagConstraints.SOUTHWEST;
finalPanel.add(new JLabel("Expression"), gdb);
gdb.weighty = 1.0;
gdb.insets.top = 0;
gdb.insets.bottom = 15;
gdb.gridy++;
finalPanel.add(new JScrollPane(m_expEdit, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER), gdb);
gdb.insets.top = 15;
gdb.insets.bottom = 15;
gdb.gridy++;
gdb.gridx = 0;
gdb.weighty = 0.0;
gdb.weightx = 0.0;
gdb.gridwidth = 2;
gdb.ipadx = 5;
gdb.fill = GridBagConstraints.NONE;
final JPanel replaceOrAppend = new JPanel(new GridBagLayout());
final GridBagConstraints gdb2 = new GridBagConstraints();
gdb2.insets = new Insets(5, 5, 5, 5);
gdb2.fill = GridBagConstraints.HORIZONTAL;
gdb2.anchor = GridBagConstraints.WEST;
gdb2.gridx = 0;
gdb2.gridy = 0;
replaceOrAppend.add(m_appendRadio, gdb2);
gdb2.gridx++;
replaceOrAppend.add(m_newColNameField, gdb2);
gdb2.gridy++;
gdb2.gridx = 0;
replaceOrAppend.add(m_replaceRadio, gdb2);
gdb2.gridx++;
replaceOrAppend.add(m_replaceCombo, gdb2);
gdb2.gridy++;
gdb2.gridx = 0;
replaceOrAppend.add(m_newTableRadio, gdb2);
gdb2.gridx++;
replaceOrAppend.add(m_newTableColNameField, gdb2);
finalPanel.add(replaceOrAppend, gdb);
return finalPanel;
}
/**
* @see NodeDialogPane#loadSettingsFrom(NodeSettingsRO, DataTableSpec[])
*/
@Override
protected void loadSettingsFrom(final NodeSettingsRO settings, final DataTableSpec[] specs)
throws NotConfigurableException {
final String exp = settings.getString(ImgJEPNodeModel.CFG_EXPRESSION, "");
final String defaultColName = "new column";
final String newColName = settings.getString(ImgJEPNodeModel.CFG_COLUMN_NAME, defaultColName);
final int tableCreationMode =
settings.getInt(ImgJEPNodeModel.CFG_TABLE_CREATION_MODE, ImgJEPNodeModel.REPLACE_COLUMN);
m_newColNameField.setText("Img Calc");
m_newTableColNameField.setText("Img Calc");
// will select newColName only if it is in the spec list
m_replaceCombo.update(specs[0], newColName);
m_currenteSpec = specs[0];
if (tableCreationMode == ImgJEPNodeModel.REPLACE_COLUMN) {
m_replaceRadio.doClick();
} else if (tableCreationMode == ImgJEPNodeModel.APPEND_COLUMN) {
m_appendRadio.doClick();
final String newColString = (newColName != null ? newColName : defaultColName);
m_newColNameField.setText(newColString);
} else {
m_newTableRadio.doClick();
final String newColString = (newColName != null ? newColName : defaultColName);
m_newTableColNameField.setText(newColString);
}
m_expEdit.setText(exp);
final DefaultListModel listModel = (DefaultListModel)m_varList.getModel();
listModel.removeAllElements();
for (int i = 0; i < specs[0].getNumColumns(); i++) {
final DataColumnSpec colSpec = specs[0].getColumnSpec(i);
if (colSpec.getType().isCompatible(ImgPlusValue.class) || colSpec.getType().isCompatible(DoubleValue.class)
|| colSpec.getType().isCompatible(IntValue.class)) {
listModel.addElement(colSpec);
}
}
m_resType.loadSettingsFrom(settings, specs);
m_adjustImgDim.loadSettingsFrom(settings, specs);
m_refColumnSelection.loadSettingsFrom(settings, specs);
}
/**
* @see NodeDialogPane#saveSettingsTo(NodeSettingsWO)
*/
@Override
protected void saveSettingsTo(final NodeSettingsWO settings) throws InvalidSettingsException {
String newColName = null;
int tableCreationMode;
if (m_replaceRadio.isSelected()) {
tableCreationMode = ImgJEPNodeModel.REPLACE_COLUMN;
newColName = m_replaceCombo.getSelectedColumn();
} else if (m_appendRadio.isSelected()) {
tableCreationMode = ImgJEPNodeModel.APPEND_COLUMN;
newColName = m_newColNameField.getText();
} else {
tableCreationMode = ImgJEPNodeModel.NEW_TABLE;
newColName = m_newTableColNameField.getText();
}
settings.addInt(ImgJEPNodeModel.CFG_TABLE_CREATION_MODE, tableCreationMode);
settings.addString(ImgJEPNodeModel.CFG_COLUMN_NAME, newColName);
final String exp = m_expEdit.getText();
settings.addString(ImgJEPNodeModel.CFG_EXPRESSION, exp);
if (m_currenteSpec != null) {
new ImgExpressionParser(exp, m_currenteSpec, -1);
}
m_resType.saveSettingsTo(settings);
m_adjustImgDim.saveSettingsTo(settings);
m_refColumnSelection.saveSettingsTo(settings);
}
}