edu.udo.scaffoldhunter.gui.dataimport.ImportMappingsDialog.java Source code

Java tutorial

Introduction

Here is the source code for edu.udo.scaffoldhunter.gui.dataimport.ImportMappingsDialog.java

Source

/*
 * Scaffold Hunter
 * Copyright (C) 2006-2008 PG504
 * Copyright (C) 2010-2011 PG552
 * See README.txt in the root directory of the Scaffold Hunter source tree
 * for details.
 *
 * Scaffold Hunter is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Scaffold Hunter 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/>.
 */

package edu.udo.scaffoldhunter.gui.dataimport;

import static edu.udo.scaffoldhunter.util.I18n._;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Collections;
import java.util.HashMap;
import java.util.Vector;

import javax.swing.AbstractListModel;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultListSelectionModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JTable;
import javax.swing.JToggleButton;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;

import org.openscience.cdk.interfaces.IAtomContainer;

import com.google.common.base.Predicates;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.jgoodies.forms.builder.PanelBuilder;
import com.jgoodies.forms.factories.ButtonBarFactory;
import com.jgoodies.forms.factories.CC;
import com.jgoodies.forms.layout.FormLayout;

import edu.udo.scaffoldhunter.gui.util.AbstractAction;
import edu.udo.scaffoldhunter.model.dataimport.ImportJob;
import edu.udo.scaffoldhunter.model.dataimport.ImportProcess;
import edu.udo.scaffoldhunter.model.dataimport.PropertyDefinitionList;
import edu.udo.scaffoldhunter.model.db.Property;
import edu.udo.scaffoldhunter.model.db.PropertyDefinition;
import edu.udo.scaffoldhunter.model.util.SHPredicates;
import edu.udo.scaffoldhunter.plugins.dataimport.PluginResults;
import edu.udo.scaffoldhunter.util.Orderings;

/**
 * A dialog to set import mappings for {@link ImportProcess}
 * <p>
 * Changes to the <code>ImportJob</code>s will be made, even if the user leaves
 * the dialog by clicking "Cancel".
 * 
 * @author Henning Garus
 * @author Till Schfer
 */
public class ImportMappingsDialog extends JDialog implements TableModelListener {

    private static final int PREVIEW_SIZE = 100;
    private static final int SCROLL_INCREMENT = 20;

    private final JScrollPane tablePane;
    private final JButton cancelButton;
    private final JButton okButton;
    private final JLabel descriptionLabel;
    private final JPanel previewPanel;
    private ImmutableList<JTable> tables;

    private Result result = Result.NONE;

    private final ImportProcess sources;
    private final PropertyDefinitionList propertyDefinitions;

    private HashMap<ImportJob, MappingTableModel> tableModels = Maps.newHashMap();
    private HashMap<ImportJob, ComboBoxModel> mergeIntoModels = Maps.newHashMap();

    /**
     * Create a new Import Mappings Dialog.
     * 
     * @param owner
     *            this dialog's owner
     * @param sources
     *            the sources represented by this dialog
     */
    public ImportMappingsDialog(Window owner, ImportProcess sources) {
        super(owner);

        this.sources = sources;

        this.propertyDefinitions = new PropertyDefinitionList(sources.getPropertyDefinitions());

        setModal(true);
        setTitle(_("ImportMappings.Title"));
        setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
        addWindowListener(new WindowCloseListener());

        FormLayout layout = new FormLayout("d, 5dlu, d, f:d:grow", "c:d, 5dlu, f:d:grow, 3dlu, center:d");

        PanelBuilder pb = new PanelBuilder(layout);
        pb.setDefaultDialogBorder();

        tablePane = new JScrollPane(buildTablePanel(), JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
                JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);

        tablePane.getVerticalScrollBar().setUnitIncrement(SCROLL_INCREMENT);

        cancelButton = new JButton(new CancelAction());
        okButton = new JButton(new OkAction());

        // restrict preferred width of the label to a sane value
        descriptionLabel = getDescriptionLabel();
        Dimension d = tablePane.getPreferredSize();
        d.height = 50;
        descriptionLabel.setPreferredSize(d);

        pb.add(descriptionLabel, CC.xyw(3, 1, 2));
        pb.add(tablePane, CC.xyw(3, 3, 2));
        previewPanel = new PreviewPanel();
        pb.add(previewPanel, CC.xywh(1, 3, 1, 3));
        pb.add(getPreviewToggle(), CC.xy(3, 5));
        pb.add(ButtonBarFactory.buildOKCancelBar(okButton, cancelButton), CC.xy(4, 5));

        add(pb.getPanel());

        getRootPane().setDefaultButton(okButton);
        pack();

        setLocationRelativeTo(owner);
    }

