org.tellervo.desktop.io.view.ImportView.java Source code

Java tutorial

Introduction

Here is the source code for org.tellervo.desktop.io.view.ImportView.java

Source

/*******************************************************************************
 * Copyright (C) 2011 Peter Brewer.
 * 
 * This program 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.
 * 
 * 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/>.
 * 
 * Contributors:
 *     Peter Brewer
 ******************************************************************************/
package org.tellervo.desktop.io.view;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JTextPane;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.border.EmptyBorder;
import javax.swing.table.TableColumn;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

import jsyntaxpane.DefaultSyntaxKit;

import org.netbeans.swing.outline.DefaultOutlineModel;
import org.netbeans.swing.outline.Outline;
import org.netbeans.swing.outline.OutlineModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tellervo.desktop.core.App;
import org.tellervo.desktop.gui.widgets.TridasEntityPickerDialog;
import org.tellervo.desktop.gui.widgets.TridasEntityPickerPanel;
import org.tellervo.desktop.gui.widgets.TridasTreeViewPanel;
import org.tellervo.desktop.gui.widgets.TridasEntityPickerPanel.EntitiesAccepted;
import org.tellervo.desktop.io.ConversionWarningTableModel;
import org.tellervo.desktop.io.LineHighlighter;
import org.tellervo.desktop.io.control.ExpandImportTreeEvent;
import org.tellervo.desktop.io.control.FileSelectedEvent;
import org.tellervo.desktop.io.control.ImportEntitySaveEvent;
import org.tellervo.desktop.io.control.ImportMergeEntitiesEvent;
import org.tellervo.desktop.io.control.ImportNodeSelectedEvent;
import org.tellervo.desktop.io.control.ImportSwapEntityEvent;
import org.tellervo.desktop.io.control.ReplaceHierarchyEvent;
import org.tellervo.desktop.io.model.ImportEntityListComboBox;
import org.tellervo.desktop.io.model.ImportModel;
import org.tellervo.desktop.io.model.TridasRepresentationTableTreeRow;
import org.tellervo.desktop.io.model.TridasRepresentationTreeModel;
import org.tellervo.desktop.io.model.TridasRepresentationTableTreeRow.ImportStatus;
import org.tellervo.desktop.model.TellervoModelLocator;
import org.tellervo.desktop.tridasv2.ui.TellervoPropertySheetPanel;
import org.tellervo.desktop.tridasv2.ui.TellervoPropertySheetTable;
import org.tellervo.desktop.tridasv2.ui.TridasPropertyEditorFactory;
import org.tellervo.desktop.tridasv2.ui.TridasPropertyRendererFactory;
import org.tellervo.desktop.tridasv2.ui.support.TridasEntityDeriver;
import org.tellervo.desktop.tridasv2.ui.support.TridasEntityProperty;
import org.tellervo.desktop.ui.Alert;
import org.tellervo.desktop.ui.Builder;
import org.tellervo.desktop.ui.FilterableComboBoxModel;
import org.tellervo.desktop.ui.I18n;
import org.tellervo.desktop.util.PopupListener;
import org.tridas.interfaces.ITridas;
import org.tridas.interfaces.ITridasSeries;
import org.tridas.io.enums.Charsets;
import org.tridas.io.exceptions.ConversionWarning;
import org.tridas.io.exceptions.InvalidDendroFileException;
import org.tridas.io.exceptions.InvalidDendroFileException.PointerType;
import org.tridas.schema.NormalTridasVariable;
import org.tridas.schema.TridasMeasurementSeries;
import org.tridas.schema.TridasObject;
import org.tridas.schema.TridasProject;
import org.tridas.schema.TridasRadius;
import org.tridas.schema.TridasSample;
import org.tridas.schema.TridasValue;
import org.tridas.schema.TridasValues;
import org.tridas.schema.TridasVariable;
import org.tridas.util.TridasObjectEx;

import com.dmurph.mvc.MVC;
import com.l2fprod.common.propertysheet.Property;
import com.l2fprod.common.propertysheet.PropertySheet;
import com.l2fprod.common.propertysheet.PropertySheetPanel;
import com.lowagie.text.Font;

import net.miginfocom.swing.MigLayout;
import javax.swing.JComboBox;

public class ImportView extends JFrame {

    private final static Logger log = LoggerFactory.getLogger(ImportView.class);

    private static final long serialVersionUID = -9142222993569420620L;

    /** The MVC Import Model **/
    private ImportModel model;

    /** Main panel into which everything is placed **/
    private final JPanel contentPanel = new JPanel();

    /** Panel and tree-table (outline) for displaying tridas representation of file **/
    private JPanel panelTreeTable;
    private Outline treeTable;

    /** Panel and text area for displaying original legacy file **/
    private JPanel panelOrigFile;

    /** Panel for displaying ring with data**/
    private JPanel panelData;

    /** Panel and table for displaying conversion warnings **/
    private JPanel panelWarnings;
    private JTable tblWarnings;

    /** Tabbed pane for original file, metadata and data tabs**/
    private JTabbedPane tabbedPane;

    /** Left/Right split pane**/
    private JSplitPane horizSplitPane;

    /** Top/Bottom split pane **/
    private JSplitPane vertSplitPane;

    /** Our property sheet panel (contains table and description) */
    private TellervoPropertySheetPanel propertiesPanel;

    /** Our properties table */
    private TellervoPropertySheetTable propertiesTable;

    /** Panel containing the edit/save changes/cancel buttons for the current entity */
    private JPanel bottombar;

    /** Panel containing combo box and buttons for selecting entities from db*/
    private JPanel topbar;

    /** The lock/unlock button for making changes to the currently selected entity */
    private JToggleButton editEntity;

    /** The save button when unlocked */
    private JButton editEntitySave;

    /** The cancel button when unlocked */
    private JButton editEntityCancel;

    /** Text associated with lock/unlock button */
    private JLabel editEntityText;

