package org.baderlab.lola.view;

import org.baderlab.lola.LolaParameterSet;
import org.baderlab.lola.LolaCurrentParameters;
import org.baderlab.lola.resources.LolaResources;
import org.baderlab.lola.view.actions.SaveLogosToFileAction;
import org.baderlab.lola.view.actions.MakeLogoTreeAction;
import org.baderlab.lola.view.actions.SaveLogoTreeToFile;
import org.baderlab.lola.view.actions.MakeLogosFromProfileSetAction;
import org.baderlab.lola.model.ProfileSet;
import org.baderlab.lola.model.Profile;
import org.baderlab.lola.model.LogoTree;
import org.baderlab.brain.util.JMultiLineToolTip;
import org.baderlab.brain.Brain;
import org.baderlab.brain.AminoAcidGrouping;
import org.ccbr.bader.yeast.view.gui.misc.JCollapsablePanel;

import javax.swing.*;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.net.URL;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import java.util.ArrayList;

/**
 Copyright 2007 Bader Lab

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU Lesser 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/>.

 */

/**
 * This Runnable constructs the main application window and underlying components.
 *
 * User: moyez
 * Date: May 25, 2007
 * Time: 11:40:19 PM
 */
public class LolaMainFrame implements Runnable {

    //data allProfiles - contains list of ALL OPEN profiles to be processed
    private ProfileSet allProfiles;

    //data allProfiles - contains list of SELECTED profiles to be processed
    private ProfileSet selectedProfiles;

    //Parameters for Lola
    private LolaParameterSet currentParamsCopy;

    //Shared widgets

    //tabbed pane
    private JTabbedPane tabbedPane;

    //files panel
    private JTextField profileFileNameTextField;
    private JTextField biasFileNameTextField;
    private JTextField outputPathTextField;
    private JCheckBox uniquePeptidesCheckBox;
    private JButton openProfileButton;
    private JButton drawLogoButton;
    private JButton saveLogoButton;
    private JButton closeSelectedProfilesButton;
    private JButton closeAllProfilesButton;
    private JButton quitButton;

    //logo settings panel
    private JPanel logoPanel;
    private colorStyleDialog logoColorStyleDialog;
    private JFormattedTextField logoStartIndexTextField;
    private JFormattedTextField logoHeightTextField;
    private JCheckBox drawAxisLabelsCheckBox;
    private JFormattedTextField logoTrimPercentageTextField;
    private JComboBox logoFormatComboBox;
    private JComboBox logoColorComboBox;
    private ArrayList<JFormattedTextField> logoColorTextFields;
    private ArrayList<JTextField> logoColorBoxes;
    private boolean isCustomColoring;           //if true, a custom coloring scheme was created at some point
                                                //NOTE: ONLY SET THIS FLAG ONCE - i.e. only if/when a custom coloring if FIRST set
                                                // subsequently, don't set it back to false (if a pre-canned coloring is selected later)
                                                // as this flag is checked to decide whether to add a "Custom" item in the coloring combo box
    private ArrayList<JButton> logoColorButtons;
    private int logoColorCategories;

    //logo tree panel
    private JPanel treePanel;
    private JTextField logoTreeTitle;
    private JTextField logoTreeOutputFilename;
    private JFormattedTextField logoTreeLeafTrimFactor;
    private JFormattedTextField logoTreeInternalNodeTrimFactor;
    private JButton createLogoTreeButton;
    private JButton saveLogoTreeButton;
    private JComboBox logoTreeLeafOrderingComboBox;
    private JComboBox groupingMethodComboBox;
    private JTextField positionFilterTextField;



    //profile pane
    JList profileList;

    private JScrollPane logoPane;

    private static final JFrame mainFrame = new JFrame(
            LolaResources.APPLICATION_NAME
            + " " + LolaResources.APPLICATION_VERSION);



    public LolaMainFrame() {
        super();

        // initialize data model
        allProfiles = new ProfileSet();
        selectedProfiles = new ProfileSet();

        // register model listener
        allProfiles.addListDataListener(new ListDataListener() {

            public void intervalAdded(ListDataEvent listDataEvent) {
                //To change body of implemented methods use File | Settings | File Templates.
                drawLogoButton.setEnabled(true);
                saveLogoButton.setEnabled(true);

                //if there >1 profiles open, allow logo tree generation
                if (allProfiles.size() > 1) {
                    createLogoTreeButton.setEnabled(true);
                    saveLogoTreeButton.setEnabled(true);
                }
            }

            public void intervalRemoved(ListDataEvent listDataEvent) {
                //To change body of implemented methods use File | Settings | File Templates.
                if (allProfiles.size() == 0) {
                    drawLogoButton.setEnabled(false);
                    saveLogoButton.setEnabled(false);
                    createLogoTreeButton.setEnabled(false);
                    saveLogoTreeButton.setEnabled(false);
                }
            }

            public void contentsChanged(ListDataEvent listDataEvent) {
                //To change body of implemented methods use File | Settings | File Templates.
            }
        });
        logoColorBoxes = new ArrayList<JTextField>();
        logoColorTextFields = new ArrayList<JFormattedTextField>();
        isCustomColoring = false;

    }

    public void run() {
        mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // make a split pane
        JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
        splitPane.setContinuousLayout(true);
        splitPane.setOneTouchExpandable(true);

        // left pane is for parameters and profile list (vertically split)
        JSplitPane leftSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
        leftSplitPane.setContinuousLayout(true);
        leftSplitPane.setOneTouchExpandable(true);

        // top left pane is the parameters pane
        JScrollPane paramPane = new JScrollPane(getParameterPanel());
        
        // bottom left pane is the profile list pane
        profileList = new JList(allProfiles);

        //TODO - display a tree instead of a list in the scroll pane using JTree
        //JTree profileTree = new JTree(allProfiles); // come back to this

        JPanel profilePanel = new JPanel(new BorderLayout());


        JScrollPane profilePane = new JScrollPane(profileList);
        profilePane.setBorder(BorderFactory.createEtchedBorder());

        profileList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
        profileList.addListSelectionListener(new ListSelectionListener() {

            public void valueChanged(ListSelectionEvent listSelectionEvent) {
//                Object obj = ((JList)listSelectionEvent.getSource()).getSelectedValue();
                Object[] objs = ((JList)listSelectionEvent.getSource()).getSelectedValues();

                //return if nothing selected
                if (objs.length < 1) return;

                //add all selected profiles to selected profile set
                selectedProfiles.removeAllElements();
                for (int i = 0; i < objs.length; i++) {
                    Profile profile = null;
                    if (objs[i] != null) {
                        profile = (Profile) objs[i];
                        selectedProfiles.addElement(profile);
                    }
                    //show last selected image
                    //TODO - instead of displaying only the last image, try showing a grid of all selected images
                    Icon icon = new ImageIcon(profile.getImage());
                    logoPane.setViewportView(new JLabel(icon));

                }
                closeSelectedProfilesButton.setEnabled(true);
//                if (obj != null) {
//                    Profile profile = (Profile) obj;
//                    Icon icon = new ImageIcon(profile.getImage());
//                    logoPane.setViewportView(new JLabel(icon));
//                }
            }
        });

        //create the bottom button panel
        JPanel bottomPanel = new JPanel(new FlowLayout());
        closeSelectedProfilesButton = new JButton("Close Selected");
        closeSelectedProfilesButton.addActionListener(new closeSelectedProfilesAction());
        closeSelectedProfilesButton.setEnabled(false);
        bottomPanel.add(closeSelectedProfilesButton);

        closeAllProfilesButton = new JButton("Close All");
        closeAllProfilesButton.addActionListener(new closeAllProfilesAction());
        closeAllProfilesButton.setEnabled(true);
        bottomPanel.add(closeAllProfilesButton);

        quitButton = new JButton("Quit");
        quitButton.addActionListener(new quitAction());
        quitButton.setEnabled(true);
        bottomPanel.add(quitButton);

        profilePanel.add(profilePane, BorderLayout.CENTER);
        profilePanel.add(bottomPanel, BorderLayout.AFTER_LAST_LINE);


        // construct left pane
        leftSplitPane.setTopComponent(paramPane);
        leftSplitPane.setBottomComponent(profilePanel);

        //right pane is the logo image pane
        Icon icon = null;
        URL imageURL;
        imageURL = LolaMainFrame.class.getResource(LolaResources.APPLICATION_WELCOME_IMAGE);
        if (imageURL != null) {
            icon = new ImageIcon(imageURL);
        }
        JLabel imageLabel = new JLabel(icon);
        logoPane = new JScrollPane(imageLabel);
        logoPane.setBorder(BorderFactory.createEtchedBorder());

        // construct main split pane
        splitPane.setLeftComponent(leftSplitPane);
        splitPane.setRightComponent(logoPane);

        // frame properties
        mainFrame.add(splitPane, BorderLayout.CENTER);
        mainFrame.setSize(1400,900);
        //frame.pack();
        mainFrame.setLocationRelativeTo(null);
        mainFrame.setVisible(true);
    };

    private JPanel getParameterPanel() {
        //get the current parameters (actually a copy to avoid changing directly on user input)
        currentParamsCopy = LolaCurrentParameters.getInstance().getParamsCopy();

        // The main panel to contain a tabbed pane
        JPanel mainPanel = new JPanel(new BorderLayout());

        //create the tabbed pane
        tabbedPane = new JTabbedPane();
        tabbedPane.addTab("Files", null, getFilesPanel(), "Set parameters that affect how sequence logos are generated.");

        logoPanel = getLogoGenerationParamsPanel();
        tabbedPane.addTab("Logo Options", null, logoPanel);

        treePanel = getLogoTreeParamsPanel();
        tabbedPane.addTab("Logo Tree", null, treePanel);
        tabbedPane.addTab("About", null, getAboutPanel());
        tabbedPane.addMouseListener(new tabbedPaneMouseListener());



        mainPanel.add(tabbedPane, BorderLayout.CENTER);
//        mainPanel.add(bottomPanel,BorderLayout.SOUTH);

        return mainPanel;
    }

