/*
* Copyright (c) 2009 The Jackson Laboratory
*
* This software was developed by Gary Churchill's Lab at The Jackson
* Laboratory (see http://research.jax.org/faculty/churchill).
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software. If not, see <http://www.gnu.org/licenses/>.
*/
package org.jax.qtl.cross.gui;
import java.awt.event.ItemEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import javax.help.CSH;
import javax.help.HelpSet;
import javax.help.SecondaryWindow;
import javax.swing.JOptionPane;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableModel;
import org.jax.qtl.QTL;
import org.jax.qtl.cross.CrossChromosome;
import org.jax.qtl.cross.GeneticMarker;
import org.jax.qtl.cross.MarkerPairQtlBasketItem;
import org.jax.qtl.cross.QtlBasket;
import org.jax.qtl.cross.QtlBasketItem;
import org.jax.qtl.cross.SingleMarkerQtlBasketItem;
import org.jax.qtl.cross.GeneticMarker.MarkerStringFormat;
import org.jax.qtl.gui.ExportDataTableAction;
import org.jax.util.TextWrapper;
import org.jax.util.io.JTableDataTable;
/**
* A panel for viewing and editing QTL basket info
* @author <A HREF="mailto:keith.sheppard@jax.org">Keith Sheppard</A>
*/
public class QtlBasketPanel extends javax.swing.JPanel
{
/**
* every {@link java.io.Serializable} is supposed to have one of these
*/
private static final long serialVersionUID = 1265801937556731429L;
/**
* our logger
*/
private static final Logger LOG = Logger.getLogger(
QtlBasketPanel.class.getName());
private static final String HELP_ID_STRING = "Edit_QTL_Basket";
private QtlBasket qtlBasket;
private final List<QtlBasketItem> selectedQtlBasketItems;
private final ListSelectionListener qtlTableSelectionListener =
new ListSelectionListener()
{
public void valueChanged(ListSelectionEvent e)
{
QtlBasketPanel.this.qtlTableSelectionChanged();
}
};
private final DocumentListener qtlCommentDocumentListener =
new DocumentListener()
{
public void changedUpdate(DocumentEvent e)
{
this.anyChange();
}
public void insertUpdate(DocumentEvent e)
{
this.anyChange();
}
public void removeUpdate(DocumentEvent e)
{
this.anyChange();
}
private void anyChange()
{
QtlBasketPanel.this.qtlCommentChanged();
}
};
private final PropertyChangeListener qtlBasketPropertyListener =
new PropertyChangeListener()
{
public void propertyChange(PropertyChangeEvent evt)
{
QtlBasketPanel.this.refreshQtlTableContents();
}
};
/**
* Constructor
* @param qtlBasket
* the qtl basket that we're displaying
*/
public QtlBasketPanel(QtlBasket qtlBasket)
{
this.qtlBasket = qtlBasket;
this.qtlBasket.addPropertyChangeListener(
this.qtlBasketPropertyListener);
this.selectedQtlBasketItems = new ArrayList<QtlBasketItem>();
this.initComponents();
this.postGuiInit();
}
/**
* Take care of the GUI initialization that wasn't handled by the
* GUI builder's code
*/
private void postGuiInit()
{
for(MarkerStringFormat currFormat: MarkerStringFormat.values())
{
this.markerFormatComboBox.addItem(currFormat);
}
String[] tableHeader = new String[] {
"QTL Type",
"QTL Name",
"Comments"};
DefaultTableModel qtlTableModel =
new DefaultTableModel(tableHeader, 0)
{
/**
* for serialization
*/
private static final long serialVersionUID = 3691035618365541184L;
/**
* {@inheritDoc}
*/
@Override
public boolean isCellEditable(int row, int column)
{
return false;
}
};
this.qtlTable.setModel(qtlTableModel);
this.refreshQtlTableContents();
this.exportTableButton.setAction(new ExportDataTableAction(
new JTableDataTable(this.qtlTable)));
// initialize the help stuff
HelpSet hs = QTL.getInstance().getMenubar().getHelpSet();
CSH.setHelpIDString(
this.helpButton,
HELP_ID_STRING);
this.helpButton.addActionListener(
new CSH.DisplayHelpFromSource(
hs,
SecondaryWindow.class.getName(),
null));
}
/**
* Respond to a change in the qtl comment
*/
protected void qtlCommentChanged()
{
if(this.selectedQtlBasketItems.size() == 1)
{
this.selectedQtlBasketItems.get(0).setComment(
this.qtlCommentEditorTextArea.getText());
this.qtlTable.repaint();
}
else
{
LOG.warning(
"QTL comment changed when it should not have been " +
"able to. Selected QTL candidates = " +
this.selectedQtlBasketItems.size());
}
}
/**
* Respond to a change in table row selection
*/
protected void qtlTableSelectionChanged()
{
int[] selectedRows = this.qtlTable.getSelectedRows();
QtlBasketItem[] contents =
this.qtlBasket.getContents().toArray(new QtlBasketItem[0]);
this.selectedQtlBasketItems.clear();
for(int selectedRow: selectedRows)
{
this.selectedQtlBasketItems.add(contents[selectedRow]);
}
this.removeQtlButton.setEnabled(!this.selectedQtlBasketItems.isEmpty());
this.refreshQtlCommentTextArea();
}
/**
* This method makes sure that the comment area is up to date with
* the selected QTLs
*/
private void refreshQtlCommentTextArea()
{
this.qtlCommentEditorTextArea.getDocument().removeDocumentListener(
this.qtlCommentDocumentListener);
if(this.selectedQtlBasketItems.isEmpty())
{
this.qtlCommentEditorTextArea.setEnabled(false);
this.qtlCommentLabel.setEnabled(false);
this.qtlCommentEditorTextArea.setText(
"Select a QTL candidate to edit its comment");
}
else if(this.selectedQtlBasketItems.size() == 1)
{
this.qtlCommentEditorTextArea.setEnabled(true);
this.qtlCommentLabel.setEnabled(true);
this.qtlCommentEditorTextArea.setText(
this.selectedQtlBasketItems.get(0).getComment());
}
else
{
this.qtlCommentEditorTextArea.setEnabled(false);
this.qtlCommentLabel.setEnabled(false);
this.qtlCommentEditorTextArea.setText(
"Cannot edit multiple QTL candidate comments simultaneously");
}
this.qtlCommentEditorTextArea.getDocument().addDocumentListener(
this.qtlCommentDocumentListener);
}
/**
* convenience function for getting a narrowed cast of the qtl table
* model
* @return
* the model
*/
private DefaultTableModel getQtlTableModel()
{
return (DefaultTableModel)this.qtlTable.getModel();
}
/**
* Update the table contents to reflect whats in the QTL basket that
* we're showing
*/
private void refreshQtlTableContents()
{
this.qtlTable.getSelectionModel().removeListSelectionListener(
this.qtlTableSelectionListener);
DefaultTableModel qtlTableModel = this.getQtlTableModel();
qtlTableModel.setRowCount(0);
QtlBasketItem[] contents =
this.qtlBasket.getContents().toArray(new QtlBasketItem[0]);
for(QtlBasketItem qtlBasketItem: contents)
{
qtlTableModel.addRow(this.createQtlTableRowFor(qtlBasketItem));
}
this.qtlTable.getSelectionModel().addListSelectionListener(
this.qtlTableSelectionListener);
this.qtlTableSelectionChanged();
}
/**
* Create a new row based on the given basket item
* @param item
* the item to create a new row for
* @return
* the array of cells for the row
*/
private Object[] createQtlTableRowFor(QtlBasketItem item)
{
return new Object[] {
new QtlBasketItemCell(item, 0),
new QtlBasketItemCell(item, 1),
new QtlBasketItemCell(item, 2)};
}
/**
* A table cell for a qtl basket item
*/
private class QtlBasketItemCell
{
private final int columnNumber;
private final QtlBasketItem basketItem;
/**
* Constructor
* @param basketItem
* the item we're holding
* @param columnNumber
* the column number for this cell
*/
public QtlBasketItemCell(QtlBasketItem basketItem, int columnNumber)
{
this.basketItem = basketItem;
this.columnNumber = columnNumber;
}
/**
* @return the basketItem
*/
public QtlBasketItem getBasketItem()
{
return this.basketItem;
}
/**
* {@inheritDoc}
*/
@Override
public String toString()
{
if(this.columnNumber == 0)
{
if(this.basketItem instanceof SingleMarkerQtlBasketItem)
{
return "marker";
}
else if(this.basketItem instanceof MarkerPairQtlBasketItem)
{
return "marker interaction";
}
else
{
throw new IllegalStateException(
"unknown type: " +
this.basketItem.getClass().getName());
}
}
else if(this.columnNumber == 1)
{
if(this.basketItem instanceof SingleMarkerQtlBasketItem)
{
SingleMarkerQtlBasketItem singleMarkerQtlBasketItem =
(SingleMarkerQtlBasketItem)this.basketItem;
return singleMarkerQtlBasketItem.getMarker().toString(
QtlBasketPanel.this.getSelectedMarkerFormat());
}
else if(this.basketItem instanceof MarkerPairQtlBasketItem)
{
MarkerPairQtlBasketItem markerPairQtlBasketItem =
(MarkerPairQtlBasketItem)this.basketItem;
return markerPairQtlBasketItem.getMarkerPair().toString(
QtlBasketPanel.this.getSelectedMarkerFormat());
}
else
{
throw new IllegalStateException(
"unknown type: " +
this.basketItem.getClass().getName());
}
}
else if(this.columnNumber == 2)
{
return this.basketItem.getComment();
}
else
{
throw new IllegalStateException(
"column number is not valid: " +
this.columnNumber);
}
}
}
/**
* Getter for the currently selected marker format
* @return
* the selected format
*/
private MarkerStringFormat getSelectedMarkerFormat()
{
return (MarkerStringFormat)this.markerFormatComboBox.getSelectedItem();
}
private void addQtlToBasket()
{
List<CrossChromosome> genoData = this.qtlBasket.getParentCross().getGenotypeData();
List<GeneticMarker> markerList = new ArrayList<GeneticMarker>();
for(CrossChromosome currChromosome: genoData)
{
markerList.addAll(currChromosome.getAnyGeneticMap().getMarkerPositions());
}
GeneticMarker[] markerArray = markerList.toArray(
new GeneticMarker[markerList.size()]);
GeneticMarker selectedMarker = (GeneticMarker)JOptionPane.showInputDialog(
this,
TextWrapper.wrapText(
"Select a marker to add to the QTL basket or cancel:",
TextWrapper.DEFAULT_DIALOG_COLUMN_COUNT),
"Add Marker to QTL Basket",
JOptionPane.PLAIN_MESSAGE,
null,
markerArray,
markerArray[0]);
if(selectedMarker != null)
{
this.qtlBasket.getContents().add(new SingleMarkerQtlBasketItem(
selectedMarker, ""));
this.qtlBasket.notifyContentsChanged();
}
else
{
LOG.info("Add QLT canceled");
}
}
/**
* This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
@SuppressWarnings("all")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
closeButton = new javax.swing.JButton();
jSplitPane1 = new javax.swing.JSplitPane();
jPanel2 = new javax.swing.JPanel();
jLabel2 = new javax.swing.JLabel();
jScrollPane2 = new javax.swing.JScrollPane();
qtlTable = new javax.swing.JTable();
addQtlButton = new javax.swing.JButton();
removeQtlButton = new javax.swing.JButton();
exportTableButton = new javax.swing.JButton();
markerFormatComboBox = new javax.swing.JComboBox();
jPanel3 = new javax.swing.JPanel();
qtlCommentLabel = new javax.swing.JLabel();
jScrollPane1 = new javax.swing.JScrollPane();
qtlCommentEditorTextArea = new javax.swing.JTextArea();
jPanel1 = new javax.swing.JPanel();
helpButton = new javax.swing.JButton();
closeButton.setText("Close");
jSplitPane1.setDividerLocation(250);
jSplitPane1.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT);
jSplitPane1.setResizeWeight(0.7);
jSplitPane1.setOneTouchExpandable(true);
jLabel2.setText("Candidate QTLs:");
jScrollPane2.setViewportView(qtlTable);
addQtlButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/images/action/add-16x16.png"))); // NOI18N
addQtlButton.setText("Add QTL ...");
addQtlButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
addQtlButtonActionPerformed(evt);
}
});
removeQtlButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/images/action/remove-16x16.png"))); // NOI18N
removeQtlButton.setText("Remove Selected QTL(s)");
removeQtlButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
removeQtlButtonActionPerformed(evt);
}
});
exportTableButton.setText("Export Table ...");
markerFormatComboBox.addItemListener(new java.awt.event.ItemListener() {
public void itemStateChanged(java.awt.event.ItemEvent evt) {
markerFormatComboBoxItemStateChanged(evt);
}
});
org.jdesktop.layout.GroupLayout jPanel2Layout = new org.jdesktop.layout.GroupLayout(jPanel2);
jPanel2.setLayout(jPanel2Layout);
jPanel2Layout.setHorizontalGroup(
jPanel2Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
.add(jPanel2Layout.createSequentialGroup()
.addContainerGap()
.add(jPanel2Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
.add(jPanel2Layout.createSequentialGroup()
.add(addQtlButton)
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
.add(removeQtlButton)
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
.add(exportTableButton))
.add(jScrollPane2, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 672, Short.MAX_VALUE)
.add(jPanel2Layout.createSequentialGroup()
.add(jLabel2)
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
.add(markerFormatComboBox, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)))
.addContainerGap())
);
jPanel2Layout.setVerticalGroup(
jPanel2Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
.add(jPanel2Layout.createSequentialGroup()
.addContainerGap()
.add(jPanel2Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
.add(jLabel2)
.add(markerFormatComboBox, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
.add(jScrollPane2, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 163, Short.MAX_VALUE)
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
.add(jPanel2Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING)
.add(addQtlButton)
.add(jPanel2Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
.add(removeQtlButton)
.add(exportTableButton))))
);
jSplitPane1.setTopComponent(jPanel2);
qtlCommentLabel.setText("QTL Comment Editor:");
qtlCommentEditorTextArea.setColumns(20);
qtlCommentEditorTextArea.setRows(5);
jScrollPane1.setViewportView(qtlCommentEditorTextArea);
org.jdesktop.layout.GroupLayout jPanel3Layout = new org.jdesktop.layout.GroupLayout(jPanel3);
jPanel3.setLayout(jPanel3Layout);
jPanel3Layout.setHorizontalGroup(
jPanel3Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
.add(jPanel3Layout.createSequentialGroup()
.addContainerGap()
.add(jPanel3Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
.add(jScrollPane1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 672, Short.MAX_VALUE)
.add(qtlCommentLabel))
.addContainerGap())
);
jPanel3Layout.setVerticalGroup(
jPanel3Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
.add(jPanel3Layout.createSequentialGroup()
.add(qtlCommentLabel)
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
.add(jScrollPane1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 55, Short.MAX_VALUE)
.addContainerGap())
);
jSplitPane1.setRightComponent(jPanel3);
helpButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/images/action/help-16x16.png"))); // NOI18N
helpButton.setText("Help ...");
jPanel1.add(helpButton);
org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
.add(jPanel1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 716, Short.MAX_VALUE)
.add(jSplitPane1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 716, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
.add(org.jdesktop.layout.GroupLayout.TRAILING, layout.createSequentialGroup()
.add(jSplitPane1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 360, Short.MAX_VALUE)
.addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
.add(jPanel1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
);
}// </editor-fold>//GEN-END:initComponents
private void addQtlButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addQtlButtonActionPerformed
this.addQtlToBasket();
}//GEN-LAST:event_addQtlButtonActionPerformed
private void removeQtlButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_removeQtlButtonActionPerformed
this.qtlBasket.getContents().removeAll(this.selectedQtlBasketItems);
this.qtlBasket.notifyContentsChanged();
}//GEN-LAST:event_removeQtlButtonActionPerformed
private void markerFormatComboBoxItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_markerFormatComboBoxItemStateChanged
if(evt.getStateChange() == ItemEvent.SELECTED)
{
this.qtlTable.repaint();
}
}//GEN-LAST:event_markerFormatComboBoxItemStateChanged
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton addQtlButton;
private javax.swing.JButton closeButton;
private javax.swing.JButton exportTableButton;
private javax.swing.JButton helpButton;
private javax.swing.JLabel jLabel2;
private javax.swing.JPanel jPanel1;
private javax.swing.JPanel jPanel2;
private javax.swing.JPanel jPanel3;
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JScrollPane jScrollPane2;
private javax.swing.JSplitPane jSplitPane1;
private javax.swing.JComboBox markerFormatComboBox;
private javax.swing.JTextArea qtlCommentEditorTextArea;
private javax.swing.JLabel qtlCommentLabel;
private javax.swing.JTable qtlTable;
private javax.swing.JButton removeQtlButton;
// End of variables declaration//GEN-END:variables
}