org.fhcrc.cpl.viewer.gui.ProteinMatcherFrame.java Source code

Java tutorial

Introduction

Here is the source code for org.fhcrc.cpl.viewer.gui.ProteinMatcherFrame.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.gui;

import org.fhcrc.cpl.toolbox.datastructure.Pair;

import java.util.*;
import java.util.List;
import java.io.*;
import java.awt.event.ActionEvent;
import java.awt.*;

import org.fhcrc.cpl.toolbox.proteomics.feature.Feature;
import org.fhcrc.cpl.toolbox.proteomics.feature.FeatureSet;
import org.fhcrc.cpl.toolbox.proteomics.feature.FeaturePepXmlWriter;
import org.fhcrc.cpl.toolbox.proteomics.feature.matching.FeatureSetMatcher;
import org.fhcrc.cpl.toolbox.proteomics.feature.matching.ClusteringFeatureSetMatcher;
import org.fhcrc.cpl.toolbox.proteomics.feature.extraInfo.MS2ExtraInfoDef;
import org.fhcrc.cpl.toolbox.proteomics.MSRun;
import org.fhcrc.cpl.viewer.Localizer;
import org.fhcrc.cpl.viewer.Application;
import org.fhcrc.cpl.toolbox.proteomics.ProteinUtilities;
import org.fhcrc.cpl.viewer.util.SharedProperties;
import org.fhcrc.cpl.toolbox.proteomics.ProteomicsRegressionUtilities;
import org.fhcrc.cpl.toolbox.gui.chart.ChartDialog;
import org.fhcrc.cpl.toolbox.gui.ListenerHelper;
import org.fhcrc.cpl.viewer.amt.*;
import org.fhcrc.cpl.toolbox.TextProvider;
import org.fhcrc.cpl.toolbox.ApplicationContext;
import org.fhcrc.cpl.toolbox.proteomics.Protein;
import org.apache.log4j.Logger;
import org.jfree.chart.plot.FastScatterPlot;
import org.jfree.chart.axis.NumberAxis;

import javax.swing.*;
import javax.swing.table.*;
import javax.swing.event.ListSelectionEvent;

/**
 * The idea is for this class to be as UI-oriented as possible.  Any heavy lifting should be
 * pushed down into ProteinMatcher or something
 */
public class ProteinMatcherFrame extends JDialog {
    private static Logger _log = Logger.getLogger(ProteinMatcherFrame.class);

    public static final float DEFAULT_MIN_PEPTIDE_PROPHET_SCORE = .95f;

    //menu action
    public static class ProteinMatcherAction extends AbstractAction {
        public void actionPerformed(ActionEvent event) {
            ProteinMatcherFrame dialog = new ProteinMatcherFrame();
            dialog.setModal(false);
            dialog.setAlwaysOnTop(false);
        }
    }

    FeatureSet _ms1Features = null;
    FeatureSet _regressionMS2Features = null;
    boolean loadedRegressionMS2Features = false;
    FeatureSet _matchingMS2Features = null;
    File _matchingMS2FeatureFile = null;
    boolean loadedMatchingMS2Features = false;
    File _fastaFile = null;

    //variables related to hydrophobicity->retention time prediction
    Map<String, Double> _regressionLineHydroToTime = null;
    Map<String, Double> _regressionLineHydroToScan = null;
    Map<String, Double> _regressionLineTimeToHydro = null;
    Map<String, Double> _regressionLineScanToHydro = null;

    protected int _scanOrTimeMode = ProteomicsRegressionUtilities.REGRESSION_MODE_TIME;
    //TODO: actually control this somewhere. For now, always adaptive
    protected int _peptideTimeToleranceMode = PeptideMatcher.PEPTIDE_TIME_TOLERANCE_MODE_FIXED;
    protected int _featureDisplayMode = ProteinDisplay.DISPLAY_MATCHED_FEATURES_MODE;

    protected ArrayList<Protein> _matchedProteins = null;
    protected ArrayList<Protein> _displayedProteins = null;
    protected HashMap<Protein, Map<String, Feature>> _proteinMatchedMS1FeatureMap = null;
    protected Map<Protein, Double> _proteinPercentCoverageMap = null;
    protected HashMap<Protein, ArrayList<Feature>> _proteinSequenceMS2FeatureMap = null;
    protected ArrayList<Pair<Feature, Feature>> _ms2ms1MatchedFeaturePairs = null;
    protected HashMap<Protein, ArrayList<Feature>> _proteinMatchedMS1MS2FeatureMap = null;
    protected AmtDatabase _amtDatabase = null;
    protected ArrayList<Feature> _unmatchedMs2Features = null;
    protected ArrayList<Feature> _currentProteinMS1AndMS2MatchedFeatures = null;

    //initial filter is empty, show all proteins
    protected String _proteinFilterString = "";

    //menu
    public JMenuItem showMS2MatchedProteinsMenuItem;
    //    public JMenuItem writePepXmlMs1Ms2MatchesMenuItem;
    public JMenuItem writePepXmlMs2RegressionMenuItem;
    public JMenuItem showPredictedHydroTimePlotMenuItem;
    public Action saveFastaAction = new SaveFastaAction();
    //    public Action writePepXmlMs1Ms2Action = new WritePepXmlMatchedMs1Ms2Action();
    public Action writePepXmlMs2RegressionAction = new WritePepXmlMs2RegressionAction();
    public Action setParametersAction = new SetParametersAction();
    public Action loadMS2RegressionAction = new LoadMS2RegressionAction();
    public Action loadOffsetFeatureSetAction = new LoadOffsetFeatureSetAction();
    public Action matchProteinsFromFastaAction = new MatchProteinsFromFastaAction();
    public Action findProteinsMatchedInMS2Action = new FindProteinsMatchedInMS2Action();
    public Action showPredictedHydroTimePlotAction = new ShowPredictedHydroTimePlotAction();

    //protein display
    public JLabel labelNumProteins;
    public JLabel proteinLabel;
    public JTable tblProteins;
    public JTextField textProteinPrefix;
    public JButton buttonFilterProteins;
    protected ProteinTableModel _proteinTableModel = null;
    protected Protein _selectedProtein = null;

    //peptide display
    public JComboBox displayMatchedUnmatchedComboBox;
    protected ArrayList<Feature> _displayedFeatures = null;
    protected ArrayList<Feature> _currentProteinMS2MatchedFeatures = null;

    public JTable tblFeatures;
    protected FeatureTableModel _featureTableModel = null;
    protected ArrayList<Feature> _selectedFeatures = null;

    //Transitory UI elements (referenced by CommandFileRunner)
    public ParametersDialog mParametersDialog = null;

    //matching parameters
    float deltaMass = FeatureSetMatcher.DEFAULT_DELTA_MASS_ABSOLUTE;
    int deltaMassType = FeatureSetMatcher.DEFAULT_DELTA_MASS_TYPE;
    int deltaScan = FeatureSetMatcher.DEFAULT_DELTA_SCAN;
    double deltaTime = FeatureSetMatcher.DEFAULT_DELTA_TIME;
    double deltaHydrophobicity = FeatureSetMatcher.DEFAULT_DELTA_HYDROPHOBICITY;
    float minPeptideProphet = DEFAULT_MIN_PEPTIDE_PROPHET_SCORE;
    int minMatchedFeatures = 1;
    int minPercentFeatureCoverage = 0;
    FeatureSetMatcher featureSetMatcher = null;

    public ProteinMatcherFrame() {
        super(ApplicationContext.getFrame(), TextProvider.getText("AMT"));
        initialize();
    }

