/*
* TipDatesPanel.java
*
* Copyright (C) 2002-2009 Alexei Drummond and Andrew Rambaut
*
* This file is part of BEAST.
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership and licensing.
*
* BEAST is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* BEAST 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with BEAST; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
package dr.app.beauti.tipdatepanel;
import dr.app.beauti.BeautiFrame;
import dr.app.beauti.BeautiPanel;
import dr.app.beauti.components.tipdatesampling.TipDateSamplingComponentOptions;
import dr.app.beauti.options.BeautiOptions;
import dr.app.beauti.options.ClockModelGroup;
import dr.app.beauti.options.DateGuesser;
import dr.app.beauti.traitspanel.TraitValueDialog;
import dr.app.beauti.types.TipDateSamplingType;
import dr.app.beauti.util.PanelUtils;
import dr.app.gui.table.DateCellEditor;
import dr.app.gui.table.TableEditorStopper;
import dr.app.gui.table.TableSorter;
import dr.evolution.util.*;
import dr.evoxml.util.DateUnitsType;
import jam.framework.Exportable;
import jam.table.TableRenderer;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.plaf.BorderUIResource;
import javax.swing.table.AbstractTableModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.EnumSet;
/**
* @author Andrew Rambaut
* @author Alexei Drummond
* @version $Id: DataPanel.java,v 1.17 2006/09/05 13:29:34 rambaut Exp $
*/
public class TipDatesPanel extends BeautiPanel implements Exportable {
/**
*
*/
private static final long serialVersionUID = 5283922195494563924L;
JScrollPane scrollPane = new JScrollPane();
JTable dataTable = null;
DataTableModel dataTableModel = null;
SetDatesAction setDatesAction = new SetDatesAction();
ClearDatesAction clearDatesAction = new ClearDatesAction();
GuessDatesAction guessDatesAction = new GuessDatesAction();
JCheckBox usingTipDates = new JCheckBox("Use tip dates");
JComboBox unitsCombo = new JComboBox(EnumSet.range(DateUnitsType.YEARS, DateUnitsType.DAYS).toArray());
JComboBox directionCombo = new JComboBox(EnumSet.range(DateUnitsType.FORWARDS, DateUnitsType.BACKWARDS).toArray());
// JComboBox tipDateSamplingCombo = new JComboBox( TipDateSamplingType.values() );
JComboBox tipDateSamplingCombo = new JComboBox(new TipDateSamplingType[] { TipDateSamplingType.NO_SAMPLING, TipDateSamplingType.SAMPLE_INDIVIDUALLY });
JComboBox tipDateTaxonSetCombo = new JComboBox();
BeautiFrame frame = null;
BeautiOptions options = null;
double[] heights = null;
private GuessDatesDialog guessDatesDialog = null;
private DateValueDialog dateValueDialog = null;
public TipDatesPanel(BeautiFrame parent) {
this.frame = parent;
dataTableModel = new DataTableModel();
TableSorter sorter = new TableSorter(dataTableModel);
dataTable = new JTable(sorter);
sorter.setTableHeader(dataTable.getTableHeader());
dataTable.getTableHeader().setReorderingAllowed(false);
// dataTable.getTableHeader().setDefaultRenderer(
// new HeaderRenderer(SwingConstants.LEFT, new Insets(0, 4, 0, 4)));
dataTable.getColumnModel().getColumn(0).setCellRenderer(
new TableRenderer(SwingConstants.LEFT, new Insets(0, 4, 0, 4)));
dataTable.getColumnModel().getColumn(0).setPreferredWidth(80);
dataTable.getColumnModel().getColumn(1).setCellRenderer(
new TableRenderer(SwingConstants.LEFT, new Insets(0, 4, 0, 4)));
dataTable.getColumnModel().getColumn(1).setPreferredWidth(80);
dataTable.getColumnModel().getColumn(1).setCellEditor(
new DateCellEditor());
dataTable.getColumnModel().getColumn(2).setCellRenderer(
new TableRenderer(SwingConstants.LEFT, new Insets(0, 4, 0, 4)));
dataTable.getColumnModel().getColumn(2).setPreferredWidth(80);
TableEditorStopper.ensureEditingStopWhenTableLosesFocus(dataTable);
dataTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent evt) {
selectionChanged();
}
});
scrollPane = new JScrollPane(dataTable,
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
scrollPane.setOpaque(false);
PanelUtils.setupComponent(unitsCombo);
PanelUtils.setupComponent(directionCombo);
JToolBar toolBar1 = new JToolBar();
toolBar1.setFloatable(false);
toolBar1.setOpaque(false);
toolBar1.setLayout(new FlowLayout(java.awt.FlowLayout.LEFT, 0, 0));
JButton button = new JButton(guessDatesAction);
PanelUtils.setupComponent(button);
toolBar1.add(button);
button = new JButton(setDatesAction);
PanelUtils.setupComponent(button);
toolBar1.add(button);
button = new JButton(clearDatesAction);
PanelUtils.setupComponent(button);
toolBar1.add(button);
toolBar1.add(new JToolBar.Separator(new Dimension(12, 12)));
final JLabel unitsLabel = new JLabel("Dates specified as ");
toolBar1.add(unitsLabel);
toolBar1.add(unitsCombo);
toolBar1.add(directionCombo);
JPanel panel1 = new JPanel(new BorderLayout(0, 0));
panel1.setOpaque(false);
panel1.add(toolBar1, "North");
panel1.add(scrollPane, "Center");
JToolBar toolBar2 = new JToolBar();
toolBar2.setFloatable(false);
toolBar2.setOpaque(false);
toolBar2.setLayout(new FlowLayout(java.awt.FlowLayout.LEFT, 0, 0));
PanelUtils.setupComponent(tipDateSamplingCombo);
tipDateSamplingCombo.setToolTipText("<html>Select whether to allow sampling<br>" +
"of all or individual tip dates.</html>");
// substitutionRateField.setToolTipText("<html>Enter the substitution rate here.</html>");
// substitutionRateField.setEnabled(true);
final JLabel tipDateSamplingLabel = new JLabel("Tip date sampling:");
toolBar2.add(tipDateSamplingLabel);
toolBar2.add(tipDateSamplingCombo);
final JLabel tipDateTaxonSetLabel = new JLabel("Apply to taxon set:");
toolBar2.add(tipDateTaxonSetLabel);
toolBar2.add(tipDateTaxonSetCombo);
setOpaque(false);
setBorder(new BorderUIResource.EmptyBorderUIResource(new java.awt.Insets(12, 12, 12, 12)));
setLayout(new BorderLayout(0, 0));
add(usingTipDates, BorderLayout.NORTH);
add(panel1, BorderLayout.CENTER);
add(toolBar2, BorderLayout.SOUTH);
tipDateSamplingCombo.addItemListener(
new java.awt.event.ItemListener() {
public void itemStateChanged(java.awt.event.ItemEvent ev) {
boolean samplingOn = tipDateSamplingCombo.getSelectedItem() != TipDateSamplingType.NO_SAMPLING;
tipDateTaxonSetLabel.setEnabled(samplingOn);
tipDateTaxonSetCombo.setEnabled(samplingOn);
fireModelsChanged();
}
}
);
clearDatesAction.setEnabled(false);
guessDatesAction.setEnabled(false);
directionCombo.setEnabled(false);
unitsLabel.setEnabled(false);
unitsCombo.setEnabled(false);
scrollPane.setEnabled(false);
dataTable.setEnabled(false);
tipDateSamplingLabel.setEnabled(false);
tipDateSamplingCombo.setEnabled(false);
tipDateTaxonSetLabel.setEnabled(false);
tipDateTaxonSetCombo.setEnabled(false);
usingTipDates.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent ev) {
boolean enabled = usingTipDates.isSelected();
clearDatesAction.setEnabled(enabled);
guessDatesAction.setEnabled(enabled);
unitsLabel.setEnabled(enabled);
unitsCombo.setEnabled(enabled);
directionCombo.setEnabled(enabled);
scrollPane.setEnabled(enabled);
dataTable.setEnabled(enabled);
tipDateSamplingCombo.setEnabled(enabled);
tipDateSamplingLabel.setEnabled(enabled);
if (options.taxonList != null) timeScaleChanged();
}
});
ItemListener listener = new ItemListener() {
public void itemStateChanged(ItemEvent ev) {
timeScaleChanged();
}
};
unitsCombo.addItemListener(listener);
directionCombo.addItemListener(listener);
}
public final void timeScaleChanged() {
Units.Type units = Units.Type.YEARS;
switch ((DateUnitsType) unitsCombo.getSelectedItem()) {
case YEARS:
units = Units.Type.YEARS;
break;
case MONTHS:
units = Units.Type.MONTHS;
break;
case DAYS:
units = Units.Type.DAYS;
break;
}
boolean backwards = directionCombo.getSelectedItem() == DateUnitsType.BACKWARDS;
for (int i = 0; i < options.taxonList.getTaxonCount(); i++) {
Date date = options.taxonList.getTaxon(i).getDate();
double d = date.getTimeValue();
Date newDate = createDate(d, units, backwards, 0.0);
options.taxonList.getTaxon(i).setDate(newDate);
}
calculateHeights();
if (options.clockModelOptions.isTipCalibrated()) { // todo correct?
for (ClockModelGroup clockModelGroup : options.clockModelOptions.getClockModelGroups()) {
options.clockModelOptions.tipTimeCalibration(clockModelGroup);
}
}
dataTableModel.fireTableDataChanged();
frame.setDirty();
}
private Date createDate(double timeValue, Units.Type units, boolean backwards, double origin) {
if (backwards) {
return Date.createTimeAgoFromOrigin(timeValue, units, origin);
} else {
return Date.createTimeSinceOrigin(timeValue, units, origin);
}
}
public void setOptions(BeautiOptions options) {
this.options = options;
setupTable();
unitsCombo.setSelectedItem(options.datesUnits);
directionCombo.setSelectedItem(options.datesDirection);
calculateHeights();
usingTipDates.setSelected(options.clockModelOptions.isTipCalibrated());
dataTableModel.fireTableDataChanged();
Object item = tipDateTaxonSetCombo.getSelectedItem();
tipDateTaxonSetCombo.removeAllItems();
tipDateTaxonSetCombo.addItem("All taxa");
for (TaxonList taxa : options.taxonSets) {
tipDateTaxonSetCombo.addItem(taxa);
}
if (item != null) {
tipDateTaxonSetCombo.setSelectedItem(item);
}
}
private void setupTable() {
dataTableModel.fireTableDataChanged();
}
public void getOptions(BeautiOptions options) {
options.datesUnits = (DateUnitsType) unitsCombo.getSelectedItem();
options.datesDirection = (DateUnitsType) directionCombo.getSelectedItem();
TipDateSamplingComponentOptions comp = (TipDateSamplingComponentOptions) options.getComponentOptions(TipDateSamplingComponentOptions.class);
comp.tipDateSamplingType = (TipDateSamplingType) tipDateSamplingCombo.getSelectedItem();
if (tipDateTaxonSetCombo.getSelectedItem() instanceof TaxonList) {
comp.tipDateSamplingTaxonSet = (TaxonList) tipDateTaxonSetCombo.getSelectedItem();
} else {
comp.tipDateSamplingTaxonSet = null;
}
}
private void fireModelsChanged() {
frame.setDirty();
}
public JComponent getExportableComponent() {
return dataTable;
}
public void selectionChanged() {
// nothing to do
}
public void setDates() {
if (options.taxonList == null) { // validation of check empty taxonList
return;
}
int result;
do {
if (dateValueDialog == null) {
dateValueDialog = new DateValueDialog(frame);
}
int[] selRows = dataTable.getSelectedRows();
if (selRows.length > 0) {
dateValueDialog.setDescription("Set date values for selected taxa");
} else {
dateValueDialog.setDescription("Set date values for all taxa");
}
result = dateValueDialog.showDialog();
if (result == -1 || result == JOptionPane.CANCEL_OPTION) {
return;
}
// currentTrait.guessTrait = true; // ?? no use?
String value = dateValueDialog.getDateValue();
java.util.Date origin = new java.util.Date(0);
double d = Double.parseDouble(value);
Date date = Date.createTimeSinceOrigin(d, Units.Type.YEARS, origin);
if (selRows.length > 0) {
for (int row : selRows) {
options.taxonList.getTaxon(row).setAttribute("date", date);
}
} else {
for (Taxon taxon : options.taxonList) {
taxon.setAttribute("date", date);
}
}
// adjust the dates to the current timescale...
timeScaleChanged();
dataTableModel.fireTableDataChanged();
} while (result < 0);
}
public void clearDates() {
for (int i = 0; i < options.taxonList.getTaxonCount(); i++) {
java.util.Date origin = new java.util.Date(0);
double d = 0.0;
Date date = Date.createTimeSinceOrigin(d, Units.Type.YEARS, origin);
options.taxonList.getTaxon(i).setAttribute("date", date);
}
// adjust the dates to the current timescale...
timeScaleChanged();
dataTableModel.fireTableDataChanged();
}
public void guessDates() {
if (guessDatesDialog == null) {
guessDatesDialog = new GuessDatesDialog(frame);
}
int result = guessDatesDialog.showDialog();
if (result == -1 || result == JOptionPane.CANCEL_OPTION) {
return;
}
DateGuesser guesser = options.dateGuesser;
guesser.guessDates = true;
guessDatesDialog.setupGuesser(guesser);
String warningMessage = null;
guesser.guessDates(options.taxonList);
if (warningMessage != null) {
JOptionPane.showMessageDialog(this, "Warning: some dates may not be set correctly - \n" + warningMessage,
"Error guessing dates",
JOptionPane.WARNING_MESSAGE);
}
// adjust the dates to the current timescale...
timeScaleChanged();
dataTableModel.fireTableDataChanged();
}
public class SetDatesAction extends AbstractAction {
/**
*
*/
private static final long serialVersionUID = -7281309694753868635L;
public SetDatesAction() {
super("Set Dates");
setToolTipText("Use this tool to set sampling date values from selected taxa");
}
public void actionPerformed(ActionEvent ae) {
setDates();
}
}
public class ClearDatesAction extends AbstractAction {
/**
*
*/
private static final long serialVersionUID = -7281309694753868635L;
public ClearDatesAction() {
super("Clear Dates");
setToolTipText("Use this tool to remove sampling dates from each taxon");
}
public void actionPerformed(ActionEvent ae) {
clearDates();
}
}
public class GuessDatesAction extends AbstractAction {
/**
*
*/
private static final long serialVersionUID = 8514706149822252033L;
public GuessDatesAction() {
super("Guess Dates");
setToolTipText("Use this tool to guess the sampling dates from the taxon labels");
}
public void actionPerformed(ActionEvent ae) {
guessDates();
}
}
private void calculateHeights() {
options.maximumTipHeight = 0.0;
if (options.taxonList == null || options.taxonList.getTaxonCount() == 0) return;
heights = null;
dr.evolution.util.Date mostRecent = null;
for (int i = 0; i < options.taxonList.getTaxonCount(); i++) {
Date date = options.taxonList.getTaxon(i).getDate();
if ((date != null) && (mostRecent == null || date.after(mostRecent))) {
mostRecent = date;
}
}
if (mostRecent != null) {
heights = new double[options.taxonList.getTaxonCount()];
TimeScale timeScale = new TimeScale(mostRecent.getUnits(), true, mostRecent.getAbsoluteTimeValue());
double time0 = timeScale.convertTime(mostRecent.getTimeValue(), mostRecent);
for (int i = 0; i < options.taxonList.getTaxonCount(); i++) {
Taxon taxon = options.taxonList.getTaxon(i);
Date date = taxon.getDate();
if (date != null) {
heights[i] = timeScale.convertTime(date.getTimeValue(), date) - time0;
taxon.setAttribute("height", heights[i]);
if (heights[i] > options.maximumTipHeight) options.maximumTipHeight = heights[i];
}
}
}
frame.setStatusMessage();
}
class DataTableModel extends AbstractTableModel {
/**
*
*/
private static final long serialVersionUID = -6707994233020715574L;
String[] columnNames = {"Name", "Date", "Height"};
public DataTableModel() {
}
public int getColumnCount() {
return columnNames.length;
}
public int getRowCount() {
if (options == null) return 0;
if (options.taxonList == null) return 0;
return options.taxonList.getTaxonCount();
}
public Object getValueAt(int row, int col) {
switch (col) {
case 0:
return options.taxonList.getTaxonId(row);
case 1:
Date date = options.taxonList.getTaxon(row).getDate();
if (date != null) {
return date.getTimeValue();
} else {
return "-";
}
case 2:
if (heights != null) {
return heights[row];
} else {
return "0.0";
}
}
return null;
}
public void setValueAt(Object aValue, int row, int col) {
if (col == 0) {
options.taxonList.getTaxon(row).setId(aValue.toString());
} else if (col == 1) {
Date date = options.taxonList.getTaxon(row).getDate();
if (date != null) {
double d = (Double) aValue;
Date newDate = createDate(d, date.getUnits(), date.isBackwards(), date.getOrigin());
options.taxonList.getTaxon(row).setDate(newDate);
}
}
timeScaleChanged();
}
public boolean isCellEditable(int row, int col) {
if (col == 0) return false;
if (col == 1) {
Date date = options.taxonList.getTaxon(row).getDate();
return (date != null);
}
return false;
}
public String getColumnName(int column) {
return columnNames[column];
}
public Class getColumnClass(int c) {
return getValueAt(0, c).getClass();
}
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append(getColumnName(0));
for (int j = 1; j < getColumnCount(); j++) {
buffer.append("\t");
buffer.append(getColumnName(j));
}
buffer.append("\n");
for (int i = 0; i < getRowCount(); i++) {
buffer.append(getValueAt(i, 0));
for (int j = 1; j < getColumnCount(); j++) {
buffer.append("\t");
buffer.append(getValueAt(i, j));
}
buffer.append("\n");
}
return buffer.toString();
}
}
}