/*
* ------------------------------------------------------------------------
*
* 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.io.kernel;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.SwingConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.basic.BasicArrowButton;
import org.knime.core.node.InvalidSettingsException;
import org.knime.core.node.defaultnodesettings.DialogComponent;
import org.knime.core.node.port.PortObjectSpec;
import org.knime.knip.base.node.nodesettings.SettingsModelSerializableObjects;
/**
* TODO Auto-generated
*
* @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>
*/
public class DialogComponentSerializableConfiguration<T> extends DialogComponent {
private class DownButtonListener implements ActionListener {
/** {@inheritDoc} */
@Override
public void actionPerformed(final ActionEvent e) {
final JButton button = (JButton)e.getSource();
final int index = m_sMoveDownButtons.indexOf(button);
moveUIControls(index, index + 1);
updateLayout();
}
}
private class RemoveButtonListener implements ActionListener {
/** {@inheritDoc} */
@Override
public void actionPerformed(final ActionEvent e) {
final JButton button = (JButton)e.getSource();
final int index = m_sRemoveButtons.indexOf(button);
final int x = m_sConfigurationButtons.get(index).getLocation().x;
final int y = m_sConfigurationButtons.get(index).getLocation().y;
final int width =
(m_sRemoveButtons.get(index).getBounds().x + m_sRemoveButtons.get(index).getBounds().width) - x;
final int height =
(m_sRemoveButtons.get(index).getBounds().y + m_sRemoveButtons.get(index).getBounds().height) - y;
if (m_sConfigurationButtons.get(index).isContentAreaFilled()) {
clearView();
}
removeUIControls(index);
updateLayout();
// Initiate a repaint on the area of the removed
// columns.
// It is a workaround which seems to be a bug in the
// swing
// repainting which occurs when the last row is deleted
// when
// more than two rows are displayed.
m_sListComponent.repaint(x, y, width, height);
}
}
private class UpButtonListener implements ActionListener {
/** {@inheritDoc} */
@Override
public void actionPerformed(final ActionEvent e) {
final JButton button = (JButton)e.getSource();
final int index = m_sMoveUpButtons.indexOf(button);
moveUIControls(index, index - 1);
updateLayout();
}
}
private class ViewButtonListener implements ActionListener {
/** {@inheritDoc} */
@Override
public void actionPerformed(final ActionEvent e) {
final JButton button = (JButton)e.getSource();
clearView();
updateView(m_sConfigurationButtons.indexOf(button));
getComponentPanel().revalidate();
}
}
private final ActionListener m_downButtonListener;
private JComboBox m_jcbConfigurationType;
private final Map<String, Class<SerializableConfiguration<T>>> m_pool;
private final JPanel m_previewConfigPanel;
private final ActionListener m_removeButtonListener;
private final List<JButton> m_sConfigurationButtons;
private final List<SerializableConfiguration<T>> m_sConfigurations;
private JComponent m_sFillComponent;
private JComponent m_sListComponent;
private final List<JButton> m_sMoveDownButtons;
private final List<JButton> m_sMoveUpButtons;
private final List<JButton> m_sRemoveButtons;
private final ActionListener m_upButtonListener;
private final ActionListener m_viewButtonListener;
public DialogComponentSerializableConfiguration(final SettingsModelSerializableObjects<SerializableSetting<T>> settingsModel,
final Map<String, Class<SerializableConfiguration<T>>> pool) {
super(settingsModel);
m_pool = pool;
m_sConfigurationButtons = new ArrayList<JButton>();
m_sMoveUpButtons = new ArrayList<JButton>();
m_sMoveDownButtons = new ArrayList<JButton>();
m_sRemoveButtons = new ArrayList<JButton>();
m_sConfigurations = new ArrayList<SerializableConfiguration<T>>();
m_viewButtonListener = new ViewButtonListener();
m_upButtonListener = new UpButtonListener();
m_downButtonListener = new DownButtonListener();
m_removeButtonListener = new RemoveButtonListener();
m_previewConfigPanel = new JPanel(new BorderLayout());
final JSplitPane jsp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
jsp.setDividerLocation(240);
jsp.setLeftComponent(createConfigurationListPanel());
jsp.setRightComponent(m_previewConfigPanel);
final JPanel main = getComponentPanel();
main.setLayout(new BorderLayout());
main.add(jsp, BorderLayout.CENTER);
main.setPreferredSize(new Dimension(800, 600));
getModel().addChangeListener(new ChangeListener() {
@Override
public void stateChanged(final ChangeEvent e) {
updateComponent();
}
});
updateComponent();
}
private void addOrUpdate(final JComponent parent, final JComponent component, final GridBagConstraints c) {
final List<Component> components = Arrays.asList(parent.getComponents());
if (components.contains(component)) {
((GridBagLayout)parent.getLayout()).setConstraints(component, c);
} else {
parent.add(component, c);
}
}
private void addUIControls(final int index, final SerializableConfiguration<T> conf) {
m_sConfigurationButtons.add(index, new JButton(conf.getName()));
m_sConfigurationButtons.get(index).setContentAreaFilled(false);
m_sConfigurationButtons.get(index).addActionListener(m_viewButtonListener);
m_sMoveUpButtons.add(index, new BasicArrowButton(SwingConstants.NORTH));
if (m_sMoveUpButtons.size() == 1) {
m_sMoveUpButtons.get(index).setEnabled(false);
}
m_sMoveUpButtons.get(index).addActionListener(m_upButtonListener);
m_sMoveDownButtons.add(index, new BasicArrowButton(SwingConstants.SOUTH));
if (m_sMoveDownButtons.size() > 1) {
m_sMoveDownButtons.get(index - 1).setEnabled(true);
}
m_sMoveDownButtons.get(index).setEnabled(false);
m_sMoveDownButtons.get(index).addActionListener(m_downButtonListener);
m_sRemoveButtons.add(index, new JButton("-"));
m_sRemoveButtons.get(index).addActionListener(m_removeButtonListener);
m_sConfigurations.add(index, conf);
}
/**
* {@inheritDoc}
*/
@Override
protected void checkConfigurabilityBeforeLoad(final PortObjectSpec[] specs) {
//
}
private void clearView() {
for (int i = 0; i < m_sConfigurationButtons.size(); i++) {
m_sConfigurationButtons.get(i).setContentAreaFilled(false);
}
m_previewConfigPanel.removeAll();
}
private JComponent createConfigurationListPanel() {
final JPanel p = new JPanel(new GridBagLayout());
final GridBagConstraints gbc = new GridBagConstraints();
gbc.anchor = GridBagConstraints.PAGE_START;
gbc.fill = GridBagConstraints.BOTH;
gbc.weightx = 1;
gbc.insets = new Insets(2, 2, 2, 2);
gbc.gridx = 0;
gbc.gridy = 0;
m_jcbConfigurationType = new JComboBox();
final DefaultComboBoxModel jcbModel = new DefaultComboBoxModel();
m_jcbConfigurationType.setModel(jcbModel);
for (final String name : m_pool.keySet()) {
jcbModel.addElement(name);
}
p.add(m_jcbConfigurationType, gbc);
gbc.gridx = 1;
gbc.weightx = 0;
final JButton add = new JButton("+");
add.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
SerializableConfiguration<T> conf;
try {
conf = m_pool.get(m_jcbConfigurationType.getSelectedItem()).newInstance();
addUIControls(m_sConfigurations.size(), conf);
updateLayout();
m_sConfigurationButtons.get(m_sConfigurationButtons.size() - 1).doClick();
} catch (final InstantiationException e1) {
e1.printStackTrace();
} catch (final IllegalAccessException e1) {
e1.printStackTrace();
}
}
});
p.add(add, gbc);
gbc.gridx = 0;
gbc.gridy = 1;
gbc.gridwidth = 2;
gbc.weightx = 1;
gbc.weighty = 1;
m_sListComponent = new JPanel(new GridBagLayout());
m_sListComponent.setBackground(Color.WHITE);
p.add(new JScrollPane(m_sListComponent), gbc);
m_sFillComponent = new JPanel();
m_sFillComponent.setBackground(Color.WHITE);
return p;
}
@SuppressWarnings("unchecked")
private SettingsModelSerializableObjects<SerializableSetting<T>> getMyModel() {
return (SettingsModelSerializableObjects<SerializableSetting<T>>)getModel();
}
private void moveUIControls(final int from, final int to) {
m_sConfigurationButtons.add(to, m_sConfigurationButtons.remove(from));
m_sConfigurations.add(to, m_sConfigurations.remove(from));
}
private void removeAllUIControls() {
for (int i = m_sConfigurationButtons.size() - 1; i >= 0; i--) {
removeUIControls(i);
}
}
private void removeUIControls(final int index) {
m_sListComponent.remove(m_sConfigurationButtons.remove(index));
m_sListComponent.remove(m_sMoveUpButtons.remove(index));
if ((index == 0) && (m_sMoveUpButtons.size() > 0)) {
m_sMoveUpButtons.get(0).setEnabled(false);
}
m_sListComponent.remove(m_sMoveDownButtons.remove(index));
if ((m_sMoveDownButtons.size() == index) && (m_sMoveDownButtons.size() > 0)) {
m_sMoveDownButtons.get(index - 1).setEnabled(false);
}
m_sListComponent.remove(m_sRemoveButtons.remove(index));
m_sConfigurations.remove(index);
}
/**
* {@inheritDoc}
*/
@Override
protected void setEnabledComponents(final boolean enabled) {
//
}
/**
* {@inheritDoc}
*/
@Override
public void setToolTipText(final String text) {
//
}
/**
* {@inheritDoc}
*/
@Override
protected void updateComponent() {
final SettingsModelSerializableObjects<SerializableSetting<T>> model = getMyModel();
final List<SerializableSetting<T>> settings = model.getObjects();
// look for enabled button
int enabledIndex = -1;
for (int index = 0; index < m_sConfigurationButtons.size(); index++) {
if (m_sConfigurationButtons.get(index).isContentAreaFilled()) {
enabledIndex = index;
break;
}
}
removeAllUIControls();
for (final SerializableSetting<T> s : settings) {
addUIControls(m_sConfigurationButtons.size(), s.createConfiguration());
}
updateLayout();
setEnabledComponents(getModel().isEnabled());
getComponentPanel().repaint();
clearView();
// re-enable last selected
if ((enabledIndex != -1) && (enabledIndex < m_sConfigurationButtons.size())) {
updateView(enabledIndex);
}
getComponentPanel().revalidate();
}
private void updateLayout() {
final GridBagConstraints gbc = new GridBagConstraints();
gbc.anchor = GridBagConstraints.PAGE_START;
gbc.fill = GridBagConstraints.BOTH;
gbc.weightx = 1;
gbc.insets = new Insets(2, 2, 2, 2);
gbc.gridy = 0;
for (int i = 0; i < m_sConfigurationButtons.size(); i++) {
gbc.gridx = 0;
gbc.weightx = 0;
addOrUpdate(m_sListComponent, m_sMoveUpButtons.get(i), gbc);
gbc.gridx++;
gbc.weightx = 0;
addOrUpdate(m_sListComponent, m_sMoveDownButtons.get(i), gbc);
gbc.gridx++;
gbc.weightx = 1;
addOrUpdate(m_sListComponent, m_sConfigurationButtons.get(i), gbc);
gbc.gridx++;
gbc.weightx = 0;
addOrUpdate(m_sListComponent, m_sRemoveButtons.get(i), gbc);
gbc.gridy++;
}
gbc.gridx = 0;
gbc.gridwidth = 4;
gbc.weighty = 1;
addOrUpdate(m_sListComponent, m_sFillComponent, gbc);
m_sListComponent.revalidate();
}
/**
* Transfers the current value from the component into the model.
*
* @throws InvalidSettingsException if a selection is required and no item is selected
*/
private void updateModel() throws InvalidSettingsException {
final List<SerializableSetting<T>> settings = new ArrayList<SerializableSetting<T>>();
for (final SerializableConfiguration<T> conf : m_sConfigurations) {
settings.add(conf.getSetting());
}
final SettingsModelSerializableObjects<SerializableSetting<T>> model = getMyModel();
model.setObjects(settings);
}
private void updateView(final int index) {
for (int i = 0; i < m_sConfigurationButtons.size(); i++) {
m_sConfigurationButtons.get(i).setContentAreaFilled(false);
}
final SerializableConfiguration<T> conf = m_sConfigurations.get(index);
m_sConfigurationButtons.get(index).setContentAreaFilled(true);
m_previewConfigPanel.add(conf.getPreviewPanel(), BorderLayout.CENTER);
m_previewConfigPanel.add(conf.getConfigurationPanel(), BorderLayout.PAGE_END);
m_previewConfigPanel.repaint();
}
/**
* {@inheritDoc}
*/
@Override
protected void validateSettingsBeforeSave() throws InvalidSettingsException {
for (final SerializableConfiguration<T> conf : m_sConfigurations) {
conf.validate();
}
updateModel();
}
}