    private JPanel getFilesPanel() {
        // Tab 1 - parameters for sequence logo generation
        JPanel filesPanel = new JPanel(new BorderLayout());

        // Tab 1 sub-panel - selection of profile file
        JPanel profileSelectionPanel = new JPanel(new BorderLayout());
        profileSelectionPanel.setBorder(BorderFactory.createTitledBorder("Profile Selection"));

        // profile file options
        JPanel profileChooserPanel = new JPanel();
        profileFileNameTextField = new JTextField();
        profileFileNameTextField.setToolTipText("The file name of the profile or list of profiles.");
        if (currentParamsCopy.getProfileFile() != null) {
            profileFileNameTextField.setText(currentParamsCopy.getProfileFile().toString());
        }
        profileFileNameTextField.setColumns(30);
        //this only handles VK_ENTER key events
        profileFileNameTextField.addActionListener(new profileFileNameAction());
        //this handles other text change events
        profileFileNameTextField.getDocument().addDocumentListener(new profileFileNameAction());
        profileChooserPanel.add(profileFileNameTextField);
        JButton profileChooseFileButton = new JButton("Browse...");
        profileChooseFileButton.addActionListener(new chooseProfileAction());
        profileChooserPanel.add(profileChooseFileButton);
        profileSelectionPanel.add(profileChooserPanel, BorderLayout.NORTH);

        //subpanel for unique peptides checkbox
        JPanel profileChooserSubSubPanel = new JPanel();
        uniquePeptidesCheckBox = new JCheckBox("Unique peptides", false) {
            public JToolTip createToolTip() {
                return new JMultiLineToolTip();
            }
        };
        uniquePeptidesCheckBox.addItemListener(new uniquePeptidesCheckBoxAction());
        uniquePeptidesCheckBox.setToolTipText("If checked, only unique peptide sequences will be loaded from the profile.");
        uniquePeptidesCheckBox.setSelected(currentParamsCopy.getUniquePeptides());
        profileChooserSubSubPanel.add(uniquePeptidesCheckBox);
        profileSelectionPanel.add(profileChooserSubSubPanel, BorderLayout.SOUTH);

        // Tab 1 sub-panel - selection of codon bias
        JPanel biasSelectionPanel = new JPanel(new BorderLayout());
        biasSelectionPanel.setBorder(BorderFactory.createTitledBorder("Codon Bias File"));
        //biasSelectionPanel.setBorder(BorderFactory.createTitledBorder("Correction File"));

        // bias file options
        JPanel biasFileChooserPanel = new JPanel();
        biasFileNameTextField = new JTextField();
        biasFileNameTextField.setToolTipText("The file name of the codon bias file specification.");
        //biasFileNameTextField.setToolTipText("The file name of the correction file specification.");        
        if (currentParamsCopy.getCodonBiasFile() != null) {
            biasFileNameTextField.setText(currentParamsCopy.getCodonBiasFile().toString());
        }
        biasFileNameTextField.setColumns(30);
        //this only handles VK_ENTER key events
        biasFileNameTextField.addActionListener(new codonBiasFileNameAction());
        //this handles other text change events
        biasFileNameTextField.getDocument().addDocumentListener(new codonBiasFileNameAction());
        biasFileChooserPanel.add(biasFileNameTextField);
        JButton biasFileChooseFileButton = new JButton("Browse...");
        biasFileChooseFileButton.addActionListener(new chooseCodonBiasAction());
        biasFileChooserPanel.add(biasFileChooseFileButton);
        biasSelectionPanel.add(biasFileChooserPanel, BorderLayout.NORTH);

        // Tab 1 sub-panel - selection of output path
        JPanel outputPathSelectionPanel = new JPanel(new BorderLayout());
        outputPathSelectionPanel.setBorder(BorderFactory.createTitledBorder("Output Directory"));
        //output path options
        JPanel outputPathChooserPanel = new JPanel();
        outputPathTextField = new JTextField();
        outputPathTextField.setToolTipText("The directory path where output files will be saved.");
        if (currentParamsCopy.getOutputPath() != null) {
            outputPathTextField.setText(currentParamsCopy.getOutputPath().toString());
        }        
        outputPathTextField.setColumns(30);
        //this only handles VK_ENTER key events
        outputPathTextField.addActionListener(new outputPathAction());
        //this handles other text change events
        outputPathTextField.getDocument().addDocumentListener(new outputPathAction());
        outputPathChooserPanel.add(outputPathTextField);
        JButton outputPathChooseFileButton = new JButton("Browse...");
        outputPathChooseFileButton.addActionListener(new chooseOutputPathAction());
        outputPathChooserPanel.add(outputPathChooseFileButton);
        outputPathSelectionPanel.add(outputPathChooserPanel, BorderLayout.NORTH);

        //create the bottom panel - Open/Clear buttons
        JPanel bottomPanel = new JPanel(new FlowLayout());
        openProfileButton = new JButton("Open");
        openProfileButton.addActionListener(new MakeLogosFromProfileSetAction(allProfiles, currentParamsCopy));
        openProfileButton.setEnabled(false);
        bottomPanel.add(openProfileButton);
        JButton clearButton = new JButton("Clear");
        clearButton.addActionListener(new clearFileFieldsAction());
        bottomPanel.add(clearButton);

        // put tab 1 together
        JPanel tab1Panel = new JPanel();
        tab1Panel.setMinimumSize(new Dimension(100,200));
        tab1Panel.setLayout(new BoxLayout(tab1Panel, BoxLayout.Y_AXIS));
        tab1Panel.add(profileSelectionPanel);
        tab1Panel.add(biasSelectionPanel);
        tab1Panel.add(outputPathSelectionPanel);
        tab1Panel.add(bottomPanel);

        filesPanel.add(tab1Panel, BorderLayout.NORTH);

        return tab1Panel;
    }

    private JPanel old_getAboutPanel() {

        JPanel aboutPanel = new JPanel();
        aboutPanel.setLayout(new FlowLayout());

        JTextArea aboutTextArea = new JTextArea();
        aboutTextArea.setText(
                "\n\n"
                + LolaResources.APPLICATION_NAME + " - "
                + LolaResources.APPLICATION_SHORT_DESCRIPTION
                + "\nVersion " + LolaResources.APPLICATION_VERSION 
                + "(" + LolaResources.APPLICATION_BUILD_DATE + ")"
                + "\n\nMoyez Dharsee\nGary Bader\n\n"
                + LolaResources.APPLICATION_WEB_URL);

        aboutTextArea.setAlignmentX(JTextArea.CENTER_ALIGNMENT);
        aboutTextArea.setAlignmentY(JTextArea.CENTER_ALIGNMENT);
        aboutTextArea.setEditable(false);
        aboutTextArea.setRows(10);
        aboutTextArea.setOpaque(false);

        aboutPanel.setAlignmentX(JPanel.CENTER_ALIGNMENT);
        aboutPanel.add(aboutTextArea);

        return aboutPanel;
    }


    /**
     * Creates the About tab - adapted from MCODE class MCODEAboutDialog
     * @return A JPanel containing the contents of the About tab
     */
    private JPanel getAboutPanel() {

        JPanel aboutPanel = new JPanel();
        aboutPanel.setLayout(new FlowLayout());

        //main panel for dialog box
        JEditorPane editorPane = new JEditorPane();
        editorPane.setOpaque(false);
        editorPane.setMargin(new Insets(10,10,10,10));
        editorPane.setEditable(false);
        editorPane.setEditorKit(new HTMLEditorKit());
        editorPane.addHyperlinkListener(new HyperlinkAction(editorPane));

        editorPane.setText(
                "<html><body><P align=center>" +
                "<b>" + LolaResources.APPLICATION_NAME + " - " + LolaResources.APPLICATION_SHORT_DESCRIPTION + "</b><BR>" +
                "Version " + LolaResources.APPLICATION_VERSION + "<BR>" +
                LolaResources.APPLICATION_BUILD_DATE + "<BR>" +
                "<BR>Moyez Dharsee<BR>" +
                "Gary Bader<BR><BR>" +
                "<a href='http://www.baderlab.org'>Bader Lab<BR>" + LolaResources.APPLICATION_WEB_URL + "</a>");
        aboutPanel.add(editorPane);
        return aboutPanel;
    }

    /**
     * Launches a web browser if a Hyperlink object is clicked
     */
    private class HyperlinkAction implements HyperlinkListener {
        JEditorPane pane;

        public HyperlinkAction(JEditorPane pane) {
            this.pane = pane;
        }

