org.fhcrc.cpl.viewer.quant.gui.QuantitationReviewer.java Source code

Java tutorial

Introduction

Here is the source code for org.fhcrc.cpl.viewer.quant.gui.QuantitationReviewer.java

Source

/*
 * Copyright (c) 2003-2012 Fred Hutchinson Cancer Research Center
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.fhcrc.cpl.viewer.quant.gui;

import org.fhcrc.cpl.toolbox.gui.chart.*;
import org.fhcrc.cpl.toolbox.gui.ListenerHelper;
import org.fhcrc.cpl.toolbox.gui.HtmlViewerPanel;
import org.fhcrc.cpl.toolbox.gui.widget.SplashFrame;
import org.fhcrc.cpl.toolbox.TextProvider;
import org.fhcrc.cpl.toolbox.ApplicationContext;
import org.fhcrc.cpl.toolbox.Rounder;
import org.fhcrc.cpl.toolbox.commandline.CommandLineModule;
import org.fhcrc.cpl.toolbox.commandline.CommandLineModuleExecutionException;
import org.fhcrc.cpl.toolbox.commandline.arguments.*;
import org.fhcrc.cpl.toolbox.filehandler.SimpleXMLEventRewriter;
import org.fhcrc.cpl.toolbox.filehandler.TempFileManager;
import org.fhcrc.cpl.toolbox.proteomics.feature.Spectrum;
import org.fhcrc.cpl.toolbox.proteomics.filehandler.ProtXmlReader;
import org.fhcrc.cpl.toolbox.proteomics.ProteinUtilities;
import org.fhcrc.cpl.viewer.gui.WorkbenchFileChooser;
import org.fhcrc.cpl.viewer.gui.WorkbenchFrame;
import org.fhcrc.cpl.viewer.gui.ViewerInteractiveModuleFrame;
import org.fhcrc.cpl.viewer.Localizer;
import org.fhcrc.cpl.viewer.ViewerUserManualGenerator;
import org.fhcrc.cpl.viewer.qa.QAUtilities;
import org.fhcrc.cpl.viewer.commandline.modules.BaseViewerCommandLineModuleImpl;
import org.fhcrc.cpl.viewer.quant.commandline.PeptideQuantVisualizationCLM;
import org.fhcrc.cpl.viewer.quant.commandline.ProteinQuantChartsCLM;
import org.fhcrc.cpl.viewer.quant.QuantEvent;
import org.fhcrc.cpl.viewer.quant.QuantEventAssessor;
import org.apache.log4j.Logger;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.event.ChartProgressEvent;

import javax.swing.*;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.ListSelectionEvent;
import javax.xml.stream.events.XMLEvent;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.EndElement;
import javax.xml.stream.XMLStreamException;
import javax.xml.namespace.QName;
import javax.imageio.ImageIO;
import java.util.*;
import java.util.List;
import java.awt.event.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URL;
import java.nio.channels.FileChannel;
import java.util.logging.Level;

/**
 * This is the main GUI screen for Qurate.  It uses SwiXML for the menu and for the broad outlines, but most of it
 * is done right here.
 */
public class QuantitationReviewer extends JDialog {
    //Quantitation events
    List<QuantEvent> quantEvents;
    //Loaded quantitation summary file
    protected File quantFile;

    //The multi-chart display panel
    protected TabbedMultiChartDisplayPanel multiChartDisplay;

    //This is the only way we keep track of the currently-displayed quantitation event.  An index into quantEvents
    protected int displayedEventIndex = 0;

    //everything
    public JPanel contentPanel;
    public JSplitPane splitPane;
    //left of splitpane
    public JPanel leftPanel;
    //right of splitpane
    public JPanel rightPanel;
    //for navigating between events
    public JPanel navigationPanel;
    JButton backButton;
    JButton forwardButton;
    JButton showEventSummaryButton;

    public ProteinQuantChartsCLM settingsCLM;

    protected SplashFrame splashFrame;
    protected final URL splashImageURL = QuantitationReviewer.class.getResource("qurate_splash.gif");

    //For assigning event statuses
    public JPanel curationPanel;
    protected ButtonGroup quantCurationButtonGroup;
    //need a reference to this in order to change title
    protected JRadioButton onePeakRatioRadioButton;
    ButtonModel unknownRadioButtonModel;
    ButtonModel goodRadioButtonModel;
    ButtonModel badRadioButtonModel;
    ButtonModel onePeakRadioButtonModel;

    protected ButtonGroup idCurationButtonGroup;
    ButtonModel idUnknownRadioButtonModel;
    ButtonModel idGoodRadioButtonModel;
    ButtonModel idBadRadioButtonModel;
    protected JButton saveChangesButton;
    protected JButton filterPepXMLButton;
    protected JTextField commentTextField;

    //For displaying automatic assessment
    public JPanel assessmentPanel;
    protected JTextField assessmentTypeTextField;
    protected JTextField assessmentDescTextField;

    protected ProteinQuantSummaryFrame quantSummaryFrame;

    //theoretical peak distribution
    public JPanel theoreticalPeaksPanel;
    protected PanelWithPeakChart theoreticalPeaksChart;

    protected ProteinSummarySelectorFrame proteinSummarySelector;

    //menu actions
    public Action helpAction = new HelpAction();
    public Action exitAction = new ExitAction();
    public Action openFileAction;
    public Action createChartsAction;
    public Action saveAction = new SaveAction(this);
    public Action filterPepXMLAction;
    public Action proteinSummaryAction;
    public Action aboutAction = new AbstractAction("About") {
        public void actionPerformed(ActionEvent e) {
            showSplashScreen();
        }
    };
    public Action summaryChartsAction = new SummaryChartsAction();

    protected QuantEventsSummaryTable eventSummaryTable;
    protected Frame eventSummaryFrame;

    //event properties
    protected QuantEvent.QuantEventPropertiesTable propertiesTable;
    protected JScrollPane propertiesScrollPane;

    //Status message
    public JPanel statusPanel;
    public JLabel messageLabel;

    //Sizes of things
    protected int leftPanelWidth = 250;
    protected int rightPanelWidth = 990;
    protected int imagePanelWidth = 980;
    protected int fullWidth = 1200;
    protected int fullHeight = 1000;
    protected int propertiesWidth = leftPanelWidth - 20;
    protected int propertiesHeight = 300;
    protected int chartPaneHeight = 950;
    protected int theoreticalPeaksPanelHeight = 150;

    protected JFrame summaryChartsFrame;
    protected TabbedMultiChartDisplayPanel summaryChartsPanel;

    protected static Logger _log = Logger.getLogger(QuantitationReviewer.class);

    public QuantitationReviewer(boolean showSplash, boolean showFileOpen) {
        if (showSplash)
            showSplashScreen();

        initGUI();
        if (showFileOpen)
            openFileAction.actionPerformed(null);

        if (showSplash)
            splashFrame.dispose();
    }

    /**
     * No-arg constructor doesn't pop up a file chooser, but it does show splash screen
     */
    public QuantitationReviewer() {
        this(true, false);
    }

    public QuantitationReviewer(List<QuantEvent> quantEvents) {
        showSplashScreen();

        initGUI();
        displayQuantEvents(quantEvents);
        splashFrame.dispose();
    }

    public QuantitationReviewer(File quantFile) throws IOException {
        showSplashScreen();

        initGUI();
        displayQuantFile(quantFile);
        splashFrame.dispose();
    }

    public void displayQuantEvents(List<QuantEvent> quantEvents) {
        this.quantEvents = quantEvents;
        displayedEventIndex = 0;
        eventSummaryTable.displayEvents(quantEvents);
        buildSummaryCharts();
        displayCurrentQuantEvent(true);
    }