    /** Labels **/
    private JLabel lblConversionWarnings;
    private JLabel lblTridasRepresentationOf;
    private JLabel lblTopBarSummaryTitle;

    /** Finish button **/
    private JButton btnFinish;

    /** Set from db button **/
    private JButton btnMergeObjects;
    private JButton btnExpandNodes;
    private JButton btnContractNodes;
    private JButton btnAssignByCode;
    private JButton btnReparseFile;

    private JTextPane txtErrorMessage;
    private ImportEntityListComboBox topChooser;
    private JButton changeButton;
    private JButton cancelChangeButton;

    private static final String CHANGE_STATE = I18n.getText("general.change");
    private static final String CHOOSE_STATE = I18n.getText("general.choose");
    private boolean changingTop;
    private ChoiceComboBoxActionListener topChooserListener;
    private JEditorPane dataPane;
    private JScrollPane scrollPane_1;
    private JComboBox cboEncoding;
    private JLabel lblCharacterEncoding;

    /**
     * Standard constructor for Import Dialog with no file provided
     */
    public ImportView() {

        topChooserListener = new ChoiceComboBoxActionListener(this);
        model = TellervoModelLocator.getInstance().getImportModel();
        MVC.showEventMonitor();
        initComponents();
        linkModel();
        initListeners();
        initLocale();
    }

    /**
     * Constructor with file already specified
     * @param file
     */
    public ImportView(File file, String fileType) {
        if (usingOldImporter(file, fileType))
            return;

        topChooserListener = new ChoiceComboBoxActionListener(this);
        model = TellervoModelLocator.getInstance().getImportModel();
        //MVC.showEventMonitor();
        initComponents();
        linkModel();
        initListeners();
        initLocale();
        FileSelectedEvent event = new FileSelectedEvent(model, file, fileType);
        event.dispatch();
    }

    /**
     * 
     * @param file
     * @param fileType
     * @return
     */
    private Boolean usingOldImporter(File file, String fileType) {

        return false;
    }

