package beast.app.beauti;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Rectangle;
import java.lang.reflect.InvocationTargetException;
import javax.swing.Box;
import javax.swing.DefaultListModel;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JViewport;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import beast.app.beauti.BeautiPanelConfig.Partition;
import beast.app.draw.InputEditor;
import beast.app.draw.InputEditor.ExpandOption;
import beast.app.util.Utils;
import beast.core.BEASTInterface;
import beast.core.Input;
import beast.core.MCMC;
import beast.core.util.CompoundDistribution;
import beast.evolution.alignment.Taxon;
import beast.evolution.branchratemodel.BranchRateModel;
import beast.evolution.likelihood.GenericTreeLikelihood;
import beast.evolution.sitemodel.SiteModel;
import beast.evolution.sitemodel.SiteModelInterface;
import beast.evolution.tree.TreeInterface;
/**
* panel making up each of the tabs in Beauti *
*/
public class BeautiPanel extends JPanel implements ListSelectionListener {
private static final long serialVersionUID = 1L;
public final static String ICONPATH = "beast/app/beauti/";
static int partitionListPreferredWidth = 120;
private JSplitPane splitPane;
/**
* document that this panel applies to *
*/
BeautiDoc doc;
public BeautiDoc getDoc() {
return doc;
}
/**
* configuration for this panel *
*/
public BeautiPanelConfig config;
/**
* panel number *
*/
int panelIndex;
/**
* partition currently on display *
*/
public int partitionIndex = 0;
/**
* box containing the list of partitions, to make (in)visible on update *
*/
JComponent partitionComponent;
/**
* list of partitions in m_listBox *
*/
JList<String> listOfPartitions;
/**
* model for m_listOfPartitions *
*/
DefaultListModel<String> listModel;
JScrollPane scroller;
/**
* component containing main input editor *
*/
Component centralComponent = null;
public BeautiPanel() {
}
public BeautiPanel(int panelIndex, BeautiDoc doc, BeautiPanelConfig config) throws NoSuchMethodException, SecurityException, ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
this.doc = doc;
this.panelIndex = panelIndex;
setLayout(new BorderLayout());
this.config = config;
if (this.config.hasPartition() != Partition.none &&
doc.getPartitions(config.hasPartitionsInput.get().toString()).size() > 1) {
splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
add(splitPane,BorderLayout.CENTER);
} else {
splitPane = null;
}
refreshPanel();
addPartitionPanel(this.config.hasPartition(), panelIndex);
setOpaque(false);
} // c'tor
void addPartitionPanel(Partition hasPartition, int panelIndex) {
Box box = Box.createVerticalBox();
if (splitPane != null && hasPartition != Partition.none) {
box.add(createList());
} else {
return;
}
box.add(Box.createVerticalGlue());
box.add(new JLabel(Utils.getIcon(panelIndex, config)));
splitPane.add(box, JSplitPane.LEFT);
if (listOfPartitions != null) {
listOfPartitions.setSelectedIndex(partitionIndex);
}
}
/**
* Create a list of partitions and return as a JComponent;
* @return
*/
JComponent createList() {
partitionComponent = new JPanel();
partitionComponent.setLayout(new BorderLayout());
JLabel partitionLabel = new JLabel("Partition");
partitionLabel.setHorizontalAlignment(SwingConstants.CENTER);
partitionComponent.add(partitionLabel, BorderLayout.NORTH);
listModel = new DefaultListModel<>();
listOfPartitions = new JList<>(listModel);
listOfPartitions.setName("listOfPartitions");
listOfPartitions.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
Dimension size = new Dimension(partitionListPreferredWidth, 300);
//listOfPartitions.setFixedCellWidth(120);
// m_listOfPartitions.setSize(size);
//listOfPartitions.setPreferredSize(size);
listOfPartitions.setMinimumSize(size);
// m_listOfPartitions.setBounds(0, 0, 100, 100);
listOfPartitions.addListSelectionListener(this);
updateList();
// AJD: This is unnecessary and not appropriate for Mac OS X look and feel
//listOfPartitions.setBorder(new BevelBorder(BevelBorder.RAISED));
JScrollPane listPane = new JScrollPane(listOfPartitions);
partitionComponent.add(listPane, BorderLayout.CENTER);
// AJD: This is unnecessary and not appropriate for Mac OS X look and feel
//partitionComponent.setBorder(new EtchedBorder());
return partitionComponent;
}
public void updateList() {
if (listModel == null) {
return;
}
listModel.clear();
if (listModel.size() > 0) {
// this is a weird bit of code, since listModel.clear should ensure that size()==0, but it doesn't
return;
}
String type = config.hasPartitionsInput.get().toString();
for (BEASTInterface partition : doc.getPartitions(type)) {
if (type.equals("SiteModel")) {
partition = (BEASTInterface) ((GenericTreeLikelihood) partition).siteModelInput.get();
} else if (type.equals("ClockModel")) {
partition = ((GenericTreeLikelihood) partition).branchRateModelInput.get();
} else if (type.equals("Tree")) {
partition = (BEASTInterface) ((GenericTreeLikelihood) partition).treeInput.get();
}
String partitionID = partition.getID();
partitionID = partitionID.substring(partitionID.lastIndexOf('.') + 1);
if (partitionID.length() > 1 && partitionID.charAt(1) == ':') {
partitionID = partitionID.substring(2);
}
listModel.addElement(partitionID);
}
if (partitionIndex >= 0 && listModel.size() > 0)
listOfPartitions.setSelectedIndex(partitionIndex);
}
// AR remove globals (doesn't seem to be used anywhere)...
// static BeautiPanel g_currentPanel = null;
public void refreshPanel() throws NoSuchMethodException, SecurityException, ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
if (doc.alignments.size() == 0) {
refreshInputPanel();
return;
}
doc.scrubAll(true, false);
// toggle splitpane
if (splitPane == null && config.hasPartition() != Partition.none &&
doc.getPartitions(config.hasPartitionsInput.get().toString()).size() > 1) {
splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
add(splitPane,BorderLayout.CENTER);
addPartitionPanel(config.hasPartition(), panelIndex);
}
if (splitPane != null && (config.hasPartition() == Partition.none ||
doc.getPartitions(config.hasPartitionsInput.get().toString()).size() <= 1)) {
remove(splitPane);
splitPane = null;
}
refreshInputPanel();
if (partitionComponent != null && config.getType() != null) {
partitionComponent.setVisible(doc.getPartitions(config.getType()).size() > 1);
}
// g_currentPanel = this;
}
void refreshInputPanel(BEASTInterface beastObject, Input<?> input, boolean addButtons, InputEditor.ExpandOption forceExpansion) throws NoSuchMethodException, SecurityException, ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
if (centralComponent != null) {
remove(centralComponent);
}
if (input != null && input.get() != null && input.getType() != null) {
InputEditor.ButtonStatus bs = config.buttonStatusInput.get();
InputEditor inputEditor = doc.getInputEditorFactory().createInputEditor(input, beastObject, addButtons, forceExpansion, bs, null, doc);
JPanel p = new JPanel();
p.setLayout(new BorderLayout());
if (isToClone()) {
ClonePartitionPanel clonePartitionPanel = new ClonePartitionPanel(this);
p.add(clonePartitionPanel, BorderLayout.NORTH);
} else {
p.add(inputEditor.getComponent(), BorderLayout.CENTER);
}
Rectangle bounds = new Rectangle(0,0);
if (scroller != null) {
// get lastPaintPosition from viewport
// HACK access it through its string representation
JViewport v = scroller.getViewport();
String vs = v.toString();
int i = vs.indexOf("lastPaintPosition=java.awt.Point[x=");
if (i > -1) {
i = vs.indexOf("y=", i);
vs = vs.substring(i+2, vs.indexOf("]", i));
i = Integer.parseInt(vs);
} else {
i = 0;
}
bounds.y = -i;
}
scroller = new JScrollPane(p);
scroller.getViewport().scrollRectToVisible(bounds);
centralComponent = scroller;
} else {
centralComponent = new JLabel("No input editors.");
}
if (splitPane != null) {
JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
panel.add(centralComponent, BorderLayout.NORTH);
splitPane.add(panel, JSplitPane.RIGHT);
} else {
add(centralComponent);
}
}
void refreshInputPanel() throws NoSuchMethodException, SecurityException, ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
doc.currentInputEditors.clear();
InputEditor.Base.g_nLabelWidth = config.labelWidthInput.get();
BEASTInterface beastObject = config;
final Input<?> input = config.resolveInput(doc, partitionIndex);
boolean addButtons = config.addButtons();
ExpandOption forceExpansion = config.forceExpansion();
refreshInputPanel(beastObject, input, addButtons, forceExpansion);
}
/**
* Clones partition identified by sourceID to targetID and type (Site/Clock/Tree model)
* as stored in config.
* @param sourceID
* @param targetID
*/
public void cloneFrom(String sourceID, String targetID) {
if (sourceID.equals(targetID)) {
return;
}
String type = config.hasPartitionsInput.get().toString();
java.util.List<BEASTInterface> list = doc.getPartitions(type);
int source = -1, target = -1;
for (int i = 0; i < list.size(); i++) {
BEASTInterface partition = list.get(i);
if (type.equals("SiteModel")) {
partition = (BEASTInterface) ((GenericTreeLikelihood) partition).siteModelInput.get();
} else if (type.equals("ClockModel")) {
partition = ((GenericTreeLikelihood) partition).branchRateModelInput.get();
} else if (type.equals("Tree")) {
partition = (BEASTInterface) ((GenericTreeLikelihood) partition).treeInput.get();
}
String partitionID = partition.getID();
partitionID = partitionID.substring(partitionID.lastIndexOf('.') + 1);
if (partitionID.length() > 1 && partitionID.charAt(1) == ':') {
partitionID = partitionID.substring(2);
}
if (partitionID.equals(sourceID)) {
source = i;
}
if (partitionID.equals(targetID)) {
target = i;
}
}
if (target == -1) {
throw new RuntimeException("Programmer error: sourceID and targetID should be in list");
}
CompoundDistribution likelihoods = (CompoundDistribution) doc.pluginmap.get("likelihood");
GenericTreeLikelihood likelihoodSource = (GenericTreeLikelihood) likelihoods.pDistributions.get().get(source);
GenericTreeLikelihood likelihood = (GenericTreeLikelihood) likelihoods.pDistributions.get().get(target);
PartitionContext oldContext = doc.getContextFor(likelihoodSource);
PartitionContext newContext = doc.getContextFor(likelihood);
// this ensures the config.sync does not set any input value
config._input.setValue(null, config);
if (type.equals("SiteModel")) {
SiteModelInterface siteModelSource = likelihoodSource.siteModelInput.get();
SiteModelInterface siteModel = null;
try {
siteModel = (SiteModel.Base) BeautiDoc.deepCopyPlugin((BEASTInterface) siteModelSource,
likelihood, (MCMC) doc.mcmc.get(), oldContext, newContext, doc, null);
} catch (RuntimeException e) {
JOptionPane.showMessageDialog(this, "Could not clone " + sourceID + " to " + targetID + " " + e.getMessage());
return;
}
likelihood.siteModelInput.setValue(siteModel, likelihood);
return;
} else if (type.equals("ClockModel")) {
BranchRateModel clockModelSource = likelihoodSource.branchRateModelInput.get();
BranchRateModel clockModel = null;
try {
clockModel = (BranchRateModel) BeautiDoc.deepCopyPlugin((BEASTInterface) clockModelSource,
likelihood, (MCMC) doc.mcmc.get(), oldContext, newContext, doc, null);
} catch (Exception e) {
JOptionPane.showMessageDialog(this, "Could not clone " + sourceID + " to " + targetID + " " + e.getMessage());
return;
}
// make sure that *if* the clock model has a tree as input, it is
// the same as for the likelihood
TreeInterface tree = null;
try {
for (Input<?> input : ((BEASTInterface) clockModel).listInputs()) {
if (input.getName().equals("tree")) {
tree = (TreeInterface) input.get();
}
}
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (tree != null && tree != likelihood.treeInput.get()) {
//likelihood.treeInput.setValue(tree, likelihood);
JOptionPane.showMessageDialog(null, "Cannot clone clock model with different trees");
return;
}
likelihood.branchRateModelInput.setValue(clockModel, likelihood);
return;
} else if (type.equals("Tree")) {
TreeInterface tree = null;
TreeInterface treeSource = likelihoodSource.treeInput.get();
try {
tree = (TreeInterface) BeautiDoc.deepCopyPlugin((BEASTInterface) treeSource, likelihood,
(MCMC) doc.mcmc.get(), oldContext, newContext, doc, null);
} catch (Exception e) {
JOptionPane.showMessageDialog(this, "Could not clone " + sourceID + " to " + targetID + " " + e.getMessage());
return;
}
// sanity check: make sure taxon sets are compatible
Taxon.assertSameTaxa(tree.getID(), tree.getTaxonset().getTaxaNames(),
likelihood.dataInput.get().getID(), likelihood.dataInput.get().getTaxaNames());
likelihood.treeInput.setValue(tree, likelihood);
return;
} else {
throw new RuntimeException("Programmer error calling cloneFrom: Should only clone Site/Clock/Tree model");
}
} // cloneFrom
private boolean isToClone() {
return listOfPartitions != null && listOfPartitions.getSelectedIndices().length > 1;
}
// public static boolean soundIsPlaying = false;
//
// public static synchronized void playSound(final String url) {
// new Thread(new Runnable() {
// public void run() {
// try {
// synchronized (this) {
// if (soundIsPlaying) {
// return;
// }
// soundIsPlaying = true;
// }
// Clip clip = AudioSystem.getClip();
// AudioInputStream inputStream = AudioSystem.getAudioInputStream(getClass().getResourceAsStream("/beast/app/beauti/" + url));
// clip.open(inputStream);
// clip.start();
// Thread.sleep(500);
// synchronized (this) {
// soundIsPlaying = false;
// }
// } catch (Exception e) {
// soundIsPlaying = false;
// System.err.println(e.getMessage());
// }
// }
// }).start();
// }
@Override
public void valueChanged(ListSelectionEvent e) {
//System.err.print("BeautiPanel::valueChanged " + m_iPartition + " => ");
if (e != null) {
config.sync(partitionIndex);
if (listOfPartitions != null) {
partitionIndex = Math.max(0, listOfPartitions.getSelectedIndex());
}
}
// BeautiPanel.playSound("woosh.wav");
//System.err.println(m_iPartition);
try {
refreshPanel();
centralComponent.repaint();
repaint();
// hack to ensure m_centralComponent is repainted RRB: is there a better way???
if (Frame.getFrames().length == 0) {
// happens at startup
return;
}
Frame frame = Frame.getFrames()[Frame.getFrames().length - 1];
frame.setSize(frame.getSize());
//Frame frame = frames[frames.length - 1];
// Dimension size = frames[frames.length-1].getSize();
// frames[frames.length-1].setSize(size);
// m_centralComponent.repaint();
// m_centralComponent.requestFocusInWindow();
centralComponent.requestFocus();
} catch (Exception ex) {
ex.printStackTrace();
}
}
} // class BeautiPanel