/*
* ClockModelPanel.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.clockModelsPanel;
import dr.app.beauti.BeautiFrame;
import dr.app.beauti.BeautiPanel;
import dr.app.beauti.ComboBoxRenderer;
import dr.app.beauti.options.BeautiOptions;
import dr.app.beauti.options.ClockModelGroup;
import dr.app.beauti.options.PartitionClockModel;
import dr.app.beauti.types.OldClockType;
import dr.app.gui.table.RealNumberCellEditor;
import dr.app.gui.table.TableEditorStopper;
import dr.evolution.datatype.DataType;
import jam.framework.Exportable;
import jam.panels.ActionPanel;
import jam.table.TableRenderer;
import javax.swing.*;
import javax.swing.border.TitledBorder;
import javax.swing.plaf.BorderUIResource;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
/**
* @author Andrew Rambaut
* @author Alexei Drummond
* @author Walter Xie
* @version $Id: ClockModelPanel.java,v 1.17 2006/09/05 13:29:34 rambaut Exp $
*/
public class OldClockModelsPanel extends BeautiPanel implements Exportable {
private static final long serialVersionUID = 2945922234432540027L;
private final String[] columnToolTips = {"Name", "Clock model",
"Decide whether to estimate this clock model",
"Provide the rate if it is fixed",
"The group which the clock model is belonging to"};
private final String[] columnToolTips2 = {"A group of clock models",
"<html>Fix mean rate of this group of clock models." +
"<br>Select this option to fix the mean substitution rate,<br>" +
"rather than try to infer it. If this option is turned off, then<br>" +
"either the sequences should have dates or the tree should have<br>" +
"sufficient calibration informations specified as priors.<br>" +
"In addition, it is only available for multi-clock partitions.</html>",
"Enter the fixed mean rate here."};
private static final int MINIMUM_TABLE_HEIGHT = 400;
JTable clockModelTable = null;
ClockModelTableModel clockModelTableModel = null;
JScrollPane scrollPane;
// JCheckBox fixedMeanRateCheck = new JCheckBox("Fix mean rate of molecular clock model to: ");
// RealNumberField meanRateField = new RealNumberField(Double.MIN_VALUE, Double.MAX_VALUE);
JTable clockGroupTable = null;
ClockGroupTableModel clockGroupTableModel = null;
BeautiFrame frame = null;
BeautiOptions options = null;
boolean settingOptions = false;
public List<ClockModelGroup> clockModelGroupList = new ArrayList<ClockModelGroup>();
public OldClockModelsPanel(BeautiFrame parent) {
this.frame = parent;
clockModelTableModel = new ClockModelTableModel();
clockModelTable = new JTable(clockModelTableModel) {
//Implement table header tool tips.
protected JTableHeader createDefaultTableHeader() {
return new JTableHeader(columnModel) {
public String getToolTipText(MouseEvent e) {
Point p = e.getPoint();
int index = columnModel.getColumnIndexAtX(p.x);
int realIndex = columnModel.getColumn(index).getModelIndex();
return columnToolTips[realIndex];
}
};
}
};
initTable(clockModelTable);
scrollPane = new JScrollPane(clockModelTable,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
scrollPane.setOpaque(false);
// PanelUtils.setupComponent(clockModelCombo);
// clockModelCombo.setToolTipText("<html>Select either a strict molecular clock or<br>or a relaxed clock model.</html>");
// clockModelCombo.addItemListener(comboListener);
// PanelUtils.setupComponent(fixedMeanRateCheck);
// fixedMeanRateCheck.setSelected(false); // default to FixRateType.ESTIMATE
// fixedMeanRateCheck.addItemListener(new ItemListener() {
// public void itemStateChanged(ItemEvent ev) {
// meanRateField.setEnabled(fixedMeanRateCheck.isSelected());
// if (fixedMeanRateCheck.isSelected()) {
// options.clockModelOptions.fixMeanRate();
// } else {
// options.clockModelOptions.fixRateOfFirstClockPartition();
// }
//
// clockModelTableModel.fireTableDataChanged();
// fireModelsChanged();
// }
// });
// fixedMeanRateCheck.setToolTipText("<html>Select this option to fix the mean substitution rate,<br>"
// + "rather than try to infer it. If this option is turned off, then<br>"
// + "either the sequences should have dates or the tree should have<br>"
// + "sufficient calibration informations specified as priors.<br>"
// + "In addition, it is only available for multi-clock partitions." + "</html>");// TODO Alexei
//
// PanelUtils.setupComponent(meanRateField);
// meanRateField.setEnabled(fixedMeanRateCheck.isSelected());
// meanRateField.setValue(1.0);
// meanRateField.addKeyListener(new java.awt.event.KeyAdapter() {
// public void keyTyped(java.awt.event.KeyEvent ev) {
// frame.setDirty();
// }
// });
// meanRateField.setToolTipText("<html>Enter the fixed mean rate here.</html>");
// meanRateField.setColumns(10);
// meanRateField.setEnabled(true);
JPanel modelPanelParent = new JPanel(new BorderLayout(12, 12));
// modelPanelParent.setLayout(new BoxLayout(modelPanelParent, BoxLayout.Y_AXIS));
modelPanelParent.setOpaque(false);
TitledBorder modelBorder = new TitledBorder("Clock Model : ");
modelPanelParent.setBorder(modelBorder);
// OptionsPanel panel = new OptionsPanel(12, 12);
// panel.addComponents(fixedMeanRateCheck, meanRateField);
// The bottom panel is now small enough that this is not necessary
// JScrollPane scrollPane2 = new JScrollPane(panel);
// scrollPane2.setOpaque(false);
// scrollPane2.setPreferredSize(new Dimension(400, 150));
modelPanelParent.add(scrollPane, BorderLayout.CENTER);
// modelPanelParent.add(panel, BorderLayout.SOUTH);
//======= Clock Model Group for Fix Mean function ==========
clockGroupTableModel = new ClockGroupTableModel();
clockGroupTable = new JTable(clockGroupTableModel) {
//Implement table header tool tips.
protected JTableHeader createDefaultTableHeader() {
return new JTableHeader(columnModel) {
public String getToolTipText(MouseEvent e) {
Point p = e.getPoint();
int index = columnModel.getColumnIndexAtX(p.x);
int realIndex = columnModel.getColumn(index).getModelIndex();
return columnToolTips2[realIndex];
}
};
}
};
clockGroupTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
clockGroupTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
clockGroupTable.getTableHeader().setReorderingAllowed(false);
TableColumn col = clockGroupTable.getColumnModel().getColumn(0);
col.setMinWidth(200);
col = clockGroupTable.getColumnModel().getColumn(1);
col.setMinWidth(40);
col.setCellRenderer(new GrayableCheckboxCellRenderer());
col = clockGroupTable.getColumnModel().getColumn(2);
col.setCellEditor(new RealNumberCellEditor(0, Double.POSITIVE_INFINITY));
col.setMinWidth(80);
TableEditorStopper.ensureEditingStopWhenTableLosesFocus(clockGroupTable);
JScrollPane d_scrollPane = new JScrollPane(clockGroupTable,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
d_scrollPane.setOpaque(false);
ActionPanel actionPanel1 = new ActionPanel(false);
actionPanel1.setAddAction(addClockGroupAction);
actionPanel1.setRemoveAction(removeClockGroupAction);
addClockGroupAction.setEnabled(false);
removeClockGroupAction.setEnabled(false);
JPanel groupPanel = new JPanel(new BorderLayout(12, 12));
groupPanel.add(d_scrollPane, BorderLayout.CENTER);
groupPanel.add(actionPanel1, BorderLayout.SOUTH);
TitledBorder traitClockBorder = new TitledBorder("Clock Model Group: ");
groupPanel.setBorder(traitClockBorder);
JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, modelPanelParent, groupPanel);
splitPane.setDividerLocation(MINIMUM_TABLE_HEIGHT);
setOpaque(false);
setLayout(new BorderLayout(12, 12));
setBorder(new BorderUIResource.EmptyBorderUIResource(new Insets(12, 12, 12, 12)));
add(splitPane, BorderLayout.CENTER);
}
Action addClockGroupAction = new AbstractAction("+") {
public void actionPerformed(ActionEvent ae) {
String s = JOptionPane.showInputDialog(frame,
"Please input the new group name below:",
"Add A New Clock Model Group Dialog",
JOptionPane.PLAIN_MESSAGE, null, null,
clockModelGroupList.size() + "_group").toString().trim();
if ((s != null) && (s.length() > 0)) {
if (options.clockModelOptions.containsGroup(s, clockModelGroupList)) {
errorMessageDialog("This name has been used already,\nplease input a new name.");
} else {
clockModelGroupList.add(new ClockModelGroup(s));
modelsChanged();
}
} else {
errorMessageDialog("Please input a name properly.");
}
}
};
Action removeClockGroupAction = new AbstractAction("-") {
public void actionPerformed(ActionEvent ae) {
int row = clockGroupTable.getSelectedRow();
if (row >= 0) {
ClockModelGroup group = clockModelGroupList.get(row);
if (options.getPartitionClockModels(group).size() > 0) {
errorMessageDialog("Cannot remove the group " + group.getName() +
",\nwhich contains clock model(s)." +
"\nPlease assign model(s) to a different group first.");
} else {
clockModelGroupList.remove(row);
modelsChanged();
}
} else {
errorMessageDialog("Please select a group properly.");
}
}
};
private void errorMessageDialog(String e) {
JOptionPane.showMessageDialog(this, e, "Clock Model Panel Error", JOptionPane.ERROR_MESSAGE);
}
private void initTable(JTable dataTable) {
dataTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
dataTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
dataTable.getTableHeader().setReorderingAllowed(false);
// clockModelTable.getTableHeader().setDefaultRenderer(
// new HeaderRenderer(SwingConstants.LEFT, new Insets(0, 4, 0, 4)));
TableColumn col = dataTable.getColumnModel().getColumn(0);
col.setCellRenderer(new ClockTableCellRenderer(SwingConstants.LEFT, new Insets(0, 4, 0, 4)));
col.setMinWidth(200);
col = dataTable.getColumnModel().getColumn(1);
ComboBoxRenderer comboBoxRenderer = new ComboBoxRenderer();
comboBoxRenderer.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);
col.setCellRenderer(comboBoxRenderer);
col.setMinWidth(260);
col = dataTable.getColumnModel().getColumn(2);
col.setMinWidth(40);
col.setCellRenderer(new GrayableCheckboxCellRenderer());
col = dataTable.getColumnModel().getColumn(3);
col.setCellRenderer(new ClockTableCellRenderer(SwingConstants.LEFT, new Insets(0, 4, 0, 4)));
col.setCellEditor(new RealNumberCellEditor(0, Double.POSITIVE_INFINITY));
col.setMinWidth(80);
col = dataTable.getColumnModel().getColumn(4);
col.setCellRenderer(comboBoxRenderer);
col.setMinWidth(200);
TableEditorStopper.ensureEditingStopWhenTableLosesFocus(dataTable);
}
private void modelsChanged() {
TableColumn col = clockModelTable.getColumnModel().getColumn(1);
col.setCellEditor(new DefaultCellEditor(new JComboBox(new OldClockType[]{
OldClockType.STRICT_CLOCK,
OldClockType.UNCORRELATED_LOGNORMAL,
// OldClockType.UNCORRELATED_CAUCHY,
// OldClockType.UNCORRELATED_GAMMA,
OldClockType.UNCORRELATED_EXPONENTIAL,
// OldClockType.AUTOCORRELATED
OldClockType.RANDOM_LOCAL_CLOCK,
})));
col = clockModelTable.getColumnModel().getColumn(4);
col.setCellEditor(new DefaultCellEditor(new JComboBox(options.clockModelOptions.getClockModelGroupNames(clockModelGroupList))));
addClockGroupAction.setEnabled(clockModelGroupList.size() > 0);
removeClockGroupAction.setEnabled(clockModelGroupList.size() > 1);
clockGroupTableModel.fireTableDataChanged();
}
private void fireModelsChanged() {
options.updatePartitionAllLinks();
clockModelTableModel.fireTableDataChanged();
clockGroupTableModel.fireTableDataChanged();
frame.setStatusMessage();
frame.setDirty();
}
// private void updateModelPanelBorder() {
// if (options.hasData()) {
// modelBorder.setTitle(options.clockModelOptions.getRateOptionClockModel().toString());
// } else {
// modelBorder.setTitle("Overall clock model(s) parameters");
// }
//
// repaint();
// }
public void setOptions(BeautiOptions options) {
this.options = options;
settingOptions = true;
clockModelGroupList = options.clockModelOptions.getClockModelGroups();
addClockGroupAction.setEnabled(clockModelGroupList.size() > 0);
removeClockGroupAction.setEnabled(clockModelGroupList.size() > 1);
// fixedMeanRateCheck.setSelected(options.clockModelOptions.getRateOptionClockModel() == FixRateType.FIX_MEAN);
// fixedMeanRateCheck.setEnabled(!(options.clockModelOptions.getRateOptionClockModel() == FixRateType.TIP_CALIBRATED
// || options.clockModelOptions.getRateOptionClockModel() == FixRateType.NODE_CALIBRATED
// || options.clockModelOptions.getRateOptionClockModel() == FixRateType.RATE_CALIBRATED));
// meanRateField.setValue(options.clockModelOptions.getMeanRelativeRate());
settingOptions = false;
int selRow = clockModelTable.getSelectedRow();
clockModelTableModel.fireTableDataChanged();
if (options.getPartitionClockModels().size() > 0) {
if (selRow < 0) {
selRow = 0;
}
clockModelTable.getSelectionModel().setSelectionInterval(selRow, selRow);
}
// fireModelsChanged();
modelsChanged();
clockModelTableModel.fireTableDataChanged();
}
public void getOptions(BeautiOptions options) {
if (settingOptions) return;
// if (fixedMeanRateCheck.isSelected()) {
// options.clockModelOptions.fixMeanRate();
// } else {
// options.clockModelOptions.fixRateOfFirstClockPartition();
// }
// options.clockModelOptions.setMeanRelativeRate(meanRateField.getValue());
// fireModelsChanged();
}
public JComponent getExportableComponent() {
return clockModelTable;
}
class ClockModelTableModel extends AbstractTableModel {
private static final long serialVersionUID = -2852144669936634910L;
// String[] columnNames = {"Clock Model Name", "Molecular Clock Model"};
final private String[] columnNames = new String[]{"Name", "Model", "Estimate", "Rate", "Group"};
public ClockModelTableModel() {
}
public int getColumnCount() {
return columnNames.length;
}
public int getRowCount() {
if (options == null) return 0;
return options.getPartitionClockModels().size();
}
public Object getValueAt(int row, int col) {
PartitionClockModel model = options.getPartitionClockModels().get(row);
switch (col) {
case 0:
return model.getName();
case 1:
return OldClockType.getType(model.getClockType(), model.getClockDistributionType());
case 2:
return model.isEstimatedRate();
case 3:
return model.getRate();
case 4:
return model.getClockModelGroup().getName();
}
return null;
}
public void setValueAt(Object aValue, int row, int col) {
PartitionClockModel model = options.getPartitionClockModels().get(row);
switch (col) {
case 0:
String name = ((String) aValue).trim();
if (name.length() > 0) {
model.setName(name);
}
break;
case 1:
OldClockType type = (OldClockType) aValue;
model.setClockType(type.getClockType());
model.setClockDistributionType(type.getClockDistributionType());
break;
case 2:
model.setEstimatedRate((Boolean) aValue);
// if (options.clockModelOptions.getRateOptionClockModel() == FixRateType.RElATIVE_TO) {
// if (!options.clockModelOptions.validateRelativeTo()) {
// JOptionPane.showMessageDialog(frame, "It must have at least one clock rate to be fixed !",
// "Validation Of Relative To ?th Rate", JOptionPane.WARNING_MESSAGE);
// model.setEstimatedRate(false);
// }
// }
break;
case 3:
model.setRate((Double) aValue, true);
// options.selectParameters();
break;
case 4:
model.setClockModelGroup(options.clockModelOptions.getGroup(aValue.toString(), clockModelGroupList));
break;
default:
throw new IllegalArgumentException("unknown column, " + col);
}
fireModelsChanged();
}
public boolean isCellEditable(int row, int col) {
boolean editable;
PartitionClockModel model = options.getPartitionClockModels().get(row);
ClockModelGroup group = model.getClockModelGroup();
switch (col) {
case 1:
editable = !(model.getDataType().getType() == DataType.MICRO_SAT ||
model.getDataType().getType() == DataType.GENERAL);
break;
case 2:// Check box
case 4:
editable = !group.isFixMean();
break;
case 3:
editable = !group.isFixMean();// && !((Boolean) getValueAt(row, 2));
break;
default:
editable = true;
}
return editable;
}
public String getColumnName(int column) {
return columnNames[column];
}
public Class getColumnClass(int c) {
if (getRowCount() == 0) {
return Object.class;
}
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();
}
}
class ClockTableCellRenderer extends TableRenderer {
public ClockTableCellRenderer(int alignment, Insets insets) {
super(alignment, insets);
}
public Component getTableCellRendererComponent(JTable aTable,
Object value,
boolean aIsSelected,
boolean aHasFocus,
int aRow, int aColumn) {
if (value == null) return this;
Component renderer = super.getTableCellRendererComponent(aTable,
value,
aIsSelected,
aHasFocus,
aRow, aColumn);
ClockModelGroup group = options.getPartitionClockModels().get(aRow).getClockModelGroup();
if (group.isFixMean() && aColumn > 1) {
renderer.setForeground(Color.gray);
// } else if (!group.isFixMean() && aColumn == 3) {
// renderer.setForeground(Color.gray);
} else {
renderer.setForeground(Color.black);
}
return this;
}
}
public class GrayableCheckboxCellRenderer extends JCheckBox implements TableCellRenderer {
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int vRowIndex, int vColIndex) {
boolean editable = table.getModel().isCellEditable(vRowIndex, vColIndex);
setBackground(editable ? Color.WHITE : Color.LIGHT_GRAY);
setEnabled(editable);
setSelected((Boolean) value);
setHorizontalAlignment(SwingConstants.CENTER);
return this;
}
}
class ClockGroupTableModel extends AbstractTableModel {
String[] columnNames = {"Group Name", "Fix Mean", "Rate"};
public ClockGroupTableModel() {
}
public int getColumnCount() {
return columnNames.length;
}
public int getRowCount() {
if (options == null) return 0;
// return options.clockModelOptions.getClockModelGroups().size();
return clockModelGroupList.size();
}
public Object getValueAt(int row, int col) {
switch (col) {
case 0:
return clockModelGroupList.get(row).getName();
case 1:
return clockModelGroupList.get(row).isFixMean();
case 2:
if (!clockModelGroupList.get(row).isFixMean()) return "";
return clockModelGroupList.get(row).getFixMeanRate();
}
return null;
}
public void setValueAt(Object aValue, int row, int col) {
switch (col) {
case 0:
String name = ((String) aValue).trim();
if (name.length() > 0) {
clockModelGroupList.get(row).setName(name);
modelsChanged();
}
break;
case 1:
ClockModelGroup group = clockModelGroupList.get(row);
group.setFixMean((Boolean) aValue);
if ((Boolean) aValue) {
options.clockModelOptions.fixMeanRate(group);
} else {
options.clockModelOptions.fixRateOfFirstClockPartition(group);
}
break;
case 2:
clockModelGroupList.get(row).setFixMeanRate((Double) aValue, options);
break;
default:
throw new IllegalArgumentException("unknown column, " + col);
}
fireModelsChanged();
}
public boolean isCellEditable(int row, int col) {
switch (col) {
case 1:// Check box
return options.getPartitionClockModels(options.clockModelOptions.
getGroup(getValueAt(row, 0).toString(), clockModelGroupList)).size() > 1;
case 2:
return /*!fixedMeanRateCheck.isSelected() &&*/ ((Boolean) getValueAt(row, 1));
default:
return true;
}
}
public String getColumnName(int column) {
return columnNames[column];
}
public Class getColumnClass(int c) {
if (getRowCount() == 0) {
return Object.class;
}
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();
}
}
}