/* * ------------------------------------------------------------------------ * * 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.node; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JPanel; import org.knime.core.data.DataTableSpec; 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.port.PortObjectSpec; /** * Node settings pane which allows one to first add the dialog components in different hierarchies and builds the dialog * after all dialog components have been collected. * * @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 LazyNodeDialogPane extends NodeDialogPane { private static final String DEFAULT_TAB_TITLE = "Options"; /** * @param currentBox * @param horizontal */ private static void addGlue(final Box box, final boolean horizontal) { if (horizontal) { box.add(Box.createVerticalGlue()); } else { box.add(Box.createHorizontalGlue()); } } /** * @param horizontal <code>true</code> if the layout is horizontal * @return the box */ private static Box createBox(final boolean horizontal) { final Box box; if (horizontal) { box = new Box(BoxLayout.X_AXIS); box.add(Box.createVerticalGlue()); } else { box = new Box(BoxLayout.Y_AXIS); box.add(Box.createHorizontalGlue()); } return box; } private JPanel m_compositePanel; private Box m_currentBox; private JPanel m_currentPanel; private final List<DialogComponent> m_dialogComponents; private boolean m_horizontal = false; private final HashMap<String, HashMap<String, List<DialogComponent>>> m_tabs; /** * Default constructor */ public LazyNodeDialogPane() { m_tabs = new LinkedHashMap<String, HashMap<String, List<DialogComponent>>>(); m_dialogComponents = new ArrayList<DialogComponent>(); createNewPanels(); super.addTab(DEFAULT_TAB_TITLE, m_compositePanel); } /** * The dialog components will be added to the first tab with no group. * * @param dc {@link DialogComponent} to add */ public void addDialogComponent(final DialogComponent dc) { addDialogComponent(DEFAULT_TAB_TITLE, "", dc); } // COPIED /** * Adds the dialog component to the specified tab (will be created, if not existent) and the group. * * @param tab * @param group * @param dc */ public void addDialogComponent(final String tab, final String group, final DialogComponent dc) { if (!m_tabs.containsKey(tab)) { m_tabs.put(tab, new LinkedHashMap<String, List<DialogComponent>>()); } final HashMap<String, List<DialogComponent>> groups = m_tabs.get(tab); if (!groups.containsKey(group)) { groups.put(group, new ArrayList<DialogComponent>()); } groups.get(group).add(dc); m_dialogComponents.add(dc); } /** * Add a new DialogComponent to the underlying dialog. It will automatically be added in the dialog and saved/loaded * from/to the config. * * @param diaC component to be added */ private void addDialogComponentToPanel(final DialogComponent diaC) { m_dialogComponents.add(diaC); m_currentBox.add(diaC.getComponentPanel()); addGlue(m_currentBox, m_horizontal); } /** * Has to be called in deriving classes after all dialog components have been added and the dialog can be built. */ protected void buildDialog() { if (m_tabs.containsKey(DEFAULT_TAB_TITLE)) { for (final String group : m_tabs.get(DEFAULT_TAB_TITLE).keySet()) { if (!group.equalsIgnoreCase("")) { createNewGroup(group); } for (final DialogComponent dc : m_tabs.get(DEFAULT_TAB_TITLE).get(group)) { addDialogComponentToPanel(dc); } } } else { removeTab(DEFAULT_TAB_TITLE); } for (final String tab : m_tabs.keySet()) { if (tab.equalsIgnoreCase(DEFAULT_TAB_TITLE)) { continue; } createNewTab(tab); for (final String group : m_tabs.get(tab).keySet()) { if (!group.equalsIgnoreCase("")) { createNewGroup(group); } for (final DialogComponent dc : m_tabs.get(tab).get(group)) { addDialogComponentToPanel(dc); } closeCurrentGroup(); } } } private void checkForEmptyBox() { if (m_currentBox.getComponentCount() == 0) { m_currentPanel.remove(m_currentBox); } } /** * Closes the current group. Further added dialog components are added to the default panel outside any border. * */ private void closeCurrentGroup() { checkForEmptyBox(); if (m_currentPanel.getComponentCount() == 0) { m_compositePanel.remove(m_currentPanel); } m_currentPanel = m_compositePanel; m_currentBox = createBox(m_horizontal); m_currentPanel.add(m_currentBox); } /** * Creates a new dialog component group and closes the current one. From now on the dialog components added with the * addDialogComponent method are added to the current group. The group is a bordered and titled panel. * * @param title - the title of the new group. */ private void createNewGroup(final String title) { checkForEmptyBox(); m_currentPanel = createSubPanel(title); m_currentBox = createBox(m_horizontal); m_currentPanel.add(m_currentBox); } private void createNewPanels() { m_compositePanel = new JPanel(); m_compositePanel.setLayout(new BoxLayout(m_compositePanel, BoxLayout.Y_AXIS)); m_currentPanel = m_compositePanel; m_currentBox = createBox(m_horizontal); m_currentPanel.add(m_currentBox); } /** * Creates a new tab in the dialog. All components added from now on are placed in that new tab. After creating a * new tab the previous tab is no longer accessible. If a tab with the same name was created before an Exception is * thrown. The new panel in the new tab has no group set (i.e. has no border). The tab is placed at the right most * position. * * @param tabTitle the title of the new tab to use from now on. Can't be null or empty. * @throws IllegalArgumentException if you specify a title that is already been used by another tab. Or if the * specified title is null or empty. * */ public void createNewTab(final String tabTitle) { createNewTabAt(tabTitle, Integer.MAX_VALUE); } /** * Creates a new tab in the dialog. All components added from now on are placed in that new tab. After creating a * new tab the previous tab is no longer accessible. If a tab with the same name was created before an Exception is * thrown. The new panel in the new tab has no group set (i.e. has no border). The new tab is placed at the * specified position (or at the right most position, if the index is too big). * * @param tabTitle the title of the new tab to use from now on. Can't be null or empty. * @param index the index to place the new tab at. Can't be negative. * @throws IllegalArgumentException if you specify a title that is already been used by another tab. Or if the * specified title is null or empty. * */ public void createNewTabAt(final String tabTitle, final int index) { if ((tabTitle == null) || (tabTitle.length() == 0)) { throw new IllegalArgumentException("The title of a tab can't be " + "null nor empty."); } // check if we already have a tab with the new title if (super.getTab(tabTitle) != null) { throw new IllegalArgumentException("A tab with the specified new" + " name (" + tabTitle + ") already exists."); } createNewPanels(); super.addTabAt(index, tabTitle, m_compositePanel); } private JPanel createSubPanel(final String title) { final JPanel panel = new JPanel(); panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), title)); m_compositePanel.add(panel); return panel; } /** * Override hook to load additional settings when all input ports are data ports. This method is the specific * implementation to {@link #loadAdditionalSettingsFrom(NodeSettingsRO, PortObjectSpec[])} if all input ports are * data ports. All elements in the <code>specs</code> argument are guaranteed to be non-null. * * @param settings The settings of the node * @param specs The <code>DataTableSpec</code> of the input tables. * @throws NotConfigurableException If not configurable */ public void loadAdditionalSettingsFrom(final NodeSettingsRO settings, final DataTableSpec[] specs) throws NotConfigurableException { } /** * This method can be overridden to load additional settings. Override this method if you have mixed input types * (different port types). Alternatively, if your node only has ordinary data inputs, consider to overwrite the * {@link #loadAdditionalSettingsFrom(NodeSettingsRO, DataTableSpec[])} method, which does the type casting already. * * @param settings the <code>NodeSettings</code> to read from * @param specs the input specs * @throws NotConfigurableException if the node can currently not be configured */ public void loadAdditionalSettingsFrom(final NodeSettingsRO settings, final PortObjectSpec[] specs) throws NotConfigurableException { final DataTableSpec[] dtsArray = new DataTableSpec[specs.length]; boolean canCallDTSMethod = true; for (int i = 0; i < dtsArray.length; i++) { final PortObjectSpec s = specs[i]; if (s instanceof DataTableSpec) { dtsArray[i] = (DataTableSpec)s; } else if (s == null) { dtsArray[i] = new DataTableSpec(); } else { canCallDTSMethod = false; } } if (canCallDTSMethod) { loadAdditionalSettingsFrom(settings, dtsArray); } } /** * Load settings for all registered components. * * @param settings the <code>NodeSettings</code> to read from * @param specs the input specs * @throws NotConfigurableException if the node can currently not be configured */ @Override public final void loadSettingsFrom(final NodeSettingsRO settings, final PortObjectSpec[] specs) throws NotConfigurableException { assert settings != null; assert specs != null; for (final DialogComponent comp : m_dialogComponents) { comp.loadSettingsFrom(settings, specs); } loadAdditionalSettingsFrom(settings, specs); } /** * This method can be overridden to save additional settings to the given settings object. * * @param settings the <code>NodeSettings</code> to write into * @throws InvalidSettingsException if the user has entered wrong values */ public void saveAdditionalSettingsTo(final NodeSettingsWO settings) throws InvalidSettingsException { assert settings != null; } /** * Save settings of all registered <code>DialogComponents</code> into the configuration object. * * @param settings the <code>NodeSettings</code> to write into * @throws InvalidSettingsException if the user has entered wrong values */ @Override public final void saveSettingsTo(final NodeSettingsWO settings) throws InvalidSettingsException { for (final DialogComponent comp : m_dialogComponents) { comp.saveSettingsTo(settings); } saveAdditionalSettingsTo(settings); } /** * Brings the specified tab to front and shows its components. * * @param tabTitle the title of the tab to select. If the specified title doesn't exist, this method does nothing. */ public void selectTab(final String tabTitle) { setSelected(tabTitle); } /** * Changes the orientation the components get placed in the dialog. * * @param horizontal <code>true</code> if the next components should be placed next to each other or * <code>false</code> if the next components should be placed below each other. */ public void setHorizontalPlacement(final boolean horizontal) { if (m_horizontal != horizontal) { m_horizontal = horizontal; checkForEmptyBox(); m_currentBox = createBox(m_horizontal); m_currentPanel.add(m_currentBox); } } }