    /**
     * Check how the user has left this dialog.
     * 
     * @return the result
     */
    public Result getResult() {
        return result;
    }

    /**
     * This creates a {@link JTable} that is used to map each {@link Property}
     * from the {@link ImportJob} to a internal {@link Property}
     * 
     * @param source
     *            the {@link ImportJob}
     * @return a new {@link JTable}
     */
    private JTable getMappingTable(ImportJob source) {
        MappingTableModel tableModel = new MappingTableModel(source);
        tableModels.put(source, tableModel);

        JTable table = new JTable(tableModel);
        table.setBorder(BorderFactory.createEmptyBorder());

        /*
         * PropertyDefinition column
         */
        table.getColumnModel().getColumn(MappingTableModel.PROPERTY_DEFINITION_COLUMN)
                .setCellRenderer(new PropertyDefinitionRenderer(null));
        if (sources.getDataset() == null) {
            /*
             * If we are importing Properties into a new Dataset
             */
            table.getColumnModel().getColumn(MappingTableModel.PROPERTY_DEFINITION_COLUMN)
                    .setCellEditor(new PropertyDefinitionCellEditor(this, propertyDefinitions,
                            source.getResults().getProbablyNumeric(), Collections.<PropertyDefinition>emptyList()));
        } else {
            /*
             * If we are merging Properties into an existing Dataset
             */
            Iterable<PropertyDefinition> existingPropDefs = Iterables.filter(
                    sources.getDataset().getPropertyDefinitions().values(),
                    Predicates.not(SHPredicates.IS_SCAFFOLD_PROPDEF));
            table.getColumnModel().getColumn(MappingTableModel.PROPERTY_DEFINITION_COLUMN)
                    .setCellEditor(new PropertyDefinitionCellEditor(this, propertyDefinitions,
                            source.getResults().getProbablyNumeric(), existingPropDefs));
        }

        /*
         * Merge strategy column
         */
        table.getColumnModel().getColumn(MappingTableModel.MERGE_STRATEGY_COLUMN)
                .setCellRenderer(new MergeStrategyRenderer(sources, source, null));
        table.getColumnModel().getColumn(MappingTableModel.MERGE_STRATEGY_COLUMN)
                .setCellEditor(new MergeStrategyEditor());

        table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        table.setRowHeight(table.getRowHeight() + 10);
        table.setRowMargin(5);
        table.getColumnModel().setColumnMargin(5);
        table.getTableHeader().setReorderingAllowed(false);

        /*
         * sizing the columns
         */
        table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
        for (int i = 0; i < table.getColumnCount(); i++) {
            if (i == MappingTableModel.PROPERTY_DEFINITION_COLUMN)
                table.getColumnModel().getColumn(i).setPreferredWidth(table.getColumnModel()
                        .getColumn(MappingTableModel.SOURCE_PROPERTY_COLUMN).getPreferredWidth());
            else
                packTableColumn(table, table.getColumnModel().getColumn(i));
        }
        return table;
    }

    private int packTableColumn(JTable table, TableColumn column) {
        int columnid = column.getModelIndex();
        int requiredWidth;
        TableCellRenderer renderer = column.getHeaderRenderer();
        if (renderer == null)
            renderer = table.getTableHeader().getDefaultRenderer();
        requiredWidth = renderer
                .getTableCellRendererComponent(table, column.getHeaderValue(), false, false, 0, columnid)
                .getPreferredSize().width;

        renderer = column.getCellRenderer();
        if (renderer == null)
            renderer = table.getDefaultRenderer(Object.class);

        for (int i = 0; i < table.getRowCount(); i++) {
            Object value = table.getValueAt(i, columnid);
            if (value == null)
                continue;

            int w = renderer.getTableCellRendererComponent(table, value, false, false, i, columnid)
                    .getPreferredSize().width;
            requiredWidth = Math.max(requiredWidth, w);
        }
        column.setPreferredWidth(requiredWidth);
        return requiredWidth;
    }

    private JLabel getDescriptionLabel() {
        JLabel l = new JLabel();
        // use html so we get line wrapping...
        l.setText("<html>" + (_("ImportMappings.Description") + "</html>"));
        return l;
    }