    public void displayQuantFile(File quantFile) throws IOException {
        this.quantFile = quantFile;
        quantEvents = QuantEvent.loadQuantEvents(quantFile);
        //handling for empty file
        if (quantEvents != null && !quantEvents.isEmpty()) {
            displayQuantEvents(quantEvents);
            setMessage("Loaded quantitation events from file " + quantFile.getAbsolutePath());
        } else
            setMessage("No events found in file " + quantFile.getAbsolutePath());
    }

    /**
     * Initialize all GUI components and display the UI
     */
    protected void initGUI() {
        settingsCLM = new ProteinQuantChartsCLM(false);

        setTitle("Qurate");
        try {
            setIconImage(ImageIO.read(WorkbenchFrame.class.getResourceAsStream("icon.gif")));
        } catch (Exception e) {
        }

        try {
            Localizer.renderSwixml("org/fhcrc/cpl/viewer/quant/gui/QuantitationReviewer.xml", this);
            assert null != contentPanel;
        } catch (Exception x) {
            ApplicationContext.errorMessage("error creating dialog", x);
            throw new RuntimeException(x);
        }

        //Menu
        openFileAction = new OpenFileAction(this);
        createChartsAction = new CreateChartsAction();
        filterPepXMLAction = new FilterPepXMLAction(this);
        proteinSummaryAction = new ProteinSummaryAction(this);

        try {
            JMenuBar jmenu = (JMenuBar) Localizer.getSwingEngine(this)
                    .render("org/fhcrc/cpl/viewer/quant/gui/QuantitationReviewerMenu.xml");
            for (int i = 0; i < jmenu.getMenuCount(); i++)
                jmenu.getMenu(i).getPopupMenu().setLightWeightPopupEnabled(false);
            this.setJMenuBar(jmenu);
        } catch (Exception x) {
            ApplicationContext.errorMessage(TextProvider.getText("ERROR_LOADING_MENUS"), x);
            throw new RuntimeException(x);
        }

        //Global stuff
        setSize(fullWidth, fullHeight);
        setContentPane(contentPanel);
        ListenerHelper helper = new ListenerHelper(this);

        GridBagConstraints gbc = new GridBagConstraints();
        gbc.fill = GridBagConstraints.BOTH;
        gbc.anchor = GridBagConstraints.PAGE_START;
        gbc.gridwidth = GridBagConstraints.REMAINDER;
        gbc.insets = new Insets(5, 5, 5, 5);
        gbc.weighty = 1;
        gbc.weightx = 1;

        leftPanel.setLayout(new GridBagLayout());
        leftPanel.setBorder(BorderFactory.createLineBorder(Color.gray));

        //Properties panel stuff
        propertiesTable = new QuantEvent.QuantEventPropertiesTable();
        propertiesScrollPane = new JScrollPane();
        propertiesScrollPane.setViewportView(propertiesTable);
        propertiesScrollPane.setMinimumSize(new Dimension(propertiesWidth, propertiesHeight));

        //event summary table; disembodied
        eventSummaryTable = new QuantEventsSummaryTable();
        eventSummaryTable.setVisible(true);
        ListSelectionModel tableSelectionModel = eventSummaryTable.getSelectionModel();
        tableSelectionModel.addListSelectionListener(new EventSummaryTableListSelectionHandler());
        JScrollPane eventSummaryScrollPane = new JScrollPane();
        eventSummaryScrollPane.setViewportView(eventSummaryTable);
        eventSummaryScrollPane.setSize(propertiesWidth, propertiesHeight);
        eventSummaryFrame = new Frame("All Events");
        eventSummaryFrame.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent event) {
                eventSummaryFrame.setVisible(false);
            }
        });
        eventSummaryFrame.setSize(950, 450);
        eventSummaryFrame.add(eventSummaryScrollPane);

        //fields related to navigation
        navigationPanel = new JPanel();
        backButton = new JButton("<");
        backButton.setToolTipText("Previous Event");
        backButton.setMaximumSize(new Dimension(50, 30));
        backButton.setEnabled(false);
        forwardButton = new JButton(">");
        forwardButton.setToolTipText("Next Event");
        forwardButton.setMaximumSize(new Dimension(50, 30));
        forwardButton.setEnabled(false);
        showEventSummaryButton = new JButton("Show All");
        showEventSummaryButton.setToolTipText("Show all events in a table");
        showEventSummaryButton.setEnabled(false);

        helper.addListener(backButton, "buttonBack_actionPerformed");
        helper.addListener(forwardButton, "buttonForward_actionPerformed");
        helper.addListener(showEventSummaryButton, "buttonShowEventSummary_actionPerformed");

        gbc.fill = GridBagConstraints.NONE;
        gbc.gridwidth = GridBagConstraints.RELATIVE;
        gbc.anchor = GridBagConstraints.WEST;
        navigationPanel.add(backButton, gbc);
        gbc.gridwidth = GridBagConstraints.RELATIVE;
        navigationPanel.add(forwardButton, gbc);
        gbc.gridwidth = GridBagConstraints.REMAINDER;
        navigationPanel.add(showEventSummaryButton, gbc);
        gbc.fill = GridBagConstraints.BOTH;
        navigationPanel.setBorder(BorderFactory.createTitledBorder("Event"));
        gbc.anchor = GridBagConstraints.PAGE_START;

        //Fields related to curation of events
        curationPanel = new JPanel();
        curationPanel.setLayout(new GridBagLayout());
        curationPanel.setBorder(BorderFactory.createTitledBorder("Curation"));
        //Quantitation curation
        JPanel quantCurationPanel = new JPanel();
        quantCurationPanel.setLayout(new GridBagLayout());
        quantCurationPanel.setBorder(BorderFactory.createTitledBorder("Quantitation"));
        quantCurationButtonGroup = new ButtonGroup();
        JRadioButton unknownRadioButton = new JRadioButton("?");
        JRadioButton goodRadioButton = new JRadioButton("Good");
        JRadioButton badRadioButton = new JRadioButton("Bad");
        onePeakRatioRadioButton = new JRadioButton("1-Peak");

        unknownRadioButton.setEnabled(false);
        goodRadioButton.setEnabled(false);
        badRadioButton.setEnabled(false);
        onePeakRatioRadioButton.setEnabled(false);

        quantCurationButtonGroup.add(unknownRadioButton);
        quantCurationButtonGroup.add(goodRadioButton);
        quantCurationButtonGroup.add(badRadioButton);
        quantCurationButtonGroup.add(onePeakRatioRadioButton);

        unknownRadioButtonModel = unknownRadioButton.getModel();
        goodRadioButtonModel = goodRadioButton.getModel();
        badRadioButtonModel = badRadioButton.getModel();
        onePeakRadioButtonModel = onePeakRatioRadioButton.getModel();

        helper.addListener(unknownRadioButton, "buttonCuration_actionPerformed");
        helper.addListener(goodRadioButton, "buttonCuration_actionPerformed");
        helper.addListener(badRadioButton, "buttonCuration_actionPerformed");
        helper.addListener(onePeakRadioButtonModel, "buttonCuration_actionPerformed");

        gbc.anchor = GridBagConstraints.WEST;
        quantCurationPanel.add(unknownRadioButton, gbc);
        quantCurationPanel.add(badRadioButton, gbc);
        quantCurationPanel.add(goodRadioButton, gbc);
        quantCurationPanel.add(onePeakRatioRadioButton, gbc);

        gbc.anchor = GridBagConstraints.PAGE_START;
        //ID curation
        JPanel idCurationPanel = new JPanel();
        idCurationPanel.setLayout(new GridBagLayout());
        idCurationPanel.setBorder(BorderFactory.createTitledBorder("ID"));
        idCurationButtonGroup = new ButtonGroup();
        JRadioButton idUnknownRadioButton = new JRadioButton("?");
        JRadioButton idGoodRadioButton = new JRadioButton("Good");
        JRadioButton idBadRadioButton = new JRadioButton("Bad");
        idUnknownRadioButton.setEnabled(false);
        idGoodRadioButton.setEnabled(false);
        idBadRadioButton.setEnabled(false);

        idCurationButtonGroup.add(idUnknownRadioButton);
        idCurationButtonGroup.add(idGoodRadioButton);
        idCurationButtonGroup.add(idBadRadioButton);
        idUnknownRadioButtonModel = idUnknownRadioButton.getModel();
        idGoodRadioButtonModel = idGoodRadioButton.getModel();
        idBadRadioButtonModel = idBadRadioButton.getModel();
        helper.addListener(idUnknownRadioButton, "buttonIDCuration_actionPerformed");
        helper.addListener(idGoodRadioButton, "buttonIDCuration_actionPerformed");
        helper.addListener(idBadRadioButton, "buttonIDCuration_actionPerformed");
        gbc.anchor = GridBagConstraints.WEST;
        idCurationPanel.add(idUnknownRadioButton, gbc);
        idCurationPanel.add(idBadRadioButton, gbc);
        idCurationPanel.add(idGoodRadioButton, gbc);

        gbc.gridwidth = GridBagConstraints.RELATIVE;
        curationPanel.add(quantCurationPanel, gbc);
        gbc.gridwidth = GridBagConstraints.REMAINDER;
        curationPanel.add(idCurationPanel, gbc);

        //curation comment
        commentTextField = new JTextField();
        commentTextField.setToolTipText("Comment on this event");
        //saves after every keypress.  Would be more efficient to save when navigating away or saving to file
        commentTextField.addKeyListener(new KeyAdapter() {
            public void keyReleased(KeyEvent e) {
                if (quantEvents == null)
                    return;
                QuantEvent quantEvent = quantEvents.get(displayedEventIndex);
                //save the comment, being careful about tabs and new lines
                quantEvent.setComment(commentTextField.getText().replace("\t", " ").replace("\n", " "));
            }

            public void keyTyped(KeyEvent e) {
            }

            public void keyPressed(KeyEvent e) {
            }
        });
        curationPanel.add(commentTextField, gbc);

        assessmentPanel = new JPanel();
        assessmentPanel.setLayout(new GridBagLayout());
        assessmentPanel.setBorder(BorderFactory.createTitledBorder("Algorithmic Assessment"));
        assessmentTypeTextField = new JTextField();
        assessmentTypeTextField.setEditable(false);
        assessmentPanel.add(assessmentTypeTextField, gbc);
        assessmentDescTextField = new JTextField();
        assessmentDescTextField.setEditable(false);
        assessmentPanel.add(assessmentDescTextField, gbc);

        //Theoretical peak distribution
        gbc.fill = GridBagConstraints.NONE;
        gbc.anchor = GridBagConstraints.CENTER;
        theoreticalPeaksPanel = new JPanel();
        theoreticalPeaksPanel.setBorder(BorderFactory.createTitledBorder("Theoretical Peaks"));
        theoreticalPeaksPanel.setLayout(new GridBagLayout());
        theoreticalPeaksPanel.setMinimumSize(new Dimension(leftPanelWidth - 10, theoreticalPeaksPanelHeight));
        theoreticalPeaksPanel.setMaximumSize(new Dimension(1200, theoreticalPeaksPanelHeight));
        showTheoreticalPeaks();

        //Add everything to the left panel
        gbc.insets = new Insets(0, 5, 0, 5);
        gbc.fill = GridBagConstraints.BOTH;
        gbc.anchor = GridBagConstraints.PAGE_START;
        leftPanel.addComponentListener(new LeftPanelResizeListener());
        gbc.weighty = 10;
        gbc.fill = GridBagConstraints.VERTICAL;
        leftPanel.add(propertiesScrollPane, gbc);
        gbc.fill = GridBagConstraints.NONE;
        gbc.weighty = 1;
        gbc.anchor = GridBagConstraints.PAGE_END;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        leftPanel.add(assessmentPanel, gbc);
        leftPanel.add(theoreticalPeaksPanel, gbc);
        gbc.fill = GridBagConstraints.HORIZONTAL;
        leftPanel.add(curationPanel, gbc);
        leftPanel.add(navigationPanel, gbc);
        gbc.fill = GridBagConstraints.BOTH;
        gbc.weighty = 1;
        gbc.anchor = GridBagConstraints.PAGE_START;

        //Chart display
        multiChartDisplay = new TabbedMultiChartDisplayPanel();
        multiChartDisplay.setResizeDelayMS(0);

        rightPanel.addComponentListener(new RightPanelResizeListener());
        rightPanel.add(multiChartDisplay, gbc);

        //status message
        messageLabel.setBackground(Color.WHITE);
        messageLabel.setFont(Font.decode("verdana plain 12"));
        messageLabel.setText(" ");

        setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
        //paranoia.  Sometimes it seems Qurate doesn't exit when you close every window
        addWindowStateListener(new WindowStateListener() {
            public void windowStateChanged(WindowEvent e) {
                if (e.getNewState() == WindowEvent.WINDOW_CLOSED) {
                    dispose();
                    System.exit(0);
                }
            }
        });

    }

    //button actions

    public void buttonBack_actionPerformed(ActionEvent event) {
        if (displayedEventIndex > 0) {
            displayedEventIndex--;
            displayCurrentQuantEvent(true);
        }
    }

    public void buttonForward_actionPerformed(ActionEvent event) {
        if (displayedEventIndex < quantEvents.size() - 1) {
            displayedEventIndex++;
            displayCurrentQuantEvent(true);
        }
    }

    public void buttonShowEventSummary_actionPerformed(ActionEvent event) {
        eventSummaryFrame.setVisible(true);
    }

    public void buttonCuration_actionPerformed(ActionEvent event) {
        QuantEvent quantEvent = quantEvents.get(displayedEventIndex);

        ButtonModel selectedButtonModel = quantCurationButtonGroup.getSelection();
        if (selectedButtonModel == goodRadioButtonModel)
            quantEvent.setQuantCurationStatus(QuantEvent.CURATION_STATUS_GOOD);
        else if (selectedButtonModel == badRadioButtonModel)
            quantEvent.setQuantCurationStatus(QuantEvent.CURATION_STATUS_BAD);
        else if (selectedButtonModel == onePeakRadioButtonModel)
            quantEvent.setQuantCurationStatus(QuantEvent.CURATION_STATUS_RATIO_ONEPEAK);
        else
            quantEvent.setQuantCurationStatus(QuantEvent.CURATION_STATUS_UNKNOWN);
    }

    public void buttonIDCuration_actionPerformed(ActionEvent event) {
        QuantEvent quantEvent = quantEvents.get(displayedEventIndex);

        ButtonModel selectedButtonModel = idCurationButtonGroup.getSelection();
        if (selectedButtonModel == idGoodRadioButtonModel)
            quantEvent.setIdCurationStatus(QuantEvent.CURATION_STATUS_GOOD);
        else if (selectedButtonModel == idBadRadioButtonModel) {
            //setting ID to bad is special -- also sets quantitation to bad
            quantEvent.setIdCurationStatus(QuantEvent.CURATION_STATUS_BAD);
            quantEvent.setQuantCurationStatus(QuantEvent.CURATION_STATUS_BAD);
            quantCurationButtonGroup.setSelected(badRadioButtonModel, true);
        } else
            quantEvent.setIdCurationStatus(QuantEvent.CURATION_STATUS_UNKNOWN);
    }

    /**
     * Build summary charts for all displayed events.
     *
     * At the moment, there's just one chart, a scatterplot of algorithm vs singlepeak ratio (log) that's clickable
     * to navigate between events
     */
    protected void buildSummaryCharts() {
        if (summaryChartsFrame != null)
            summaryChartsFrame.dispose();
        int chartWidth = 800;
        int chartHeight = 800;
        summaryChartsPanel = new TabbedMultiChartDisplayPanel();
        summaryChartsFrame = new JFrame();
        summaryChartsFrame.setDefaultCloseOperation(JDialog.HIDE_ON_CLOSE);
        summaryChartsFrame.setTitle("Summary Charts");
        try {
            //Data values for the two series (good and bad) on the chart
            List<Float> algLogRatiosGood = new ArrayList<Float>();
            List<Float> singlePeakLogRatiosGood = new ArrayList<Float>();
            List<Float> algLogRatiosBad = new ArrayList<Float>();
            List<Float> singlePeakLogRatiosBad = new ArrayList<Float>();
            double initialDomainCrosshairValue = 0;
            double initialRangeCrosshairValue = 0;
            for (int i = 0; i < quantEvents.size(); i++) {
                QuantEvent quantEvent = quantEvents.get(i);
                List<Float> algList = algLogRatiosGood;
                List<Float> singleList = singlePeakLogRatiosGood;
                if (quantEvent.getAlgorithmicAssessment().getStatus() != QuantEventAssessor.FLAG_REASON_OK) {
                    algList = algLogRatiosBad;
                    singleList = singlePeakLogRatiosBad;
                }
                float domainVal = (float) Math.log(Math.max(0.0001, quantEvent.getRatio()));
                algList.add(domainVal);
                float rangeVal = (float) Math.log(Math.max(0.0001, quantEvent.getRatioOnePeak()));
                singleList.add(rangeVal);
                if (i == displayedEventIndex) {
                    initialDomainCrosshairValue = domainVal;
                    initialRangeCrosshairValue = rangeVal;
                }
            }
            //This scatterplot will show algorithm ratio vs. singlepeak ratio, colored differently by good/bad assessment,
            //and will let the user pick datapoints and go to those events, via crosshairs
            PanelWithScatterPlot pwsp = new PanelWithScatterPlot(true);
            pwsp.setName("Algorithm vs. Single Peak Log Ratio");
            boolean hasGood = false;
            if (!algLogRatiosGood.isEmpty()) {
                pwsp.addData(algLogRatiosGood, singlePeakLogRatiosGood, "Assessed Good");
                pwsp.setSeriesColor(0, Color.GREEN);
                hasGood = true;
            }
            if (!algLogRatiosBad.isEmpty()) {
                pwsp.addData(algLogRatiosBad, singlePeakLogRatiosBad, "Assessed Bad");
                pwsp.setSeriesColor(hasGood ? 1 : 0, Color.RED);
            }
            pwsp.setAxisLabels("Algorithm Log Ratio", "Single Peak Log Ratio");
            pwsp.setSize(chartWidth, chartHeight);
            pwsp.setMinimumSize(new Dimension(chartWidth, chartHeight));
            pwsp.setPreferredSize(new Dimension(chartWidth, chartHeight));

            //change the displayed event when the user selects a new one
            pwsp.addCrosshairsAndListener(new CrosshairChangeListener() {
                public void crosshairValueChanged(ChartProgressEvent event) {
                    for (int i = 0; i < quantEvents.size(); i++) {
                        QuantEvent quantEvent = quantEvents.get(i);
                        //it would be less work to store these values, but this isn't done all that often
                        double domainDiff = (float) Math.log(Math.max(0.0001, quantEvent.getRatio()))
                                - (float) domainValue;
                        double rangeDiff = (float) Math.log(Math.max(0.0001, quantEvent.getRatioOnePeak()))
                                - (float) rangeValue;

                        if (Math.abs(domainDiff) < 0.001 && Math.abs(rangeDiff) < 0.001) {
                            if (displayedEventIndex != i) {
                                displayedEventIndex = i;
                                displayCurrentQuantEvent(false);
                            }
                            break;
                        }
                    }
                }
            }, initialDomainCrosshairValue, initialRangeCrosshairValue);

            summaryChartsFrame.setSize(new Dimension(pwsp.getWidth() + 10, pwsp.getHeight() + 50));
            summaryChartsFrame.add(pwsp);
        } catch (NullPointerException e) {
            infoMessage("Warning: failed to generate heuristic summary chart.");
        }

    }

    /**
     * Update lots of UI components after a change of quantitation event
     */
    protected void updateUIAfterChange(boolean shouldUpdateTable) {
        QuantEvent quantEvent = quantEvents.get(displayedEventIndex);

        if (displayedEventIndex > 0)
            backButton.setEnabled(true);
        else
            backButton.setEnabled(false);
        if (displayedEventIndex < quantEvents.size() - 1)
            forwardButton.setEnabled(true);
        else
            forwardButton.setEnabled(false);
        showEventSummaryButton.setEnabled(true);

        onePeakRatioRadioButton.setText("" + Rounder.round(quantEvent.getRatioOnePeak(), 2));
        Enumeration<AbstractButton> quantButtons = quantCurationButtonGroup.getElements();
        while (quantButtons.hasMoreElements())
            quantButtons.nextElement().setEnabled(true);
        Enumeration<AbstractButton> idButtons = idCurationButtonGroup.getElements();
        while (idButtons.hasMoreElements())
            idButtons.nextElement().setEnabled(true);

        navigationPanel.setBorder(BorderFactory
                .createTitledBorder("Event " + (displayedEventIndex + 1) + " / " + quantEvents.size()));

        ButtonModel buttonModelToSelect = null;
        switch (quantEvent.getQuantCurationStatus()) {
        case QuantEvent.CURATION_STATUS_UNKNOWN:
            buttonModelToSelect = unknownRadioButtonModel;
            break;
        case QuantEvent.CURATION_STATUS_GOOD:
            buttonModelToSelect = goodRadioButtonModel;
            break;
        case QuantEvent.CURATION_STATUS_BAD:
            buttonModelToSelect = badRadioButtonModel;
            break;
        case QuantEvent.CURATION_STATUS_RATIO_ONEPEAK:
            buttonModelToSelect = onePeakRadioButtonModel;
            break;
        }
        quantCurationButtonGroup.setSelected(buttonModelToSelect, true);

        switch (quantEvent.getIdCurationStatus()) {
        case QuantEvent.CURATION_STATUS_UNKNOWN:
            buttonModelToSelect = idUnknownRadioButtonModel;
            break;
        case QuantEvent.CURATION_STATUS_GOOD:
            buttonModelToSelect = idGoodRadioButtonModel;
            break;
        case QuantEvent.CURATION_STATUS_BAD:
            buttonModelToSelect = idBadRadioButtonModel;
            break;
        }
        idCurationButtonGroup.setSelected(buttonModelToSelect, true);

        multiChartDisplay.setPreferredSize(new Dimension(rightPanel.getWidth(), rightPanel.getHeight()));
        multiChartDisplay.updateUI();

        commentTextField.setText(quantEvent.getComment() != null ? quantEvent.getComment() : "");
        commentTextField.setToolTipText(
                quantEvent.getComment() != null ? quantEvent.getComment() : "Comment on this event");

        //dhmay danger of infinite loop here
        if (shouldUpdateTable)
            eventSummaryTable.getSelectionModel().setSelectionInterval(displayedEventIndex, displayedEventIndex);

        QuantEventAssessor.QuantEventAssessment assessment = quantEvent.getAlgorithmicAssessment();
        if (assessment == null) {
            assessmentTypeTextField.setText("");
            assessmentDescTextField.setText("");
            assessmentTypeTextField.setBackground(Color.LIGHT_GRAY);
        } else {
            assessmentTypeTextField.setText(QuantEventAssessor.flagReasonDescriptions[assessment.getStatus()]);
            assessmentDescTextField.setText(assessment.getExplanation());
            Color bgColor = assessmentPanel.getBackground();
            switch (assessment.getStatus()) {
            case QuantEventAssessor.FLAG_REASON_UNEVALUATED:
                bgColor = Color.YELLOW;
                break;
            case QuantEventAssessor.FLAG_REASON_OK:
                bgColor = Color.GREEN;
                break;
            default:
                bgColor = Color.RED;
                break;
            }
            assessmentTypeTextField.setBackground(bgColor);
        }
        assessmentDescTextField.setToolTipText(assessmentDescTextField.getText());

        showTheoreticalPeaks();
    }

    /**
     * Calculate and show theoretical isotopic distribution peaks, with light encroaching on heavy
     * if necessary
     */
    protected void showTheoreticalPeaks() {
        int horizSlop = 30;
        int vertSlop = 35;
        if (System.getProperty("os.name").toLowerCase().contains("mac")) {
            horizSlop = 40;
            vertSlop = 45;
        }

        int chartWidth = Math.max(200, leftPanelWidth - horizSlop);
        int chartHeight = Math.max(theoreticalPeaksPanel.getHeight(), theoreticalPeaksPanelHeight) - vertSlop;
        if (theoreticalPeaksChart != null && theoreticalPeaksChart.getComponentCount() > 0)
            theoreticalPeaksPanel.remove(0);

        QuantEvent quantEvent = null;
        if (quantEvents != null) {
            quantEvent = quantEvents.get(displayedEventIndex);

            theoreticalPeaksChart = QuantitationVisualizer.buildTheoreticalPeakChart(quantEvent, chartWidth,
                    chartHeight);

            GridBagConstraints gbc = new GridBagConstraints();
            gbc.fill = GridBagConstraints.BOTH;
            gbc.anchor = GridBagConstraints.PAGE_START;
            gbc.gridwidth = GridBagConstraints.REMAINDER;
            theoreticalPeaksPanel.add(theoreticalPeaksChart, gbc);
            float lightNeutralMass = (quantEvent.getLightMz() - Spectrum.HYDROGEN_ION_MASS)
                    * quantEvent.getCharge();
            float heavyNeutralMass = (quantEvent.getHeavyMz() - Spectrum.HYDROGEN_ION_MASS)
                    * quantEvent.getCharge();
            theoreticalPeaksPanel.setToolTipText("LightMass=" + lightNeutralMass + ", HeavyMass=" + heavyNeutralMass
                    + ", Ratio=" + quantEvent.getRatio());
            theoreticalPeaksChart.updateUI();
        }
    }

    /**
     * Take care of the charts and the properties panel
     *
     * @param shouldUpdateTable Should we update the events table?  Need this to avoid infinite loop
     */
    protected void displayCurrentQuantEvent(boolean shouldUpdateTable) {
        QuantEvent quantEvent = quantEvents.get(displayedEventIndex);

        List<PanelWithChart> multiChartPanels = multiChartDisplay.getChartPanels();

        //first-time initialization
        if (multiChartPanels == null || multiChartPanels.isEmpty()) {
            multiChartDisplay.addChartPanel(new PanelWithBlindImageChart("Intensity Sum"));
            multiChartDisplay.addChartPanel(new PanelWithBlindImageChart("Spectrum"));
            multiChartDisplay.addChartPanel(new PanelWithBlindImageChart("Scans"));
            if (quantEvent.getFile3D() != null)
                multiChartDisplay.addChartPanel(new PanelWithBlindImageChart("3D"));
        }

        try {
            PanelWithBlindImageChart intensitySumChart = (PanelWithBlindImageChart) multiChartPanels.get(0);
            PanelWithBlindImageChart spectrumChart = (PanelWithBlindImageChart) multiChartPanels.get(1);
            PanelWithBlindImageChart scansChart = (PanelWithBlindImageChart) multiChartPanels.get(2);

            spectrumChart.setImage(ImageIO.read(quantEvent.getSpectrumFile()));
            scansChart.setImage(ImageIO.read(quantEvent.getScansFile()));
            if (quantEvent.getIntensitySumFile() != null)
                intensitySumChart.setImage(ImageIO.read(quantEvent.getIntensitySumFile()));

            if (quantEvent.getFile3D() != null && multiChartPanels.size() > 3) {
                PanelWithBlindImageChart chart3D = (PanelWithBlindImageChart) multiChartPanels.get(3);
                if (quantEvent.getFile3D().exists())
                    chart3D.setImage(ImageIO.read(quantEvent.getFile3D()));
                else {
                    _log.debug("Warning: no 3D chart for this event");
                }
            }
        } catch (IOException e) {
            ApplicationContext.errorMessage("Failure displaying charts", e);
        }

        propertiesTable.displayQuantEvent(quantEvent);

        updateUIAfterChange(shouldUpdateTable);
    }

    public int getDisplayedEventIndex() {
        return displayedEventIndex;
    }

    public void setDisplayedEventIndex(int displayedEventIndex) {
        this.displayedEventIndex = displayedEventIndex;
    }

    /**
     * Manually manage the size of the multi-chart panel
     */
    protected class RightPanelResizeListener implements ComponentListener {
        public void componentResized(ComponentEvent event) {
            multiChartDisplay.setPreferredSize(new Dimension(rightPanel.getWidth(), rightPanel.getHeight() - 5));
        }

        public void componentMoved(ComponentEvent event) {
        }

        public void componentShown(ComponentEvent event) {
        }

        public void componentHidden(ComponentEvent event) {
        }
    }

    /**
     * Manually manage the size of the properties table
     */
    protected class LeftPanelResizeListener implements ComponentListener {
        public void componentResized(ComponentEvent event) {
            propertiesScrollPane
                    .setPreferredSize(new Dimension(leftPanel.getWidth() - 15, propertiesScrollPane.getHeight()));
        }

        public void componentMoved(ComponentEvent event) {
        }

        public void componentShown(ComponentEvent event) {
        }

        public void componentHidden(ComponentEvent event) {
        }
    }

    /**
     * Display a dialog box with info message
     * @param message
     */
    public static void infoMessage(String message) {
        JOptionPane.showMessageDialog(ApplicationContext.getFrame(), message, "Information",
                JOptionPane.INFORMATION_MESSAGE);
    }

    /**
     * Display a dialog box with info message and stack trace
     * @param message
     * @param t
     */
    protected void errorMessage(String message, Throwable t) {
        if (null != t) {
            message = message + "\n" + t.getMessage() + "\n";

            StringWriter sw = new StringWriter();
            PrintWriter w = new PrintWriter(sw);
            t.printStackTrace(w);
            w.flush();
            message += "\n";
            message += sw.toString();
        }
        ApplicationContext.errorMessage(message, t);
        JOptionPane.showMessageDialog(ApplicationContext.getFrame(), message, "Information",
                JOptionPane.INFORMATION_MESSAGE);
    }

    /**
     * Remove all the events the user has designated as 'bad' from the pepXML file they came from
     * TODO: report how many events weren't found
     * @param quantEvents
     * @param pepXmlFile
     * @param outFile
     * @throws IOException
     * @throws XMLStreamException
     */
    public static void filterBadEventsFromFile(List<QuantEvent> quantEvents, File pepXmlFile, File outFile)
            throws IOException, XMLStreamException {
        Map<String, List<Integer>> fractionBadQuantScanListMap = new HashMap<String, List<Integer>>();
        Map<String, List<Integer>> fractionBadIDScanListMap = new HashMap<String, List<Integer>>();
        Map<String, Map<Integer, Float>> fractionScanNewRatioMap = new HashMap<String, Map<Integer, Float>>();

        for (QuantEvent quantEvent : quantEvents) {
            String fraction = quantEvent.getFraction();

            if (quantEvent.getIdCurationStatus() == QuantEvent.CURATION_STATUS_BAD) {
                List<Integer> thisFractionList = fractionBadIDScanListMap.get(fraction);
                if (thisFractionList == null) {
                    thisFractionList = new ArrayList<Integer>();
                    fractionBadIDScanListMap.put(fraction, thisFractionList);
                }
                thisFractionList.add(quantEvent.getScan());
                for (QuantEvent otherEvent : quantEvent.getOtherEvents())
                    if (!thisFractionList.contains(otherEvent.getScan()))
                        thisFractionList.add(otherEvent.getScan());
                _log.debug("Stripping ID for " + (quantEvent.getOtherEvents().size() + 1) + " events for peptide "
                        + quantEvent.getPeptide() + " from fraction " + fraction);
            }
            //only if the ID was unknown or good do we check quant -- quant is automatically
            //filtered for bad IDs
            else if (quantEvent.getQuantCurationStatus() == QuantEvent.CURATION_STATUS_BAD) {
                List<Integer> thisFractionList = fractionBadQuantScanListMap.get(fraction);
                if (thisFractionList == null) {
                    thisFractionList = new ArrayList<Integer>();
                    fractionBadQuantScanListMap.put(fraction, thisFractionList);
                }
                thisFractionList.add(quantEvent.getScan());
                int numOtherEvents = 0;
                if (quantEvent.getOtherEvents() != null) {
                    for (QuantEvent otherEvent : quantEvent.getOtherEvents())
                        if (!thisFractionList.contains(otherEvent.getScan()))
                            thisFractionList.add(otherEvent.getScan());
                    numOtherEvents = quantEvent.getOtherEvents().size();
                }
                ApplicationContext.infoMessage("Stripping Quantitation for " + (numOtherEvents + 1)
                        + " events for peptide " + quantEvent.getPeptide() + " from fraction " + fraction);
            }
            //dhmay adding 20090723
            else if (quantEvent.getQuantCurationStatus() == QuantEvent.CURATION_STATUS_RATIO_ONEPEAK) {
                Map<Integer, Float> thisFractionMap = fractionScanNewRatioMap.get(fraction);
                if (thisFractionMap == null) {
                    thisFractionMap = new HashMap<Integer, Float>();
                    fractionScanNewRatioMap.put(fraction, thisFractionMap);
                }
                float newRatio = quantEvent.getRatioOnePeak();
                thisFractionMap.put(quantEvent.getScan(), newRatio);
                for (QuantEvent otherEvent : quantEvent.getOtherEvents())
                    if (!thisFractionMap.containsKey(otherEvent.getScan()))
                        thisFractionMap.put(otherEvent.getScan(), newRatio);
                ApplicationContext.infoMessage(
                        "Changing ratios to " + newRatio + " for " + (quantEvent.getOtherEvents().size() + 1)
                                + " events for peptide " + quantEvent.getPeptide() + " from fraction " + fraction);
            }
        }
        ApplicationContext.infoMessage("OK, decided what to do.  Now to strip the IDs from the pepXML file....");
        File actualOutFile = outFile;
        String dummyOwner = "DUMMY_OWNER_QURATE_STRIP";
        boolean sameInputAndOutput = pepXmlFile.getAbsolutePath().equals(outFile.getAbsolutePath());
        if (sameInputAndOutput)
            actualOutFile = TempFileManager.createTempFile("temp_quratestrip_" + outFile.getName(), dummyOwner);

        StripQuantOrIDPepXmlRewriter quantStripper = new StripQuantOrIDPepXmlRewriter(pepXmlFile, actualOutFile,
                fractionBadIDScanListMap, fractionBadQuantScanListMap, fractionScanNewRatioMap);
        quantStripper.rewrite();
        quantStripper.close();
        if (sameInputAndOutput) {
            //file-copying.  Java 1.6 still doesn't have a reasonable and concise way to do it.
            FileChannel inChannel = new FileInputStream(actualOutFile).getChannel();
            FileChannel outChannel = new FileOutputStream(outFile).getChannel();
            try {
                inChannel.transferTo(0, inChannel.size(), outChannel);
            } catch (IOException e) {
                throw e;
            } finally {
                if (inChannel != null)
                    inChannel.close();
                if (outChannel != null)
                    outChannel.close();
            }
            TempFileManager.deleteTempFiles(dummyOwner);
        }
        ApplicationContext.infoMessage("Done!  Saved stripped file to " + outFile.getAbsolutePath());
    }

    /**
     * Set status message.  Separate thread necessary or UI hangs
     * @param message
     */
    public void setMessage(String message) {
        if (EventQueue.isDispatchThread()) {
            if (null == message || 0 == message.length())
                message = " ";
            messageLabel.setText(message);
        } else {
            final String msg = message;
            EventQueue.invokeLater(new Runnable() {
                public void run() {
                    setMessage(msg);
                }
            });
        }
    }

    /**
     *  pepXML rewriter that can strip out quantitation events or entire spectrum_query tags.
     * dhmay 20090723: adding ability to alter ratios of events
     */
    static class StripQuantOrIDPepXmlRewriter extends SimpleXMLEventRewriter {
        //Track the bad stuff by fraction name and scan number
        Map<String, List<Integer>> fractionBadQuantScansMap;
        Map<String, List<Integer>> fractionBadIDScansMap;
        Map<String, Map<Integer, Float>> fractionScanNewRatioMap;

        protected boolean insideSkippedQuantEvent = false;
        protected boolean insideSkippedSpectrumQuery = false;

        protected boolean insideScanWithSkippedEvent = false;
        protected boolean insideScanWithRatioChange = false;

        List<Integer> currentFractionBadQuantScans;
        List<Integer> currentFractionBadIDScans;
        Map<Integer, Float> currentFractionScanNewRatioMap;

        float currentScanNewRatio;

        public StripQuantOrIDPepXmlRewriter(File inputFile, File outputFile,
                Map<String, List<Integer>> fractionBadIDScansMap,
                Map<String, List<Integer>> fractionBadQuantScansMap,
                Map<String, Map<Integer, Float>> fractionScanNewRatioMap) {
            super(inputFile.getAbsolutePath(), outputFile.getAbsolutePath());
            this.fractionBadQuantScansMap = fractionBadQuantScansMap;
            this.fractionBadIDScansMap = fractionBadIDScansMap;
            this.fractionScanNewRatioMap = fractionScanNewRatioMap;

        }

        public void add(XMLEvent event) throws XMLStreamException {
            if (!insideSkippedQuantEvent && !insideSkippedSpectrumQuery) {
                super.add(event);
            }
        }

        /**
         * special handling for keeping track of fraction and dealing with the skipped stuff
         * @param event
         * @throws XMLStreamException
         */
        public void handleStartElement(StartElement event) throws XMLStreamException {
            QName qname = event.getName();
            String elementName = qname.getLocalPart();
            XMLEvent eventToAdd = event;

            if ("msms_run_summary".equals(elementName)) {
                Attribute baseNameAttr = event.getAttributeByName(new QName("base_name"));
                String baseName = baseNameAttr.getValue();
                currentFractionBadQuantScans = fractionBadQuantScansMap.get(baseName);
                currentFractionBadIDScans = fractionBadIDScansMap.get(baseName);
                currentFractionScanNewRatioMap = fractionScanNewRatioMap.get(baseName);
            } else if ("spectrum_query".equals(elementName)) {
                int scan = Integer.parseInt(event.getAttributeByName(new QName("start_scan")).getValue());
                if (currentFractionBadIDScans != null && currentFractionBadIDScans.contains(scan)) {
                    insideSkippedSpectrumQuery = true;
                    _log.debug("Skipping ID for scan " + scan);
                } else if (currentFractionBadQuantScans != null && currentFractionBadQuantScans.contains(scan)) {
                    insideScanWithSkippedEvent = true;
                    _log.debug("Skipping quantitation for scan " + scan);
                } else if (currentFractionScanNewRatioMap != null
                        && currentFractionScanNewRatioMap.containsKey(scan)) {
                    insideScanWithRatioChange = true;
                    currentScanNewRatio = currentFractionScanNewRatioMap.get(scan);
                }
            } else if ("analysis_result".equals(elementName)) {
                if (insideScanWithSkippedEvent) {
                    String analysisType = event.getAttributeByName(new QName("analysis")).getValue();
                    if ("q3".equals(analysisType) || "xpress".equals(analysisType))
                        insideSkippedQuantEvent = true;
                }
            }
            //Adjust Q3 or XPress ratios.  Note: doesn't affect Q3's Q2 ratio or KL scores
            else if ("xpressratio_result".equals(elementName) || "q3ratio_result".equals(elementName)) {
                if (insideScanWithRatioChange) {
                    float lightArea = (float) Rounder.round(
                            Float.parseFloat(event.getAttributeByName(new QName("light_area")).getValue()), 4);

                    //guard against division by 0.  If we do this, light area 100 is arbitrary
                    boolean lightWas0 = false;
                    if (lightArea == 0) {
                        lightWas0 = true;
                        lightArea = 100f;
                    }
                    float newHeavyArea = (float) Rounder.round(lightArea / currentScanNewRatio, 4);

                    _log.debug("Changing ratio to " + currentScanNewRatio + ".  Light=" + lightArea + ", heavy="
                            + newHeavyArea + ".  Light was 0? " + lightWas0);

                    SimpleStartElement newEvent = new SimpleStartElement(qname.getLocalPart());
                    Iterator<Attribute> attIter = event.getAttributes();
                    while (attIter.hasNext()) {
                        Attribute oldAttr = attIter.next();
                        String attrName = oldAttr.getName().getLocalPart();
                        if (attrName.equals("decimal_ratio")) {
                            newEvent.addAttribute("decimal_ratio", currentScanNewRatio);
                        } else if (attrName.equals("heavy_area")) {
                            newEvent.addAttribute("heavy_area", newHeavyArea);
                        } else if (attrName.equals("light_area")) {
                            newEvent.addAttribute("light_area", lightArea);
                        } else if (attrName.equals("heavy2light_ratio")) {
                            newEvent.addAttribute("heavy2light_ratio", 1.0f / currentScanNewRatio);
                        } else
                            newEvent.addAttribute(attrName, oldAttr.getValue());
                    }
                    eventToAdd = newEvent.getEvent();
                }
            }

            add(eventToAdd);
        }

        /**
         * Pop out of skipping mode
         * @param event
         * @throws XMLStreamException
         */
        public void handleEndElement(EndElement event) throws XMLStreamException {
            QName qname = event.getName();

            add(event);
            if (insideSkippedQuantEvent && "analysis_result".equals(qname.getLocalPart()))
                insideSkippedQuantEvent = false;
            else if ("spectrum_query".equals(qname.getLocalPart())) {
                insideScanWithSkippedEvent = false;
                insideSkippedSpectrumQuery = false;
                insideScanWithRatioChange = false;
            }
        }
    }

    /**
     * Display chart dialog
     */
    protected class SummaryChartsAction extends AbstractAction {
        public void actionPerformed(ActionEvent event) {
            if (summaryChartsFrame != null)
                summaryChartsFrame.setVisible(true);
        }
    }

    /**
     * Display help from static help file
     */
    public static class HelpAction extends AbstractAction {
        public void actionPerformed(ActionEvent event) {
            try {
                JDialog dialog = HtmlViewerPanel
                        .showResourceInDialog("org/fhcrc/cpl/viewer/quant/gui/qurate_help.html", "Qurate Help");
            } catch (Exception e) {
                ApplicationContext.errorMessage("Error displaying help", e);
            }
        }
    }

    /**
     * "Create Charts" in the File menu. Create charts for running this on, by invoking the
     * 'quantchart' command programmatically, letting user specify args
     */
    protected class CreateChartsAction extends AbstractAction {
        public void actionPerformed(ActionEvent event) {
            Thread createChartsThread = new Thread(new Runnable() {
                public void run() {
                    PeptideQuantVisualizationCLM createChartsModule = new PeptideQuantVisualizationCLM();
                    ViewerInteractiveModuleFrame interactFrame = new ViewerInteractiveModuleFrame(
                            createChartsModule, true, null);
                    interactFrame.setUserManualGenerator(new ViewerUserManualGenerator());
                    boolean shouldExecute = interactFrame.collectArguments();

                    if (shouldExecute) {

                        try {
                            setMessage("Building charts.  This could take a while.  Details on command line.");
                            contentPanel.updateUI();
                            createChartsModule.execute();
                            infoMessage("Saved charts.  Opening summary file "
                                    + createChartsModule.getOutTsvFile().getAbsolutePath());
                            contentPanel.updateUI();
                            try {
                                displayQuantFile(createChartsModule.getOutTsvFile());
                            } catch (IOException e) {
                                ApplicationContext.errorMessage(
                                        "Failed to open quantitation file " + quantFile.getAbsolutePath(), e);
                            }
                        } catch (Exception e) {
                            String message = "Error creating charts: " + e.getMessage();

                            errorMessage(message, e);
                        }

                    }
                }
            });
            createChartsThread.start();
        }
    }

    /**
     * Open a tsv file
     */
    protected class OpenFileAction extends AbstractAction {
        protected Component parentComponent;

        public OpenFileAction(Component parentComponent) {
            this.parentComponent = parentComponent;
        }

        public void actionPerformed(ActionEvent event) {
            WorkbenchFileChooser wfc = new WorkbenchFileChooser();
            if (quantFile != null)
                wfc.setSelectedFile(quantFile);
            wfc.setDialogTitle("Open Quantitation Event File");
            int chooserStatus = wfc.showOpenDialog(parentComponent);
            //if user didn't hit OK, ignore
            if (chooserStatus != JFileChooser.APPROVE_OPTION)
                return;
            quantFile = wfc.getSelectedFile();
            try {
                displayQuantFile(quantFile);
            } catch (IOException e) {
                errorMessage("Failed to open quantitation file " + quantFile.getAbsolutePath(), e);
            }
        }
    }

    /**
     * Save changes to a file
     */
    protected class SaveAction extends AbstractAction {
        protected Component parentComponent;

        public SaveAction(Component parentComponent) {
            this.parentComponent = parentComponent;
        }

        public void actionPerformed(ActionEvent event) {
            WorkbenchFileChooser wfc = new WorkbenchFileChooser();
            if (quantFile != null)
                wfc.setSelectedFile(quantFile);
            wfc.setDialogTitle("Output File");
            int chooserStatus = wfc.showOpenDialog(parentComponent);
            //if user didn't hit OK, ignore
            if (chooserStatus != JFileChooser.APPROVE_OPTION)
                return;
            quantFile = wfc.getSelectedFile();
            try {
                QuantEvent.saveQuantEventsToTSV(quantEvents, quantFile, true, true);
                setMessage("Saved changes to file " + quantFile.getAbsolutePath());
            } catch (IOException e) {
                errorMessage("ERROR: failed to save file " + quantFile.getAbsolutePath(), e);
            }
        }

    }

    public void showProteinQuantSummaryFrame(List<ProtXmlReader.Protein> proteins) {
        showProteinQuantSummaryFrame(proteins, null);
    }

    public void showProteinQuantSummaryFrame(List<ProtXmlReader.Protein> proteins,
            Map<String, List<String>> proteinGenesMap) {
        List<QuantEvent> selectedQuantEvents = null;

        if (quantSummaryFrame != null)
            quantSummaryFrame.dispose();

        //        File outFile = settingsCLM.outFile;
        //        if (outFile == null)
        //        {
        //            //We may not actually want to keep the output file, in which case we need a temp file
        //            outFile = TempFileManager.createTempFile("qurate_ProteinSelectedActionListener.tsv",
        //                "DUMMY_ProteinSelectedActionListener_CALLER");
        //        }

        try {
            quantSummaryFrame = new ProteinQuantSummaryFrame(settingsCLM.mzXmlDir);

            quantSummaryFrame.setExistingQuantEvents(quantEvents);
            quantSummaryFrame.setProteinGeneMap(proteinGenesMap);
            setMessage("Locating quantitation events for " + proteins.size() + " proteins...");
            _log.debug("About to displayData");
            quantSummaryFrame.displayData(settingsCLM.pepXmlFile, settingsCLM.protXmlFile, proteins);
            System.err.println("ran displayData");
            setMessage("");

            quantSummaryFrame.setModal(true);
            quantSummaryFrame.setVisible(true);

            selectedQuantEvents = quantSummaryFrame.getSelectedQuantEvents();
            quantSummaryFrame.dispose();
        } catch (IllegalArgumentException e) {
            infoMessage(e.getMessage());
            return;
        } finally {
            if (quantSummaryFrame != null)
                quantSummaryFrame.dispose();
        }
        //will have no effect if temp file not created
        TempFileManager.deleteTempFiles("DUMMY_ProteinSelectedActionListener_CALLER");

        if (selectedQuantEvents == null || selectedQuantEvents.isEmpty())
            return;

        setMessage(selectedQuantEvents.size() + " events selected for charts");
        if (quantEvents == null)
            quantEvents = new ArrayList<QuantEvent>();
        quantEvents.addAll(selectedQuantEvents);
        eventSummaryTable.setEvents(selectedQuantEvents);
        displayedEventIndex = quantEvents.size() - selectedQuantEvents.size();
        buildSummaryCharts();
        displayCurrentQuantEvent(true);
    }

    /**
     * Clean up the windows that might be open
     */
    public void dispose() {
        _log.debug("QuantitationReviewer.dispose()");
        if (quantSummaryFrame != null)
            quantSummaryFrame.dispose();
        if (proteinSummarySelector != null)
            proteinSummarySelector.dispose();
        if (eventSummaryFrame != null)
            eventSummaryFrame.dispose();
        if (summaryChartsFrame != null)
            summaryChartsFrame.dispose();
        super.dispose();
    }

    public File getQuantFile() {
        return quantFile;
    }

    public void setQuantFile(File quantFile) {
        this.quantFile = quantFile;
    }

    /**
     * Displays the splash screen.  Side effect: sets the splashFrame variable, so it can be disposed later
     */
    protected void showSplashScreen() {
        splashFrame = new SplashFrame(splashImageURL);
        splashFrame.setVisible(true);
    }

    //supporting inner classes

    protected class ProteinSelectedActionListener implements ActionListener {
        public void actionPerformed(ActionEvent e) {
            if (proteinSummarySelector.getSelectedProteins() != null
                    && !proteinSummarySelector.getSelectedProteins().isEmpty()) {
                showProteinQuantSummaryFrame(proteinSummarySelector.getSelectedProteins());
            }
        }
    }

    protected class ProteinSummaryAction extends AbstractAction {
        List<QuantEvent> selectedQuantEvents = null;

        protected Component parentComponent;

        public ProteinSummaryAction(Component parentComponent) {
            this.parentComponent = parentComponent;
        }

        public void actionPerformed(ActionEvent event) {
            ViewerInteractiveModuleFrame interactFrame = new ViewerInteractiveModuleFrame(settingsCLM, true, null);
            interactFrame.setModal(true);
            interactFrame.setTitle("Settings");
            interactFrame.setUserManualGenerator(new ViewerUserManualGenerator());
            boolean hasRunSuccessfully = interactFrame.collectArguments();
            interactFrame.dispose();
            if (!hasRunSuccessfully)
                return;

            if (settingsCLM.proteins != null) {
                showProteinQuantSummaryFrame(settingsCLM.proteins, settingsCLM.getProteinGeneListMap());
            } else {
                if (proteinSummarySelector == null || !proteinSummarySelector.isVisible())
                    try {
                        proteinSummarySelector = new ProteinSummarySelectorFrame();
                        proteinSummarySelector.setMinProteinProphet(settingsCLM.minProteinProphet);
                        proteinSummarySelector.setMinHighRatio(settingsCLM.minHighRatio);
                        proteinSummarySelector.setMaxLowRatio(settingsCLM.maxLowRatio);
                        proteinSummarySelector.setProteinGeneMap(settingsCLM.proteinGeneListMap);
                        proteinSummarySelector.addSelectionListener(new ProteinSelectedActionListener());
                        proteinSummarySelector.displayProteins(settingsCLM.protXmlFile);
                        proteinSummarySelector.setVisible(true);
                    } catch (Exception e) {
                        errorMessage("Error opening ProtXML file " + settingsCLM.protXmlFile.getAbsolutePath(), e);
                    }
            }
        }
    }

    /**
     * Action to quit
     */
    protected class ExitAction extends AbstractAction {
        public void actionPerformed(ActionEvent event) {
            setVisible(false);
            dispose();
        }
    }

    /**
     * Action to remove bad events and IDs from the file they came from.  Choose source and output pepXML
     * file
     */
    protected class FilterPepXMLAction extends AbstractAction {
        protected Component parentComponent;

        public FilterPepXMLAction(Component parentComponent) {
            this.parentComponent = parentComponent;
        }

        public void actionPerformed(ActionEvent event) {
            if (quantEvents == null || quantEvents.isEmpty()) {
                infoMessage("No events selected for filtering.");
                return;
            }

            boolean foundBad = false;
            for (QuantEvent quantEvent : quantEvents) {
                int idStatus = quantEvent.getIdCurationStatus();
                int quantStatus = quantEvent.getQuantCurationStatus();
                if (idStatus == QuantEvent.CURATION_STATUS_BAD || (quantStatus != QuantEvent.CURATION_STATUS_GOOD
                        && quantStatus != QuantEvent.CURATION_STATUS_UNKNOWN)) {
                    foundBad = true;
                    break;
                }
            }
            if (!foundBad) {
                infoMessage("No selected events have 'Bad' or 'Single-Peak Ratio' status.");
                return;
            }

            WorkbenchFileChooser wfc = new WorkbenchFileChooser();
            wfc.setDialogTitle("Choose PepXML File to Filter");
            int chooserStatus = wfc.showOpenDialog(parentComponent);
            //if user didn't hit OK, ignore
            if (chooserStatus != JFileChooser.APPROVE_OPTION)
                return;
            final File file = wfc.getSelectedFile();
            if (null == file)
                return;

            WorkbenchFileChooser wfc2 = new WorkbenchFileChooser();
            wfc2.setSelectedFile(file);
            wfc2.setDialogTitle("Choose Output File");

            chooserStatus = wfc2.showOpenDialog(parentComponent);
            //if user didn't hit OK, ignore
            if (chooserStatus != JFileChooser.APPROVE_OPTION)
                return;
            final File outFile = wfc2.getSelectedFile();
            if (null == outFile)
                return;

            try {
                setMessage("Filtering bad events from file...");
                //do this in a separate thread so the UI doesn't freeze
                Thread filterThread = new Thread(new Runnable() {
                    public void run() {
                        try {
                            filterBadEventsFromFile(quantEvents, file, outFile);
                            setMessage("Filtered events saved to file " + outFile.getAbsolutePath());
                        } catch (Exception e) {
                            errorMessage("Failed to filter file " + file.getAbsolutePath(), e);
                        }
                    }
                });

                filterThread.start();
            } catch (Exception e) {
                errorMessage("Error filtering bad events from file " + file.getAbsolutePath() + " into file "
                        + outFile.getAbsolutePath(), e);
            }
        }
    }

    /**
     * display the properties for the selected event, if only one's selected
     */
    public class EventSummaryTableListSelectionHandler implements ListSelectionListener {
        protected int oldIndex = -1;

        public void valueChanged(ListSelectionEvent e) {
            if (!e.getValueIsAdjusting()) {
                if (eventSummaryTable.getSelectedIndex() >= 0 && oldIndex != eventSummaryTable.getSelectedIndex()) {
                    displayedEventIndex = eventSummaryTable.getSelectedIndex();
                    displayCurrentQuantEvent(false);
                    oldIndex = eventSummaryTable.getSelectedIndex();
                }
            }
        }
    }

}