    /**
     * Initialise the GUI components
     */
    private void initComponents() {
        DefaultSyntaxKit.initKit();

        setTitle("Import to Database");
        setBounds(100, 100, 804, 734);
        getContentPane().setLayout(new BorderLayout(0, 0));
        contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
        getContentPane().add(contentPanel);
        {

        }
        contentPanel.setLayout(new BorderLayout(0, 0));
        {
            vertSplitPane = new JSplitPane();
            vertSplitPane.setOneTouchExpandable(true);
            vertSplitPane.setOrientation(JSplitPane.VERTICAL_SPLIT);
            contentPanel.add(vertSplitPane);
            {
                panelWarnings = new JPanel();
                panelWarnings.setBorder(new EmptyBorder(5, 5, 5, 5));
                vertSplitPane.setRightComponent(panelWarnings);
                panelWarnings.setLayout(new BorderLayout(0, 8));
                {
                    lblConversionWarnings = new JLabel("Conversion warnings:");
                    panelWarnings.add(lblConversionWarnings, BorderLayout.NORTH);
                }
                {
                    JScrollPane scrollPane = new JScrollPane();
                    lblConversionWarnings.setLabelFor(scrollPane);
                    panelWarnings.add(scrollPane);
                    {
                        tblWarnings = new JTable();
                        scrollPane.setViewportView(tblWarnings);
                    }
                }
            }
            horizSplitPane = new JSplitPane();
            vertSplitPane.setLeftComponent(horizSplitPane);
            horizSplitPane.setOneTouchExpandable(true);
            horizSplitPane.setBorder(null);
            horizSplitPane.setDividerLocation(0.5);
            tabbedPane = new JTabbedPane(JTabbedPane.TOP);
            tabbedPane.setBorder(null);
            horizSplitPane.setRightComponent(tabbedPane);
            {
                JPanel panelMetadata = new JPanel();
                panelMetadata.setLayout(new BorderLayout());

                topbar = new JPanel();
                bottombar = new JPanel();
                topbar.setLayout(new BoxLayout(topbar, BoxLayout.X_AXIS));
                bottombar.setLayout(new BoxLayout(bottombar, BoxLayout.X_AXIS));
                ////////// TOP BAR
                lblTopBarSummaryTitle = new JLabel("");
                topChooser = new ImportEntityListComboBox();
                changeButton = new JButton(CHANGE_STATE);
                cancelChangeButton = new JButton(I18n.getText("general.revert"));
                topbar.add(lblTopBarSummaryTitle);
                topbar.add(Box.createHorizontalStrut(6));
                topbar.add(topChooser);
                topbar.add(Box.createHorizontalStrut(6));
                topbar.add(changeButton);
                topbar.add(Box.createHorizontalStrut(6));
                topbar.add(cancelChangeButton);
                topbar.add(Box.createHorizontalGlue());
                topbar.setBorder(BorderFactory.createEmptyBorder(2, 8, 2, 8));

                changingTop = false;
                cancelChangeButton.setVisible(false);
                panelMetadata.add(topbar, BorderLayout.NORTH);

                ////////// BOTTOM BAR
                editEntity = new JToggleButton();
                editEntity.setIcon(scaleIcon20x20((ImageIcon) Builder.getIcon("lock.png", Builder.ICONS, 32)));
                editEntity.setSelectedIcon(
                        scaleIcon20x20((ImageIcon) Builder.getIcon("unlock.png", Builder.ICONS, 32)));
                editEntity.setBorderPainted(false);
                editEntity.setContentAreaFilled(false);
                editEntity.setFocusable(false);

                bottombar.add(editEntity);

                editEntityText = new JLabel(I18n.getText("general.initializing").toLowerCase());
                editEntityText.setLabelFor(editEntity);

                editEntitySave = new JButton(I18n.getText("general.saveChanges"));
                editEntityCancel = new JButton(I18n.getText("general.cancel"));

                // don't let an errant enter key fire these buttons!
                editEntitySave.setDefaultCapable(false);
                editEntityCancel.setDefaultCapable(false);

                bottombar.add(editEntityText);
                bottombar.add(Box.createHorizontalGlue());
                bottombar.add(editEntitySave);
                bottombar.add(Box.createHorizontalStrut(6));
                bottombar.add(editEntityCancel);
                bottombar.add(Box.createHorizontalStrut(6));

                panelMetadata.add(bottombar, BorderLayout.SOUTH);

                // Create metadata table and panel to hold it
                propertiesTable = new TellervoPropertySheetTable();
                propertiesPanel = new TellervoPropertySheetPanel(propertiesTable);
                //propertiesPanel.getTable().setEnabled(false);

                // Set various properties of the properties panel!
                propertiesPanel.setRestoreToggleStates(true);
                propertiesPanel.setToolBarVisible(false);
                propertiesPanel.setDescriptionVisible(true);
                propertiesPanel.setMode(PropertySheet.VIEW_AS_CATEGORIES);
                propertiesPanel.getTable().setRowHeight(24);
                propertiesPanel.getTable().setRendererFactory(new TridasPropertyRendererFactory());
                propertiesPanel.getTable().setEditorFactory(new TridasPropertyEditorFactory());

                panelMetadata.add(propertiesPanel, BorderLayout.CENTER);
                tabbedPane.addTab("Extracted TRiDaS Metadata", null, panelMetadata, null);

            }
            {
                panelData = new JPanel();
                tabbedPane.addTab("Extracted Data", null, panelData, null);
                panelData.setLayout(new BorderLayout(0, 0));

                scrollPane_1 = new JScrollPane();
                panelData.add(scrollPane_1);

                dataPane = new JEditorPane();
                dataPane.setEditable(false);
                scrollPane_1.setViewportView(dataPane);
                tabbedPane.setEnabledAt(1, false);
            }
            panelOrigFile = new JPanel();
            tabbedPane.insertTab("Original file", null, panelOrigFile, null, 0);
            panelOrigFile.setLayout(new MigLayout("", "[159.00px][173.00][grow]", "[fill][][707px,grow,fill]"));

            cboEncoding = new JComboBox();
            lblCharacterEncoding = new JLabel("Character encoding:");
            panelOrigFile.add(lblCharacterEncoding, "flowx,cell 0 1,alignx left");
            for (String s : Charsets.getReadingCharsets()) {
                if (s.equals(Charset.defaultCharset().displayName())) {
                    continue;
                }
                cboEncoding.addItem(s);
            }

            panelOrigFile.add(cboEncoding, "cell 1 1 2 1,alignx left");
            {

            }

            txtErrorMessage = new JTextPane();
            txtErrorMessage.setForeground(Color.RED);
            txtErrorMessage.setVisible(false);
            panelOrigFile.add(txtErrorMessage, "cell 0 0 3 1,growx,aligny top");
            {
                panelTreeTable = new JPanel();
                panelTreeTable.setBorder(new EmptyBorder(5, 5, 5, 5));
                horizSplitPane.setLeftComponent(panelTreeTable);
                panelTreeTable.setLayout(new BorderLayout(0, 0));
                {
                    //btnMergeObjects = new JButton("Merge objects together");
                    //panelTreeTable.add(btnMergeObjects, BorderLayout.SOUTH);
                }
                {
                    lblTridasRepresentationOf = new JLabel("TRiDaS representation of file:");
                    panelTreeTable.add(lblTridasRepresentationOf, BorderLayout.NORTH);

                    treeTable = new Outline();

                    // Set the renderdata
                    treeTable.setRenderDataProvider(new TridasOutlineRenderData());

                    // Add tree to scroll pane
                    JScrollPane treeTableScrollPane = new JScrollPane();
                    treeTableScrollPane.setViewportView(treeTable);
                    panelTreeTable.add(treeTableScrollPane, BorderLayout.CENTER);

                }
            }
        }
        {
            JPanel buttonPanel = new JPanel();
            buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
            getContentPane().add(buttonPanel, BorderLayout.SOUTH);
            {
                btnFinish = new JButton("Finish");
                buttonPanel.add(btnFinish);
            }
        }
        pack();
        //this.setSize(new Dimension(800,600));
        vertSplitPane.setDividerLocation(0.8);
        horizSplitPane.setDividerLocation(0.5);
        setExtendedState(getExtendedState() | Frame.MAXIMIZED_BOTH);
        setIconImage(Builder.getApplicationIcon());

        chooseOrCancelUIUpdate();
        enableEditing(false);
        updateConversionWarningsTable();

        initToolbar();

    }

    /**
     * Set up the toolbar
     */
    private void initToolbar() {
        JToolBar toolBar = new JToolBar("Toolbar");

        btnMergeObjects = new JButton("Merge all objects");
        btnMergeObjects.setToolTipText("Merge all objects");
        btnMergeObjects.setIcon(Builder.getIcon("merge.png", 22));
        toolBar.add(btnMergeObjects);

        btnExpandNodes = new JButton("Expand nodes");
        btnExpandNodes.setToolTipText("Expand nodes");
        btnExpandNodes.setIcon(Builder.getIcon("view_tree.png", 22));
        toolBar.add(btnExpandNodes);

        btnContractNodes = new JButton("Contract nodes");
        btnContractNodes.setToolTipText("Contract nodes");
        btnContractNodes.setIcon(Builder.getIcon("contract_tree.png", 22));
        toolBar.add(btnContractNodes);

        btnAssignByCode = new JButton("Assign by code");
        btnAssignByCode.setToolTipText("Assign by code");
        btnAssignByCode.setIcon(Builder.getIcon("barcode.png", 22));
        toolBar.add(btnAssignByCode);

        btnReparseFile = new JButton("Reparse file");
        btnReparseFile.setToolTipText("Reparse file");
        btnReparseFile.setIcon(Builder.getIcon("reload.png", 22));
        toolBar.add(btnReparseFile);

        getContentPane().add(toolBar, BorderLayout.PAGE_START);
    }