    private JPanel buildTablePanel() {
        ImmutableList.Builder<JTable> listBuilder = ImmutableList.builder();
        JPanel tablesPanel = new JPanel();
        tablesPanel.setLayout(new BoxLayout(tablesPanel, BoxLayout.Y_AXIS));

        for (ImportJob job : sources.getJobs()) {
            FormLayout layout = new FormLayout("right:p, 4dlu, 125dlu, 7dlu, right:p, 4dlu, 100dlu, 20dlu, p, p:g",
                    "p, 10dlu, p, 5dlu, p, p, p, 5dlu, p, p, 5dlu");

            PanelBuilder pb = new PanelBuilder(layout);
            pb.setDefaultDialogBorder();

            pb.addSeparator(String.format("%s (%s)", job.getJobName(), job.getPlugin().getTitle()),
                    CC.xyw(1, 1, 10));

            pb.addLabel(_("ImportMappings.MoleculeTitle"), CC.xy(1, 3));
            pb.add(getTitlePropertyComboBox(job.getTitlePropertyModel()), CC.xy(3, 3));

            pb.addLabel(_("ImportMappings.MoleculeTitleMergeStrategy"), CC.xy(5, 3));
            pb.add(getTitleMergeStrategyComboBox(job.getTitleMergeStrategyModel()), CC.xy(7, 3));
            pb.addLabel(_("ImportMappings.StructureMergeStrategy"), CC.xy(5, 5));
            pb.add(getStructureMergeStrategyComboBox(job.getStructureMergeStrategyModel()), CC.xy(7, 5));
            pb.add(new JButton(new MapAllAction(job)), CC.xy(9, 3));

            JTable table = getMappingTable(job);
            pb.add(table.getTableHeader(), CC.xyw(1, 9, 10));
            pb.add(table, CC.xyw(1, 10, 10));
            listBuilder.add(table);
            table.getModel().addTableModelListener(this);

            if (sources.getDataset() != null) {
                pb.add(getSpacedTitledSeparator(_("ImportMappings.MergeProperty")), CC.xyw(1, 6, 10));

                // merge by property
                pb.addLabel(_("ImportMappings.MergeBy"), CC.xy(1, 7));
                JComboBox mergeByComboBox = getMergeByComboBox(job, table);
                pb.add(mergeByComboBox, CC.xy(3, 7));

                // merge to property
                pb.addLabel(_("ImportMappings.MergeTo"), CC.xy(5, 7));
                JComboBox mergeToComboBox = getMergeToComboBox(job, table, mergeByComboBox);
                pb.add(mergeToComboBox, CC.xy(7, 7));
                mergeIntoModels.put(job, mergeToComboBox.getModel());

                /*
                 * the renderer of the table must be aware of the
                 * mergeByComboBox state to color the cell
                 */
                table.setDefaultRenderer(Object.class, new ActivatableTableCellRenderer(mergeByComboBox));
                table.getColumnModel().getColumn(MappingTableModel.PROPERTY_DEFINITION_COLUMN)
                        .setCellRenderer(new PropertyDefinitionRenderer(mergeByComboBox));
                table.getColumnModel().getColumn(MappingTableModel.MERGE_STRATEGY_COLUMN)
                        .setCellRenderer(new MergeStrategyRenderer(sources, job, mergeByComboBox));
            }

            JPanel panel = (pb.getPanel());
            panel.setMaximumSize(new Dimension(Integer.MAX_VALUE, panel.getPreferredSize().height));
            tablesPanel.add(panel);
        }

        tables = listBuilder.build();
        return tablesPanel;
    }

    /**
     * Constructs a titled {@link JSeparator}
     * 
     * @param label
     *            the title
     * @return the {@link JPanel} with the titled {@link JSeparator}
     */
    private JPanel getSpacedTitledSeparator(String label) {
        PanelBuilder pb = new PanelBuilder(new FormLayout("fill:pref:grow", "5dlu, bottom:pref, 3dlu"));
        pb.addSeparator(label, CC.xy(1, 2));

        return pb.getPanel();
    }

