/*
* 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.viewer.quant.QuantEvent;
import org.fhcrc.cpl.viewer.quant.QuantEventAssessor;
import org.fhcrc.cpl.toolbox.Rounder;
import javax.swing.*;
import javax.swing.table.*;
import java.util.*;
import java.util.List;
import java.awt.*;
import java.awt.event.*;
/**
* Display quantitative event info in a table.
*
* Ratio is indicated by a number, and also by a slider indicating the ratio in log space
*/
public class QuantEventsSummaryTable extends JTable
{
//shading for alternating peptides. Not a great idea if the table is re-sorted
protected List<Integer> shadedTableRows = new ArrayList<Integer>();
//List of events that have already been selected, should not be allowed to be deselected
protected List<Integer> alreadySelectedRows = new ArrayList<Integer>();
protected List<QuantEvent> quantEvents = new ArrayList<QuantEvent>();
protected Map<String, Integer> fractionNameNumberMap = new HashMap<String, Integer>();
protected TableColumn logRatioSliderColumn;
protected TableColumn proteinColumn;
protected TableColumn fractionColumn;
protected TableColumn scanColumn;
protected TableColumn assessmentColumn;
protected TableColumn geneColumn;
protected Map<String, List<String>> proteinGenesMap;
protected int quantCurationColumnIndex;
protected int quantAlgAssessmentColumnIndex;
// protected QuantEventChangeListener changeListener = new QuantEventChangeListener();
protected int ratioColumnIndex = 0;
protected TableRowSorter<TableModel> sorter;
DefaultTableModel model = new DefaultTableModel(0, 13)
{
//all cells uneditable
public boolean isCellEditable(int row, int column)
{
if (column == 0)
{
if (alreadySelectedRows == null || !alreadySelectedRows.contains(row))
return true;
}
return false;
}
public Class getColumnClass(int columnIndex)
{
// String columnName = getColumnName(columnIndex); //(String) getColumn(columnIndex).getHeaderValue();*///
if (columnIndex < 0)
return String.class;
int viewColumnIndex = convertColumnIndexToView(columnIndex);
if (viewColumnIndex < 0)
return String.class;
TableColumn column =
getColumnModel().getColumn(viewColumnIndex);
String columnName = (String) column.getHeaderValue();
if ("Ratio".equals(columnName))// || "Light".equals(columnName) || "Heavy".equals(columnName))
return Float.class;
else if ("LogRatio".equals(columnName))
return JSlider.class;
else if ("Scan".equals(columnName))
return Integer.class;
else return super.getColumnClass(columnIndex);
// else return String.class;
}
};
/**
* Hide the Protein column. There's no undoing this
*/
public void hideProteinColumn()
{
this.removeColumn(proteinColumn);
// proteinColumn.setMaxWidth(0);
}
/**
* Hide the fraction column. There's no undoing this
*/
public void hideFractionColumn()
{
this.removeColumn(fractionColumn);
}
/**
* Hide the fraction column. There's no undoing this
*/
public void hideScanColumn()
{
this.removeColumn(scanColumn);
}
/**
* Hide the Assessment column. There's no undoing this
*/
public void hideAssessmentColumn()
{
this.removeColumn(assessmentColumn);
// assessmentColumn.setMaxWidth(0);
}
/**
* Hide the Gene column. There's no undoing this
*/
public void hideGeneColumn()
{
this.removeColumn(geneColumn);
// geneColumn.setMaxWidth(0);
}
public QuantEventsSummaryTable()
{
setModel(model);
sorter = new TableRowSorter<TableModel>(model);
int columnNum = 0;
List<String> columnNames = new ArrayList<String>();
geneColumn = getColumnModel().getColumn(columnNum++);
geneColumn.setHeaderValue("Gene");
columnNames.add("Gene");
geneColumn.setPreferredWidth(90);
proteinColumn = getColumnModel().getColumn(columnNum++);
proteinColumn.setHeaderValue("Protein");
columnNames.add("Protein");
proteinColumn.setPreferredWidth(90);
TableColumn peptideColumn = getColumnModel().getColumn(columnNum++);
peptideColumn.setHeaderValue("Peptide");
columnNames.add("Peptide");
peptideColumn.setPreferredWidth(170);
peptideColumn.setMinWidth(140);
fractionColumn = getColumnModel().getColumn(columnNum++);
fractionColumn.setHeaderValue("Fraction");
columnNames.add("Fraction");
scanColumn = getColumnModel().getColumn(columnNum++);
scanColumn.setHeaderValue("Scan");
columnNames.add("Scan");
getColumnModel().getColumn(columnNum).setHeaderValue("Charge");
columnNames.add("Charge");
getColumnModel().getColumn(columnNum++).setPreferredWidth(45);
getColumnModel().getColumn(columnNum).setHeaderValue("Prob");
columnNames.add("Prob");
getColumnModel().getColumn(columnNum++).setPreferredWidth(50);
getColumnModel().getColumn(columnNum).setHeaderValue("Ratio");
columnNames.add("Ratio");
ratioColumnIndex = columnNum;
getColumnModel().getColumn(columnNum++).setPreferredWidth(50);
getColumnModel().getColumn(columnNum++).setHeaderValue("Light");
columnNames.add("Light");
getColumnModel().getColumn(columnNum++).setHeaderValue("Heavy");
columnNames.add("Heavy");
logRatioSliderColumn = getColumnModel().getColumn(columnNum);
logRatioSliderColumn.setHeaderValue("LogRatio");
columnNames.add("LogRatio");
JSliderRenderer sliderRenderer = new JSliderRenderer();
logRatioSliderColumn.setCellRenderer(sliderRenderer);
logRatioSliderColumn.setPreferredWidth(280);
logRatioSliderColumn.setMinWidth(100);
//special comparator for slider column
sorter.setComparator(columnNum++, new Comparator<Integer>() {
public int compare(Integer o1, Integer o2)
{
return o1 > o2 ? 1 : o1 < o2 ? -1 : 0;
}
});
quantAlgAssessmentColumnIndex = columnNum;
assessmentColumn = getColumnModel().getColumn(columnNum++);
assessmentColumn.setHeaderValue("Assessment");
assessmentColumn.setCellRenderer(new FlagQuantStatusRenderer());
columnNames.add("Assessment");
quantCurationColumnIndex = columnNum;
TableColumn evaluationColumn = getColumnModel().getColumn(columnNum++);
evaluationColumn.setHeaderValue("Evaluation");
evaluationColumn.setCellRenderer(new CurationStatusRenderer());
columnNames.add("Evaluation");
getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
setRowSorter(sorter);
// model.setColumnIdentifiers(columnNames.toArray(new String[columnNames.size()]));
}
public void setSelectionMode(int mode)
{
getSelectionModel().setSelectionMode(mode);
}
/**
* Returns model, not view, index
* @return
*/
public int getSelectedIndex()
{
int[] selectedIndices = getSelectedIndices();
if (selectedIndices == null || selectedIndices.length < 1 || selectedIndices.length > 1)
return -1;
return selectedIndices[0];
}
public int[] getSelectedIndices()
{
int[] rawRows = super.getSelectedRows();
if (rawRows == null)
return null;
for (int i=0; i<rawRows.length; i++)
{
rawRows[i] = convertRowIndexToModel(rawRows[i]);
}
return rawRows;
}
protected Color altRowColor = new Color(235, 235, 235);
/**
* Shades alternate peptides in different colors.
*/
public Component prepareRenderer(TableCellRenderer renderer, int row, int column)
{
Component c = super.prepareRenderer(renderer, row, column);
Color previousForegroundColor = c.getForeground();
//Need to be careful: don't recolor the foreground if it's a special color
boolean shouldRecolorForeground = previousForegroundColor != Color.red &&
previousForegroundColor != Color.green && previousForegroundColor != Color.blue;
if (isCellSelected(row, column))
{
c.setBackground(UIManager.getColor("Table.selectionBackground"));
if (shouldRecolorForeground)
{
Color selectedForegroundColor = UIManager.getColor("Table.selectionForeground");
if (alreadySelectedRows != null && alreadySelectedRows.contains(row))
selectedForegroundColor = Color.GRAY;
c.setForeground(selectedForegroundColor);
}
}
else
{
Color rowColor = UIManager.getColor("Table.background");
if (shadedTableRows.contains(row))
rowColor = altRowColor;
c.setBackground(rowColor);
if (shouldRecolorForeground)
{
Color unselectedForegroundColor = UIManager.getColor("Table.foreground");
if (alreadySelectedRows != null && alreadySelectedRows.contains(row))
unselectedForegroundColor = Color.GRAY;
c.setForeground(unselectedForegroundColor);
}
}
return c;
}
/**
* Remove all properties from table
*/
public void clearProperties()
{
while (model.getRowCount() > 0)
{
model.removeRow(0);
}
}
/**
* Map fraction names to numbers
* @param events
*/
protected void buildFractionNameNumberMap(List<QuantEvent> events)
{
Set<String> fractionNames = new HashSet<String>();
for (QuantEvent quantEvent : events)
{
String fractionName = quantEvent.getFraction();
if (fractionName != null)
{
fractionNames.add(fractionName);
}
}
if (fractionNames.isEmpty())
fractionNameNumberMap = null;
else
{
fractionNameNumberMap = new HashMap<String, Integer>();
List<String> fractionNamesList = new ArrayList<String>(fractionNames);
Collections.sort(fractionNamesList);
for (int i=0; i<fractionNamesList.size(); i++)
fractionNameNumberMap.put(fractionNamesList.get(i), i+1);
}
}
public void setEvents(List<QuantEvent> events)
{
buildFractionNameNumberMap(events);
for (QuantEvent quantEvent : events)
{
addEvent(quantEvent, false);
}
quantEvents = new ArrayList<QuantEvent>(events);
}
public void addEvent(QuantEvent quantEvent, boolean alreadySelected)
{
String previousPeptide = "";
int numRows = model.getRowCount();
boolean previousRowShaded = false;
quantEvent.addQuantCurationStatusListener(new QuantEventChangeListener(numRows));
quantEvent.addAlgAssessmentStatusListener(new QuantEventAlgAssessmentChangeListener(numRows));
if (numRows > 0)
{
previousPeptide = model.getValueAt(numRows-1, 3).toString();
if (shadedTableRows.contains(numRows-1))
previousRowShaded = true;
}
String peptide = quantEvent.getPeptide();
boolean shaded = ((previousRowShaded && peptide.equals(previousPeptide)) ||
(!previousRowShaded && !peptide.equals(previousPeptide)));
if (shaded)
shadedTableRows.add(numRows);
model.setRowCount(numRows + 1);
String fraction = quantEvent.getFraction();
int fractionNum = 0;
if (fractionNameNumberMap != null && fraction != null &&
fractionNameNumberMap.containsKey(fraction))
fractionNum = fractionNameNumberMap.get(fraction);
int colNum = 0;
String geneValue = "";
String protein = quantEvent.getProtein();
if (proteinGenesMap != null && proteinGenesMap.containsKey(protein))
{
//it would be better to do this once and cache it
StringBuffer geneValueBuf = new StringBuffer();
boolean first = true;
for (String gene : proteinGenesMap.get(protein))
{
if (!first)
geneValueBuf.append(",");
geneValueBuf.append(gene);
first = false;
}
geneValue = geneValueBuf.toString();
}
model.setValueAt(geneValue, numRows, colNum++);
model.setValueAt(protein, numRows, colNum++);
model.setValueAt(peptide, numRows, colNum++);
model.setValueAt("" + fractionNum, numRows, colNum++);
model.setValueAt(quantEvent.getScan(), numRows, colNum++);
model.setValueAt("" + quantEvent.getCharge(), numRows, colNum++);
model.setValueAt("" + Rounder.round(quantEvent.getPeptideProphet(),3), numRows, colNum++);
model.setValueAt((float) Rounder.round(quantEvent.getRatio(),3), numRows, colNum++);
model.setValueAt("" + Rounder.round(quantEvent.getLightIntensity(),1), numRows, colNum++);
model.setValueAt("" + Rounder.round(quantEvent.getHeavyIntensity(),1), numRows, colNum++);
model.setValueAt(integerizeRatio(quantEvent.getRatio()), numRows, colNum++);
String assessmentString = "";
QuantEventAssessor.QuantEventAssessment assessment = quantEvent.getAlgorithmicAssessment();
if (assessment != null)
assessmentString = QuantEventAssessor.flagReasonCodes[assessment.getStatus()];
model.setValueAt(assessmentString, numRows, colNum++);
model.setValueAt(QuantEvent.convertCurationStatusToString(quantEvent.getQuantCurationStatus()),
numRows, colNum++);
if (alreadySelected)
{
alreadySelectedRows.add(numRows);
model.setValueAt(true, numRows, 0);
}
}
protected int integerizeRatio(float ratio)
{
float ratioBound = 10f;
float logRatioBounded =
(float) Math.log(Math.min(ratioBound, Math.max(1.0f / ratioBound, ratio)));
return (int) (logRatioBounded * 100 / (2 * Math.log(ratioBound))) + 50;
}
public void displayEvents(List<QuantEvent> quantEvents)
{
displayEvents(quantEvents, null);
}
/**
* Display a list of events. Indicate selection for the events that have already been selected
* @param quantEvents
* @param alreadySelectedEventIndices
*/
public void displayEvents(List<QuantEvent> quantEvents, List<Integer> alreadySelectedEventIndices)
{
clearProperties();
buildFractionNameNumberMap(quantEvents);
if (fractionNameNumberMap == null || fractionNameNumberMap.isEmpty() || fractionNameNumberMap.size() == 1)
hideFractionColumn();
this.quantEvents = quantEvents;
shadedTableRows = new ArrayList<Integer>();
boolean anyEventHasAssessment = false;
for (int i=0; i<quantEvents.size(); i++)
{
QuantEvent quantEvent = quantEvents.get(i);
if (quantEvent.getAlgorithmicAssessment() != null)
anyEventHasAssessment = true;
boolean alreadySelected = (alreadySelectedEventIndices != null &&
alreadySelectedEventIndices.contains(i));
addEvent(quantEvent, alreadySelected);
}
// if (!anyEventHasAssessment)
// hideAssessmentColumn();
if (proteinGenesMap == null)
{
hideGeneColumn();
}
}
/**
* Return all checked rows except the alreadySelectedRows rows
* @return
*/
public List<QuantEvent> getSelectedEvents()
{
List<QuantEvent> selectedQuantEvents = new ArrayList<QuantEvent>();
int[] selectedIndices = getSelectedIndices();
if (selectedIndices != null)
{
for (int i : selectedIndices)
selectedQuantEvents.add(quantEvents.get(i));
}
return selectedQuantEvents;
}
public class JSliderRenderer implements TableCellRenderer
{
protected JSlider slider = new JSlider();
public JSliderRenderer()
{
slider.setMinimum(0);
slider.setMaximum(100);
slider.setPaintLabels(false);
slider.setPaintTicks(false);
slider.setMajorTickSpacing(25);
slider.setPreferredSize(new Dimension(280, 15));
slider.setPreferredSize(new Dimension(100, 15));
slider.setToolTipText("Log ratio, bounded at 0.1 and 10");
}
public JSliderRenderer(float ratio)
{
this();
slider.setValue(integerizeRatio(ratio));
}
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column)
{
//if value is an integer, set the slider. Otherwise, it's a String for the header, ignore
if (Integer.class.isAssignableFrom(value.getClass()))
slider.setValue((Integer)value);
return slider;
}
}
public class CurationStatusRenderer extends DefaultTableCellRenderer
{
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column)
{
Component c =
super.getTableCellRendererComponent(table, value,
isSelected, hasFocus,
row, column);
int status = QuantEvent.parseCurationStatusString((String) value);
Color color = c.getForeground();
switch (status)
{
case QuantEvent.CURATION_STATUS_GOOD:
color = Color.green;
break;
case QuantEvent.CURATION_STATUS_RATIO_ONEPEAK:
color = Color.blue;
break;
case QuantEvent.CURATION_STATUS_BAD:
color = Color.red;
break;
default:
color = Color.black;
break;
}
c.setForeground(color);
return c;
}
}
public class FlagQuantStatusRenderer extends DefaultTableCellRenderer
{
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column)
{
Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
QuantEvent event = quantEvents.get(table.convertRowIndexToModel(row));
int status = QuantEventAssessor.FLAG_REASON_UNEVALUATED;
if (event.getAlgorithmicAssessment() != null)
status = event.getAlgorithmicAssessment().getStatus();//QuantEventAssessor.parseAssessmentCodeString((String) value);
Color color;
switch (status)
{
case QuantEventAssessor.FLAG_REASON_OK:
color = Color.green;
break;
case QuantEventAssessor.FLAG_REASON_UNEVALUATED:
color = Color.black;
break;
default:
color = Color.red;
break;
}
c.setForeground(color);
return c;
}
}
/**
* Make the header of the logratio column display a slider with the given ratio
* @param ratio
*/
public void setLogRatioHeaderRatio(float ratio)
{
JSliderRenderer renderer = new JSliderRenderer(ratio);
renderer.slider.setToolTipText("Protein log ratio");
logRatioSliderColumn.setHeaderRenderer(renderer);
}
protected class RatioRowFilter extends RowFilter<TableModel, Object>
{
protected float maxLowRatioValue;
protected float minHighRatioValue;
public RatioRowFilter(float maxLowRatioValue, float minHighRatioValue)
{
this.maxLowRatioValue = maxLowRatioValue;
this.minHighRatioValue = minHighRatioValue;
}
public boolean include(RowFilter.Entry entry)
{
float ratio = (Float) entry.getValue(ratioColumnIndex);
return include(ratio);
}
public boolean include(float ratio)
{
return (ratio <= maxLowRatioValue || ratio >= minHighRatioValue);
}
}
public void showOnlyExtremeRatios(float maxLowRatioValue, float minHighRatioValue)
{
RowFilter<TableModel, Object> rf = new RatioRowFilter(maxLowRatioValue, minHighRatioValue);
sorter.setRowFilter(rf);
}
class SelectAllListener implements ItemListener
{
public void itemStateChanged(ItemEvent e)
{
Object source = e.getSource();
if (!(source instanceof AbstractButton)) return;
boolean checked = e.getStateChange() == ItemEvent.SELECTED;
for(int x = 0, y = getRowCount(); x < y; x++)
{
setValueAt(checked, x, 0);
}
}
}
public Map<String, Integer> getFractionNameNumberMap()
{
return fractionNameNumberMap;
}
public void setFractionNameNumberMap(Map<String, Integer> fractionNameNumberMap)
{
this.fractionNameNumberMap = fractionNameNumberMap;
}
public Map<String, List<String>> getProteinGenesMap()
{
return proteinGenesMap;
}
public void setProteinGenesMap(Map<String, List<String>> proteinGenesMap)
{
this.proteinGenesMap = proteinGenesMap;
}
protected class QuantEventChangeListener implements ActionListener
{
protected int row;
public QuantEventChangeListener(int row)
{
this.row = row;
}
public void actionPerformed(ActionEvent event)
{
int currentValue =QuantEvent.parseCurationStatusString(
(String) model.getValueAt(row, quantCurationColumnIndex));
int newValue = quantEvents.get(row).getQuantCurationStatus();
if (currentValue != newValue)
model.setValueAt(QuantEvent.convertCurationStatusToString(
newValue), row, quantCurationColumnIndex);
updateUI();
}
}
protected class QuantEventAlgAssessmentChangeListener implements ActionListener
{
protected int row;
public QuantEventAlgAssessmentChangeListener(int row)
{
this.row = row;
}
public void actionPerformed(ActionEvent event)
{
//adding evaluation status listener
int currentAlgValue = QuantEventAssessor.FLAG_REASON_UNEVALUATED;
String currentValueString = (String) model.getValueAt(row, quantAlgAssessmentColumnIndex);
if (currentValueString != null && currentValueString.length() > 0)
currentAlgValue =QuantEventAssessor.parseAssessmentCodeString(currentValueString);
int newAlgValue = QuantEventAssessor.FLAG_REASON_UNEVALUATED;
QuantEventAssessor.QuantEventAssessment assessment = quantEvents.get(row).getAlgorithmicAssessment();
if (assessment != null)
newAlgValue = assessment.getStatus();
if (currentAlgValue != newAlgValue)
model.setValueAt(QuantEventAssessor.getAssessmentCodeDesc(
newAlgValue), row, quantAlgAssessmentColumnIndex);
updateUI();
}
}
}