/* * PriorsPanel.java * * Copyright (C) 2002-2006 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.priorsPanel; import dr.app.beauti.BeautiFrame; import dr.app.beauti.BeautiPanel; import dr.app.beauti.components.hpm.HierarchicalModelComponentOptions; import dr.app.beauti.components.hpm.HierarchicalPhylogeneticModel; import dr.app.beauti.options.BeautiOptions; import dr.app.beauti.options.ClockModelGroup; import dr.app.beauti.options.Parameter; import dr.app.beauti.types.ClockType; import dr.app.beauti.types.PriorType; import dr.app.beauti.util.PanelUtils; import dr.app.gui.table.TableEditorStopper; import dr.util.NumberFormatter; 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.TableCellRenderer; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.List; /** * @author Andrew Rambaut * @author Alexei Drummond * @version $Id: PriorsPanel.java,v 1.9 2006/09/05 13:29:34 rambaut Exp $ */ public class PriorsPanel extends BeautiPanel implements Exportable { private static final long serialVersionUID = -2936049032365493416L; JScrollPane scrollPane = new JScrollPane(); JTable priorTable = null; PriorTableModel priorTableModel = null; JLabel messageLabel = new JLabel(); JButton linkButton = null; public ArrayList<Parameter> parameters = new ArrayList<Parameter>(); BeautiFrame frame = null; BeautiOptions options = null; boolean hasUndefinedPrior = false; boolean hasImproperPrior = false; boolean hasRate = false; private final boolean isDefaultOnly; private final static boolean HIERARCHICAL_ENABLED = true; // Change to false to disable public PriorsPanel(BeautiFrame parent, boolean isDefaultOnly) { this.frame = parent; this.isDefaultOnly = isDefaultOnly; priorTableModel = new PriorTableModel(this); priorTable = new JTable(priorTableModel); priorTable.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_LAST_COLUMN); priorTable.getTableHeader().setReorderingAllowed(false); // priorTable.getTableHeader().setDefaultRenderer( // new HeaderRenderer(SwingConstants.LEFT, new Insets(0, 4, 0, 4))); priorTable.getColumnModel().getColumn(0).setCellRenderer( new TableRenderer(SwingConstants.LEFT, new Insets(0, 4, 0, 4))); priorTable.getColumnModel().getColumn(0).setMinWidth(200); priorTable.getColumnModel().getColumn(1).setCellRenderer( new ButtonRenderer(SwingConstants.LEFT, new Insets(0, 8, 0, 8))); priorTable.getColumnModel().getColumn(1).setCellEditor( new ButtonEditor(SwingConstants.LEFT, new Insets(0, 8, 0, 8))); priorTable.getColumnModel().getColumn(1).setMinWidth(260); priorTable.getColumnModel().getColumn(2).setCellRenderer( new TableRenderer(SwingConstants.LEFT, new Insets(0, 4, 0, 4))); priorTable.getColumnModel().getColumn(2).setMinWidth(30); priorTable.getColumnModel().getColumn(3).setCellRenderer( new TableRenderer(SwingConstants.LEFT, new Insets(0, 4, 0, 4))); priorTable.getColumnModel().getColumn(3).setMinWidth(410); TableEditorStopper.ensureEditingStopWhenTableLosesFocus(priorTable); scrollPane = new JScrollPane(priorTable, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); scrollPane.setOpaque(false); Action setHierarchicalAction = new AbstractAction("Link parameters into a phylogenetic hierarchical model") { public void actionPerformed(ActionEvent actionEvent) { // Make list of selected parameters; int[] rows = priorTable.getSelectedRows(); hierarchicalButtonPressed(rows); } }; linkButton = new JButton(setHierarchicalAction); linkButton.setVisible(true); linkButton.setEnabled(false); linkButton.setToolTipText(HierarchicalPhylogeneticModel.TIP_TOOL); messageLabel.setText(getMessage()); setOpaque(false); setLayout(new BorderLayout(0, 0)); setBorder(new BorderUIResource.EmptyBorderUIResource(new java.awt.Insets(12, 12, 12, 12))); if (!isDefaultOnly) { add(new JLabel("Priors for model parameters and statistics:"), BorderLayout.NORTH); } add(scrollPane, BorderLayout.CENTER); if (HIERARCHICAL_ENABLED && !isDefaultOnly) { JPanel southPanel = new JPanel(); southPanel.setLayout(new BorderLayout(0, 0)); JToolBar toolBar1 = new JToolBar(); toolBar1.setFloatable(false); toolBar1.setOpaque(false); toolBar1.setLayout(new BoxLayout(toolBar1, BoxLayout.X_AXIS)); PanelUtils.setupComponent(linkButton); toolBar1.add(linkButton); southPanel.add(toolBar1, BorderLayout.NORTH); southPanel.add(messageLabel, BorderLayout.SOUTH); add(southPanel, BorderLayout.SOUTH); } else { add(messageLabel, BorderLayout.SOUTH); } priorTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent evt) { selectionChanged(); } }); } public void selectionChanged() { int[] selRows = priorTable.getSelectedRows(); boolean hasSelection = (selRows != null && selRows.length != 0); linkButton.setEnabled(hasSelection); } public void setOptions(BeautiOptions options) { this.options = options; parameters = options.selectParameters(); messageLabel.setText(getMessage()); priorTableModel.fireTableDataChanged(); validate(); repaint(); } private String getMessage() { String message = "<html>"; if (isDefaultOnly) { message += "<ul><li>These priors listed above are still set to the default values " + "and need to be reviewed.</li>"; hasUndefinedPrior = false; hasImproperPrior = false; hasRate = false; for (Parameter param : parameters) { if (param.priorType == PriorType.UNDEFINED) { hasUndefinedPrior = true; } if (param.isPriorImproper()) { hasImproperPrior = true; } if (param.getBaseName().endsWith("clock.rate") || param.getBaseName().endsWith(ClockType.UCED_MEAN) || param.getBaseName().endsWith(ClockType.UCLD_MEAN)) { hasRate = true; } } if (hasUndefinedPrior) { message += "<li><b><font color=\"#E42217\">These priors need to be defined by the user.</font></b></li>"; } if (hasImproperPrior) { message += "<li><b><font color=\"#B4B417\">Warning: one or more parameters have improper priors.</font></b></li>"; } if (hasRate) { message += "<li>" + "Priors on clock rates in particular should be proper such as a high variance lognormal or gamma distribution " + "with a mean appropriate for the organism and units of time employed.</li>"; } message += "</ul>"; } else { message += "* Marked parameters currently have a default prior distribution. " + "You should check that these are appropriate."; } return message + "</html>"; } public void setParametersList(BeautiOptions options) { this.options = options; parameters.clear(); for (Parameter param : options.selectParameters()) { if (!param.isPriorEdited() || param.isPriorImproper()) { parameters.add(param); } } messageLabel.setText(getMessage()); if (continueButton != null) { continueButton.setEnabled(!hasUndefinedPrior); } } private PriorDialog priorDialog = null; // private DiscretePriorDialog discretePriorDialog = null; private HierarchicalPriorDialog hierarchicalPriorDialog = null; private JButton continueButton = null; public void setContinueButton(JButton continueButton) { this.continueButton = continueButton; if (continueButton != null) { continueButton.setEnabled(!hasUndefinedPrior); } } private void hierarchicalButtonPressed(int[] rows) { if (rows.length < 2) { JOptionPane.showMessageDialog(frame, "Less than two parameters selected.", "Parameter linking error", JOptionPane.WARNING_MESSAGE); return; } Double lowerBound = null; Double upperBound = null; List<Parameter> paramList = new ArrayList<Parameter>(); for (int i = 0; i < rows.length; ++i) { Parameter parameter = parameters.get(rows[i]); if (parameter.isStatistic) { JOptionPane.showMessageDialog(frame, "Statistics are not currently allowed.", "HPM parameter linking error", JOptionPane.WARNING_MESSAGE); return; } boolean sameBounds = true; if (lowerBound == null) { lowerBound = parameter.truncationLower; } else { if (lowerBound != parameter.truncationLower) { sameBounds = false; } } if (upperBound == null) { upperBound = parameter.truncationUpper; } else { if (upperBound != parameter.truncationUpper) { sameBounds = false; } } if (!sameBounds) { JOptionPane.showMessageDialog(frame, "Only parameters that share the same bounds\n" + "should be included in a HPM.", "HPM parameter link error", JOptionPane.WARNING_MESSAGE); return; // Bail out } paramList.add(parameter); } if (hierarchicalPriorDialog != null) { // Already called // Check to see if selected parameters are already in a HPM HierarchicalModelComponentOptions comp = (HierarchicalModelComponentOptions) options.getComponentOptions(HierarchicalModelComponentOptions.class); boolean anyConflicts = false; for (Parameter parameter : paramList) { if (comp.isHierarchicalParameter(parameter)) { anyConflicts = true; break; } } if (anyConflicts) { int option = JOptionPane.showConfirmDialog(this, "At one selected parameter already exists in a HPM.\n" + "Constructing a new prior will remove these parameter\n" + "from the original model. Continue?", "HPM warning", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); if (option == JOptionPane.NO_OPTION) { return; } } } if (hierarchicalPriorDialog == null) { hierarchicalPriorDialog = new HierarchicalPriorDialog(frame, options); } boolean done = false; while (!done) { int result = hierarchicalPriorDialog.showDialog(paramList); if (result == JOptionPane.OK_OPTION && hierarchicalPriorDialog.validateModelName()) { hierarchicalPriorDialog.getArguments(); done = true; } if (result == JOptionPane.CANCEL_OPTION) { return; } } // Remove parameters from old list for (Parameter parameter : paramList) { HierarchicalModelComponentOptions comp = (HierarchicalModelComponentOptions) options.getComponentOptions(HierarchicalModelComponentOptions.class); if (comp.isHierarchicalParameter(parameter)) { comp.removeParameter(this, parameter, false); } } // Add HPM to component manager hierarchicalPriorDialog.addHPM(paramList); for (Parameter parameter : paramList) { parameter.setPriorEdited(true); } priorTableModel.fireTableDataChanged(); } private void priorButtonPressed(int row) { Parameter param = parameters.get(row); if (HIERARCHICAL_ENABLED && hierarchicalPriorDialog != null) { // Check that parameter is not already in a HPM HierarchicalModelComponentOptions comp = (HierarchicalModelComponentOptions) options.getComponentOptions(HierarchicalModelComponentOptions.class); if (comp.isHierarchicalParameter(param)) { int option = JOptionPane.showConfirmDialog(this, "Parameter already exists in a HPM. Selecting a\n" + "new prior will remove the parameter. Continue?", "HPM warning", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); if (option == JOptionPane.NO_OPTION) { return; } } } int result; if (priorDialog == null) { priorDialog = new PriorDialog(frame); } priorDialog.setParameter(param); do { result = priorDialog.showDialog(); } while (result == JOptionPane.OK_OPTION && priorDialog.hasInvalidInput(true)); if (result == JOptionPane.OK_OPTION) { // move to individual Dialog, otherwise it will change if Cancel priorDialog.getArguments(param); // Only do this if OK button is pressed (not cancel): if (HIERARCHICAL_ENABLED && hierarchicalPriorDialog != null) { // Possibly remove parameter from its HPM HierarchicalModelComponentOptions comp = (HierarchicalModelComponentOptions) options.getComponentOptions(HierarchicalModelComponentOptions.class); if (comp.isHierarchicalParameter(param)) { if (comp.removeParameter(this, param, true) == JOptionPane.NO_OPTION) { // Bail out System.err.println("Bailing out of modification"); return; } System.err.println("Parameter removed from an HPM"); } } param.setPriorEdited(true); if (isDefaultOnly) { setParametersList(options); } if (param.getBaseName().endsWith("treeModel.rootHeight") || param.taxaId != null) { // param.taxa != null is TMRCA if (options.treeModelOptions.isNodeCalibrated(param)) { List<ClockModelGroup> groupList; if (options.useStarBEAST) { groupList = options.clockModelOptions.getClockModelGroups(); } else { groupList = options.clockModelOptions.getClockModelGroups(options.getDataPartitions(param.getOptions())); } for (ClockModelGroup clockModelGroup : groupList) { options.clockModelOptions.nodeCalibration(clockModelGroup); } frame.setAllOptions(); // } else { // options.clockModelOptions.fixRateOfFirstClockPartition(); } } priorTableModel.fireTableDataChanged(); } } public void getOptions(BeautiOptions options) { } public JComponent getExportableComponent() { return priorTable; } NumberFormatter formatter = new NumberFormatter(4); class DoubleRenderer extends TableRenderer { private static final long serialVersionUID = -2614341608257369805L; public DoubleRenderer(int alignment, Insets insets) { super(true, alignment, insets); } public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { String s; if (((Double) value).isNaN()) { s = "random"; } else { s = formatter.format((Double) value); } return super.getTableCellRendererComponent(table, s, isSelected, hasFocus, row, column); } } public class ButtonRenderer extends JButton implements TableCellRenderer { protected Color undefinedColour = new Color(0xE4, 0x22, 0x17); protected Color improperColour = new Color(0xB4, 0xB4, 0x17); private static final long serialVersionUID = -2416184092883649169L; public ButtonRenderer(int alignment, Insets insets) { setOpaque(true); setHorizontalAlignment(alignment); setMargin(insets); // setFont(UIManager.getFont("SmallSystemFont")); // putClientProperty("JComponent.sizeVariant", "small"); // putClientProperty("JButton.buttonType", "square"); } public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { setEnabled(table.isEnabled()); setFont(table.getFont()); if (isSelected) { //setForeground(table.getSelectionForeground()); setBackground(table.getSelectionBackground()); } else { //setForeground(table.getForeground()); setBackground(UIManager.getColor("Button.background")); } String text = (value == null) ? "" : value.toString(); if (text.toString().startsWith("?")) { setForeground(undefinedColour); } else if (text.toString().startsWith("!")) { setForeground(improperColour); } else { setForeground(UIManager.getColor("Button.foreground")); } setText(text); return this; } } public class ButtonEditor extends DefaultCellEditor { private static final long serialVersionUID = 6372738480075411674L; protected JButton button; private String label; private boolean isPushed; private int row; public ButtonEditor(int alignment, Insets insets) { super(new JCheckBox()); button = new JButton(); button.setOpaque(true); button.setHorizontalAlignment(alignment); button.setMargin(insets); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { fireEditingStopped(); } }); // button.setFont(UIManager.getFont("SmallSystemFont")); // button.putClientProperty("JComponent.sizeVariant", "small"); // button.putClientProperty("JButton.buttonType", "square"); } public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { button.setEnabled(table.isEnabled()); button.setFont(table.getFont()); if (isSelected) { // button.setForeground(table.getSelectionForeground()); button.setBackground(table.getSelectionBackground()); } else { // button.setForeground(table.getForeground()); button.setBackground(UIManager.getColor("Button.background")); } label = (value == null) ? "" : value.toString(); button.setText(label); isPushed = true; this.row = row; return button; } public Object getCellEditorValue() { if (isPushed) { priorButtonPressed(row); } isPushed = false; return label; } public boolean stopCellEditing() { isPushed = false; return super.stopCellEditing(); } protected void fireEditingStopped() { super.fireEditingStopped(); } } }