    /**
     * Link to MVC Model so that we can update the GUI when events are detected
     */
    private void linkModel() {
        model.addPropertyChangeListener(new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                String name = evt.getPropertyName();

                if (name.equals(ImportModel.ORIGINAL_FILE)) {
                    updateOriginalFile(null);
                } else if (name.equals(ImportModel.CONVERSION_WARNINGS)) {
                    // New conversion warnings so set the table
                    updateConversionWarningsTable();
                } else if (name.equals(ImportModel.SELECTED_ROW)) {
                    // An entity in the tree-table has been selected so 
                    // update the metadata panel

                    // Check that the tree table is expanded to show the selected node
                    int selRow = treeTable.getSelectedRow();
                    DefaultMutableTreeNode guiSelectedEntity = (DefaultMutableTreeNode) treeTable.getValueAt(selRow,
                            0);
                    DefaultMutableTreeNode modelSelectedEntity = model.getSelectedRow().getDefaultMutableTreeNode();
                    if ((guiSelectedEntity == null) || (!guiSelectedEntity.equals(modelSelectedEntity))) {
                        TreePath path = getPath(modelSelectedEntity);
                        treeTable.getOutlineModel().getTreePathSupport().expandPath(path);
                    }

                    // Set up entity model listeners
                    linkRowModel();

                    // Update the metadata panel
                    updateMetadataPanel();
                    updateEntityChooser();

                    // Enable/disable assign by code button depending on selected entity type

                    if ((modelSelectedEntity.getUserObject() instanceof TridasMeasurementSeries)
                            || (modelSelectedEntity.getUserObject() instanceof TridasSample)) {
                        btnAssignByCode.setEnabled(true);
                    } else {
                        btnAssignByCode.setEnabled(false);
                    }

                } else if (name.equals(ImportModel.TREE_MODEL)) {
                    // Tree model has changed so update the tree-table
                    updateTreeTable();
                } else if (name.equals(ImportModel.INVALID_FILE_EXCEPTION)) {
                    setGUIForInvalidFile();
                }

            }

        });
    }

    private void linkRowModel() {

        model.getSelectedRow().addPropertyChangeListener(new PropertyChangeListener() {

            public void propertyChange(PropertyChangeEvent evt) {
                String name = evt.getPropertyName();

                if (name.equals(TridasRepresentationTableTreeRow.CURRENT_ENTITY)) {
                    propertiesPanel.readFromObject(model.getSelectedRow().getCurrentEntity());
                }

            }
        });
    }

    /**
     * Set up the MVC listeners for dispatching events from this GUI
     */
    private void initListeners() {
        final Component glue = (Component) this;
        final JFrame glue2 = this;

        // Listen to entities being selected in tree table
        this.treeTable.addMouseListener(new PopupListener() {

            @Override
            public void mouseClicked(MouseEvent e) {
                selectCurrentEntity();
            }

            @Override
            public void showPopup(MouseEvent e) {
                log.debug("showPopup called");

                int selRow = treeTable.getSelectedRow();
                DefaultMutableTreeNode selectedEntity = (DefaultMutableTreeNode) treeTable.getValueAt(selRow, 0);
                showPopupMenu((JComponent) e.getSource(), e.getX(), e.getY(), selectedEntity);

            }
        });

        // Listen for set from db button press
        this.btnFinish.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {

                Integer outstanding = model.getCountOutstandingEntities();
                String plural = "y that has";
                if (outstanding > 1)
                    plural = "ies that have";

                if (outstanding > 0) {
                    Object[] options = { "Yes", "No", "Cancel" };
                    int n = JOptionPane.showOptionDialog(glue,
                            "You still have " + outstanding + " entit" + plural + " not been imported.\n"
                                    + "Are you sure you want to close the import dialog?",
                            "Outstanding entities", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE,
                            null, options, options[2]);

                    if (n == JOptionPane.YES_OPTION)
                        dispose();

                } else {
                    dispose();
                }

            }
        });

        this.changeButton.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                if (changeButton.getText().equals(CHANGE_STATE)) {
                    changingTop = true;
                } else if (changeButton.getText().equals(CHOOSE_STATE)) {
                    changingTop = false;
                    propertiesTable.setHasWatermark(false);
                    TridasRepresentationTableTreeRow oldrow = model.getSelectedRow();
                    DefaultMutableTreeNode node = new DefaultMutableTreeNode(
                            (ITridas) topChooser.getSelectedItem());
                    TridasRepresentationTableTreeRow newrow = new TridasRepresentationTableTreeRow(node,
                            ImportStatus.STORED_IN_DATABASE);
                    ImportSwapEntityEvent event = new ImportSwapEntityEvent(model, newrow, oldrow);
                    event.dispatch();

                }
                chooseOrCancelUIUpdate();
            }
        });

        this.cancelChangeButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                changingTop = false;
                chooseOrCancelUIUpdate();
            }
        });

        editEntity.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {

                if (editEntity.isSelected()) {
                    log.debug("Starting to edit entity");
                } else {
                    log.debug("Stopping entity edit");
                }

                enableEditing(editEntity.isSelected());
            }
        });

        editEntitySave.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                // TODO Need to replace 'null' below with this dialog

                saveChanges();

            }
        });

        editEntityCancel.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {

                if (isUserOkWithLoosingAnyChanges()) {
                    model.getSelectedRow().revertChanges();
                    editEntity.setSelected(false);
                    enableEditing(false);
                    selectCurrentEntity();
                }
            }
        });

        btnMergeObjects.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                ImportMergeEntitiesEvent event = new ImportMergeEntitiesEvent(model, TridasObject.class);
                event.dispatch();
            }
        });

        btnExpandNodes.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                ExpandImportTreeEvent e2 = new ExpandImportTreeEvent(true, model.getTreeModel(), treeTable);
                e2.dispatch();
            }
        });

        btnContractNodes.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                ExpandImportTreeEvent e2 = new ExpandImportTreeEvent(false, model.getTreeModel(), treeTable);
                e2.dispatch();
            }
        });

        btnAssignByCode.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                DefaultMutableTreeNode modelSelectedEntity = model.getSelectedRow().getDefaultMutableTreeNode();
                updateHierarchyByCodeDialog(modelSelectedEntity, glue2);
            }
        });

        btnReparseFile.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {

                //default icon, custom title
                int n = JOptionPane
                        .showConfirmDialog(glue2,
                                "Any changes you've not committed to the database will be lost.\n"
                                        + "Are you sure you want to continue?",
                                "Confirm", JOptionPane.YES_NO_OPTION);

                if (n == JOptionPane.YES_OPTION) {

                    File file = null;
                    String fileType = null;
                    try {
                        file = model.getFileToImport();
                        fileType = model.getFileType();
                    } catch (Exception e2) {
                        Alert.error("Error", "Unable to reparse file: " + e2.getLocalizedMessage());
                    }
                    FileSelectedEvent event = new FileSelectedEvent(model, file, fileType);
                    event.dispatch();
                }

            }
        });

    }

    private void selectCurrentEntity() {
        // Check the user isn't part way through changing this entity
        if (!isUserOkWithLoosingAnyChanges())
            return;

        if (changingTop) {
            changingTop = false;
            chooseOrCancelUIUpdate();
        }
        int selRow = treeTable.getSelectedRow();
        DefaultMutableTreeNode selectedEntity = (DefaultMutableTreeNode) treeTable.getValueAt(selRow, 0);
        ImportStatus status = (ImportStatus) treeTable.getValueAt(selRow, 1);
        TridasRepresentationTableTreeRow row = new TridasRepresentationTableTreeRow(selectedEntity, status);
        ImportNodeSelectedEvent event = new ImportNodeSelectedEvent(model, row);
        event.dispatch();
        model.setSelectedRow(row);
    }

    private void saveChanges() {
        ITridas entity = model.getSelectedRow().getCurrentEntity();
        propertiesPanel.writeToObject(entity);
        log.debug("About to save entity " + entity.getTitle());

        if (entity instanceof TridasMeasurementSeries) {
            TridasMeasurementSeries en = (TridasMeasurementSeries) entity;
            if (en.isSetValues()) {
                if (en.getValues().get(0).isSetVariable()) {
                    TridasVariable variable = new TridasVariable();
                    variable.setNormalTridas(NormalTridasVariable.RING_WIDTH);
                    en.getValues().get(0).setVariable(variable);
                }
            }

            model.getSelectedRow().setCurrentEntity(en);
        }

        ImportEntitySaveEvent event = new ImportEntitySaveEvent(model, null);
        event.dispatch();
    }

    /**
     * Localise the GUI components
     */
    private void initLocale() {

    }

    protected void showPopupMenu(JComponent source, int x, int y, final DefaultMutableTreeNode selectedEntity) {
        // define the popup
        JPopupMenu popupMenu = new JPopupMenu();
        final JFrame glue = this;

        if (selectedEntity.getUserObject() instanceof TridasMeasurementSeries) {
            JMenuItem setHierarchyMenu = new JMenuItem("Set hierarchy by labcode");

            setHierarchyMenu.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent arg0) {

                    updateHierarchyByCodeDialog(selectedEntity, glue);

                }

            });

            popupMenu.add(setHierarchyMenu);
            popupMenu.setOpaque(true);
            popupMenu.setLightWeightPopupEnabled(false);

            popupMenu.show(source, x, y);
        }
    }

    private void updateHierarchyByCodeDialog(DefaultMutableTreeNode selectedEntity, JFrame glue) {

        ITridas newParent = null;
        if ((selectedEntity.getUserObject() instanceof TridasMeasurementSeries)) {

            newParent = TridasEntityPickerDialog.pickEntity(glue, "Select entity", ITridasSeries.class,
                    EntitiesAccepted.SPECIFIED_ENTITY_UP_TO_PROJECT);

            log.debug("User wants to set hierarchy of " + ((ITridas) selectedEntity.getUserObject()).getTitle()
                    + " using labcode for " + newParent.getTitle());
        } else if ((selectedEntity.getUserObject() instanceof TridasSample)) {

            newParent = TridasEntityPickerDialog.pickSpecificEntity(this, "Select entity", TridasSample.class);

            log.debug("User wants to set hierarchy of " + ((ITridas) selectedEntity.getUserObject()).getTitle()
                    + " using labcode for " + newParent.getTitle());
        }

        if (newParent == null)
            return;

        ReplaceHierarchyEvent event = new ReplaceHierarchyEvent(model, glue, selectedEntity, newParent);
        event.dispatch();

        ExpandImportTreeEvent e2 = new ExpandImportTreeEvent(true, model.getTreeModel(), treeTable);
        e2.dispatch();

        return;
    }

    /**
     * Set up the conversion warnings table with the provided warnings
     * @param warnings
     */
    private void updateConversionWarningsTable() {
        ConversionWarning[] warnings = model.getConversionWarnings();
        if (warnings == null) {
            vertSplitPane.setDividerLocation(1.0);
            return;
        }
        if (warnings.length == 0) {
            vertSplitPane.setDividerLocation(1.0);
            return;
        }

        ConversionWarningTableModel cwModel = new ConversionWarningTableModel(warnings);
        tblWarnings.setModel(cwModel);
        lblConversionWarnings.setText("Conversion warnings (" + warnings.length + "):");
        TableColumn col = tblWarnings.getColumnModel().getColumn(0);
        int width = 70;
        col.setMinWidth(width);
        col.setPreferredWidth(width);
        col = tblWarnings.getColumnModel().getColumn(1);
        col.setMinWidth(width);
        col.setPreferredWidth(width);
        col = tblWarnings.getColumnModel().getColumn(2);
        col.setMinWidth(width);
        col.setPreferredWidth(5000);

        vertSplitPane.setDividerLocation(0.8);
    }

    /**
     * Update the data panel to show the ring width values
     * 
     * @param row
     */
    private void updateDataPanel() {

        ITridas entity = (ITridas) model.getSelectedRow().getDefaultMutableTreeNode().getUserObject();

        if (!(entity instanceof TridasMeasurementSeries)) {
            // Not a series

            if (tabbedPane.getSelectedIndex() == 2) {
                // Select the metadata tab if currently on the data tab
                tabbedPane.setSelectedIndex(1);
            }

            // Disable the data tab 
            tabbedPane.setEnabledAt(2, false);
            return;
        }

        TridasMeasurementSeries series = (TridasMeasurementSeries) entity;

        log.debug("Values group count = " + series.getValues().size());
        log.debug("Value count = " + series.getValues().get(0).getValues().size());

        TridasVariable var = new TridasVariable();
        var.setNormalTridas(NormalTridasVariable.RING_WIDTH);

        TridasValues seriesGroup = series.getValues().get(0);
        seriesGroup.setVariable(var);

        List<TridasValue> values = seriesGroup.getValues();
        ArrayList<Number> valuesint = new ArrayList<Number>();
        ArrayList<Integer> countint = new ArrayList<Integer>();
        StringBuilder datavals = new StringBuilder();

        if (seriesGroup.isSetUnit()) {
            if (seriesGroup.getUnit().isSetNormalTridas()) {
                datavals.append("Units=" + seriesGroup.getUnit().getNormalTridas().toString() + "\n\n");
            }
        }

        for (TridasValue v : values) {
            valuesint.add(Integer.valueOf(v.getValue()));
            countint.add(v.getCount());
            datavals.append(v.getValue() + "\n");
        }

        dataPane.setText(datavals.toString());

        //panelData.setLayout(new BorderLayout());
        //panelData.add(dataPane, BorderLayout.CENTER);
        tabbedPane.setEnabledAt(2, true);
    }

    /**
     * Set the metadata panel to show the provided entity
     * @param parentNode 
     * @param entity
     */
    private void updateMetadataPanel() {

        if (!isUserOkWithLoosingAnyChanges()) {
            return;
        }

        // Enable or disable the data page depending on current entity type
        updateDataPanel();

        // Set the text in the top bar
        //setTopBarSummaryTitle(parentNode);

        // Extract the entity and entity type from the select table tree row
        ITridas currentEntity = model.getSelectedRow().getCurrentEntity();
        Class<? extends ITridas> currentEntityType = model.getSelectedRow().getCurrentEntityClass();

        // Extract the parent entity 
        try {
            ITridas parentEntity = model.getSelectedRow().getParentEntity();
            log.debug("Set parent entity successfully to: " + parentEntity.getTitle());
        } catch (Exception e) {
            log.warn("Unable to set parent entity");
        }

        // Swapping entities so disable editing
        //enableEditing(false);   

        // Derive a property list
        List<TridasEntityProperty> properties = TridasEntityDeriver.buildDerivationList(currentEntityType);
        Property[] propArray = properties.toArray(new Property[properties.size()]);

        // Set properties for current entity type
        propertiesPanel.setProperties(propArray);
        propertiesTable.expandAllBranches(true);

        // Add data to table from entity
        if (currentEntity != null) {
            propertiesPanel.readFromObject(currentEntity);
            propertiesPanel.setEnabled(true);
            editEntity.setVisible(true);
        } else {
            propertiesPanel.setEnabled(false);
            editEntity.setVisible(false);
        }

        // Set the watermark text across metadata panel
        if (model.getSelectedRow().getImportStatus().equals(ImportStatus.PENDING)) {
            propertiesTable.setHasWatermark(true);
            propertiesTable.setWatermarkText(I18n.getText("general.preview").toUpperCase());
        } else if (model.getSelectedRow().getImportStatus().equals(ImportStatus.IGNORE)) {
            propertiesTable.setHasWatermark(true);
            propertiesTable.setWatermarkText("IGNORED");
        } else {
            propertiesTable.setHasWatermark(false);
        }

        // Update the combo box list
        //ImportEntityListChangedEvent ev = new ImportEntityListChangedEvent(model.getCurrentEntityModel(), currentEntity, parentEntity);
        //ev.dispatch();

        // Set the top chooser to the correct current value
        selectInTopChooser();

    }

    /**
     * Called by either button press to update the UI state
     */
    private void chooseOrCancelUIUpdate() {
        // disable/enable editing
        editEntity.setEnabled(!changingTop);
        topChooser.setEnabled(changingTop);
        changeButton.setText(changingTop ? CHOOSE_STATE : CHANGE_STATE);
        cancelChangeButton.setVisible(changingTop);
        //propertiesTable.setEditable(changingTop);

    }

    /**
     * Update the entity chooser combo box based on the current entity
     * and its parent
     * 
     * @param thisEntity
     * @param parentEntity
     */
    private void updateEntityChooser() {
        List<? extends ITridas> entities = model.getSelectedRow().getEntityList();
        topChooser.setList(entities);
        ITridas currentEntity = model.getSelectedRow().getCurrentEntity();
        ITridas parentEntity = model.getSelectedRow().getParentEntity();

        if (currentEntity instanceof TridasProject) {
            // This entity is a project so set list to null as 
            // projects aren't supported
            setTopChooserEnabled(false, "Projects are not supported in Tellervo");
            return;
        } else if (currentEntity instanceof TridasObject) {
            // This entity is an object so grab object dictionary
            setTopChooserEnabled(true, null);
            return;
        }

        // Otherwise, check that the parent is already in db by checking
        // the identifier domain and set list accordingly 
        try {
            if (parentEntity.getIdentifier().getDomain().equals(App.domain)) {
                if (currentEntity instanceof TridasMeasurementSeries) {
                    setTopChooserEnabled(false, null);
                    return;
                } else {
                    setTopChooserEnabled(true, null);
                    return;
                }
            } else {
                setTopChooserEnabled(false, "Fix parent entity first");
            }
        } catch (Exception e) {
            setTopChooserEnabled(false, "Fix parent entity first");
            return;
        }

        this.topbar.repaint();
    }

    /**
     * Set whether the top chooser combo box panel is enabled or not
     * 
     * @param b
     * @param message
     */
    private void setTopChooserEnabled(Boolean b, String message) {
        topChooser.setVisible(b);
        changeButton.setVisible(b);
        lblTopBarSummaryTitle.setText(message);
    }

    /**
     * Set the tree-table panel to show the data specified in the 
     * provided treeModel.
     * @param treeModel
     */
    private void updateTreeTable() {

        TridasRepresentationTreeModel treeModel = model.getTreeModel();

        OutlineModel mdl = DefaultOutlineModel.createOutlineModel(treeModel, treeModel, true, "TRiDaS Entities");

        this.treeTable.setModel(mdl);
        ExpandImportTreeEvent e2 = new ExpandImportTreeEvent(true, model.getTreeModel(), treeTable);
        e2.dispatch();
    }

    /**
     * Update the GUI to show the original legacy file.  If the highlightline 
     * integer is included then this line will be highlighted for the user.
     * Typically used to show an error.
     * 
     * @param highlightline
     */
    private void updateOriginalFile(Integer highlightline) {
        final JEditorPane txtOriginalFile = new JEditorPane();
        JScrollPane scrollPane = new JScrollPane(txtOriginalFile);
        try {
            panelOrigFile.remove(3);
        } catch (Exception e) {
        }
        panelOrigFile.add(scrollPane, "cell 0 2 3 1,grow");

        txtOriginalFile.setEditable(false);
        txtOriginalFile.setFont(new java.awt.Font("Courier", 0, 12));

        txtOriginalFile.setContentType("text/xml");

        // Original file has changed so update GUI to show its contents
        String contents = model.getFileToImportContents();
        if (contents != null) {
            txtOriginalFile.setText(contents);
        }

        if (highlightline != null) {
            try {
                txtOriginalFile.addCaretListener(new LineHighlighter(Color.red, highlightline));
                this.tabbedPane.setSelectedIndex(0);
                int pos = 1;
                for (int i = 0; i < highlightline; i++) {
                    pos = txtOriginalFile.getText().indexOf("\n", pos + 1);
                }

                txtOriginalFile.setCaretPosition(pos);
            } catch (NumberFormatException ex) {
            }
        }
    }

    /**
     * Set the GUI to illustrate an invalid file
     * @param fileException
     */
    private void setGUIForInvalidFile() {

        InvalidDendroFileException e = model.getFileException();

        if (e == null)
            return;

        String error = e.getLocalizedMessage();
        // Try and highlight the line in the input file that is to blame if poss
        if (e.getPointerType().equals(PointerType.LINE) && e.getPointerNumber() != null
                && !e.getPointerNumber().equals("0")) {
            Integer line = null;
            try {
                line = Integer.parseInt(e.getPointerNumber());
                error += "\n\n" + "The file is shown below with the problematic line highlighted.";
            } catch (NumberFormatException ex) {
            }
            updateOriginalFile(line);

        }

        tabbedPane.setEnabledAt(1, false);
        horizSplitPane.setDividerLocation(0.0);
        panelTreeTable.setVisible(false);
        panelWarnings.setVisible(false);

        txtErrorMessage.setVisible(true);
        txtErrorMessage.setText(error);
        pack();
        this.tabbedPane.setSelectedIndex(0);

    }

    /** Scale an icon down to 20x20 */
    private ImageIcon scaleIcon20x20(ImageIcon icon) {
        return new ImageIcon(icon.getImage().getScaledInstance(20, 20, Image.SCALE_SMOOTH));
    }

    /**
     * Select an item in the combo box
     * Use because TridasEntity.equals compares things we don't care about
     * 
     * @param thisEntity
     */
    private void selectInTopChooser() {
        // disable actionListener firing when we change combobox selection
        topChooserListener.setEnabled(false);
        ITridas currentEntity = model.getSelectedRow().getCurrentEntity();

        try {
            if (currentEntity == null) {
                topChooser.setSelectedItem(ImportEntityListComboBox.NEW_ITEM);
                return;
            } else if (!currentEntity.isSetIdentifier()) {
                log.debug("No identifier");
                topChooser.setSelectedItem(ImportEntityListComboBox.NEW_ITEM);
                return;
            } else if (!currentEntity.getIdentifier().isSetDomain()) {
                log.debug("No domain");
                topChooser.setSelectedItem(ImportEntityListComboBox.NEW_ITEM);
                return;
            } else if (!currentEntity.getIdentifier().getDomain().equals(App.domain)) {
                log.debug("Different domain - this one is: " + currentEntity.getIdentifier().getDomain()
                        + " and not " + App.domain);
                topChooser.setSelectedItem(ImportEntityListComboBox.NEW_ITEM);
                return;
            } else {
                log.debug("Correct domain: " + currentEntity.getIdentifier().getDomain());

            }

            FilterableComboBoxModel model = (FilterableComboBoxModel) topChooser.getModel();

            // find it in the list...
            ITridas listEntity;
            if ((listEntity = entityInList(currentEntity, model.getElements())) != null) {
                topChooser.setSelectedItem(listEntity);
                return;
            }

            // blech, it wasn't in the list -> add it
            model.insertElementAt(currentEntity, 2);
            topChooser.setSelectedItem(currentEntity);
        } finally {
            // deal with the selection
            handleComboSelection(false);
            // re-enable combo box actionlistener firing
            topChooserListener.setEnabled(true);
        }
    }

    /**
     * Silly actionListener class that we can add/remove when we want to programmatically change the combo box
     * without triggering side effects
     */
    private class ChoiceComboBoxActionListener implements ActionListener {
        private final ImportView panel;
        private boolean enabled;

        public ChoiceComboBoxActionListener(ImportView panel) {
            this.panel = panel;
            this.enabled = true;
        }

        public void setEnabled(boolean enabled) {
            this.enabled = enabled;
        }

        public void actionPerformed(ActionEvent e) {
            if (enabled) {

                panel.handleComboSelection(true);
            }
        }
    }

    /**
     * Checks to see if the entity is in the given list
     * 
     * @param entity
     * @param list
     * @return The entity in the list (may be another instance) or null
     */
    private ITridas entityInList(ITridas entity, List<?> list) {
        for (Object o : list) {
            if (o instanceof ITridas) {
                ITridas otherEntity = (ITridas) o;

                if (matchEntities(entity, otherEntity))
                    return otherEntity;
            }
        }

        return null;
    }

    /**
     * Initialize the state when we:
     * a) choose something new in the combo box (load children!)
     * b) programmatically set something in the combo box (don't load children!)
     */
    private void handleComboSelection(boolean loadChildren) {
        /*   Object obj = topChooser.getSelectedItem();
               
           //fixes bug where combobox is hidden in derived series
           if(obj == null){
              return;
           }
           else if(obj instanceof ITridas) 
           {
                  
              temporarySelectingEntity = (ITridas) obj;
                  
              if(propertiesTable.hasWatermark()){
           propertiesTable.setWatermarkText(I18n.getText("general.preview"));
              }            
            
              // start loading the list of children right away
              //if(loadChildren && currentMode != EditType.RADIUS)
              //   lists.prepareChildList(temporarySelectingEntity);   
              propertiesPanel.readFromObject(temporarySelectingEntity);   
           }
               
              */
    }

    /**
     * Compare entities based on type, title, and site code (if they're TridasObjectEx)
     * @param e1
     * @param e2
     * @return
     */
    private boolean matchEntities(ITridas e1, ITridas e2) {
        // either are null? -> no match
        if (e1 == null || e2 == null)
            return false;

        // easy way out: identity!
        if (e1 == e2)
            return true;

        // they're not related classes -> not equal!
        if (!(e1.getClass().isAssignableFrom(e2.getClass()) || e2.getClass().isAssignableFrom(e1.getClass())))
            return false;

        // compare lab codes for TridasObjectExes
        if (e1 instanceof TridasObjectEx && e2 instanceof TridasObjectEx) {
            TridasObjectEx obj1 = (TridasObjectEx) e1;
            TridasObjectEx obj2 = (TridasObjectEx) e2;

            // not the same lab code -> not equal
            if (obj1.hasLabCode() && obj2.hasLabCode() && !obj1.getLabCode().equals(obj2.getLabCode()))
                return false;
        }

        // we found a match!
        if (e1.getTitle().equals(e2.getTitle()))
            return true;

        return false;
    }

    /**
     * Get the tree path for a node
     * 
     * @param node
     * @return
     */
    @SuppressWarnings("unchecked")
    private TreePath getPath(TreeNode node) {
        @SuppressWarnings("rawtypes")
        List list = new ArrayList();
        // Add all nodes to list 
        while (node != null) {
            list.add(node);
            node = node.getParent();
        }
        Collections.reverse(list);
        // Convert array of nodes to TreePath 
        return new TreePath(list.toArray());
    }

    /**
     * Called to enable editing
     * Responsible for loading the duplicate copy into the editor
     * 
     * @param enabled
     */
    protected void enableEditing(boolean enabled) {
        propertiesTable.setEditable(enabled);

        String entityname = "entity";
        try {
            entityname = TridasTreeViewPanel.getFriendlyClassName(model.getSelectedRow().getCurrentEntityClass());
        } catch (Exception e) {
        }

        editEntityText.setFont(editEntityText.getFont().deriveFont(Font.BOLD));
        editEntityText.setText(enabled ? I18n.getText("metadata.currentlyEditingThis") + " " + entityname
                : I18n.getText("metadata.clickLockToEdit") + " " + entityname);

        editEntity.setSelected(enabled);

        if (enabled) {
            // Remove any 'preview' watermark
            propertiesTable.setHasWatermark(false);
        }

        // disable choosing other stuff while we're editing
        // but only if something else doesn't disable it first
        if (topChooser.isEnabled()) {
            topChooser.setEnabled(!enabled);
            changeButton.setEnabled(!enabled);
        }

        // show/hide our buttons
        editEntitySave.setEnabled(true);
        editEntityCancel.setEnabled(true);
        editEntitySave.setVisible(enabled);
        editEntityCancel.setVisible(enabled);
    }

    /**
     * @return true if the user wants to lose changes, false otherwise
     */
    private boolean isUserOkWithLoosingAnyChanges() {

        if (changingTop || editEntity.isSelected()) {
            int ret = JOptionPane.showConfirmDialog(this, I18n.getText("question.confirmChangeForm"),
                    I18n.getText("question.continue"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);

            if (ret == JOptionPane.YES_OPTION) {
                changingTop = false;
                enableEditing(false);
                chooseOrCancelUIUpdate();
            }

            return (ret == JOptionPane.YES_OPTION);
        } else {
            enableEditing(false);
            return true;
        }

    }

    /**
     * @return true if the user wants to lose the new selection, false otherwise
     */
    @SuppressWarnings("unused")
    private boolean isUserOkWithLoosingSelection() {

        // nothing new has been selected, nothing to lose
        Class<? extends ITridas> currentEntityType = model.getSelectedRow().getCurrentEntityClass();

        if (!model.getSelectedRow().isDirty())
            return true;

        int ret = JOptionPane.showConfirmDialog(this, I18n.getText("question.confirmChangeForm"),
                I18n.getText("question.continue"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);

        return (ret == JOptionPane.YES_OPTION);
    }

}