/* * PriorsPanel.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.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.components.linkedparameters.LinkedParameter; import dr.app.beauti.components.linkedparameters.LinkedParameterComponentOptions; import dr.app.beauti.options.*; import dr.app.beauti.types.ClockType; import dr.app.beauti.types.FixRateType; 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.DefaultTableCellRenderer; 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; JButton hpmButton = null; JButton unlinkButton = null; public List<Parameter> parameters = new ArrayList<Parameter>(); BeautiFrame frame = null; BeautiOptions options = null; boolean hasUndefinedPrior = false; boolean hasImproperPrior = false; boolean hasRate = false; private final boolean isDefaultOnly; 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); DefaultTableCellRenderer renderer = new DefaultTableCellRenderer() { // override renderer preparation public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { // allow default preparation super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); Parameter parameter = parameters.get(row); if (isSelected) { setForeground(parameter.isLinked ? Color.LIGHT_GRAY : SystemColor.textHighlightText); } else { setForeground(parameter.isLinked ? Color.GRAY : SystemColor.controlText); } // if (parameter.isLinked) { // setFont(getFont().deriveFont(Font.ITALIC)); // } return this; } }; priorTable.getColumnModel().getColumn(0).setCellRenderer(renderer); priorTable.getColumnModel().getColumn(2).setCellRenderer(renderer); priorTable.getColumnModel().getColumn(3).setCellRenderer(renderer); scrollPane = new JScrollPane(priorTable, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); scrollPane.setOpaque(false); Action setLinkedParametersAction = new AbstractAction("Link parameters together") { public void actionPerformed(ActionEvent actionEvent) { // Make list of selected parameters; int[] rows = priorTable.getSelectedRows(); linkButtonPressed(rows); } }; linkButton = new JButton(setLinkedParametersAction); linkButton.setVisible(true); linkButton.setEnabled(false); linkButton.setToolTipText("<html>Join the selected parameters so their values are<br>" + "kept the same. This will create a new controlling<br>" + "parameter which can be given a prior."); Action setHierarchicalAction = new AbstractAction("Link parameters into a hierarchical model") { public void actionPerformed(ActionEvent actionEvent) { // Make list of selected parameters; int[] rows = priorTable.getSelectedRows(); hierarchicalButtonPressed(rows); } }; hpmButton = new JButton(setHierarchicalAction); hpmButton.setVisible(true); hpmButton.setEnabled(false); hpmButton.setToolTipText(HierarchicalPhylogeneticModel.TIP_TOOL); Action setUnlinkAction = new AbstractAction("Unlink parameters") { public void actionPerformed(ActionEvent actionEvent) { // Make list of selected parameters; int[] rows = priorTable.getSelectedRows(); unlinkButtonPressed(rows); } }; unlinkButton = new JButton(setUnlinkAction); unlinkButton.setVisible(true); unlinkButton.setEnabled(false); unlinkButton.setToolTipText("<html>Unlink parameters joined as a linked parameter<br>" + "or hierarchical model. Will delete the enclosing<br>" + "model."); 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 (!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); PanelUtils.setupComponent(hpmButton); toolBar1.add(hpmButton); toolBar1.addSeparator(); PanelUtils.setupComponent(unlinkButton); toolBar1.add(unlinkButton); 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() { LinkedParameterComponentOptions comp = (LinkedParameterComponentOptions) options.getComponentOptions(LinkedParameterComponentOptions.class); int[] selRows = priorTable.getSelectedRows(); int jointPriorCount = 0; for (int i = 0; i < selRows.length; ++i) { Parameter parameter = parameters.get(selRows[i]); if (parameter.isLinked || comp.getLinkedParameterForArgument(parameter) != null) { jointPriorCount++; } } linkButton.setEnabled(selRows.length > 1); hpmButton.setEnabled(selRows.length > 1); unlinkButton.setEnabled(jointPriorCount > 0); } 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 HierarchicalPriorDialog hierarchicalPriorDialog = null; private JointPriorDialog linkParameterDialog = null; private JButton continueButton = null; public void setContinueButton(JButton continueButton) { this.continueButton = continueButton; if (continueButton != null) { continueButton.setEnabled(!hasUndefinedPrior); } } private void linkButtonPressed(int[] rows) { Parameter firstParameter = parameters.get(rows[0]); // gather the selected parameters... List<Parameter> selectedParameters = new ArrayList<Parameter>(); boolean isLinked = false; for (int i = 0; i < rows.length; ++i) { Parameter parameter = parameters.get(rows[i]); if (parameter.isStatistic) { JOptionPane.showMessageDialog(frame, "Statistics cannot be linked together or\n" + "linked with parameter.", "Parameter linking error", JOptionPane.WARNING_MESSAGE); return; // Bail out } if (parameter.truncationLower != firstParameter.truncationLower || parameter.truncationUpper != firstParameter.truncationUpper || options.getOperator(parameter).getOperatorType() != options.getOperator(firstParameter).getOperatorType()) { JOptionPane.showMessageDialog(frame, "Only parameters that share the same bounds\n" + "and have the same operator types can be linked.", "Parameter linking error", JOptionPane.WARNING_MESSAGE); return; // Bail out } if (parameter.isLinked) { isLinked = true; } selectedParameters.add(parameters.get(rows[i])); } if (isLinked) { int option = JOptionPane.showConfirmDialog(this, "One or more selected parameters are already linked or\n" + "part of a hierarchical model.\n\n" + "Constructing a new prior will remove these parameter\n" + "from the original model. Continue?", "Link warning", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); if (option == JOptionPane.NO_OPTION) { return; } } LinkedParameterComponentOptions comp = (LinkedParameterComponentOptions) options.getComponentOptions(LinkedParameterComponentOptions.class); LinkedParameter linkedParameter = comp.createLinkedParameter(null, selectedParameters); editLinkedParameter(linkedParameter, selectedParameters); } private void unlinkButtonPressed(int[] rows) { for (int i = 0; i < rows.length; ++i) { Parameter parameter = parameters.get(rows[i]); if (parameter.isLinked) { removeFromJointPrior(parameter); } } priorTableModel.fireTableDataChanged(); frame.setAllOptions(); } private void hierarchicalButtonPressed(int[] rows) { if (rows.length < 2) { JOptionPane.showMessageDialog(frame, "Fewer 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 cannot be used in a hierarchical model.", "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 parameter = parameters.get(row); LinkedParameterComponentOptions lpco = (LinkedParameterComponentOptions) options.getComponentOptions(LinkedParameterComponentOptions.class); HierarchicalModelComponentOptions hmco = (HierarchicalModelComponentOptions) options.getComponentOptions(HierarchicalModelComponentOptions.class); if (lpco.isArgumentParameter(parameter)) { LinkedParameter linkedParameter = lpco.getLinkedParameterForArgument(parameter); editLinkedParameter(linkedParameter, linkedParameter.getDependentParameterList()); } else { if (hmco.isHierarchicalParameter(parameter)) { int option = JOptionPane.showConfirmDialog(this, "This parameter forms part of an Hierarchical Model.\n" + "Selecting a new prior will remove the parameter from\n" + "this HPM.\nContinue?", "Hierarchical parameter warning", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); if (option == JOptionPane.NO_OPTION) { return; } } if (parameter.isLinked) { int option = JOptionPane.showConfirmDialog(this, "Parameter linked to other parameter. Selecting a\n" + "new prior will remove the link.\nContinue?", "Linked parameter warning", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); if (option == JOptionPane.NO_OPTION) { return; } } editPrior(parameter); } } private void editLinkedParameter(LinkedParameter linkedParameter, List<Parameter> selectedParameters) { Parameter sourceParameter = linkedParameter.getArgumentParameter(); Operator sourceOperator = linkedParameter.getArgumentOperator(); List<Parameter> compatibleParameters = new ArrayList<Parameter>(); for (Parameter parameter : parameters) { boolean isCompatible = true; if (parameter.isStatistic) { isCompatible = false; } if (parameter.truncationLower != sourceParameter.truncationLower) { isCompatible = false; } Operator operator = options.getOperator(parameter); if (operator == null || operator.getOperatorType() != sourceOperator.getOperatorType()) { isCompatible = false; } if (isCompatible) { compatibleParameters.add(parameter); } } if (linkParameterDialog == null) { linkParameterDialog = new JointPriorDialog(frame, options); } linkParameterDialog.setLinkedParameter(linkedParameter); linkParameterDialog.setCompatibleParameterList(compatibleParameters); linkParameterDialog.setDependentParameterList(selectedParameters); boolean done = false; while (!done) { int result = linkParameterDialog.showDialog(); if (result == JOptionPane.OK_OPTION && linkParameterDialog.validateModelName()) { done = true; } if (result == JOptionPane.CANCEL_OPTION) { return; } } linkedParameter.setName(linkParameterDialog.getName()); linkedParameter.linkDependentParameters(linkParameterDialog.getDependentParameterList()); linkParameterDialog.getArguments(linkedParameter.getArgumentParameter()); for (Parameter parameter : linkParameterDialog.getDependentParameterList()) { parameter.setPriorEdited(true); } priorTableModel.fireTableDataChanged(); frame.setAllOptions(); } private void editPrior(Parameter parameter) { if (priorDialog == null) { priorDialog = new PriorDialog(frame); } priorDialog.setParameter(parameter); int result; do { result = priorDialog.showDialog(); } while (result == JOptionPane.OK_OPTION && priorDialog.hasInvalidInput(true)); if (result == JOptionPane.OK_OPTION) { // Only do this if OK button is pressed (not cancel): // If this was part of a linked parameter, remove it from that... removeFromJointPrior(parameter); // move to individual Dialog, otherwise it will change if Cancel priorDialog.getArguments(parameter); parameter.setPriorEdited(true); if (isDefaultOnly) { setParametersList(options); } if (parameter.getBaseName().endsWith("treeModel.rootHeight") || parameter.taxaId != null) { // param.taxa != null is TMRCA if (options.treeModelOptions.isNodeCalibrated(parameter)) { List<PartitionTreeModel> treeModels; if (options.useStarBEAST) { treeModels = options.getPartitionTreeModels(); } else { treeModels = options.getPartitionTreeModels(options.getDataPartitions(parameter.getOptions())); } for (PartitionTreeModel treeModel : treeModels) { treeModel.setNodeCalibrations(true); } frame.setAllOptions(); } } priorTableModel.fireTableDataChanged(); frame.setAllOptions(); } } private void removeFromJointPrior(Parameter parameter) { LinkedParameterComponentOptions comp = (LinkedParameterComponentOptions) options.getComponentOptions(LinkedParameterComponentOptions.class); LinkedParameter linkedParameter = comp.getLinkedParameter(parameter); if (linkedParameter != null) { comp.removeDependentParameter(parameter); } // Possibly remove parameter from its HPM HierarchicalModelComponentOptions hmco = (HierarchicalModelComponentOptions) options.getComponentOptions(HierarchicalModelComponentOptions.class); if (hmco.isHierarchicalParameter(parameter)) { if (hmco.removeParameter(this, parameter, true) == JOptionPane.NO_OPTION) { // Bail out System.err.println("Bailing out of modification"); return; } System.err.println("Parameter removed from an HPM"); } } 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(); } } }