/*
* TreesPanel.java
*
* Copyright (c) 2002-2015 Alexei Drummond, Andrew Rambaut and Marc Suchard
*
* 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.treespanel;
import dr.app.beauti.BeautiFrame;
import dr.app.beauti.BeautiPanel;
import dr.app.beauti.options.*;
import dr.app.beauti.types.TreePriorType;
import dr.app.gui.table.TableEditorStopper;
import dr.evolution.datatype.Microsatellite;
import jam.framework.Exportable;
import jam.table.TableRenderer;
import javax.swing.*;
import javax.swing.border.TitledBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.plaf.BorderUIResource;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Andrew Rambaut
* @author Walter Xie
* @version $Id:$
*/
public class TreesPanel extends BeautiPanel implements Exportable {
public final static boolean DEBUG = false;
private static final long serialVersionUID = 2778103564318492601L;
// private JComboBox userTreeCombo = new JComboBox();
// private JButton button;
// private CreateTreeAction createTreeAction = new CreateTreeAction();
// private TreeDisplayPanel treeDisplayPanel;
private BeautiFrame frame = null;
public BeautiOptions options = null;
private JTable treesTable = null;
private TreesTableModel treesTableModel = null;
// private GenerateTreeDialog generateTreeDialog = null;
private boolean settingOptions = false;
// boolean hasAlignment = false;
public JCheckBox linkTreePriorCheck = new JCheckBox("Link tree prior for all trees");
JPanel treeModelPanelParent;
public PartitionTreeModel currentTreeModel = null;
TitledBorder treeModelBorder;
Map<PartitionTreeModel, PartitionTreeModelPanel> treeModelPanels = new HashMap<PartitionTreeModel, PartitionTreeModelPanel>();
JPanel treePriorPanelParent;
TitledBorder treePriorBorder;
Map<PartitionTreePrior, PartitionTreePriorPanel> treePriorPanels = new HashMap<PartitionTreePrior, PartitionTreePriorPanel>();
public TreesPanel(BeautiFrame parent, Action removeTreeAction) {
super();
this.frame = parent;
treesTableModel = new TreesTableModel();
treesTable = new JTable(treesTableModel);
treesTable.getTableHeader().setReorderingAllowed(false);
treesTable.getTableHeader().setResizingAllowed(false);
// treesTable.getTableHeader().setDefaultRenderer(
// new HeaderRenderer(SwingConstants.LEFT, new Insets(0, 4, 0, 4)));
final TableColumnModel model = treesTable.getColumnModel();
final TableColumn tableColumn0 = model.getColumn(0);
tableColumn0.setCellRenderer(new ModelsTableCellRenderer(SwingConstants.LEFT, new Insets(0, 4, 0, 4)));
TableEditorStopper.ensureEditingStopWhenTableLosesFocus(treesTable);
treesTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
treesTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent evt) {
selectionChanged();
}
});
JScrollPane scrollPane = new JScrollPane(treesTable,
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
scrollPane.setOpaque(false);
JPanel controlPanel1 = new JPanel(new FlowLayout(FlowLayout.LEFT));
controlPanel1.setOpaque(false);
setCurrentModelAndPrior(null);
JPanel panel1 = new JPanel(new BorderLayout(0, 0));
panel1.setOpaque(false);
panel1.add(scrollPane, BorderLayout.CENTER);
treeModelPanelParent = new JPanel(new FlowLayout(FlowLayout.LEFT));
treeModelPanelParent.setOpaque(false);
treeModelBorder = new javax.swing.border.TitledBorder("Tree Model");
treeModelPanelParent.setBorder(treeModelBorder);
JScrollPane scrollPane2 = new JScrollPane(treeModelPanelParent, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
scrollPane2.setOpaque(false);
scrollPane2.setBorder(null);
scrollPane2.getViewport().setOpaque(false);
treePriorPanelParent = new JPanel(new FlowLayout(FlowLayout.LEFT));
treePriorPanelParent.setOpaque(false);
treePriorBorder = new TitledBorder("Tree Prior");
treePriorPanelParent.setBorder(treePriorBorder);
JPanel panel3 = new JPanel(new FlowLayout(FlowLayout.LEFT));
panel3.setOpaque(false);
linkTreePriorCheck.setEnabled(false);
linkTreePriorCheck.setSelected(true);
linkTreePriorCheck.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent ev) {
updateShareSameTreePriorChanged();
}
});
linkTreePriorCheck.setToolTipText("Decide whether to use one tree prior for all trees");
panel3.add(linkTreePriorCheck);
JPanel panel4 = new JPanel(new BorderLayout());
panel4.setOpaque(false);
panel4.add(treePriorPanelParent, BorderLayout.NORTH);
panel4.add(scrollPane2, BorderLayout.CENTER);
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, panel1, panel4);
splitPane.setDividerLocation(180);
splitPane.setContinuousLayout(true);
splitPane.setBorder(BorderFactory.createEmptyBorder());
splitPane.setOpaque(false);
setOpaque(false);
setLayout(new BorderLayout(0, 0));
setBorder(new BorderUIResource.EmptyBorderUIResource(new Insets(12, 12, 12, 12)));
add(panel3, BorderLayout.NORTH);
add(splitPane, BorderLayout.CENTER);
}
public void updateShareSameTreePriorChanged() {
if (linkTreePriorCheck.isSelected()) {
options.linkTreePriors(currentTreeModel.getPartitionTreePrior());
} else {
options.unLinkTreePriors(currentTreeModel);
}
setCurrentModelAndPrior(currentTreeModel); // this is important to make panel refreshing
fireTreePriorsChanged();
}
private void updateTreePriorBorder() {
if (options.useStarBEAST) {
treePriorBorder.setTitle("Species tree prior used to start all gene tree models");
} else if (options.isShareSameTreePrior()) {
treePriorBorder.setTitle("Tree prior shared by all tree models");
} else {
treePriorBorder.setTitle("Tree Prior - " + currentTreeModel.getPartitionTreePrior().getName());
}
repaint();
}
public void fireTreePriorsChanged() {
options.updatePartitionAllLinks();
frame.setDirty();
}
public void updatePriorPanelForSpeciesAnalysis() {
if (options.useStarBEAST) linkTreePriorCheck.setSelected(true); // not to set false if non-*BEAST
updateShareSameTreePriorChanged();
if (currentTreeModel.getPartitionTreePrior() != null) treePriorPanelParent.removeAll();
if (options.getPartitionTreePriors().size() > 0) {
if (options.useStarBEAST) {
options.getPartitionTreePriors().get(0).setNodeHeightPrior(TreePriorType.SPECIES_YULE);
} else {
// if (options.hasData() && options.contains(Microsatellite.INSTANCE)) {
// linkTreePriorCheck.setEnabled(false);
// } else {
// linkTreePriorCheck.setEnabled(true);
// }
options.getPartitionTreePriors().get(0).setNodeHeightPrior(TreePriorType.CONSTANT);
}
PartitionTreePriorPanel p = new PartitionTreePriorPanel(options.getPartitionTreePriors().get(0), this);
treePriorPanels.put(options.getPartitionTreePriors().get(0), p);
for (PartitionTreeModel model : options.getPartitionTreeModels()) {
if (model != null && treeModelPanels.get(model) != null) treeModelPanels.get(model).setupPanel();
}
treePriorPanelParent.add(p);
}
updateTreePriorBorder();
repaint();
}
private void selectionChanged() {
int selRow = treesTable.getSelectedRow();
if (selRow >= options.getPartitionTreeModels().size()) {
selRow = 0;
treesTable.getSelectionModel().setSelectionInterval(selRow, selRow);
}
if (selRow >= 0) {
PartitionTreeModel ptm = options.getPartitionTreeModels().get(selRow);
setCurrentModelAndPrior(ptm);
//TODO treeDisplayPanel.setTree(options.userTrees.get(selRow));
}
}
/**
* Sets the current model that this model panel is displaying
*
* @param model the new model to display
*/
private void setCurrentModelAndPrior(PartitionTreeModel model) {
if (model != null) {
if (currentTreeModel != null) treeModelPanelParent.removeAll();
PartitionTreeModelPanel panel = treeModelPanels.get(model);
if (panel == null) {
panel = new PartitionTreeModelPanel(frame, model, options);
treeModelPanels.put(model, panel);
}
currentTreeModel = model;
treeModelBorder.setTitle("Tree Model - " + model.getName());
treeModelPanelParent.add(panel);
// ++++++++++++++ PartitionTreePrior ++++++++++++++++
if (model.getPartitionTreePrior() != null) treePriorPanelParent.removeAll();
PartitionTreePrior prior = model.getPartitionTreePrior();
PartitionTreePriorPanel panel1 = treePriorPanels.get(prior);
if (panel1 == null) {
panel1 = new PartitionTreePriorPanel(prior, this);
treePriorPanels.put(prior, panel1);
}
// currentTreePrior = prior;
updateTreePriorBorder();
treePriorPanelParent.add(panel1);
repaint();
} else {
//TODO
}
}
private void resetPanel() {
if (!options.hasData()) {
currentTreeModel = null;
treeModelPanels.clear();
// currentTreePrior = null;
treePriorPanels.clear();
treeModelPanelParent.removeAll();
treeModelBorder.setTitle("Tree Model");
treePriorPanelParent.removeAll();
treePriorBorder.setTitle("Tree Prior");
return;
}
}
public void updateLinkTreePriorEnablility() {
boolean selected = !(options.getPartitionTreeModels().size() < 2
|| options.contains(Microsatellite.INSTANCE) || options.useStarBEAST);
linkTreePriorCheck.setEnabled(selected);
}
public void setOptions(BeautiOptions options) {
this.options = options;
resetPanel();
settingOptions = true;
updateLinkTreePriorEnablility();
linkTreePriorCheck.setSelected(options.isShareSameTreePrior()); // important
for (PartitionTreeModel model : options.getPartitionTreeModels()) {
if (model != null && treeModelPanels.get(model) != null) treeModelPanels.get(model).setupPanel();
}
for (PartitionTreePrior prior : options.getPartitionTreePriors()) {
PartitionTreePriorPanel ptpp = treePriorPanels.get(prior);
if (ptpp != null) {
ptpp.setTreePriorChoices(options.useStarBEAST, options.getPartitionTreeModels().size() > 1,
options.clockModelOptions.isTipCalibrated());
// setTreePriorChoices should be always before setOptions
ptpp.setOptions();
// if (options.contains(Microsatellite.INSTANCE)) {
// ptpp.setMicrosatelliteTreePrior();
// } else
ptpp.repaint();
}
}
settingOptions = false;
int selRow = treesTable.getSelectedRow();
treesTableModel.fireTableDataChanged();
if (options.getPartitionTreeModels().size() > 0) {
if (selRow < 0) {
selRow = 0;
}
treesTable.getSelectionModel().setSelectionInterval(selRow, selRow);
}
if (currentTreeModel == null && options.getPartitionTreeModels().size() > 0) {
treesTable.getSelectionModel().setSelectionInterval(0, 0);
}
// fireShareSameTreePriorChanged();
validate();
repaint();
}
public void getOptions(BeautiOptions options) {
if (settingOptions) return;
// Set<PartitionTreeModel> models = treeModelPanels.keySet();
//
// for (PartitionTreeModel model : models) {
// treeModelPanels.get(model).getOptions(options);
// }
for (PartitionTreePrior prior : options.getPartitionTreePriors()) {
PartitionTreePriorPanel ptpp = treePriorPanels.get(prior);
if (ptpp != null) {
ptpp.getOptions();
}
}
}
public BeautiFrame getFrame() {
return frame;
}
public JComponent getExportableComponent() {
// return treeDisplayPanel;
return this;
}
private boolean isUsed(int row) {
PartitionTreeModel model = options.getPartitionTreeModels().get(row);
for (AbstractPartitionData partition : options.dataPartitions) {
if (partition.getPartitionTreeModel() == model) {
return true;
}
}
return false;
}
class TreesTableModel extends AbstractTableModel {
private static final long serialVersionUID = -6707994233020715574L;
String[] columnNames = {"Trees"};
public TreesTableModel() {
}
public int getColumnCount() {
return columnNames.length;
}
public int getRowCount() {
if (options == null) return 0;
return options.getPartitionTreeModels().size();
}
public Object getValueAt(int row, int col) {
PartitionTreeModel model = options.getPartitionTreeModels().get(row);
switch (col) {
case 0:
return model.getName();
default:
throw new IllegalArgumentException("unknown column, " + col);
}
}
public void setValueAt(Object aValue, int row, int col) {
// Tree tree = options.userTrees.get(row);
switch (col) {
case 0:
String name = ((String) aValue).trim();
if (name.length() > 0) {
PartitionTreeModel model = options.getPartitionTreeModels().get(row);
model.setName(name);
// keep tree prior name same as tree model name
PartitionTreePrior prior = model.getPartitionTreePrior();
prior.setName(name);
fireTreePriorsChanged();
}
break;
default:
throw new IllegalArgumentException("unknown column, " + col);
}
}
public boolean isCellEditable(int row, int col) {
boolean editable;
switch (col) {
case 0:// name
editable = true;
break;
default:
editable = false;
}
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 ModelsTableCellRenderer extends TableRenderer {
public ModelsTableCellRenderer(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);
if (!isUsed(aRow))
renderer.setForeground(Color.gray);
else
renderer.setForeground(Color.black);
return this;
}
}
// Action addTreeAction = new AbstractAction("+") {
// public void actionPerformed(ActionEvent ae) {
// createTree();
// }
// };
// public class CreateTreeAction extends AbstractAction {
// public CreateTreeAction() {
// super("Create Tree");
// setToolTipText("Create a NJ or UPGMA tree using a data partition");
// }
//
// public void actionPerformed(ActionEvent ae) {
// createTree();
// }
// }
}