        public void hyperlinkUpdate(HyperlinkEvent event) {
            if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
                cytoscape.util.OpenBrowser.openURL(event.getURL().toString());
            }
        }
    }

    private JPanel getLogoTreeParamsPanel() {

        //title sub-panel for basic logo tree settings
        JPanel logoTreeOutputPanel = new JPanel();
        logoTreeOutputPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
        logoTreeOutputPanel.setLayout(new BorderLayout());
        logoTreeOutputPanel.setBorder(BorderFactory.createTitledBorder("Basic Options"));

        //output left sub-panel
        JPanel leftOutputPanel = new JPanel();
        //LayoutManager layout = new BoxLayout(leftOutputPanel, BoxLayout.Y_AXIS);
        LayoutManager layout = new GridBagLayout();
        leftOutputPanel.setLayout(layout);
        leftOutputPanel.setAlignmentX(Component.LEFT_ALIGNMENT);        


        //tree title
        JPanel titlePanel = new JPanel();
        LayoutManager titleLayout = new BoxLayout(titlePanel, BoxLayout.X_AXIS);
        titlePanel.setLayout(titleLayout);
        JLabel treeTitleLabel = new JLabel("Title: ");
        treeTitleLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
        logoTreeTitle = new JFormattedTextField();
        logoTreeTitle.setAlignmentX(Component.LEFT_ALIGNMENT);
        logoTreeTitle.setToolTipText("Title to be displayed at the top of the logo tree image.");
        logoTreeTitle.setColumns(30);
        logoTreeTitle.addPropertyChangeListener("value", new textFieldChangeListener());
        
        //initialize title text field value if a valid title is available
        String title = currentParamsCopy.getTreeTitle();
        if (title != null) logoTreeTitle.setText(title);

        addGridBagComponent(leftOutputPanel, treeTitleLabel, 0, 0, 1, 1,
                GridBagConstraints.LINE_START, GridBagConstraints.NONE);
        addGridBagComponent(leftOutputPanel, logoTreeTitle, 1, 0, 1, 1,
                GridBagConstraints.CENTER, GridBagConstraints.BOTH);

        //output file name
        JPanel outputPanel = new JPanel();
        LayoutManager outputLayout = new BoxLayout(outputPanel, BoxLayout.X_AXIS);
        outputPanel.setLayout(outputLayout);
        JLabel outputNameLabel = new JLabel();
        outputNameLabel.setText("Output Filename: ");
        outputNameLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
        logoTreeOutputFilename = new JFormattedTextField();
        logoTreeOutputFilename.setAlignmentX(Component.LEFT_ALIGNMENT);
        logoTreeOutputFilename.setToolTipText("Name of logo tree output file. DIRECTORY PATH IS NOT REQUIRED.   The file will be saved in the Output Directory set in the Files panel.");
        logoTreeOutputFilename.setColumns(30);
        logoTreeOutputFilename.addPropertyChangeListener("value", new textFieldChangeListener());
        addGridBagComponent(leftOutputPanel, outputNameLabel, 0, 1, 1, 1,
                GridBagConstraints.LINE_START, GridBagConstraints.NONE);
        addGridBagComponent(leftOutputPanel, logoTreeOutputFilename, 1, 1, 1, 1,
                GridBagConstraints.CENTER, GridBagConstraints.BOTH);


//        leftOutputPanel.add(outputPanel);

        //output right sub-panel
        JPanel rightOutputPanel = new JPanel();
        rightOutputPanel.setLayout(new FlowLayout());
        //logo format
        JPanel logoFormatPanel = new JPanel();
        JLabel logoFormatLabel = new JLabel();
        logoFormatLabel.setText("Output Format:");
        logoFormatComboBox = new JComboBox(LolaResources.TREE_IMAGE_FORMAT_STRINGS);
        logoFormatComboBox.setToolTipText("Ouput file format of logo tree.");
        //set currently selected format
        String currentFormat = currentParamsCopy.getLogoImageFileFormat();
        if (currentFormat != null) {
            for (int i = 0; i < LolaResources.TREE_IMAGE_FORMAT_STRINGS.length; i++) {
                if (currentFormat.equalsIgnoreCase(LolaResources.TREE_IMAGE_FORMAT_STRINGS[i])) {
                    logoFormatComboBox.setSelectedIndex(i);
                    break;
                }
            }
        }
        logoFormatComboBox.addActionListener(new selectLogoTreeFormatAction());
        addGridBagComponent(leftOutputPanel, logoFormatLabel, 0, 2, 1, 1,
                GridBagConstraints.LINE_START, GridBagConstraints.NONE);
        addGridBagComponent(leftOutputPanel, logoFormatComboBox, 1, 2, 1, 1,
                GridBagConstraints.LINE_START, GridBagConstraints.NONE);



        logoTreeOutputPanel.add(leftOutputPanel, BorderLayout.BEFORE_FIRST_LINE);
        logoTreeOutputPanel.add(rightOutputPanel, BorderLayout.AFTER_LAST_LINE);

        //create the bottom button panel
        JPanel bottomPanel = new JPanel(new FlowLayout());
        createLogoTreeButton = new JButton("Generate");
//        createLogoTreeButton.addActionListener(new MakeLogoTreeAction(allProfiles, currentParamsCopy));
        createLogoTreeButton.addActionListener(new generateLogoTreeAction());
        createLogoTreeButton.setEnabled(false);
        bottomPanel.add(createLogoTreeButton);
        saveLogoTreeButton = new JButton("Save");
        saveLogoTreeButton.addActionListener(new SaveLogoTreeToFile(allProfiles, currentParamsCopy));
        saveLogoTreeButton.setEnabled(false);
        bottomPanel.add(saveLogoTreeButton);

        //the panel to return
        JPanel thePanel = new JPanel(new BorderLayout());
        thePanel.add(logoTreeOutputPanel, BorderLayout.NORTH);
        thePanel.add(createLogoTreeAdvancedOptionsPanel(), BorderLayout.CENTER);
        thePanel.add(bottomPanel, BorderLayout.SOUTH);
        return thePanel;


    }

    private JCollapsablePanel createLogoTreeAdvancedOptionsPanel() {

        //main panel
        JCollapsablePanel mainPanel = new JCollapsablePanel("Advanced Options");
        mainPanel.setAlignmentX(JCollapsablePanel.LEFT_ALIGNMENT);


        //sub-panel to hold all widgets
        JPanel subPanel = new JPanel();
//        subPanel.setLayout(new BoxLayout(subPanel, BoxLayout.Y_AXIS));
//        subPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
        LayoutManager mainLayout = new GridBagLayout();
        subPanel.setLayout(mainLayout);
        subPanel.setAlignmentX(Component.LEFT_ALIGNMENT);


        //leaf node trim factor
        JPanel leafTrimFactorPanel = new JPanel();
        leafTrimFactorPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
        LayoutManager leafTrimFactorLayout = new BoxLayout(leafTrimFactorPanel, BoxLayout.X_AXIS);
        leafTrimFactorPanel.setLayout(leafTrimFactorLayout);
        JLabel leafTrimFactorLabel = new JLabel();
        leafTrimFactorLabel.setText("Trim Factor for Leaf Nodes: ");
        leafTrimFactorLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
        logoTreeLeafTrimFactor = new JFormattedTextField();
        logoTreeLeafTrimFactor.setAlignmentX(Component.LEFT_ALIGNMENT);
        logoTreeLeafTrimFactor.setToolTipText("Trim factor for leaf nodes");
        logoTreeLeafTrimFactor.setColumns(5);
        logoTreeLeafTrimFactor.addPropertyChangeListener("value", new textFieldChangeListener());
        addGridBagComponent(subPanel, leafTrimFactorLabel, 0, 0, 1, 1,
                GridBagConstraints.LINE_START, GridBagConstraints.NONE);
        addGridBagComponent(subPanel, logoTreeLeafTrimFactor, 1, 0, 1, 1,
                GridBagConstraints.LINE_START, GridBagConstraints.NONE);
        logoTreeLeafTrimFactor.setValue(Brain.LEAFNODE_TRIM_FACTOR_DEFAULT);

        //internal node trim factor
        JPanel internalNodeTrimFactorPanel = new JPanel();
        internalNodeTrimFactorPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
        LayoutManager internalNodeTrimFactorLayout = new BoxLayout(internalNodeTrimFactorPanel, BoxLayout.X_AXIS);
        internalNodeTrimFactorPanel.setLayout(internalNodeTrimFactorLayout);
        JLabel internalNodeTrimFactorLabel = new JLabel();
        internalNodeTrimFactorLabel.setText("Trim Factor for Internal Nodes: ");
        internalNodeTrimFactorLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
        logoTreeInternalNodeTrimFactor = new JFormattedTextField();
        logoTreeInternalNodeTrimFactor.setAlignmentX(Component.LEFT_ALIGNMENT);
        logoTreeInternalNodeTrimFactor.setToolTipText("Trim factor for leaf nodes");
        logoTreeInternalNodeTrimFactor.setColumns(5);
        logoTreeInternalNodeTrimFactor.addPropertyChangeListener("value", new textFieldChangeListener());
        addGridBagComponent(subPanel, internalNodeTrimFactorLabel, 0, 1, 1, 1,
                GridBagConstraints.LINE_START, GridBagConstraints.NONE);
        addGridBagComponent(subPanel, logoTreeInternalNodeTrimFactor, 1, 1, 1, 1,
                GridBagConstraints.LINE_START, GridBagConstraints.NONE);
        logoTreeInternalNodeTrimFactor.setValue(Brain.INTERNALNODE_TRIM_FACTOR_DEFAULT);

        //leaf ordering
        JPanel leafOrderingPanel = new JPanel();
        leafOrderingPanel.setLayout(new FlowLayout());
        JLabel leafOrderingLabel = new JLabel("Leaf Ordering Method:");
        logoTreeLeafOrderingComboBox = new JComboBox(Brain.LEAF_ORDERING_METHODS);
        logoTreeLeafOrderingComboBox.setToolTipText("Algorithm used for ordering leaf nodes");
        String currentOrdering = Brain.LEAF_ORDERING_DEFAULT;
        if (currentOrdering != null) {
            for (int i = 0; i < Brain.LEAF_ORDERING_METHODS.length; i++) {
                if (currentOrdering.equalsIgnoreCase(Brain.LEAF_ORDERING_METHODS[i])) {
                    logoTreeLeafOrderingComboBox.setSelectedIndex(i);
                    break;
                }
            }
        }
        logoTreeLeafOrderingComboBox.addActionListener(new selectLogoTreeOrderingAction());
        addGridBagComponent(subPanel, leafOrderingLabel, 0, 2, 1, 1,
                GridBagConstraints.LINE_START, GridBagConstraints.NONE);
        addGridBagComponent(subPanel, logoTreeLeafOrderingComboBox, 1, 2, 1, 1,
                GridBagConstraints.LINE_START, GridBagConstraints.NONE);

        //amino acid grouping
        JPanel groupingPanel = new JPanel();
        groupingPanel.setLayout(new FlowLayout());
        JLabel groupingLabel = new JLabel("Amino Acid Grouping:");
        groupingMethodComboBox = new JComboBox(AminoAcidGrouping.GROUPING_METHODS);
        groupingMethodComboBox.setToolTipText(getGroupingComboBoxTooltip());
        String selectedGrouping = currentParamsCopy.getAminoAcidGrouping().getSelectedGrouping();
        for (int i = 0; i < AminoAcidGrouping.GROUPING_METHODS.length; i++) {
            if (selectedGrouping.equalsIgnoreCase(AminoAcidGrouping.GROUPING_METHODS[i])) {
                groupingMethodComboBox.setSelectedIndex(i);
                break;
            }
        }
        groupingMethodComboBox.addActionListener(new selectGroupingMethodAction());
        addGridBagComponent(subPanel, groupingLabel, 0, 3, 1, 1,
                GridBagConstraints.LINE_START, GridBagConstraints.NONE);
        addGridBagComponent(subPanel, groupingMethodComboBox, 1, 3, 1, 1,
                GridBagConstraints.LINE_START, GridBagConstraints.NONE);

        //output path options
        JPanel positionFilterPanel = new JPanel();
        positionFilterPanel.setLayout(new FlowLayout());
        JLabel positionFilterLabel = new JLabel("Position Filter:");
        positionFilterTextField = new JTextField();
        positionFilterTextField.setToolTipText("Filter positions used to calculate distance (i.e. 1,4,7-9).");

        positionFilterTextField.setColumns(15);
        //this only handles VK_ENTER key events
        positionFilterTextField.addActionListener(new positionFilterAction());
        //this handles other text change events
        positionFilterTextField.getDocument().addDocumentListener(new positionFilterAction());
        addGridBagComponent(subPanel, positionFilterLabel, 0, 4, 1, 1,
                GridBagConstraints.LINE_START, GridBagConstraints.NONE);
        addGridBagComponent(subPanel, positionFilterTextField, 1, 4, 1, 1,
                GridBagConstraints.LINE_START, GridBagConstraints.NONE);

        //setup main panel
        mainPanel.getContentPane().add(subPanel, BorderLayout.NORTH);
        return mainPanel;


    }

    public class ColorPicker extends JFrame {

      public ColorPicker() {
        super("JColorChooser Test Frame");
        setSize(200, 100);
        final Container contentPane = getContentPane();
        final JButton go = new JButton("Show JColorChooser");
        go.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            Color c;
            c = JColorChooser.showDialog(
                      ((Component)e.getSource()).getParent(),
                      "Demo", Color.blue);
            contentPane.setBackground(c);
          }
        });
        contentPane.add(go, BorderLayout.SOUTH);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
      }
    }

    public class origColorPicker extends JFrame {

      public origColorPicker() {
        super("JColorChooser Test Frame");
        setSize(200, 100);
        final Container contentPane = getContentPane();
        final JButton go = new JButton("Show JColorChooser");
        go.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            Color c;
            c = JColorChooser.showDialog(
                      ((Component)e.getSource()).getParent(),
                      "Demo", Color.blue);
            contentPane.setBackground(c);
          }
        });
        contentPane.add(go, BorderLayout.SOUTH);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
      }
    }


    private static void addGridBagComponent(
            Container container, Component component,
            int gridx, int gridy, int gridwidth, int gridheight, int anchor, int fill) {

        Insets insets = new Insets(10,10,10,10);
        GridBagConstraints gbc = new GridBagConstraints(gridx, gridy, gridwidth, gridheight, 1.0, 1.0,
                anchor, fill, insets, 1, 0);
        container.add(component, gbc);

    }

    

    private JPanel getLogoGenerationParamsPanel() {

        // titled sub-panel for logo settings
        JPanel logoSettingsPanel = new JPanel();
        logoSettingsPanel.setLayout(new FlowLayout());
        logoSettingsPanel.setBorder(BorderFactory.createTitledBorder("Basic Options"));

        // left panel in logo settings
        JPanel leftSettingsPanel = new JPanel();
        leftSettingsPanel.setLayout(new BoxLayout(leftSettingsPanel, BoxLayout.Y_AXIS));

        //start index
        JPanel startIndexPanel = new JPanel(new BorderLayout());
        JLabel startIndexLabel = new JLabel();
        startIndexLabel.setText("Start Index:");
        logoStartIndexTextField = new JFormattedTextField();
        logoStartIndexTextField.setToolTipText("The first index on the X-axis of the logo graph.");
        logoStartIndexTextField.setValue(currentParamsCopy.getLogoStartIndex());
        logoStartIndexTextField.setColumns(3);
        logoStartIndexTextField.addPropertyChangeListener("value", new textFieldChangeListener());
//        logoStartIndexTextField.setInputVerifier(new numericalTextVerifier()); //just experimenting
        startIndexPanel.add(startIndexLabel, BorderLayout.WEST);
        startIndexPanel.add(logoStartIndexTextField, BorderLayout.EAST);
        startIndexPanel.setAlignmentX(JPanel.RIGHT_ALIGNMENT);
        leftSettingsPanel.add(startIndexPanel);

        //logo height
        JPanel logoHeightPanel = new JPanel(new BorderLayout());
        JLabel logoHeightLabel = new JLabel();
        logoHeightLabel.setText("Height:");
        logoHeightTextField = new JFormattedTextField();
        logoHeightTextField.setToolTipText("The height of the logo image, in pixels.");
        logoHeightTextField.setValue(currentParamsCopy.getLogoHeight());
        logoHeightTextField.setColumns(3);
        logoHeightTextField.addPropertyChangeListener("value", new textFieldChangeListener());
        logoHeightPanel.add(logoHeightLabel, BorderLayout.WEST);
        logoHeightPanel.add(logoHeightTextField, BorderLayout.EAST);
        logoHeightPanel.setAlignmentX(JPanel.RIGHT_ALIGNMENT);
        leftSettingsPanel.add(Box.createRigidArea(new Dimension(0,5)));
        leftSettingsPanel.add(logoHeightPanel);

        // right panel in logo settings
        JPanel rightSettingsPanel = new JPanel();
        rightSettingsPanel.setLayout(new BoxLayout(rightSettingsPanel, BoxLayout.Y_AXIS));


        //logo format
        JPanel logoFormatPanel = new JPanel();
        JLabel logoFormatLabel = new JLabel();
        logoFormatLabel.setText("Output Format:");
        logoFormatComboBox = new JComboBox(LolaResources.IMAGE_FORMAT_STRINGS);
        logoFormatComboBox.setToolTipText("Ouput file format of logo image.");
        //set currently selected format
        String currentFormat = currentParamsCopy.getLogoImageFileFormat();
        if (currentFormat != null) {
            for (int i = 0; i < LolaResources.IMAGE_FORMAT_STRINGS.length; i++) {
                if (currentFormat.equalsIgnoreCase(LolaResources.IMAGE_FORMAT_STRINGS[i])) {
                    logoFormatComboBox.setSelectedIndex(i);
                    break;
                }
            }
        }
        logoFormatComboBox.addActionListener(new selectLogoFormatAction());
        logoFormatPanel.add(logoFormatLabel);
        logoFormatPanel.add(logoFormatComboBox);
        logoFormatPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
        rightSettingsPanel.add(logoFormatPanel);

        //logo labels
        JPanel logoLigandPositionLabelsOnlyPanel = new JPanel(new BorderLayout());
        JLabel logoLigandPositionLabelsOnlyLabel = new JLabel();
        logoLigandPositionLabelsOnlyLabel.setText("Draw axis labels:");
        drawAxisLabelsCheckBox = new JCheckBox();
        drawAxisLabelsCheckBox.setToolTipText("Draws x and y axis labels.");
        drawAxisLabelsCheckBox.setSelected(currentParamsCopy.getDrawAxisLabels());
        drawAxisLabelsCheckBox.addActionListener(new selectCheckBoxAction());
        logoLigandPositionLabelsOnlyPanel.add(logoLigandPositionLabelsOnlyLabel, BorderLayout.WEST);
        logoLigandPositionLabelsOnlyPanel.add(drawAxisLabelsCheckBox, BorderLayout.EAST);
        logoLigandPositionLabelsOnlyPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
        rightSettingsPanel.add(logoLigandPositionLabelsOnlyPanel);

        //create the bottom button panel
        JPanel bottomPanel = new JPanel(new FlowLayout());
        drawLogoButton = new JButton("Redraw All");
//        drawLogoButton.addActionListener(new DuplicateProfilesAndLogosAction(selectedProfiles, allProfiles, currentParamsCopy));
        drawLogoButton.addActionListener(new MakeLogosFromProfileSetAction(allProfiles, currentParamsCopy));
        drawLogoButton.setEnabled(false);
        bottomPanel.add(drawLogoButton);

        saveLogoButton = new JButton("Save All");
        saveLogoButton.addActionListener(new SaveLogosToFileAction(allProfiles, currentParamsCopy));
        saveLogoButton.setEnabled(false);
        bottomPanel.add(saveLogoButton);

//        JButton clearButton = new JButton("Clear");
//        clearButton.addActionListener(new clearFileFieldsAction());
//        bottomPanel.add(clearButton);


        //construct the main panel
        logoSettingsPanel.add(leftSettingsPanel);
        logoSettingsPanel.add(Box.createRigidArea(new Dimension(0,50)));
        logoSettingsPanel.add(rightSettingsPanel);
        JPanel thePanel = new JPanel(new BorderLayout());
        thePanel.add(logoSettingsPanel, BorderLayout.NORTH);
        thePanel.add(createLogoAdvancedOptionsPanel(), BorderLayout.CENTER);
        thePanel.add(bottomPanel, BorderLayout.SOUTH);
        return thePanel;
    }




    private JCollapsablePanel createLogoAdvancedOptionsPanel() {

        //main panel
        JCollapsablePanel mainPanel = new JCollapsablePanel("Advanced Options");
        mainPanel.setAlignmentX(JCollapsablePanel.LEFT_ALIGNMENT);


        //sub-panel to hold all widgets
        JPanel subPanel = new JPanel();
//        subPanel.setLayout(new BoxLayout(subPanel, BoxLayout.Y_AXIS));
//        subPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
        LayoutManager mainLayout = new GridBagLayout();
        subPanel.setLayout(mainLayout);
        subPanel.setAlignmentX(Component.LEFT_ALIGNMENT);


        //logo trim percentage
        JLabel logoTrimPercentageLabel = new JLabel();
        logoTrimPercentageLabel.setText("Trim Percentage:");
        logoTrimPercentageTextField = new JFormattedTextField() {
            public JToolTip createToolTip() {
                JMultiLineToolTip t = new JMultiLineToolTip();
                t.setColumns(40);
                return t;
            }
        };
        logoTrimPercentageTextField.setToolTipText("If columns on either end of the profile are not above this height" +
                " (in terms of percentage of total possible column height), they will be trimmed." +
                " This has the effect of highlighting the most informative contiguous region. The value must be" +
                " between 0 and 1, inclusively.");
        logoTrimPercentageTextField.setValue(currentParamsCopy.getLogoTrimPercentage());
        logoTrimPercentageTextField.setColumns(3);
        logoTrimPercentageTextField.addPropertyChangeListener("value", new textFieldChangeListener());
        addGridBagComponent(subPanel, logoTrimPercentageLabel, 0, 0, 1, 1,
                GridBagConstraints.LINE_START, GridBagConstraints.NONE);
        addGridBagComponent(subPanel, logoTrimPercentageTextField, 1, 0, 1, 1,
                GridBagConstraints.LINE_START, GridBagConstraints.NONE);


        //color style panel
        JPanel colorPanel = new JPanel();
        colorPanel.setLayout(new FlowLayout());

        //colour style
        JLabel logoColorLabel = new JLabel();
        logoColorLabel.setText("Color Style: ");
        logoColorComboBox = new JComboBox(AminoAcidGrouping.COLORING_METHODS) {
            public JToolTip createToolTip() {
                JMultiLineToolTip t = new JMultiLineToolTip();
                t.setColumns(40);
                return t;
            }
        };
        logoColorComboBox.setToolTipText(getColorComboBoxTooltip());
        String selectedColor = currentParamsCopy.getAminoAcidGrouping().getSelectedColoring();
        if (selectedColor != null) {
            for (int i = 0; i < AminoAcidGrouping.COLORING_METHODS.length; i++) {
                if (selectedColor.equalsIgnoreCase(AminoAcidGrouping.COLORING_METHODS[i])) {
                    logoColorComboBox.setSelectedIndex(i);
                    break;
                }
            }
        }
        logoColorComboBox.addActionListener(new selectColorStyleAction());
        //customize color style button
        final JButton customizeColorButton = new JButton("Customize...");
        customizeColorButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                logoColorStyleDialog = new colorStyleDialog(mainFrame, "Customize Color Style");
                logoColorStyleDialog.setResizable(false);
                logoColorStyleDialog.pack();
                logoColorStyleDialog.setVisible(true);
            }
        });

        // Shirley
        
        //put color widgets together
        addGridBagComponent(subPanel, logoColorLabel, 0, 2, 1, 1,
                GridBagConstraints.LINE_START, GridBagConstraints.NONE);
        addGridBagComponent(subPanel, logoColorComboBox, 1, 2, 1, 1,
                GridBagConstraints.LINE_START, GridBagConstraints.NONE);
        addGridBagComponent(subPanel, customizeColorButton, 2, 2, 1, 1,
                GridBagConstraints.LINE_START, GridBagConstraints.NONE);

        //setup main panel
        mainPanel.getContentPane().add(subPanel, BorderLayout.NORTH);
        return mainPanel;


    }

    /*
    Generates the tooltip text for the logo color combo box,
    based on the descriptions and symbols defined for the selected coloring method.
     */
    private String getColorComboBoxTooltip() {
        AminoAcidGrouping aaGroup = currentParamsCopy.getAminoAcidGrouping();
        ArrayList symbolList = aaGroup.getColorSymbolList();
        ArrayList categoryList = aaGroup.getColorCategoryList();

        String tooltip = aaGroup.getSelectedDescription();
        for (int i = 0; i < categoryList.size(); i++) {
            tooltip += " - " + symbolList.get(i) + " (" + categoryList.get(i) + ")";
        }
        return tooltip;

    }

    /*
    Generates the tooltip text for the amino acid grouping combo box,
    based on the descriptions and symbols defined for the selected grouping method.
     */
    private String getGroupingComboBoxTooltip() {
        AminoAcidGrouping aaGroup = currentParamsCopy.getAminoAcidGrouping();
        ArrayList symbolList = aaGroup.getGroupList();
        ArrayList categoryList = aaGroup.getGroupCategoryList();

        String tooltip = aaGroup.getSelectedDescription();
        for (int i = 0; i < categoryList.size(); i++) {
            tooltip += " - " + symbolList.get(i) + " (" + categoryList.get(i) + ")";
        }
        return tooltip;

    }

    private class colorStyleDialog extends JDialog {
        private JTextArea errorTextArea;
        private JButton okButton;

        public colorStyleDialog(Frame frame, String s) {
            super(frame, s);


            //main panel
            JPanel mainPanel = new JPanel(new BorderLayout());

            //setup the color panel
            JPanel colorPanel = new JPanel();
            LayoutManager panelLayout = new GridBagLayout();
            colorPanel.setLayout(panelLayout);
            colorPanel.setAlignmentX(Component.CENTER_ALIGNMENT);


            //column labels
            JLabel symbolLabel = new JLabel("Group");
            JLabel colourLabel = new JLabel("Color");
            addGridBagComponent(colorPanel, symbolLabel, 0, 0, 1, 1,
                    GridBagConstraints.LINE_START, GridBagConstraints.NONE);
            addGridBagComponent(colorPanel, colourLabel, 1, 0, 1, 1,
                    GridBagConstraints.LINE_START, GridBagConstraints.NONE);


            logoColorBoxes.clear();
            logoColorTextFields.clear();

            final int originalNumberOfColors = currentParamsCopy.getAminoAcidGrouping().getNumberOfColors();
            for (int i = 0; i < originalNumberOfColors; i++) {
                final int j = i;

                final JTextField colorBox = new JTextField();
                logoColorBoxes.add(colorBox);
                colorBox.setColumns(3);
                colorBox.setEditable(false);
                colorBox.setBackground((Color) currentParamsCopy.getAminoAcidGrouping().getColorList().get(j));

                final JFormattedTextField groupTextField = new JFormattedTextField();
                logoColorTextFields.add(groupTextField);
                groupTextField.setColumns(10);
                groupTextField.setValue(currentParamsCopy.getAminoAcidGrouping().getColorSymbolList().get(j));
                groupTextField.addKeyListener(new KeyListener() {

                    public void keyTyped(KeyEvent keyEvent) {

                        logoColorStyleDialog.setErrorMessage("");
                        char charTyped = keyEvent.getKeyChar();
                        if (Character.isLetter(charTyped) && LolaResources.AMINO_ACIDS_STRING.contains(Character.toString(charTyped).toUpperCase())) {
                            logoColorStyleDialog.setErrorMessage("");
                            logoColorStyleDialog.okButtonSetEnabled(true);
                        }
                        else if ((charTyped != KeyEvent.VK_BACK_SPACE) && (charTyped != KeyEvent.VK_DELETE)) {
                            logoColorStyleDialog.setErrorMessage("Invalid symbol: " + charTyped);
                            logoColorStyleDialog.okButtonSetEnabled(false);

                        }

                    }

                    //if ESC is pressed, close the dialog (same as clicking Cancel)
                    public void keyPressed(KeyEvent keyEvent) {
                        if (keyEvent.getKeyCode() == KeyEvent.VK_ESCAPE) {
                            setVisible(false);
                        }
                    }

                    public void keyReleased(KeyEvent keyEvent) {
                    }
                });

                final JButton selectColorButton = new JButton("Select...");
                selectColorButton.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        Color c;
                        c = JColorChooser.showDialog(
                                  ((Component)e.getSource()).getParent(),
                                  "Select Symbol Color", (Color) (currentParamsCopy.getAminoAcidGrouping().getColorList()).get(j));
                        colorBox.setBackground(c);
                        //currentParamsCopy.getAminoAcidGrouping().getColorList().set(j, c);
                    }
                });


                //add widgets to panel
                addGridBagComponent(colorPanel, groupTextField, 0, i+1, 1, 1,
                        GridBagConstraints.LINE_START, GridBagConstraints.NONE);
                addGridBagComponent(colorPanel, colorBox, 1, i+1, 1, 1,
                        GridBagConstraints.LINE_START, GridBagConstraints.NONE);
                addGridBagComponent(colorPanel, selectColorButton, 2, i+1, 1, 1,
                        GridBagConstraints.LINE_START, GridBagConstraints.NONE);
            }

            //error panel to display messages
            JPanel errorPanel = new JPanel(new FlowLayout());
            errorTextArea = new JTextArea();
            errorTextArea.setColumns(30);
            errorTextArea.setRows(4);            
            errorTextArea.setLineWrap(true);
            errorTextArea.setFont(new Font(null, Font.PLAIN, 10));
            errorTextArea.setForeground(Color.RED);
            errorTextArea.setBackground(logoPanel.getBackground());
            errorTextArea.setEditable(false);
            errorPanel.add(errorTextArea);

            //bottom panel with OK/Cancel buttons
            JPanel bottomPanel = new JPanel(new FlowLayout());
            okButton = new JButton("OK");
            okButton.addActionListener(new validateLogoColorStyleAction());
            JButton cancelButton = new JButton("Cancel");
            cancelButton.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent actionEvent) {
                    setVisible(false);
                }
            });
            bottomPanel.add(okButton);
            bottomPanel.add(cancelButton);

            mainPanel.add(colorPanel, BorderLayout.NORTH);
            mainPanel.add(errorPanel, BorderLayout.CENTER);
            mainPanel.add(bottomPanel, BorderLayout.SOUTH);


            this.setContentPane(mainPanel);
            this.setLocationRelativeTo(frame);
            this.setModal(true);

            //if ESC is pressed while this dialog is active, close this dialog
            //TODO - this doesn't work!
            this.addKeyListener(new KeyListener() {
                public void keyTyped(KeyEvent keyEvent) {
                    if (keyEvent.getKeyCode() == KeyEvent.VK_ESCAPE) {
                        logoColorStyleDialog.setVisible(false);
                    }
                }

                public void keyPressed(KeyEvent keyEvent) {
                    if (keyEvent.getKeyCode() == KeyEvent.VK_ESCAPE) {
                        logoColorStyleDialog.setVisible(false);
                    }
                }

                public void keyReleased(KeyEvent keyEvent) {
                    if (keyEvent.getKeyCode() == KeyEvent.VK_ESCAPE) {
                        logoColorStyleDialog.setVisible(false);
                    }
                }
            });


        }

        public void setErrorMessage(String errorMessage) {
            if (errorMessage != null) {
                errorTextArea.setText(errorMessage);
                errorTextArea.setVisible(true);
            }
        }

        public void okButtonSetEnabled(boolean flag) {
            okButton.setEnabled(flag);
        }
    }


    /**
     * ActionListener for the OK button in the logo colour style dialog
     * This listener will validate the
     */
    private class validateLogoColorStyleAction implements ActionListener {

        public void actionPerformed(ActionEvent actionEvent) {
            AminoAcidGrouping aaGrouping = currentParamsCopy.getAminoAcidGrouping();
            int numColors = currentParamsCopy.getAminoAcidGrouping().getNumberOfColors();
            String errorMessage = "";
            boolean isError = false;

            //verify that amino acids symbols are fully represented

            //construct an ArrayList and a concatenated string of the symbols provided
            ArrayList givenSymbols = new ArrayList<String>();
            String givenSymbolsString = "";
            for (int i = 0; i < logoColorTextFields.size(); i++) {
                char s[] = logoColorTextFields.get(i).getText().toUpperCase().toCharArray();
                for (int j = 0; j < s.length; j++) {
                    givenSymbols.add(s[j]);
                    givenSymbolsString += s[j];
                }
            }

            //determine missing residue symbols
            String missingResidues = "";
            for (int i = 0; i < LolaResources.AMINO_ACIDS.length; i++) {
                if (!givenSymbols.contains(LolaResources.AMINO_ACIDS[i])) {
                     missingResidues += LolaResources.AMINO_ACIDS[i];
                }
            }

            //if input contains errors, generate the error message
            errorMessage = "Please correct the following errors:";
            if (missingResidues.length() > 0) {
                isError = true;
                errorMessage += "\nSome symbols are missing: " + missingResidues;
            }
            if (!containsValidSymbols(givenSymbolsString.toCharArray())) {
                isError = true;
                errorMessage += "\nSome characters provided are not valid symbols.";
            }
            if (!containsUniqueSymbols(givenSymbolsString.toCharArray())) {
                isError = true;
                errorMessage += "\nSome symbols have been provided more than once.";
            }
            if (isError) {
                logoColorStyleDialog.setErrorMessage(errorMessage);
                return;
            }

            //if we reach this point, the input has passed the validation checks

            //save the symbol and colour settings
            if (logoColorBoxes.size() == numColors) {
                //we only iterate over logoColorBoxes assuming logoColorTextFields is the same sizes
                for (int i = 0; i < logoColorBoxes.size(); i++) {
                    aaGrouping.setColorSymbols(i, logoColorTextFields.get(i).getText().toUpperCase());
                    aaGrouping.setColor(i, logoColorBoxes.get(i).getBackground());
                }
            }

            //if not done yet, add a "Custom" item in the color style combo box
            if (isCustomColoring == false) {
                //since the flag is false, the Custom item has not been added before, so add it
                logoColorComboBox.addItem(new String("Custom"));
                logoColorComboBox.setSelectedIndex(logoColorComboBox.getModel().getSize() - 1);
                isCustomColoring = true;    //this flag indicates that a custom color was selected
                                            //we blindly assume here that some change was made
                                            //BUT this may not be the case (e.g. OK pressed without changes)
                                            //TODO - verify that a change was really made before setting this flag
            }

            //close the dialog
            logoColorStyleDialog.setVisible(false);
        }

        
        private boolean containsValidSymbols(char[] symbols) {
            if (symbols == null) {
                throw new IllegalArgumentException("Null argument received by symbol validator");
            }
            for (int i = 0; i < symbols.length; i++) {
                if (!LolaResources.AMINO_ACIDS_STRING.contains(Character.toString(symbols[i]).toUpperCase())) {
                    return false;
                }
            }
            return true;
        }

        private boolean containsUniqueSymbols(char[] symbols) {
            if (symbols == null) {
                throw new IllegalArgumentException("Null argument received by symbol uniqueness validator");
            }
            for (int i = 0; i < symbols.length; i++) {
                for (int j = 0; j < symbols.length; j++) {
                    if (i == j) continue;
                    if (symbols[i] == symbols[j]) {
                        return false;
                    }
                }
            }
            return true;
        }
    }




    /**
     * Input verifier for JTextField components.
     * TODO This input verifier doesn't seem to work - fix it.
     */
    class numericalTextVerifier extends InputVerifier {

        /**
         * Returns true iff text can be parsed into an integer.
         * @param jComponent A JTextField component
         * @return boolean
         */
        public boolean verify(JComponent jComponent) {
            if (jComponent instanceof JTextField) {
                String text = ((JTextField) jComponent).getText();
                if (text != null) {
                    try {
                        int i = Integer.valueOf(text);
                        return true;
                    } catch (NumberFormatException e) {
                        return false;
                    }
                }
                return false;
            }
            return false;  //To change body of implemented methods use File | Settings | File Templates.
        }

        public boolean shouldYieldFocus() {
            return false;
        }
    }

    
    /**
     * Handles setting profile filename if the user edits the text field directly without using the file chooser
     */
    private class profileFileNameAction extends AbstractAction implements DocumentListener {

        profileFileNameAction() {
            super();
        }

        //any change that is made is handled the same - parameter is updated
        private void handleChange() {
            String value = profileFileNameTextField.getText();
            if (value != null) {
                File file = new File(value);
                currentParamsCopy.setProfileFile(file);

                //enable open profile button, redraw button, save button
                openProfileButton.setEnabled(true);

                //set output path text field to this profile file's parent path
                String parentPath = file.getParent();
                if (parentPath != null) {
                    outputPathTextField.setText(parentPath);
                }

                //set logo tree output name text field
                String fileName = file.getName()
                        + LolaResources.TREE_FILE_SUFFIX
                        + LolaResources.getFileExtension(currentParamsCopy.getTreeImageFormat());

                logoTreeOutputFilename.setText(fileName);
                currentParamsCopy.setTreeFilename(fileName);
            }
        }

        public void actionPerformed(ActionEvent e) {
            handleChange();
        }

        public void changedUpdate(DocumentEvent e) {
            handleChange();
        }

        public void insertUpdate(DocumentEvent e) {
            handleChange();
        }

        public void removeUpdate(DocumentEvent e) {
            handleChange();
        }
    }

    /**
     * Handles choosing a profile
     */
    private class chooseProfileAction extends AbstractAction {

        chooseProfileAction() {
            super();
        }

        public void actionPerformed(ActionEvent e) {
            //File chosenFile = FileUtil.getFile("Select profiles...", FileUtil.LOAD);
            File chosenFile = browseFile("Select Profile File", false);
            if (chosenFile != null) {
                currentParamsCopy.setProfileFile(chosenFile);
                profileFileNameTextField.setText(chosenFile.toString());
            }
        }
    }

    
    /**
     * Handles setting of the 'unique peptides' parameter
     */
    private class uniquePeptidesCheckBoxAction implements ItemListener {

        public void itemStateChanged(ItemEvent e) {
            if (e.getStateChange() == ItemEvent.DESELECTED) {
                currentParamsCopy.setUniquePeptides(false);
            }
            else if (e.getStateChange() == ItemEvent.SELECTED) {
                currentParamsCopy.setUniquePeptides(true);
            }
        }
    }


    /**
     * Handles setting codon bias specification filename if the user edits the text field directly without using the file chooser
     */
    private class codonBiasFileNameAction extends AbstractAction implements DocumentListener {

        codonBiasFileNameAction() {
            super();
        }

        //any change that is made is handled the same - parameter is updated
        private void handleChange() {
            String value = biasFileNameTextField.getText();
            if (value != null) {
                File file = new File(value);
                currentParamsCopy.setCodonBiasFile(file);
            }
        }

        public void actionPerformed(ActionEvent e) {
            handleChange();
        }

        public void changedUpdate(DocumentEvent e) {
            handleChange();
        }

        public void insertUpdate(DocumentEvent e) {
            handleChange();
        }

        public void removeUpdate(DocumentEvent e) {
            handleChange();
        }
    }

    /**
     * Handles setting codon bias specification filename if the user edits the text field directly without using the file chooser
     */
    private class positionFilterAction extends AbstractAction implements DocumentListener {

        positionFilterAction() {
            super();
        }

        // Filter should contain the positions in ascending order
        private int[] parseFilter(String filter)
        {
            int start = Integer.MIN_VALUE;
            int end = Integer.MAX_VALUE;
            String positions[] = filter.split(",");
            for (int i = 0; i < positions.length; i++) {
                String position = positions[i];
                if (position.indexOf("-") > 0) {
                    //interval
                    String[] startEnd = position.split("-");
                    start = Integer.parseInt(startEnd[0]);
                    end = Integer.parseInt(startEnd[1]);
                }
            }
            return new int[]{start,end};
        }

        //any change that is made is handled the same - parameter is updated
        private void handleChange() {
            String value = positionFilterTextField.getText();

            //TODO add a method to validate the filter string and output a visible message if the string is not valid
            // If the filter string is invalid, an exception is thrown and the tree will not be generated

            if (value != null) {
                currentParamsCopy.setPositionFilter(value);
            }
        }

        public void actionPerformed(ActionEvent e) {
            handleChange();
        }

        public void changedUpdate(DocumentEvent e) {
            handleChange();
        }

        public void insertUpdate(DocumentEvent e) {
            handleChange();
        }

        public void removeUpdate(DocumentEvent e) {
            handleChange();
        }
    }

    /**
     * Handles choosing a codon bias file
     */
    private class chooseCodonBiasAction extends AbstractAction {

        chooseCodonBiasAction() {
            super();
        }

        public void actionPerformed(ActionEvent e) {
            //File chosenFile = FileUtil.getFile("Select Codon Bias Specification...", FileUtil.LOAD);
            File chosenFile = browseFile("Select Codon Bias Specification File", false);
            if (chosenFile != null) {
                currentParamsCopy.setCodonBiasFile(chosenFile);
                biasFileNameTextField.setText(chosenFile.toString());
            }
        }
    }


    /**
     * Handles setting output path if the user edits the text field directly without using the file chooser
     */
    private class outputPathAction extends AbstractAction implements DocumentListener {

        outputPathAction() {
            super();
        }

        //any change that is made is handled the same - parameter is updated
        private void handleChange() {
            String value = outputPathTextField.getText();
            if (value != null) {
                File file = new File(value);
                currentParamsCopy.setOutputPath(file);
            }
        }

        public void actionPerformed(ActionEvent e) {
            handleChange();
        }

        public void changedUpdate(DocumentEvent e) {
            handleChange();
        }

        public void insertUpdate(DocumentEvent e) {
            handleChange();
        }

        public void removeUpdate(DocumentEvent e) {
            handleChange();
        }
    }

    /**
     * Handles choosing an output path using the file chooser
     */
    private class chooseOutputPathAction extends AbstractAction {

        chooseOutputPathAction() {
            super();
        }

        public void actionPerformed(ActionEvent e) {
            //File chosenFile = FileUtil.getFile("Select Codon Bias Specification...", FileUtil.LOAD);
            File chosenFile = browseDirectory("Select Output Path", true);
            if (chosenFile != null) {
                currentParamsCopy.setOutputPath(chosenFile);
                outputPathTextField.setText(chosenFile.toString());
            }
        }
    }

    /**
     * Handles setting output path if the user edits the text field directly without using the file chooser
     */
    private class logoStartIndexTextFieldAction extends AbstractAction implements DocumentListener {

        logoStartIndexTextFieldAction() {
            super();
        }

        //any change that is made is handled the same - parameter is updated
        private void handleChange() {
            String value = logoStartIndexTextField.getText();
            if (value != null) {
                try {
                    currentParamsCopy.setLogoStartIndex(Integer.valueOf(value));
                } catch (NumberFormatException e) {
                    //TODO report a visible error message if this happens
                }
            }
        }

        public void actionPerformed(ActionEvent e) {
            handleChange();
        }

        public void changedUpdate(DocumentEvent e) {
            handleChange();
        }

        public void insertUpdate(DocumentEvent e) {
            handleChange();
        }

        public void removeUpdate(DocumentEvent e) {
            handleChange();
        }
    }
    /**
     * Handles setting of values in text fields
     */
    private class textFieldChangeListener implements PropertyChangeListener {

        public void propertyChange(PropertyChangeEvent event) {
            Object obj = event.getSource();
            if (obj instanceof JTextField) {
                JTextField textField = (JTextField) obj;
                String text = textField.getText();
                if (text != null) {
                    //deal with negative value
                    boolean isNegative = false;
                    if ((text.length() > 1) && text.substring(0,1).equals("-")) {
                        isNegative = true;
                        Number value = (Number) Integer.valueOf(text);
                    }
                    //logo start index
                    if (textField == logoStartIndexTextField) {
                        
//                        Number value = (Number) Integer.valueOf(text);
                        Number value = (Number) logoStartIndexTextField.getValue();
                        if (value != null) {
                            currentParamsCopy.setLogoStartIndex(value.intValue());
                        } else {
                            //TODO report a visible error message if this happens
                            logoStartIndexTextField.setValue(currentParamsCopy.getLogoStartIndex());
                        }
                    }
                    //logo height
                    else if (textField == logoHeightTextField) {            
                        Number value = (Number) logoHeightTextField.getValue();
                        if ((value != null) && (value.intValue() >= LolaResources.LOGO_HEIGHT_MIN)
                                && (value.intValue() <= LolaResources.LOGO_HEIGHT_MAX)) {
                            currentParamsCopy.setLogoHeight(value.intValue());
                        } else {
                            //TODO report a visible error message if this happens
                            logoHeightTextField.setValue(currentParamsCopy.getLogoHeight());
                        }
                    }
                    //logo trim percentage
                    else if (textField == logoTrimPercentageTextField) {
                        Number value = (Number) logoTrimPercentageTextField.getValue();
                        if ((value != null) && (value.doubleValue() >= LolaResources.LOGO_TRIM_PERCENTAGE_MIN)
                                && (value.doubleValue() <= LolaResources.LOGO_TRIM_PERCENTAGE_MAX)) {
                            currentParamsCopy.setLogoTrimPercentage(value.doubleValue());
                        } else {
                            //TODO report a visible error message if this happens
                            logoTrimPercentageTextField.setValue(currentParamsCopy.getLogoTrimPercentage());
                        }
                    }
                    //logo tree title
                    else if (textField == logoTreeTitle) {
                        if ((text != null) && (text.length() > 0)) {
                            currentParamsCopy.setTreeTitle(text);
                        }
                        else {
                            currentParamsCopy.setTreeTitle("");
                        }
                    }
                    //logo tree filename
                    else if (textField == logoTreeOutputFilename) {
                        if ((text != null) && (text.length() > 0)) {
                            currentParamsCopy.setTreeFilename(text);
                        }
                    }
                    //logo tree leaf node trim factor
                    else if (textField == logoTreeLeafTrimFactor) {
                        Number value = (Number) logoTreeLeafTrimFactor.getValue();
                        if ((value != null) && (value.doubleValue() >= Brain.LEAFNODE_TRIM_FACTOR_MIN)
                                && (value.doubleValue() <= Brain.LEAFNODE_TRIM_FACTOR_MAX)) {
                            currentParamsCopy.setTreeLeafTrimFactor(value.doubleValue());
                        } else {
                            //TODO report a visible error message if this happens
                            logoTreeLeafTrimFactor.setValue(currentParamsCopy.getTreeLeafTrimFactor());
                        }
                    }
                    //logo tree internal node trim factor
                    else if (textField == logoTreeInternalNodeTrimFactor) {
                        Number value = (Number) logoTreeInternalNodeTrimFactor.getValue();
                        if ((value != null) && (value.doubleValue() >= Brain.INTERNALNODE_TRIM_FACTOR_MIN)
                                && (value.doubleValue() <= Brain.INTERNALNODE_TRIM_FACTOR_MAX)) {
                            currentParamsCopy.setTreeInternalNodeTrimFactor(value.doubleValue());
                        } else {
                            //TODO report a visible error message if this happens
                            logoTreeInternalNodeTrimFactor.setValue(currentParamsCopy.getTreeInternalNodeTrimFactor());
                        }
                    }
                }
            }
        }
    }


    /**
     * Handles choosing a logo output file format
     */
    private class selectLogoFormatAction extends AbstractAction {
        selectLogoFormatAction() {
            super();
        }

        public void actionPerformed(ActionEvent event) {
            if ("comboBoxChanged".equals(event.getActionCommand())) {
                JComboBox cb = (JComboBox) event.getSource();
                String formatName = (String) cb.getSelectedItem();
                currentParamsCopy.setLogoImageFormat(formatName);
            }
        }
    }

    /**
     * Handles choosing a logo color style
     */
    private class selectColorStyleAction extends AbstractAction {
        selectColorStyleAction() {
            super();
        }

        public void actionPerformed(ActionEvent event) {
            if ("comboBoxChanged".equals(event.getActionCommand())) {
                JComboBox cb = (JComboBox) event.getSource();
                String colorName = (String) cb.getSelectedItem();
                if (!logoColorComboBox.getSelectedItem().toString().equalsIgnoreCase("Custom")) {
                    currentParamsCopy.getAminoAcidGrouping().setColoring(colorName);
                }
                logoColorComboBox.setToolTipText(getColorComboBoxTooltip());
            }
        }
    }

    /**
     * Handles choosing a logo tree output file format
     */
    private class selectLogoTreeFormatAction extends AbstractAction {
        selectLogoTreeFormatAction() {
            super();
        }

        public void actionPerformed(ActionEvent event) {
            if ("comboBoxChanged".equals(event.getActionCommand())) {
                JComboBox cb = (JComboBox) event.getSource();
                String formatName = (String) cb.getSelectedItem();
                currentParamsCopy.setTreeImageFormat(formatName);
                
                //set logo tree output name text field
                File file = currentParamsCopy.getProfileFile();
                String fileName = file.getName();
                String outputFileName = fileName
                        + LolaResources.TREE_FILE_SUFFIX
                        + LolaResources.getFileExtension(currentParamsCopy.getTreeImageFormat());

                logoTreeOutputFilename.setText(outputFileName);
                currentParamsCopy.setTreeFilename(outputFileName);

                // TODO: Should also update LogoTree getOutputPath to contain the right output path 
            }
        }
    }


    /**
     * Handles choosing a logo tree ordering method
     */
    private class selectLogoTreeOrderingAction extends AbstractAction {
        selectLogoTreeOrderingAction() {
            super();
        }

        public void actionPerformed(ActionEvent event) {
            if ("comboBoxChanged".equals(event.getActionCommand())) {
                JComboBox cb = (JComboBox) event.getSource();
                String methodName = (String) cb.getSelectedItem();
                currentParamsCopy.setTreeLeafOrderingMethod(methodName);
            }
        }
    }

    /**
     * Handles choosing a logo tree amino acid grouping method
     */
    private class selectGroupingMethodAction extends AbstractAction {
        selectGroupingMethodAction() {
            super();
        }

        public void actionPerformed(ActionEvent event) {
            if ("comboBoxChanged".equals(event.getActionCommand())) {
                JComboBox cb = (JComboBox) event.getSource();
                String methodName = (String) cb.getSelectedItem();
                currentParamsCopy.getAminoAcidGrouping().setGrouping(methodName);
                groupingMethodComboBox.setToolTipText(getGroupingComboBoxTooltip());
                
                //changing the grouping method also resets the colour scheme (unless No Grouping or a Custom coloring was selected)
                if (!methodName.equalsIgnoreCase(AminoAcidGrouping.GROUPING_NO_GROUPING) && !isCustomColoring) {

                    //we assume here that there is a coloring method with the same name as the selected grouping method
                    currentParamsCopy.getAminoAcidGrouping().setColoring(methodName);

                    //change the selected logo coloring method in the logo color combo box
                    for (int i = 0; i < AminoAcidGrouping.COLORING_METHODS.length; i++) {
                        if (methodName.equalsIgnoreCase(AminoAcidGrouping.COLORING_METHODS[i])) {
                            logoColorComboBox.setSelectedIndex(i);
                            break;
                        }
                    }
                }
            }
        }
    }
    private class selectCheckBoxAction extends AbstractAction
    {
      public void actionPerformed(ActionEvent actionEvent) {
        AbstractButton abstractButton = (AbstractButton) actionEvent.getSource();
        boolean selected = abstractButton.getModel().isSelected();
        currentParamsCopy.setDrawAxisLabels(selected);

      }
    };
    /**
     * Displays a file dialog with the given title and returns the selected directory.
     * @return a File object for the selected file.
     */
    private File browseDirectory(String title, boolean directoryOnly) {
        //FileDialog fileDialog = new FileDialog(mainFrame, title, FileDialog.LOAD);
        JFileChooser fileDialog = new JFileChooser();
        if (directoryOnly) {
            fileDialog.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        }
        fileDialog.setDialogTitle(title);
        fileDialog.setVisible(true);
        fileDialog.showDialog(mainFrame, "OK");
        File file = fileDialog.getSelectedFile();

        return file;
    }

    /**
     * Displays a file dialog with the given title and returns the selected file.
     * @param title
     * @param directoryOnly
     * @return
     */
    private File browseFile(String title, boolean directoryOnly) {
        FileDialog fileDialog = new FileDialog(mainFrame, title, FileDialog.LOAD);
        fileDialog.setTitle(title);
        fileDialog.setVisible(true);

        String filePath = fileDialog.getFile();
        if (filePath != null) {
            return new File(fileDialog.getDirectory()+"/"+ fileDialog.getFile() );
        }
        return null;
    }

    /**
     * Action for the cancel button (does not save parameters)
     */
    private class clearFileFieldsAction extends AbstractAction {

        clearFileFieldsAction() {
            super();
        }

        public void actionPerformed(ActionEvent e) {
            profileFileNameTextField.setText(null);
            biasFileNameTextField.setText(null);
            outputPathTextField.setText(null);
            logoTreeOutputFilename.setText(null);
            openProfileButton.setEnabled(false);
            createLogoTreeButton.setEnabled(false);
            saveLogoTreeButton.setEnabled(false);


        }
    }

    /**
     * Close all selected profiles.
     */
    private class closeSelectedProfilesAction implements ActionListener {

        public void actionPerformed(ActionEvent actionEvent) {

            while (selectedProfiles.size() > 0) {
                Profile p = (Profile) selectedProfiles.get(0);
                allProfiles.removeElement(p);
                selectedProfiles.removeElement(p);
            }
            profileList.setSelectedIndex(0);
        }
    }

    /**
     * Close all open profiles.
     */
    private class closeAllProfilesAction implements ActionListener {

        public void actionPerformed(ActionEvent actionEvent) {

            allProfiles.removeAllElements();
            closeSelectedProfilesButton.setEnabled(false);

            logoPane.setViewportView(null);
        }
    }

    /**
     * Exit the application.
     */
    private class quitAction implements ActionListener {

        public void actionPerformed(ActionEvent actionEvent) {
            Runtime.getRuntime().exit(0);
        }
    }

    /**
     * Displays the logo tree for all open profiles in the logo pane
     */
    private void displayLogoTree() {
        try {
            LogoTree tree = allProfiles.getLogoTree();
            if (tree != null) {
                Image image = tree.getImage();
                if (image != null) {
                    Icon icon = new ImageIcon(image);
                    logoPane.setViewportView(new JLabel(icon));
                }
                else {
                    throw new Exception ("Could not obtain logo tree image.");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * Display the (last) selected image based on the logo selected in profileList
     */
    private void displaySelectedLogo() {
        Object[] objs = profileList.getSelectedValues();
        if (objs.length < 1) return;
        Profile profile = (Profile)objs[objs.length-1];
        Icon icon = new ImageIcon(profile.getImage());
        logoPane.setViewportView(new JLabel(icon));
    }


    /**
     * A mouse listener for the main JTabbedPane.
     * Displays the logo tree image in the logo pane when the Logo Tree tab is selected with the mosue. 
     */
    private class tabbedPaneMouseListener implements MouseListener {

        public void mouseClicked(MouseEvent mouseEvent) {
            Component selectedComponent = tabbedPane.getSelectedComponent();
            if (selectedComponent.equals(treePanel)) {
                displayLogoTree();
            }
            else if (selectedComponent.equals(logoPanel)) {
                displaySelectedLogo();
            }
        }

        public void mousePressed(MouseEvent mouseEvent) {
            //do nothing
        }

        public void mouseReleased(MouseEvent mouseEvent) {
            //do nothing
        }

        public void mouseEntered(MouseEvent mouseEvent) {
            //do nothing
        }

        public void mouseExited(MouseEvent mouseEvent) {
            //do nothing
        }
    }

    /**
     * This is an attempt to fire an event to MakeLogoTreeAction and, when done, display the generated logo tree.
     * TODO - make sure this is the "correct" way of doing this, i.e. is it "ok" for an action to fire another action??
     *
     */
    private class generateLogoTreeAction implements ActionListener {
        public void actionPerformed(ActionEvent actionEvent) {
            ActionListener action = null;
            try {
                action = new MakeLogoTreeAction(allProfiles, currentParamsCopy);
            } catch (OutOfMemoryError e) {
                System.out.println("Out of memory!!");
                JOptionPane.showMessageDialog(mainFrame, "Out of memory!");
            }
            action.actionPerformed(actionEvent);
            displayLogoTree();
        }
    }
}