    /**
     * initial setup of UI and class variables
     */
    public void initialize() {
        _ms1Features = getMS1Features();
        if (_ms1Features == null) {
            ApplicationContext.infoMessage(TextProvider.getText("AMT_REQUIRES_DISPLAYED_FEATURES"));
            this.setVisible(false);
            this.dispose();
            return;
        }
        this.setVisible(true);

        //graphical stuff
        try {
            JMenuBar jmenu = (JMenuBar) Localizer.getSwingEngine(this)
                    .render("org/fhcrc/cpl/viewer/gui/ProteinMatcherMenu.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);
        }

        Container contentPanel;
        try {
            contentPanel = Localizer.renderSwixml("org/fhcrc/cpl/viewer/gui/ProteinMatcherFrame.xml", this);
            setContentPane(contentPanel);
            pack();
        } catch (Exception x) {
            ApplicationContext.errorMessage(TextProvider.getText("ERROR_CREATING_DIALOG"), x);
            throw new RuntimeException(x);
        }

        Dimension d = contentPanel.getPreferredSize();
        setBounds(600, 100, (int) d.getWidth(), (int) d.getHeight());

        ListenerHelper helper = new ListenerHelper(this);
        helper.addListener(tblProteins.getSelectionModel(), "tblProteinsModel_valueChanged");
        helper.addListener(buttonFilterProteins, "buttonFilterProteins_actionPerformed");
        //hitting enter in the text field should act the same as hitting the filter button.  Hack, focus, whatever
        helper.addListener(textProteinPrefix, "buttonFilterProteins_actionPerformed");
        helper.addListener(tblFeatures.getSelectionModel(), "tblFeaturesModel_valueChanged");

        _proteinTableModel = new ProteinTableModel();
        tblProteins.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        tblProteins.setAutoCreateColumnsFromModel(false);
        tblProteins.setModel(_proteinTableModel);
        tblProteins.setColumnModel(_proteinTableModel.columnModel);

        if (null != displayMatchedUnmatchedComboBox) {
            //TODO: should really use TextProvider here, and use an internal value for determining state
            displayMatchedUnmatchedComboBox.addItem("matched");
            displayMatchedUnmatchedComboBox.addItem("unmatched peptides");
            displayMatchedUnmatchedComboBox.addItem("unmatched ms2");
            helper.addListener(displayMatchedUnmatchedComboBox, "displayMatchedUnmatchedComboBox_actionPerformed");
        }

        _featureTableModel = new FeatureTableModel();
        tblFeatures.setModel(_featureTableModel);

        tblFeatures.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
        tblFeatures.setAutoCreateColumnsFromModel(false);

        ProteinTablePopupMenu proteinPopup = new ProteinTablePopupMenu();

        tblProteins.setComponentPopupMenu(proteinPopup);
        proteinLabel.setComponentPopupMenu(proteinPopup);
    }

    /**
     * clean up and shut down this window
     */
    protected void quit() {
        ApplicationContext.setProperty(SharedProperties.HIGHLIGHT_FEATURES, null);
        ApplicationContext.setProperty(SharedProperties.HIGHLIGHT_FEATURES2, null);
        ApplicationContext.setProperty(SharedProperties.HIGHLIGHT_FEATURES3, null);

        this.setVisible(false);
        this.dispose();
    }

    /**
     * handler for the "display matched or unmatched features" combo box
     * @param e
     */
    public void displayMatchedUnmatchedComboBox_actionPerformed(ActionEvent e) {
        String comboBoxValue = (String) displayMatchedUnmatchedComboBox.getSelectedItem();
        //TODO: the actual strings "matched" and "unmatched" should be internal values, not displayed
        if ("matched".equals(comboBoxValue)
                && _featureDisplayMode != ProteinDisplay.DISPLAY_MATCHED_FEATURES_MODE) {
            _featureDisplayMode = ProteinDisplay.DISPLAY_MATCHED_FEATURES_MODE;
            _displayedFeatures = new ArrayList<Feature>();
            _displayedFeatures.addAll(_proteinMatchedMS1FeatureMap.get(_selectedProtein).values());
            if (_displayedFeatures == null) {
                _displayedFeatures = new ArrayList<Feature>();
            }
            _featureTableModel.setDisplayedFeatures(_displayedFeatures);
            updateMainWindowHighlight();
        } else if ("unmatched peptides".equals(comboBoxValue)
                && _featureDisplayMode != ProteinDisplay.DISPLAY_UNMATCHED_PROTEIN_PEPTIDES_MODE) {
            //TODO: no longer doing this, should remove
            _featureDisplayMode = ProteinDisplay.DISPLAY_UNMATCHED_PROTEIN_PEPTIDES_MODE;
            //            _displayedFeatures = _unmatchedProteinPeptideMap.get(_selectedProtein);
            if (_displayedFeatures == null) {
                _displayedFeatures = new ArrayList<Feature>();
            }
            _featureTableModel.setDisplayedFeatures(_displayedFeatures);
            updateMainWindowHighlight();
        } else if ("unmatched ms2".equals(comboBoxValue)
                && _featureDisplayMode != ProteinDisplay.DISPLAY_UNMATCHED_MS2_FEATURES_MODE) {
            _featureDisplayMode = ProteinDisplay.DISPLAY_UNMATCHED_MS2_FEATURES_MODE;
            _displayedFeatures = _unmatchedMs2Features;
            _featureTableModel.setDisplayedFeatures(_displayedFeatures);
            updateMainWindowHighlight();
        }
    }

    /**
     * Handler for protein selection
     * @param e
     */
    public void tblProteinsModel_valueChanged(ListSelectionEvent e) {
        int selectedRow = tblProteins.getSelectedRow();
        if (selectedRow >= 0) {
            _selectedProtein = _proteinTableModel.getSelectedProtein(tblProteins.getSelectedRow());
            /*
            System.err.println("**Name="+_selectedProtein.getTextCode());
            if (_selectedProtein.getAliases() != null)
              for (int i=0; i<_selectedProtein.getAliases().length; i++) System.err.println("---alias="+_selectedProtein.getAliases()[i].getDescription());
            System.err.println("**Header: "+ _selectedProtein.getHeader());
            System.err.println("**OrigHeader: "+ _selectedProtein.getOrigHeader());
            ProteinDisplay.openNCBIBrowserWindow(_selectedProtein);
            */
            _selectedFeatures = null;
            if (_featureDisplayMode == ProteinDisplay.DISPLAY_MATCHED_FEATURES_MODE) {
                _displayedFeatures = new ArrayList<Feature>();
                _displayedFeatures.addAll(_proteinMatchedMS1FeatureMap.get(_selectedProtein).values());

                if (_displayedFeatures == null) {
                    _displayedFeatures = new ArrayList<Feature>();
                }
                _featureTableModel.setDisplayedFeatures(_displayedFeatures);
            }
            //TODO: no longer doing this, should remove
            else if (_featureDisplayMode == ProteinDisplay.DISPLAY_UNMATCHED_PROTEIN_PEPTIDES_MODE) {
                //                _displayedFeatures = _unmatchedProteinPeptideMap.get(_selectedProtein);
                if (_displayedFeatures == null) {
                    _displayedFeatures = new ArrayList<Feature>();
                }
                _featureTableModel.setDisplayedFeatures(_displayedFeatures);
            } else if (_featureDisplayMode == ProteinDisplay.DISPLAY_UNMATCHED_MS2_FEATURES_MODE) {
                _displayedFeatures = _unmatchedMs2Features;
            }

            _currentProteinMS2MatchedFeatures = getMatchedMS2Features(_selectedProtein);

            //update protein sequence html
            updateProteinSequence();
            updateMainWindowHighlight();
        }
    }

    /**
     * update the main window feature highlighting
     */
    protected void updateMainWindowHighlight() {
        //update highlighted features in main window
        Feature[] displayedFeatureArray = null;
        if (_displayedFeatures != null)
            displayedFeatureArray = _displayedFeatures.toArray(new Feature[0]);

        if (_featureDisplayMode == ProteinDisplay.DISPLAY_MATCHED_FEATURES_MODE) {
            Feature[] currentProteinMS2MatchedFeatureArray = null;
            if (_currentProteinMS2MatchedFeatures != null)
                currentProteinMS2MatchedFeatureArray = _currentProteinMS2MatchedFeatures.toArray(new Feature[0]);

            ApplicationContext.setProperty(SharedProperties.HIGHLIGHT_FEATURES, displayedFeatureArray);
            ApplicationContext.setProperty(SharedProperties.HIGHLIGHT_FEATURES2,
                    currentProteinMS2MatchedFeatureArray);
            ApplicationContext.setProperty(SharedProperties.HIGHLIGHT_FEATURES3, null);
        } else if (_featureDisplayMode == ProteinDisplay.DISPLAY_UNMATCHED_MS2_FEATURES_MODE
                || _featureDisplayMode == ProteinDisplay.DISPLAY_UNMATCHED_PROTEIN_PEPTIDES_MODE) {
            ApplicationContext.setProperty(SharedProperties.HIGHLIGHT_FEATURES, displayedFeatureArray);
            ApplicationContext.setProperty(SharedProperties.HIGHLIGHT_FEATURES2, null);
            ApplicationContext.setProperty(SharedProperties.HIGHLIGHT_FEATURES3, null);

        }
    }

    /**
     * Update the protein sequence that's displayed in the middle of the window
     */
    protected void updateProteinSequence() {
        _currentProteinMS1AndMS2MatchedFeatures = getMatchedMS1AndMS2Features(_selectedProtein);
        //update protein sequence html
        proteinLabel.setText(ProteinDisplay.getProteinSequenceHtml(_selectedProtein, _displayedFeatures,
                _currentProteinMS2MatchedFeatures, _currentProteinMS1AndMS2MatchedFeatures, _selectedFeatures,
                _featureDisplayMode));
    }

    /**
     * get the matched ms1 features for a given protein.  If none, return empty
     * arraylist
     * @param protein
     * @return
     */
    protected ArrayList<Feature> getMatchedMS1Features(Protein protein) {
        ArrayList<Feature> result = null;
        if (_proteinMatchedMS1FeatureMap != null) {
            result.addAll(_proteinMatchedMS1FeatureMap.get(protein).values());
        }
        if (result == null)
            result = new ArrayList<Feature>();
        return result;
    }

    /**
     * get the matched ms2 features for a given protein
     * @param protein
     * @return
     */
    protected ArrayList<Feature> getMatchedMS2Features(Protein protein) {
        ArrayList<Feature> result = null;
        if (_proteinSequenceMS2FeatureMap != null)
            result = _proteinSequenceMS2FeatureMap.get(protein);
        if (result == null)
            result = new ArrayList<Feature>();
        return result;
    }

    /**
     * get the matched features that match both ms1 and ms2 for a given protein.  Features are considered
     * identical if they have the same peptide.
     * NOTE: The actual Feature itself will be pulled from the MS2 feature list
     * @param protein
     * @return
     */
    protected ArrayList<Feature> getMatchedMS1AndMS2Features(Protein protein) {
        /*
                ArrayList<Pair> result = new ArrayList<Pair>();
                ArrayList<Feature> ms1Features = getMatchedMS1Features(protein);
                if (ms1Features == null || ms1Features.size() == 0)
        return result;
                ArrayList<Feature> ms2Features = getMatchedMS2Features(protein);
            if (ms2Features == null || ms2Features.size() == 0)
        return result;
            
            
                //take no chances.  Don't use collection methods to compare features, go right to the peptide
                for (int i=0; i<ms1Features.size(); i++)
                {
        Feature ms1Feature = ms1Features.get(i);
        for (int j=0; j<ms2Features.size(); j++)
        {
            if (ms1Feature.getPeptide() != null &&
                ms1Feature.getPeptide().equalsIgnoreCase(ms2Features.get(j).getPeptide()))
            {
                result.add(ms2Features.get(j));
                break;
            }
        }
                }
                return result;
        */
        if (_proteinMatchedMS1MS2FeatureMap == null)
            return new ArrayList<Feature>(0);
        return _proteinMatchedMS1MS2FeatureMap.get(protein);
    }

    /**
     * hide the choice of regression mode, time or scan
     * @return
     */
    protected Map<String, Double> getRegressionLineHydroToScanOrTime() {
        if (_scanOrTimeMode == ProteomicsRegressionUtilities.REGRESSION_MODE_TIME)
            return _regressionLineHydroToTime;
        else
            return _regressionLineHydroToScan;
    }

    /**
     * hide the choice of regression mode, time or scan
     * @return
     */
    protected Map<String, Double> getRegressionLineScanOrTimeToHydro() {
        if (_scanOrTimeMode == ProteomicsRegressionUtilities.REGRESSION_MODE_TIME)
            return _regressionLineTimeToHydro;
        else
            return _regressionLineScanToHydro;
    }

    /**
     * Query the user for an MS2 feature file to load for regression
     */
    public void loadMS2RegressionFeatures() {
        File regressionFeatureFile = WorkbenchFileChooser
                .chooseExistingFile(TextProvider.getText("MS2_FILE_FOR_REGRESSION"));
        loadMS2RegressionFeatures(regressionFeatureFile);
    }

    /**
     * load a specified MS2 feature file for regression.  Calculate the regression line for both scan and time
     * Note:  any feature offsets found in the feature file will NOT be used in regression line
     * calculation, as that line needs to be unmuddled by individual known feature offsets
     * @param regressionFeatureFile
     */
    public void loadMS2RegressionFeatures(File regressionFeatureFile) {
        if (regressionFeatureFile == null)
            return;
        try {
            _regressionMS2Features = new FeatureSet(regressionFeatureFile);
            //Arrays.sort(_regressionMS2Features.getFeatures(),new Feature.MassAscComparator());
            //for (int i=0; i<_regressionMS2Features.getFeatures().length; i++)
            //  System.err.println(_regressionMS2Features.getFeatures()[i].getMass() + ", " +_regressionMS2Features.getFeatures()[i].getPProphet());

            FeatureSet.FeatureSelector peptideProphetFeatureSelector = new FeatureSet.FeatureSelector();
            peptideProphetFeatureSelector.setMinPProphet(minPeptideProphet);
            MSRun run = (MSRun) ApplicationContext.getProperty(SharedProperties.MS_RUN);

            _regressionMS2Features.populateTimesForMS2Features(run);
            _regressionMS2Features.filter(peptideProphetFeatureSelector);

            _regressionLineHydroToScan = AmtUtilities.calculateHydrophobicityScanOrTimeRelationship(
                    _regressionMS2Features.getFeatures(), ProteomicsRegressionUtilities.REGRESSION_MODE_SCAN,
                    false);
            _regressionLineHydroToTime = AmtUtilities.calculateHydrophobicityScanOrTimeRelationship(
                    _regressionMS2Features.getFeatures(), ProteomicsRegressionUtilities.REGRESSION_MODE_TIME,
                    false);
            _regressionLineScanToHydro = AmtUtilities.calculateScanOrTimeHydrophobicityRelationship(
                    _regressionMS2Features.getFeatures(), ProteomicsRegressionUtilities.REGRESSION_MODE_SCAN,
                    false);
            _regressionLineTimeToHydro = AmtUtilities.calculateScanOrTimeHydrophobicityRelationship(
                    _regressionMS2Features.getFeatures(), ProteomicsRegressionUtilities.REGRESSION_MODE_TIME,
                    false);

            if (_regressionLineHydroToScan == null || _regressionLineHydroToTime == null) {
                ApplicationContext.infoMessage(TextProvider.getText("ERROR_LOADING_FEATURE_FILE_FILENAME",
                        regressionFeatureFile.getAbsolutePath()));
                quit();
            } else {
                double[] coeffs = new double[2];
                coeffs[1] = AmtUtilities.getSlopeFromRegressionLine(getRegressionLineScanOrTimeToHydro());
                coeffs[0] = AmtUtilities.getInterceptFromRegressionLine(getRegressionLineScanOrTimeToHydro());
                //build a map of known offsets from the features
                AmtDatabaseBuilder.createAmtDatabaseForRun(_regressionMS2Features, _scanOrTimeMode, coeffs, true,
                        null, false);
                loadedRegressionMS2Features = true;
                writePepXmlMs2RegressionMenuItem.setEnabled(true);
                showPredictedHydroTimePlotMenuItem.setEnabled(true);
            }

            //System.err.println("***Parameters: " + _ms1Features.getFeatures().length + ", " + _regressionMS2Features.getFeatures().length + ", " + deltaMass + ", " + deltaMassType + ", " + deltaTime + ", "  + Window2DFeatureSetMatcher.REGRESSION_MODE_TIME + ", " + minPeptideProphet);
            //enable matching menu item, since we now have a basis for prediction
            //System.err.println("****placeholder*** Hydro-scan relationship:  Slope: " +
            //                   _regressionLineHydroToScan.first + "; Intercept: " + _regressionLineHydroToScan.second +"\n" +
            //                   "\nHydro-time relationship:  Slope: " +
            //                   _regressionLineHydroToTime.first + "; Intercept: " + _regressionLineHydroToTime.second +"\n");

        } catch (Exception e) {
            ApplicationContext.errorMessage(TextProvider.getText("ERROR_LOADING_FEATURE_FILE_FILENAME",
                    regressionFeatureFile.getAbsolutePath()), e);
            quit();
        }
    }

    public void buttonFilterProteins_actionPerformed(ActionEvent e) {
        _proteinFilterString = textProteinPrefix.getText();

        filterDisplayedProteins();
    }

    /**
     * handler for peptide selection
     * @param e
     */
    public void tblFeaturesModel_valueChanged(ListSelectionEvent e) {
        int selectedRow = tblFeatures.getSelectedRow();
        if (selectedRow >= 0) {
            _selectedFeatures = _featureTableModel.getSelectedFeatures(tblFeatures.getSelectedRows());
            updateProteinSequence();
            ApplicationContext.setProperty(SharedProperties.HIGHLIGHT_FEATURES3,
                    _selectedFeatures.toArray(new Feature[0]));
            //select the feature -- scroll to it in the main window, and show its details
            if (_selectedFeatures.size() == 1)
                ((WorkbenchFrame) (Application.getInstance().getFrame())).imageComponent
                        .selectFeature(_selectedFeatures.get(0));
        }
    }

    /**
     * Narrow down the displayed proteins, and remove protein and peptide selections
     */
    protected void filterDisplayedProteins() {
        if ("".equals(_proteinFilterString))
            _displayedProteins = _matchedProteins;
        else {
            String regexString = _proteinFilterString.toUpperCase();
            //System.err.println("Before: " +regexString);
            //wildcards
            if (_proteinFilterString.contains("*")) {
                StringBuffer regexBuf = new StringBuffer();
                for (int i = 0; i < _proteinFilterString.length(); i++) {
                    char currentChar = _proteinFilterString.charAt(i);
                    if (currentChar == '*') {
                        regexBuf.append(".*");
                    } else
                        regexBuf.append(Character.toUpperCase(currentChar));
                }
                regexString = regexBuf.toString();
            }
            //System.err.println("after: " +regexString);

            _displayedProteins = new ArrayList<Protein>();

            for (Protein protein : _matchedProteins) {
                String currentHeader = protein.getHeader();
                if (currentHeader.toUpperCase().matches(regexString)) {
                    _displayedProteins.add(protein);
                }
            }
            updateDisplayedProteins();

            //clear out dependent fields
            proteinLabel.setText("");
            _displayedFeatures = new ArrayList<Feature>(0);
            _featureTableModel.setDisplayedFeatures(_displayedFeatures);
        }
    }

    /**
     * Pick a featureset to represent the ms1 features.  By convention this is the most
     * recently chosen, visible featureset
     * @return
     */
    protected FeatureSet getMS1Features() {
        FeatureSet result = null;

        //TODO:  Does this do what it's supposed to?
        List<FeatureSet> featureSets = (List<FeatureSet>) ApplicationContext
                .getProperty(SharedProperties.FEATURE_SETS);

        if (null == featureSets)
            return null;
        else {
            for (FeatureSet fs : featureSets) {
                if (fs.isDisplayed())
                    result = fs;
            }
        }
        return result;
    }

    /**
     * For CommandFileRunner
     * @param fastaFile
     */
    public void findMatchedMS1Proteins(File fastaFile) {
        _fastaFile = fastaFile;
        findMatchedMS1Proteins();
    }

    /**
     * Find all proteins in the loaded fasta file that match MS1 features
     */
    public void findMatchedMS1Proteins() {
        try {
            ArrayList<Protein> proteinsInFasta = ProteinUtilities.loadProteinsFromFasta(_fastaFile);
            //handling for no matches
            if (proteinsInFasta.size() == 0) {
                ApplicationContext.infoMessage(TextProvider.getText("NO_PROTEINS_FOUND"));
                return;
            }

            MSRun run = (MSRun) ApplicationContext.getProperty(SharedProperties.MS_RUN);

            double deltaScanOrTime = deltaScan;
            if (_scanOrTimeMode == ProteomicsRegressionUtilities.REGRESSION_MODE_TIME)
                deltaScanOrTime = deltaTime;
            Protein[] proteinArray = proteinsInFasta.toArray(new Protein[0]);
            //TODO: the last argument in this call indicates that we should be matching peptides not
            //TODO: found in the AMT database. this may not be the right thing to do in all cases...
            //TODO: might need to provide control over this in the UI
            _proteinMatchedMS1FeatureMap = ProteinMatcher.findMatchesForAllProteins(_ms1Features, proteinArray,
                    MS2ExtraInfoDef.getFeatureSetModifications(_regressionMS2Features), _amtDatabase,
                    minMatchedFeatures);
            //            _proteinPercentCoverageMap = ProteinMatcher.createProteinPercentCoverageMap(_proteinMatchedMS1FeatureMap);
            HashMap<Protein, Map<String, Feature>> filteredProteinMatchedMS1FeatureMap = new HashMap<Protein, Map<String, Feature>>();
            for (Protein currentProtein : _proteinPercentCoverageMap.keySet()) {
                if (_proteinPercentCoverageMap.get(currentProtein) >= minPercentFeatureCoverage) {
                    filteredProteinMatchedMS1FeatureMap.put(currentProtein,
                            _proteinMatchedMS1FeatureMap.get(currentProtein));
                }
            }
            _proteinMatchedMS1FeatureMap = filteredProteinMatchedMS1FeatureMap;

            //Create a map of features to the number of peptide masses that match them
            //TODO: restore this            
            //            createProteinMS1FeatureNumberMassMatchesMap(proteinArray);

            //Iterator<ArrayList<Feature>> featureArrayIterator = _proteinMatchedMS1FeatureMap.values().iterator();
            //while (featureArrayIterator.hasNext())
            //{
            //ArrayList<Feature> bunchOFeatures = featureArrayIterator.next();
            //for (int i=0; i<bunchOFeatures.size(); i++)
            //    System.err.println("Peptide: " + bunchOFeatures.get(i).getPeptide() +
            //                        ", offset: " + bunchOFeatures.get(i).getProperty("PREDICTED_TIME_OFFSET"));
            //}
            //update UI
            _displayedFeatures = new ArrayList<Feature>(0);
            _featureTableModel.setDisplayedFeatures(_displayedFeatures);

            Set<Protein> matchedProteinSet = _proteinMatchedMS1FeatureMap.keySet();
            Iterator<Protein> matchedProteinIterator = matchedProteinSet.iterator();
            _matchedProteins = new ArrayList<Protein>(matchedProteinSet.size());
            while (matchedProteinIterator.hasNext())
                _matchedProteins.add(matchedProteinIterator.next());

            //            Collections.sort(_matchedProteins, new Protein.HeaderComparator());
            //sort by MS1 matches
            Collections.sort(_matchedProteins, new ProteinMS1PercentCoverageComparator());
            _displayedProteins = _matchedProteins;

            updateDisplayedProteins();

            _proteinTableModel.hideMS2Columns();

            tblProteins.getSelectionModel().setSelectionInterval(0, 0);
            tblProteinsModel_valueChanged(null);

            showMS2MatchedProteinsMenuItem.setEnabled(true);
            buttonFilterProteins.setEnabled(true);

        } catch (Exception e) {
            ApplicationContext.errorMessage(TextProvider.getText("ERROR_MATCHING_FEATURES"), e);

        }

    }

    /**
     * Write out a pepXml file containing the features in the regression MS2 file.
     * Each feature will contain the observed offset from predicted elution time.
     * NOTE: if a secondary offset file is loaded explicitly, those offsets will
     * also get written out along with the offsets from the regression file
     * @param outPepXmlFile
     */
    public void writePepXmlMs2RegressionOffsetFile(File outPepXmlFile) {
        if (!loadedRegressionMS2Features)
            return;

        try {
            FeaturePepXmlWriter pepXmlWriter = new FeaturePepXmlWriter(_regressionMS2Features.getFeatures(),
                    MS2ExtraInfoDef.getFeatureSetModifications(_regressionMS2Features));
            pepXmlWriter.write(outPepXmlFile);
        } catch (Exception e) {
            e.printStackTrace(System.err);
        }
    }

    /**
     * Write out a pepXml file containing only the features that were matched in both MS1
     * and MS2.  Each feature will contain the observed offset from predicted elution time
     * @param outPepXmlFile
     */
    /*
        public void writePepXmlMs1Ms2Matches(File outPepXmlFile)
        {
    if (!loadedMatchingMS2Features)
        return;
        
    ArrayList<Feature> allMatchedFeatures = new ArrayList<Feature>();
        
    Iterator<Protein> proteinIterator = _proteinMatchedMS1MS2FeatureMap.keySet().iterator();
    while (proteinIterator.hasNext())
    {
        ArrayList<Feature> currentProteinMatchedFeatures = getMatchedMS1AndMS2Features(proteinIterator.next());
        allMatchedFeatures.addAll(currentProteinMatchedFeatures);
    }
        
    try
    {
        
        FeaturePepXmlWriter pepXmlWriter = new FeaturePepXmlWriter(allMatchedFeatures.toArray(new Feature[0]),
                                                     _matchingMS2Features.getModifications());
        pepXmlWriter.write(outPepXmlFile);
    }
    catch (Exception e)
    {
        e.printStackTrace(System.err);
    }
        }
    */

    /**
     * Cover method passing in a file
     * @param matchingMS2FeatureFile
     */
    public void findMatchedMS2Proteins(File matchingMS2FeatureFile) {
        try {
            _matchingMS2Features = new FeatureSet(matchingMS2FeatureFile);
            if (_matchingMS2Features.getLoadStatus() != FeatureSet.FEATURESET_LOAD_SUCCESS) {
                ApplicationContext.infoMessage(TextProvider.getText("" + "ERROR_LOADING_FEATURE_FILE_FILENAME",
                        matchingMS2FeatureFile.getAbsolutePath()) + ": "
                        + _matchingMS2Features.getLoadStatusMessage());
                return;
            }

            FeatureSet.FeatureSelector peptideProphetFeatureSelector = new FeatureSet.FeatureSelector();
            peptideProphetFeatureSelector.setMinPProphet(minPeptideProphet);
            _matchingMS2Features.filter(peptideProphetFeatureSelector);

            _matchingMS2FeatureFile = matchingMS2FeatureFile;

        } catch (Exception e) {
            ApplicationContext.errorMessage(TextProvider.getText("ERROR_LOADING_FEATURE_FILE_FILENAME",
                    matchingMS2FeatureFile.getAbsolutePath()), e);

        }
        findMatchedMS2Proteins();
    }

    /**
     * Identify all matchingMS2 features with peptides that occur in the matched proteins.
     * Match MS1 features with matchingMS2 Features
     */
    public void findMatchedMS2Proteins() {
        MSRun run = (MSRun) ApplicationContext.getProperty(SharedProperties.MS_RUN);
        //using the ms2 feature set for _matching_, find the features with peptides that are
        //found in each protein.  This is used for display of matched peptides.
        //Only match if peptideprophet score above supplied cutoff
        //TODO: restore this, or something
        //        _proteinSequenceMS2FeatureMap =
        //                ProteinMatcher.mapProteinsToFeatures(_matchedProteins,
        //                                                     _matchingMS2Features.getFeatures(),
        //                                                     minPeptideProphet);

        //this arraylist will hold all the unmatched ms2 features
        _unmatchedMs2Features = new ArrayList<Feature>();

        ClusteringFeatureSetMatcher featureSetMatcher = new ClusteringFeatureSetMatcher();
        featureSetMatcher.init(deltaMass, deltaMassType, (float) deltaHydrophobicity);
        FeatureSetMatcher.FeatureMatchingResult featureMatchingResult = featureSetMatcher
                .matchFeatures(_ms1Features, _matchingMS2Features);

        //associate ms1/ms2 matches with individual proteins based on peptide
        //(may be multiply associated)
        //TODO: should probably move this into ProteinMatcher for commandline use
        _proteinMatchedMS1MS2FeatureMap = new HashMap<Protein, ArrayList<Feature>>();
        Iterator<Protein> proteinIterator = _proteinMatchedMS1FeatureMap.keySet().iterator();
        while (proteinIterator.hasNext()) {
            Protein currentProtein = proteinIterator.next();
            String currentProteinSequence = currentProtein.getSequenceAsString();
            ArrayList<Feature> currentProteinMS1MS2Matches = new ArrayList<Feature>();
            for (int i = 0; i < _ms2ms1MatchedFeaturePairs.size(); i++) {
                Pair currentPair = _ms2ms1MatchedFeaturePairs.get(i);
                Feature ms2Feature = (Feature) currentPair.first;
                //null test shouldn't really be necessary.  Paranoia
                if (MS2ExtraInfoDef.getFirstPeptide(ms2Feature) != null
                        && currentProteinSequence.contains(MS2ExtraInfoDef.getFirstPeptide(ms2Feature))) {
                    currentProteinMS1MS2Matches.add(ms2Feature);
                }
            }
            _proteinMatchedMS1MS2FeatureMap.put(currentProtein, currentProteinMS1MS2Matches);
        }

        //display changes
        if (_selectedProtein != null)
            _currentProteinMS2MatchedFeatures = getMatchedMS2Features(_selectedProtein);
        tblProteinsModel_valueChanged(null);
        loadedMatchingMS2Features = true;
        _proteinTableModel.showMS2Columns();
        _featureTableModel.fireTableDataChanged();
    }

    protected void updateDisplayedProteins() {
        _proteinTableModel.setDisplayedProteins(_displayedProteins);
        labelNumProteins.setText("" + _displayedProteins.size());
    }

    /**
     * Loads a FeatureSet from a file, solely for capturing a mapping between peptides and
     * their observed offsets.  Throws away the featureset
     * @param offsetFeatureFile
     */
    public void loadOffsetsFromFeatureFile(File offsetFeatureFile) {
        /*
                FeatureSet features = null;
                int numPreviousOffsets = _peptideOffsetMap.size();
                try
                {
        features = new FeatureSet(offsetFeatureFile);
        if (features.getLoadStatus() != FeatureSet.FEATURESET_LOAD_SUCCESS)
        {
            ApplicationContext.infoMessage(TextProvider.getText("ERROR_LOADING_FEATURE_FILE_FILENAME", offsetFeatureFile.getAbsolutePath()) +
                    ": " + features.getLoadStatusMessage());
            return;
        }
                }
                catch (Exception e)
                {
        ApplicationContext.errorMessage(TextProvider.getText("ERROR_LOADING_FEATURE_FILE_FILENAME", offsetFeatureFile.getAbsolutePath()), e);
        return;
                }
                //build a map of known offsets from the features
                ProteinMatcher.augmentPeptideOffsetMap(_peptideOffsetMap, features.getFeatures());
                // _regressionMS2FeaturesPeptideOffsetMap = new HashMap<String,Double>();
                ApplicationContext.infoMessage(TextProvider.getText("LOADED_X_OFFSETS_FROM_FILE","" + (_peptideOffsetMap.size() - numPreviousOffsets)));
        */
    }

    protected void showPredictedHydroTimePlot() {
        //clone because we're sorting
        Feature[] regressionMS2FeatureArray = _regressionMS2Features.getFeatures().clone();
        Arrays.sort(regressionMS2FeatureArray, new Feature.ScanAscComparator());
        int numQualifyingFeatures = 0;
        for (int i = 0; i < regressionMS2FeatureArray.length; i++) {
            if (MS2ExtraInfoDef.getPeptideProphet(regressionMS2FeatureArray[i]) >= minPeptideProphet)
                numQualifyingFeatures++;
        }
        float[][] scatterPlotData = new float[2][numQualifyingFeatures];
        int[] sortedScanArray = new int[numQualifyingFeatures];
        int arrayIndex = 0;
        for (int i = 0; i < regressionMS2FeatureArray.length; i++) {
            if (MS2ExtraInfoDef.getPeptideProphet(regressionMS2FeatureArray[i]) >= minPeptideProphet) {
                Feature ms2Feature = regressionMS2FeatureArray[i];
                scatterPlotData[0][arrayIndex] = (float) AmtUtilities
                        .calculateNormalizedHydrophobicity(MS2ExtraInfoDef.getFirstPeptide(ms2Feature));
                //                scatterPlotData[1][arrayIndex] = ms2Feature.getScan();
                sortedScanArray[arrayIndex] = ms2Feature.getScan();
                arrayIndex++;
            }
        }
        MSRun run = (MSRun) ApplicationContext.getProperty(SharedProperties.MS_RUN);
        double[] timesArrayTemp = AmtUtilities.getTimesForSortedScanArray(run, sortedScanArray);
        float[] timesArray = new float[timesArrayTemp.length];
        for (int i = 0; i < timesArrayTemp.length; i++)
            timesArray[i] = (float) timesArrayTemp[i];
        scatterPlotData[1] = timesArray;

        FastScatterPlot scatterPlot = new FastScatterPlot(scatterPlotData,
                new NumberAxis(TextProvider.getText("CALCULATED_HYDROPHOBICITY")),
                new NumberAxis(TextProvider.getText("TIME")));
        scatterPlot.setPaint(Color.BLUE);
        scatterPlot.setOutlineStroke(new BasicStroke(10));
        ChartDialog chartDialog = new ChartDialog(scatterPlot);
        chartDialog.setLocation(getLocation());
        chartDialog.setVisible(true);
    }

    /**
     * tablemodel for the protein list table
     */
    private class ProteinTableModel extends DefaultTableModel {
        protected final String PROTEIN_COLUMN_NAME = "PROTEIN";
        protected final String MS2_FEATURES_COLUMN_NAME = "MS2_FEATURES";
        protected final String MS1_FEATURES_COLUMN_NAME = "MS1_FEATURES";
        protected final String MS1_MS2_FEATURES_COLUMN_NAME = "MS1_AND_MS2";
        protected final String MS1_PERCENT_FEATURE_COVERAGE_COLUMN_NAME = "PERCENT_COVERAGE";

        protected ArrayList<Protein> _displayedProteins = new ArrayList<Protein>();

        protected TableColumnModel columnModel = null;

        Hashtable<String, TableColumn> columnHash;
        Hashtable<TableColumn, String> columnReverseHash;

        public ProteinTableModel() {
            columnHash = new Hashtable<String, TableColumn>();
            columnReverseHash = new Hashtable<TableColumn, String>();

            TableColumn proteinColumn = new TableColumn();
            proteinColumn.setHeaderValue(TextProvider.getText(PROTEIN_COLUMN_NAME));
            proteinColumn.setModelIndex(0);
            columnHash.put(PROTEIN_COLUMN_NAME, proteinColumn);
            columnReverseHash.put(proteinColumn, PROTEIN_COLUMN_NAME);

            TableColumn ms1Column = new TableColumn();
            ms1Column.setHeaderValue(TextProvider.getText(MS1_FEATURES_COLUMN_NAME));
            ms1Column.setModelIndex(1);
            columnHash.put(MS1_FEATURES_COLUMN_NAME, ms1Column);
            columnReverseHash.put(ms1Column, MS1_FEATURES_COLUMN_NAME);

            TableColumn ms1FeatureCoverageColumn = new TableColumn();
            ms1FeatureCoverageColumn.setHeaderValue(TextProvider.getText(MS1_PERCENT_FEATURE_COVERAGE_COLUMN_NAME));
            ms1FeatureCoverageColumn.setModelIndex(2);
            columnHash.put(MS1_PERCENT_FEATURE_COVERAGE_COLUMN_NAME, ms1FeatureCoverageColumn);
            columnReverseHash.put(ms1FeatureCoverageColumn, MS1_PERCENT_FEATURE_COVERAGE_COLUMN_NAME);

            TableColumn ms2Column = new TableColumn();
            ms2Column.setHeaderValue(TextProvider.getText(MS2_FEATURES_COLUMN_NAME));
            ms2Column.setModelIndex(3);
            columnHash.put(MS2_FEATURES_COLUMN_NAME, ms2Column);
            columnReverseHash.put(ms2Column, MS2_FEATURES_COLUMN_NAME);

            TableColumn ms1ms2Column = new TableColumn();
            ms1ms2Column.setHeaderValue(TextProvider.getText(MS1_MS2_FEATURES_COLUMN_NAME));
            ms1ms2Column.setModelIndex(4);
            columnHash.put(MS1_MS2_FEATURES_COLUMN_NAME, ms1ms2Column);
            columnReverseHash.put(ms1ms2Column, MS1_MS2_FEATURES_COLUMN_NAME);

            columnModel = new DefaultTableColumnModel();
            columnModel.addColumn(proteinColumn);
            columnModel.addColumn(ms1Column);
            columnModel.addColumn(ms1FeatureCoverageColumn);
        }

        public void showMS2Columns() {
            //just in case it's already there, don't show two copies
            columnModel.removeColumn(columnHash.get(MS2_FEATURES_COLUMN_NAME));
            columnModel.removeColumn(columnHash.get(MS1_MS2_FEATURES_COLUMN_NAME));
            columnModel.addColumn(columnHash.get(MS2_FEATURES_COLUMN_NAME));
            columnModel.addColumn(columnHash.get(MS1_MS2_FEATURES_COLUMN_NAME));
        }

        public void hideMS2Columns() {
            columnModel.removeColumn(columnHash.get(MS2_FEATURES_COLUMN_NAME));
            columnModel.removeColumn(columnHash.get(MS1_MS2_FEATURES_COLUMN_NAME));
        }

        public void setDisplayedProteins(ArrayList<Protein> displayedProteins) {
            _displayedProteins = displayedProteins;
            fireTableDataChanged();
        }

        public int getRowCount() {
            if (_displayedProteins == null)
                return 0;
            return _displayedProteins.size();
        }

        public Class getColumnClass(int c) {
            String columnName = getNameForColumn(c);
            if (PROTEIN_COLUMN_NAME.equals(columnName))
                return String.class;
            else if (MS2_FEATURES_COLUMN_NAME.equals(columnName))
                return Integer.class;
            else if (MS1_FEATURES_COLUMN_NAME.equals(columnName))
                return Integer.class;
            else if (MS1_PERCENT_FEATURE_COVERAGE_COLUMN_NAME.equals(columnName))
                return String.class;
            else if (MS1_MS2_FEATURES_COLUMN_NAME.equals(columnName))
                return Integer.class;
            else
                return Object.class;
        }

        public boolean isCellEditable(int row, int col) {
            return false;
        }

        protected String getNameForColumn(int col) {
            return columnReverseHash.get(columnModel.getColumn(col));
        }

        public Object getValueAt(int row, int col) {

            if (row >= _displayedProteins.size())
                return null;

            Protein protein = _displayedProteins.get(row);
            String columnName = getNameForColumn(col);
            if (PROTEIN_COLUMN_NAME.equals(columnName))
                return protein.getLookup();
            else if (MS2_FEATURES_COLUMN_NAME.equals(columnName))
                return getMatchedMS2Features(protein).size();
            else if (MS1_FEATURES_COLUMN_NAME.equals(columnName))
                return getMatchedMS1Features(protein).size();
            else if (MS1_PERCENT_FEATURE_COVERAGE_COLUMN_NAME.equals(columnName))
                return round(ProteinDisplay.calculatePercentCovered(getMatchedMS1Features(protein),
                        protein.getSequenceAsString()), 2) + "%";
            else if (MS1_MS2_FEATURES_COLUMN_NAME.equals(columnName))
                return getMatchedMS1AndMS2Features(protein).size();
            else
                return "";
        }

        public Protein getSelectedProtein(int i) {
            return _displayedProteins.get(i);
        }

    }

    /**
     * tablemodel for the peptide display table
     */
    private class FeatureTableModel extends AbstractTableModel {
        protected ArrayList<Feature> _displayedFeatures = new ArrayList<Feature>();
        protected String[] _displayedFeatureHtmlStrings = null;

        public void setDisplayedFeatures(ArrayList<Feature> displayedFeatures) {
            _displayedFeatures = displayedFeatures;
            if (_displayedFeatures != null) {
                Collections.sort(_displayedFeatures, new Feature.MassAscComparator());
                _displayedFeatureHtmlStrings = ProteinDisplay.getHtmlForFeatures(_displayedFeatures,
                        _currentProteinMS1AndMS2MatchedFeatures, _selectedFeatures, _featureDisplayMode);
            }
            fireTableDataChanged();
        }

        public int getRowCount() {
            return _displayedFeatures.size();
        }

        public int getColumnCount() {
            return columnNames.length;
        }

        public Class getColumnClass(int c) {
            switch (c) {
            case 0:
                return Object.class;
            case 1:
                return Integer.class;
            case 2:
                return Float.class;
            case 3:
                return Float.class;
            case 4:
                return Integer.class;
            default:
                return Object.class;
            }
        }

        public boolean isCellEditable(int row, int col) {
            return false;
        }

        public Object getValueAt(int row, int col) {
            if (row >= _displayedFeatures.size())
                return null;

            Feature feature = _displayedFeatures.get(row);
            switch (col) {
            case 0:
                return _displayedFeatureHtmlStrings[row];
            //                    return feature.getPeptide();
            case 1:
                return feature.getScan();
            case 2:
                return feature.getMass();
            case 3:
                return feature.getTotalIntensity();
            default:
                return "";
            }
        }

        private String[] columnNames = new String[] { TextProvider.getText("PEPTIDE"), TextProvider.getText("SCAN"),
                TextProvider.getText("MASS"), TextProvider.getText("TOTAL_INTENSITY"),
                TextProvider.getText("MASS_MATCHES") };

        public String getColumnName(int i) {
            return i >= 0 && i < columnNames.length ? columnNames[i] : "";
        }

        public ArrayList<Feature> getSelectedFeatures(int[] indexes) {
            if (indexes == null || indexes.length == 0)
                return null;

            ArrayList<Feature> featureList = new ArrayList<Feature>(indexes.length);

            for (int i = 0; i < indexes.length; i++) {
                featureList.add(_displayedFeatures.get(indexes[i]));
            }
            return featureList;
        }

    }

    /**
     *  menu action for saving a fasta file containing the currently displayed proteins
     */
    public class SaveFastaAction extends AbstractAction {
        public void actionPerformed(ActionEvent evt) {
            if (_displayedProteins == null) {
                ApplicationContext.infoMessage(TextProvider.getText("NO_PROTEINS_TO_SAVE"));
                return;
            }

            WorkbenchFileChooser chooser = new WorkbenchFileChooser();
            int chooserStatus = chooser.showOpenDialog(ApplicationContext.getFrame());
            //if user didn't hit OK, ignore
            if (chooserStatus != JFileChooser.APPROVE_OPTION)
                return;
            File outFastaFile = chooser.getSelectedFile();

            try {
                Protein.saveProteinArrayToFasta(_displayedProteins.toArray(new Protein[0]), outFastaFile);
                ApplicationContext.infoMessage(TextProvider.getText("SAVED_FASTA_FILE"));
            } catch (Exception e) {
                ApplicationContext.errorMessage(TextProvider.getText("ERROR_SAVING_FASTA"), e);
            }
        }
    }

    /**
     *  menu action for loading an ms2 feature file for regression
     */
    public class SetParametersAction extends AbstractAction {
        public void actionPerformed(ActionEvent evt) {
            mParametersDialog = new ParametersDialog();
            mParametersDialog.setVisible(true);
        }
    }

    //for accessing this action through CommandFileRunner
    public SetParametersAction getSetParametersAction() {
        return new SetParametersAction();
    }

    /**
     *  menu action for loading an ms2 feature file for regression
     */
    public class LoadMS2RegressionAction extends AbstractAction {
        public void actionPerformed(ActionEvent evt) {
            loadMS2RegressionFeatures();
        }
    }

    /**
     * menu action for loading a fasta file for protein matching
     */
    public class MatchProteinsFromFastaAction extends AbstractAction {
        public void actionPerformed(ActionEvent evt) {
            _fastaFile = WorkbenchFileChooser.chooseExistingFile("CHOOSE_FASTA_FILE");
            if (_fastaFile == null)
                return;
            findMatchedMS1Proteins();
        }
    }

    /**
     *  menu action for loading an ms2 feature file for regression
     */
    public class ShowPredictedHydroTimePlotAction extends AbstractAction {
        public void actionPerformed(ActionEvent evt) {
            showPredictedHydroTimePlot();
        }
    }

    /**
     *  menu action for loading an ms2 feature file for regression
     */
    public class FindProteinsMatchedInMS2Action extends AbstractAction {
        public void actionPerformed(ActionEvent evt) {
            File matchingFeatureFile = WorkbenchFileChooser.chooseExistingFile("MATCHING_MS2_FILE");
            if (matchingFeatureFile == null)
                return;
            findMatchedMS2Proteins(matchingFeatureFile);

        }
    }

    /**
     *  menu action for
     */
    public class LoadOffsetFeatureSetAction extends AbstractAction {
        public void actionPerformed(ActionEvent evt) {
            File offsetFeatureFile = WorkbenchFileChooser.chooseExistingFile("OFFSET_FEATURE_FILE");
            if (offsetFeatureFile == null)
                return;
            loadOffsetsFromFeatureFile(offsetFeatureFile);
        }
    }

    /**
     * menu action for writing out a pepxml file containing all the features matched
     * between ms1 and ms2 feature sets
     */
    /*
        public class WritePepXmlMatchedMs1Ms2Action extends AbstractAction
        {
    public void actionPerformed(ActionEvent evt)
    {
        WorkbenchFileChooser chooser = new WorkbenchFileChooser();
        chooser.setDialogTitle(TextProvider.getText("OUTPUT_PEPXML_FILE"));
        chooser.showOpenDialog(ApplicationContext.getFrame());
        File outPepXmlFile = chooser.getSelectedFile();
        writePepXmlMs1Ms2Matches(outPepXmlFile);
    }
        }
    */

    public class WritePepXmlMs2RegressionAction extends AbstractAction {
        public void actionPerformed(ActionEvent evt) {
            WorkbenchFileChooser chooser = new WorkbenchFileChooser();
            chooser.setDialogTitle(TextProvider.getText("OUTPUT_PEPXML_FILE"));
            int chooserStatus = chooser.showOpenDialog(ApplicationContext.getFrame());
            //if user didn't hit OK, ignore
            if (chooserStatus != JFileChooser.APPROVE_OPTION)
                return;
            File outPepXmlFile = chooser.getSelectedFile();
            writePepXmlMs2RegressionOffsetFile(outPepXmlFile);
        }
    }

    /**
     * context menu for pulling up protein information
     */
    protected class ProteinTablePopupMenu extends JPopupMenu {
        JMenuItem menuItemNCBI;

        public ProteinTablePopupMenu() {
            super();

            menuItemNCBI = new JMenuItem(TextProvider.getText("NCBI_WEB_LOOKUP_SELECTED_PROTEIN"));

            ListenerHelper helper = new ListenerHelper(this);
            helper.addListener(menuItemNCBI, "menuItemNCBI_actionPerformed");

            //layout
            add(menuItemNCBI);
        }

        public void menuItemNCBI_actionPerformed(ActionEvent event) {
            if (_selectedProtein != null)
                ProteinDisplay.openNCBIBrowserWindow(_selectedProtein);
        }

    }

    /**
     * Compare proteins based on how many MS1 matches they have.  The one with fewer
     * matches is deemed the greater.  This is for display in the protein table
     */
    protected class ProteinMS1MatchesComparator implements Comparator {
        public int compare(Object o1, Object o2) {
            int o1Features = getMatchedMS1Features((Protein) o1).size();
            int o2Features = getMatchedMS1Features((Protein) o2).size();
            return o1Features < o2Features ? 1 : o2Features < o1Features ? -1 : 0;
        }
    }

    /*
     * Compare proteins based on percent feature coverage.  The one with fewer
     * matches is deemed the greater.  This is for display in the protein table
     */
    protected class ProteinMS1PercentCoverageComparator implements Comparator {
        public int compare(Object o1, Object o2) {
            double o1Percent = _proteinPercentCoverageMap.get(o1);
            double o2Percent = _proteinPercentCoverageMap.get(o2);

            return o1Percent < o2Percent ? 1 : o2Percent < o1Percent ? -1 : 0;
        }
    }

    /**
     * dialog box for specifying parameters.  This is also operated by CommandFileRunner
     */
    public class ParametersDialog extends JDialog {
        public JTextField textDeltaMass;
        public JComboBox comboBoxDaPpm;
        public JTextField textDeltaScanTime;
        public JComboBox comboBoxScanTime;
        public JTextField textMinMatchedFeatures;
        public JTextField textMinPercentFeatureCoverage;
        public JTextField textMinPeptideProphet;

        public JButton buttonCancel;
        public JButton buttonOK;

        protected ProteinMatcherFrame _proteinMatcherFrame = null;

        public ParametersDialog() {
            super(ApplicationContext.getFrame(), TextProvider.getText("SET_PARAMETERS"));

            //graphical stuff
            Container contentPanel = null;
            try {
                contentPanel = Localizer.renderSwixml("org/fhcrc/cpl/viewer/gui/AMTParametersDialog.xml", this);
                setContentPane(contentPanel);
                pack();
            } catch (Exception x) {
                ApplicationContext.errorMessage(TextProvider.getText("ERROR_CREATING_DIALOG"), x);
                throw new RuntimeException(x);
            }

            //TODO: should really use TextProvider here, and use an internal value for determining state
            comboBoxDaPpm.addItem("Daltons");
            comboBoxDaPpm.addItem("PPM");
            comboBoxScanTime.addItem("scans");
            comboBoxScanTime.addItem("seconds");

            textDeltaMass.setText(Double.toString(round((double) deltaMass, 2)));

            if (deltaMassType == FeatureSetMatcher.DELTA_MASS_TYPE_PPM)
                comboBoxDaPpm.setSelectedItem(comboBoxDaPpm.getItemAt(1));
            else
                comboBoxDaPpm.setSelectedItem(comboBoxDaPpm.getItemAt(0));

            if (_scanOrTimeMode == ProteomicsRegressionUtilities.REGRESSION_MODE_TIME) {
                comboBoxScanTime.setSelectedItem(comboBoxScanTime.getItemAt(1));
                textDeltaScanTime.setText(Double.toString(round(deltaTime, 2)));
            } else {
                comboBoxScanTime.setSelectedItem(comboBoxScanTime.getItemAt(0));
                textDeltaScanTime.setText(Integer.toString(deltaScan));
            }

            textMinMatchedFeatures.setText(Integer.toString(minMatchedFeatures));
            textMinPercentFeatureCoverage.setText(Integer.toString(minPercentFeatureCoverage));
            textMinPeptideProphet.setText(Double.toString(round((double) minPeptideProphet, 2)));

            ListenerHelper helper = new ListenerHelper(this);
            helper.addListener(buttonCancel, "buttonCancel_actionPerformed");
            helper.addListener(buttonOK, "buttonOK_actionPerformed");

            getRootPane().setDefaultButton(buttonOK);
        }

        public void buttonOK_actionPerformed(ActionEvent event) {
            //one big try block to catch all parsing problems.  This could
            //be broken up.
            //Assign everything to temporary variables.  Then, if everything is parsed
            //ok, assign all the real variables
            try {
                float tempDeltaMass = (float) Double.parseDouble(textDeltaMass.getText());
                int tempDeltaMassType = FeatureSetMatcher.DELTA_MASS_TYPE_ABSOLUTE;
                if ("PPM".equals(comboBoxDaPpm.getSelectedItem()))
                    tempDeltaMassType = FeatureSetMatcher.DELTA_MASS_TYPE_PPM;
                int tempMinMatchedFeatures = (int) Double.parseDouble(textMinMatchedFeatures.getText());
                int tempMinPercentFeatureCoverage = (int) Double
                        .parseDouble(textMinPercentFeatureCoverage.getText());
                float tempMinPeptideProphet = (float) Double.parseDouble(textMinPeptideProphet.getText());

                String comboBoxValue = (String) comboBoxScanTime.getSelectedItem();

                //TODO: the actual strings "elution" and "scan" should be internal values, not displayed
                int tempScanOrTimeMode = ProteomicsRegressionUtilities.REGRESSION_MODE_SCAN;
                double tempDeltaTime = deltaTime;
                int tempDeltaScan = deltaScan;
                if ("seconds".equals(comboBoxValue)) {
                    tempScanOrTimeMode = ProteomicsRegressionUtilities.REGRESSION_MODE_TIME;
                    tempDeltaTime = (float) Double.parseDouble(textDeltaScanTime.getText());
                } else if ("scans".equals(comboBoxValue)) {
                    tempScanOrTimeMode = ProteomicsRegressionUtilities.REGRESSION_MODE_SCAN;
                    tempDeltaScan = (int) Double.parseDouble(textDeltaScanTime.getText());
                }

                //if we got here, we're ok, assign the real variables
                deltaMass = tempDeltaMass;
                deltaMassType = tempDeltaMassType;
                minMatchedFeatures = tempMinMatchedFeatures;
                minPercentFeatureCoverage = tempMinPercentFeatureCoverage;
                minPeptideProphet = tempMinPeptideProphet;
                _scanOrTimeMode = tempScanOrTimeMode;
                if (_scanOrTimeMode == ProteomicsRegressionUtilities.REGRESSION_MODE_TIME)
                    deltaTime = tempDeltaTime;
                else
                    deltaScan = tempDeltaScan;

                if (_fastaFile != null)
                    findMatchedMS1Proteins();
                if (_matchingMS2Features != null)
                    findMatchedMS2Proteins();

                this.setVisible(false);
                this.dispose();
            } catch (NumberFormatException e) {
                ApplicationContext.errorMessage(TextProvider.getText("BAD_PARAMETER_ERROR_MESSAGE"), e);
            }
        }

        public void buttonCancel_actionPerformed(ActionEvent e) {
            this.setVisible(false);
            this.dispose();
        }
    }

    /** can't find a good way to use this yet
        // This comparator is used to sort vectors of data
        public static class ColumnSorter implements Comparator {
    int colIndex;
    boolean ascending;
    ColumnSorter(int colIndex, boolean ascending) {
        this.colIndex = colIndex;
        this.ascending = ascending;
    }
    public int compare(Object a, Object b) {
        Vector v1 = (Vector)a;
        Vector v2 = (Vector)b;
        Object o1 = v1.get(colIndex);
        Object o2 = v2.get(colIndex);
        
        // Treat empty strains like nulls
        if (o1 instanceof String && ((String)o1).length() == 0) {
            o1 = null;
        }
        if (o2 instanceof String && ((String)o2).length() == 0) {
            o2 = null;
        }
        
        // Sort nulls so they appear last, regardless
        // of sort order
        if (o1 == null && o2 == null) {
            return 0;
        } else if (o1 == null) {
            return 1;
        } else if (o2 == null) {
            return -1;
        } else if (o1 instanceof Comparable) {
            if (ascending) {
                return ((Comparable)o1).compareTo(o2);
            } else {
                return ((Comparable)o2).compareTo(o1);
            }
        } else {
            if (ascending) {
                return o1.toString().compareTo(o2.toString());
            } else {
                return o2.toString().compareTo(o1.toString());
            }
        }
    }
        }
        
        // Regardless of sort order (ascending or descending), null values always appear last.
        // colIndex specifies a column in model.
        public static void sortAllRowsBy(DefaultTableModel model, int colIndex, boolean ascending) {
    Vector data = model.getDataVector();
    Collections.sort(data, new ColumnSorter(colIndex, ascending));
    model.fireTableStructureChanged();
        }
    */
    //TODO: this should really be a public static method in a common class somewhere
    protected static final double factors[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 };

    protected static double round(double n, int places) {
        if (places < 0 || places > 8)
            places = 8;

        return Math.round(n * factors[places]) / factors[places];
    }

}