    private JComboBox getMergeByComboBox(ImportJob job, JTable table) {
        ComboBoxModel model = job.getMergeByModel();
        JComboBox box = new JComboBox(model);
        box.addActionListener(new MergeByListener(table));
        box.setRenderer(new CellRendererWithNullValue(_("DataImport.MergeByStructure")));

        box.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                updateOkState();
            }
        });

        return box;
    }

    private JComboBox getMergeToComboBox(final ImportJob job, JTable table, JComboBox mergeByCombobox) {
        Iterable<PropertyDefinition> existingPropDefsIt = Iterables.filter(
                sources.getDataset().getPropertyDefinitions().values(),
                Predicates.not(SHPredicates.IS_SCAFFOLD_PROPDEF));
        final Vector<PropertyDefinition> existingPropDefs = new Vector<PropertyDefinition>();
        Iterables.addAll(existingPropDefs, existingPropDefsIt);

        JComboBox mergeToComboBox = new JComboBox();
        final MergeToModel mergeToModel = new MergeToModel(existingPropDefs, mergeToComboBox, mergeByCombobox);
        mergeToComboBox.setModel(mergeToModel);

        mergeToComboBox.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                updateOkState();
                job.setInternalMergeBy((PropertyDefinition) mergeToModel.getSelectedItem());
            }
        });

        return mergeToComboBox;
    }

    private JComboBox getTitlePropertyComboBox(ComboBoxModel model) {
        JComboBox box = new JComboBox(model);
        box.setRenderer(new CellRendererWithNullValue(""));
        return box;
    }

    private JComboBox getTitleMergeStrategyComboBox(ComboBoxModel model) {
        JComboBox box = new JComboBox(model);
        return box;
    }

    private JComboBox getStructureMergeStrategyComboBox(ComboBoxModel model) {
        return new JComboBox(model);
    }

    private JToggleButton getPreviewToggle() {
        JToggleButton b = new JToggleButton();
        b.setText(_("ImportMappings.ShowPreview"));
        b.addItemListener(new ItemListener() {

            @Override
            public void itemStateChanged(ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    previewPanel.setVisible(true);
                    pack();
                } else {
                    previewPanel.setVisible(false);
                    pack();
                }
            }
        });
        return b;
    }

    @Override
    public void pack() {
        super.pack();
        Dimension d = getSize();
        d.height = 800;
        d.width += 50;
        setSize(d);

    }

    private void updateOkState() {
        boolean allMergePropertiesSelected = true;
        for (ComboBoxModel model : mergeIntoModels.values()) {
            if (model.getSelectedItem() == null) {
                allMergePropertiesSelected = false;
            }
        }

        okButton.setEnabled(!mergeIntoExistingDataset() || allMergePropertiesSelected);
    }

    private boolean mergeIntoExistingDataset() {
        return sources.getDataset() != null;
    }

    /**
     * The result of this dialog.
     */
    public static enum Result {
        /**
         * The dialog is still open, no result has been determined yet.
         */
        NONE,
        /**
         * The dialog has been closed by clicking cancel.
         */
        CANCELED,
        /**
         * The dialog has been closed by clicking OK.
         */
        OK
    }

    private class CancelAction extends AbstractAction {
        public CancelAction() {
            super(_("Button.Cancel"));
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            result = Result.CANCELED;
            dispose();
        }
    }

    private class OkAction extends AbstractAction {
        public OkAction() {
            super(_("Button.OK"));
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            result = Result.OK;
            dispose();
        }
    }

    private class WindowCloseListener extends WindowAdapter {
        @Override
        public void windowClosing(WindowEvent e) {
            result = Result.CANCELED;
            dispose();
        }
    }

    private class MapAllAction extends AbstractAction {

        private final ImportJob job;

        public MapAllAction(ImportJob job) {
            super(_("ImportMappings.MapUnmappedProperties"));
            this.job = job;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            job.mapAllUnmappedProperties(propertyDefinitions);
            tableModels.get(job).dataUpdated();
        }

    }

    @Override
    public void tableChanged(TableModelEvent e) {
        if (e.getColumn() == MappingTableModel.PROPERTY_DEFINITION_COLUMN
                || e.getColumn() == MappingTableModel.MERGE_STRATEGY_COLUMN)
            for (JTable table : tables)
                table.repaint();
    }

    private class PreviewPanel extends JPanel implements ListSelectionListener {

        private final Multimap<PluginResults, IAtomContainer> previewCache = HashMultimap.create();
        private final JTable table;
        private PluginResults results = null;
        private String soureProperty = "";

        public PreviewPanel() {
            super(new BorderLayout());
            setVisible(false);
            table = new JTable(PREVIEW_SIZE, 1);
            table.setEnabled(false);
            table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
            table.getColumnModel().getColumn(0).setHeaderValue("");
            table.getColumnModel().getColumn(0).setPreferredWidth(200);
            table.setPreferredScrollableViewportSize(table.getPreferredSize());
            JScrollPane sp = new JScrollPane(table);
            add(sp, BorderLayout.CENTER);
            for (JTable t : tables)
                t.getSelectionModel().addListSelectionListener(this);
        }

        public void setPreview(PluginResults results, String sourceProperty) {
            this.results = results;
            this.soureProperty = sourceProperty;
            if (isVisible())
                updatePreview();
        }

        private void updatePreview() {
            if (results != null) {
                if (!previewCache.containsKey(results)) {
                    // copy PREVIEW_SIZE molecules into the cache
                    Iterables.addAll(previewCache.get(results),
                            Iterables.limit(results.getMolecules(), PREVIEW_SIZE));
                }
                table.getColumnModel().getColumn(0).setHeaderValue(soureProperty);
                table.getTableHeader().resizeAndRepaint();
                int i = 0;
                for (IAtomContainer m : previewCache.get(results))
                    table.setValueAt(m.getProperty(soureProperty), i++, 0);
            }

        }

        @Override
        public void setVisible(boolean aFlag) {
            if (aFlag)
                updatePreview();
            super.setVisible(aFlag);
        }

        @Override
        public void valueChanged(ListSelectionEvent e) {
            DefaultListSelectionModel model = (DefaultListSelectionModel) e.getSource();
            // find the corresponding table (this or one listener per table...)
            JTable table = null;
            for (JTable t : tables) {
                if (t.getSelectionModel().equals(model)) {
                    table = t;
                    break;
                }
            }
            assert (table != null);

            if (table.getSelectedRow() == -1)
                return;
            int index = -1;
            for (int i = 0; i < tables.size(); i++)
                if (tables.get(i) != table)
                    tables.get(i).clearSelection();
                else
                    index = i;
            assert (index >= 0);
            setPreview(sources.getJobs().get(index).getResults(),
                    (String) table.getValueAt(table.getSelectedRow(), MappingTableModel.SOURCE_PROPERTY_COLUMN));
        }
    }

    private static class MergeByListener implements ActionListener {

        private final JTable table;

        MergeByListener(JTable table) {
            super();
            this.table = table;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            /*
             * Inform the PropertyDefinitionCellEditor and the table model about
             * the selected merge property
             */
            JComboBox comboBox = (JComboBox) e.getSource();
            String selected = (String) comboBox.getSelectedItem();
            MappingTableModel tableModel = (MappingTableModel) table.getModel();
            tableModel.setMappingProperty(selected);
            tableModel.dataUpdated();
        }
    }

    private class MergeToModel extends AbstractListModel implements ComboBoxModel {
        private Object mergeToProperty;
        private Vector<PropertyDefinition> existingPropDefs;
        private JComboBox mergeByCombobox;
        private JComboBox mergeToCombobox;

        /**
         * Constructor
         * 
         * @param existingPropDefs
         * @param mergeToCombobox
         * @param mergeByCombobox
         */
        @SuppressWarnings("unchecked")
        public MergeToModel(Vector<PropertyDefinition> existingPropDefs, final JComboBox mergeToCombobox,
                final JComboBox mergeByCombobox) {
            this.mergeToCombobox = mergeToCombobox;
            this.mergeByCombobox = mergeByCombobox;
            this.existingPropDefs = (Vector<PropertyDefinition>) existingPropDefs.clone();

            mergeToCombobox.setEnabled(mergeByCombobox.getSelectedIndex() != -1);
            sort();

            mergeByCombobox.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    sort();
                    mergeToCombobox.setEnabled(mergeByCombobox.getSelectedIndex() != -1);
                }
            });
        }

        /**
         * Sort the items and the selected mergeByItem to the top
         */
        private void sort() {
            /*
             * if there is an existing property definition with the same title
             * move it to the beginning of the vector
             */
            Collections.sort(existingPropDefs, Orderings.PROPERTY_DEFINITION_BY_TITLE);

            PropertyDefinition found = null;
            for (PropertyDefinition propDef : existingPropDefs) {
                Object selectedMergeByItem = mergeByCombobox.getModel().getSelectedItem();
                if (propDef.getTitle().equals(selectedMergeByItem)) {
                    found = propDef;
                    break;
                }
            }
            if (found != null) {
                existingPropDefs.remove(found);
                existingPropDefs.add(0, found);
                mergeToCombobox.setSelectedIndex(0);
            }
        }

        @Override
        public int getSize() {
            return existingPropDefs.size();
        }

        @Override
        public Object getElementAt(int index) {
            return existingPropDefs.get(index);
        }

        @Override
        public void setSelectedItem(Object anItem) {
            mergeToProperty = (anItem != null ? anItem : null);
        }

        @Override
        public Object getSelectedItem() {
            return mergeToProperty;
        }
    }
}