<commit_before>/*
 * TraitsPanel.java
 *
 * Copyright (C) 2002-2009 <NAME> and <NAME>
 *
 * 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.traitspanel;

import dr.app.beauti.BeautiFrame;
import dr.app.beauti.BeautiPanel;
import dr.app.beauti.ComboBoxRenderer;
import dr.app.beauti.datapanel.DataPanel;
import dr.app.beauti.options.BeautiOptions;
import dr.app.beauti.options.TraitData;
import dr.app.beauti.options.TraitGuesser;
import dr.app.beauti.util.PanelUtils;
import dr.app.gui.table.TableEditorStopper;
import dr.app.gui.table.TableSorter;
import dr.evolution.util.Taxa;
import dr.evolution.util.Taxon;
import jam.framework.Exportable;
import jam.panels.ActionPanel;
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.AbstractTableModel;
import javax.swing.table.TableColumn;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.*;

/**
 * @author <NAME>
 * @version $Id: DataPanel.java,v 1.17 2006/09/05 13:29:34 rambaut Exp $
 */
public class TraitsPanel extends BeautiPanel implements Exportable {

    private static final long serialVersionUID = 5283922195494563924L;

    private static final int MINIMUM_TABLE_WIDTH = 140;

    private static final String ADD_TRAITS_TOOLTIP = "<html>Define a new trait for the current taxa</html>";
    private static final String IMPORT_TRAITS_TOOLTIP = "<html>Import one or more traits for these taxa from a tab-delimited<br>" +
            "file. Taxa should be in the first column and the trait names<br>" +
            "in the first row</html>";
    private static final String GUESS_TRAIT_VALUES_TOOLTIP = "<html>This attempts to extract values for this trait that are<br>" +
            "encoded in the names of the selected taxa.</html>";
    private static final String SET_TRAIT_VALUES_TOOLTIP = "<html>This sets values for this trait for all<br>" +
            "the selected taxa.</html>";
    private static final String CLEAR_TRAIT_VALUES_TOOLTIP = "<html>This clears all the values currently assigned to taxa for<br>" +
            "this trait.</html>";
    private static final String CREATE_TRAIT_PARTITIONS_TOOLTIP = "<html>Create a data partition for the selected traits.</html>";

    public final JTable traitsTable;
    private final TraitsTableModel traitsTableModel;

    private final JTable dataTable;
    private final DataTableModel dataTableModel;

    private final BeautiFrame frame;
    private final DataPanel dataPanel;

    private BeautiOptions options = null;

    private TraitData currentTrait = null; // current trait

    private CreateTraitDialog createTraitDialog = null;
    private GuessTraitDialog guessTraitDialog = null;
    private TraitValueDialog traitValueDialog = null;

    AddTraitAction addTraitAction = new AddTraitAction();
    CreateTraitPartitionAction createTraitPartitionAction = new CreateTraitPartitionAction();

    public TraitsPanel(BeautiFrame parent, DataPanel dataPanel, Action importTraitsAction) {

        this.frame = parent;
        this.dataPanel = dataPanel;

        traitsTableModel = new TraitsTableModel();
//        TableSorter sorter = new TableSorter(traitsTableModel);
//        traitsTable = new JTable(sorter);
//        sorter.setTableHeader(traitsTable.getTableHeader());
        traitsTable = new JTable(traitsTableModel);

        traitsTable.getTableHeader().setReorderingAllowed(false);
        traitsTable.getTableHeader().setResizingAllowed(false);
//        traitsTable.getTableHeader().setDefaultRenderer(
//                new HeaderRenderer(SwingConstants.LEFT, new Insets(0, 4, 0, 4)));

        TableColumn col = traitsTable.getColumnModel().getColumn(1);
        ComboBoxRenderer comboBoxRenderer = new ComboBoxRenderer(TraitData.TraitType.values());
        comboBoxRenderer.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);
        col.setCellRenderer(comboBoxRenderer);

        TableEditorStopper.ensureEditingStopWhenTableLosesFocus(traitsTable);

        traitsTable.getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
        traitsTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
            public void valueChanged(ListSelectionEvent evt) {
                traitSelectionChanged();
//                dataTableModel.fireTableDataChanged();
            }
        });

        JScrollPane scrollPane1 = new JScrollPane(traitsTable,
                JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
        scrollPane1.setOpaque(false);

        dataTableModel = new DataTableModel();
        TableSorter sorter = new TableSorter(dataTableModel);
        dataTable = new JTable(sorter);
        sorter.setTableHeader(dataTable.getTableHeader());

        dataTable.getTableHeader().setReorderingAllowed(false);
//        dataTable.getTableHeader().setDefaultRenderer(
//                new HeaderRenderer(SwingConstants.LEFT, new Insets(0, 4, 0, 4)));

        dataTable.getColumnModel().getColumn(0).setCellRenderer(
                new TableRenderer(SwingConstants.LEFT, new Insets(0, 4, 0, 4)));
        dataTable.getColumnModel().getColumn(0).setPreferredWidth(80);

        col = dataTable.getColumnModel().getColumn(1);
        comboBoxRenderer = new ComboBoxRenderer();
        comboBoxRenderer.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);
        col.setCellRenderer(comboBoxRenderer);

        TableEditorStopper.ensureEditingStopWhenTableLosesFocus(dataTable);

//        dataTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
//            public void valueChanged(ListSelectionEvent evt) {
//                traitSelectionChanged();
//            }
//        });

        JScrollPane scrollPane2 = new JScrollPane(dataTable,
                JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
        scrollPane2.setOpaque(false);

        JToolBar toolBar1 = new JToolBar();
        toolBar1.setFloatable(false);
        toolBar1.setOpaque(false);

        toolBar1.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));

        JButton button;

        button = new JButton(addTraitAction);
        PanelUtils.setupComponent(button);
        button.setToolTipText(ADD_TRAITS_TOOLTIP);
        toolBar1.add(button);

        button = new JButton(importTraitsAction);
        PanelUtils.setupComponent(button);
        button.setToolTipText(IMPORT_TRAITS_TOOLTIP);
        toolBar1.add(button);

        toolBar1.add(new JToolBar.Separator(new Dimension(12, 12)));

        button = new JButton(new GuessTraitsAction());
        PanelUtils.setupComponent(button);
        button.setToolTipText(GUESS_TRAIT_VALUES_TOOLTIP);
        toolBar1.add(button);

        button = new JButton(new SetValueAction());
        PanelUtils.setupComponent(button);
        button.setToolTipText(SET_TRAIT_VALUES_TOOLTIP);
        toolBar1.add(button);

        // Don't see the need for a clear values button
//        button = new JButton(new ClearTraitAction());
//        PanelUtils.setupComponent(button);
//        button.setToolTipText(CLEAR_TRAIT_VALUES_TOOLTIP);
//        toolBar1.add(button);
        button = new JButton(createTraitPartitionAction);
        PanelUtils.setupComponent(button);
        button.setToolTipText(CREATE_TRAIT_PARTITIONS_TOOLTIP);
        toolBar1.add(button);

        ActionPanel actionPanel1 = new ActionPanel(false);
        actionPanel1.setAddAction(addTraitAction);
        actionPanel1.setRemoveAction(removeTraitAction);
//        actionPanel1.getAddActionButton().setTooltipText(ADD_TRAITS_TOOLTIP);

        removeTraitAction.setEnabled(false);

        JPanel controlPanel1 = new JPanel(new FlowLayout(FlowLayout.LEFT));
        controlPanel1.setOpaque(false);
        controlPanel1.add(actionPanel1);

        JPanel panel1 = new JPanel(new BorderLayout(0, 0));
        panel1.setOpaque(false);
        panel1.add(scrollPane1, BorderLayout.CENTER);
        panel1.add(controlPanel1, BorderLayout.SOUTH);
        panel1.setMinimumSize(new Dimension(MINIMUM_TABLE_WIDTH, 0));

        JPanel panel2 = new JPanel(new BorderLayout(0, 0));
        panel2.setOpaque(false);
        panel2.add(toolBar1, BorderLayout.NORTH);
        panel2.add(scrollPane2, BorderLayout.CENTER);

        JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
                panel1, panel2);
        splitPane.setDividerLocation(MINIMUM_TABLE_WIDTH);
        splitPane.setContinuousLayout(true);
        splitPane.setBorder(BorderFactory.createEmptyBorder());
        splitPane.setOpaque(false);

        setOpaque(false);
        setBorder(new BorderUIResource.EmptyBorderUIResource(new Insets(12, 12, 12, 12)));
        setLayout(new BorderLayout(0, 0));
        add(splitPane, BorderLayout.CENTER);
        add(toolBar1, BorderLayout.NORTH);

    }

    public void setOptions(BeautiOptions options) {
        this.options = options;

//        int selRow = traitsTable.getSelectedRow();
//        traitsTableModel.fireTableDataChanged();
//
//        if (selRow < 0) {
//            selRow = 0;
//        }
//        traitsTable.getSelectionModel().setSelectionInterval(selRow, selRow);


//        if (selectedTrait == null) {
//            traitsTable.getSelectionModel().setSelectionInterval(0, 0);
//        }

        traitsTableModel.fireTableDataChanged();
        dataTableModel.fireTableDataChanged();

        validate();
        repaint();
    }

    public void getOptions(BeautiOptions options) {
//        int selRow = traitsTable.getSelectedRow();
//        if (selRow >= 0 && options.traitsOptions.selecetedTraits.size() > 0) {
//            selectedTrait = options.traitsOptions.selecetedTraits.get(selRow);
//        }
//        options.datesUnits = unitsCombo.getSelectedIndex();
//        options.datesDirection = directionCombo.getSelectedIndex();
    }

    public JComponent getExportableComponent() {
        return dataTable;
    }

    public void fireTraitsChanged() {
        if (currentTrait != null) {
//        if (currentTrait.getName().equalsIgnoreCase(TraitData.Traits.TRAIT_SPECIES.toString())) {
//            frame.setupStarBEAST();
//        } else
            if (currentTrait != null && currentTrait.getTraitType() == TraitData.TraitType.DISCRETE) {
                frame.updateDiscreteTraitAnalysis();
            }

//            if (selRow > 0) {
//                traitsTable.getSelectionModel().setSelectionInterval(selRow-1, selRow-1);
//            } else if (selRow == 0 && options.traitsOptions.traits.size() > 0) { // options.traitsOptions.traits.size() after remove
//                traitsTable.getSelectionModel().setSelectionInterval(0, 0);
//            }

            traitsTableModel.fireTableDataChanged();
            options.updatePartitionAllLinks();
            frame.setDirty();
        }
    }

    private void traitSelectionChanged() {
        int selRow = traitsTable.getSelectedRow();
        if (selRow >= 0) {
            currentTrait = options.traits.get(selRow);
//            traitsTable.getSelectionModel().setSelectionInterval(selRow, selRow);
            removeTraitAction.setEnabled(true);
//        } else {
//            currentTrait = null;
//            removeTraitAction.setEnabled(false);
        }

        if (options.traits.size() <= 0) {
            currentTrait = null;
            removeTraitAction.setEnabled(false);
        }
        dataTableModel.fireTableDataChanged();
//        traitsTableModel.fireTableDataChanged();
    }


    public void clearTraitValues(String traitName) {
        options.clearTraitValues(traitName);

        dataTableModel.fireTableDataChanged();
    }

    public void guessTrait() {
        if (options.taxonList == null) { // validation of check empty taxonList
            return;
        }

        if (currentTrait == null) {
            if (!addTrait()) {
                return; // if addTrait() cancel then false
            }
        }
        int result;
        do {
            TraitGuesser currentTraitGuesser = new TraitGuesser(currentTrait);
            if (guessTraitDialog == null) {
                guessTraitDialog = new GuessTraitDialog(frame);
            }
            guessTraitDialog.setDescription("Extract values for trait '" + currentTrait + "' from taxa labels");
            result = guessTraitDialog.showDialog();

            if (result == -1 || result == JOptionPane.CANCEL_OPTION) {
                return;
            }

            guessTraitDialog.setupGuesserFromDialog(currentTraitGuesser);

            try {
                int[] selRows = dataTable.getSelectedRows();
                if (selRows.length > 0) {
                    Taxa selectedTaxa = new Taxa();

                    for (int row : selRows) {
                        Taxon taxon = (Taxon) dataTable.getValueAt(row, 0);
                        selectedTaxa.addTaxon(taxon);
                    }
                    currentTraitGuesser.guessTrait(selectedTaxa);
                } else {
                    currentTraitGuesser.guessTrait(options.taxonList);
                }

            } catch (IllegalArgumentException iae) {
                JOptionPane.showMessageDialog(this, iae.getMessage(), "Unable to guess trait value", JOptionPane.ERROR_MESSAGE);
                result = -1;
            }

            dataTableModel.fireTableDataChanged();
        } while (result < 0);
    }

    public void setTraitValue() {
        if (options.taxonList == null) { // validation of check empty taxonList
            return;
        }

        int result;
        do {
            if (traitValueDialog == null) {
                traitValueDialog = new TraitValueDialog(frame);
            }

            int[] selRows = dataTable.getSelectedRows();

            if (selRows.length > 0) {
                traitValueDialog.setDescription("Set values for trait '" + currentTrait + "' for selected taxa");
            } else {
                traitValueDialog.setDescription("Set values for trait '" + currentTrait + "' for all taxa");
            }

            result = traitValueDialog.showDialog();

            if (result == -1 || result == JOptionPane.CANCEL_OPTION) {
                return;
            }

//            currentTrait.guessTrait = true; // ?? no use?
            String value = traitValueDialog.getTraitValue();

            try {
                if (selRows.length > 0) {
                    for (int row : selRows) {
                        Taxon taxon = (Taxon) dataTable.getValueAt(row, 0);
                        taxon.setAttribute(currentTrait.getName(), value);
                    }
                } else {
                    for (Taxon taxon : options.taxonList) {
                        taxon.setAttribute(currentTrait.getName(), value);
                    }
                }

            } catch (IllegalArgumentException iae) {
                JOptionPane.showMessageDialog(this, iae.getMessage(), "Unable to guess trait value", JOptionPane.ERROR_MESSAGE);
                result = -1;
            }

            dataTableModel.fireTableDataChanged();
        } while (result < 0);
    }

    public boolean addTrait() {
        return addTrait("Untitled");

        // The 'species' trait doesn't need to be a reserved keyword since the StarBEAST button was added
        // http://code.google.com/p/beast-mcmc/issues/detail?id=388
//        if (options.traitExists(TraitData.TRAIT_SPECIES)) {
//            JOptionPane.showMessageDialog(frame, "Keyword \"species\" has been reserved for *BEAST !" +
//                    "\nPlease use a different trait name.", "Illegal Argument Exception", JOptionPane.ERROR_MESSAGE);
//            options.removeTrait(TraitData.TRAIT_SPECIES);
////            options.useStarBEAST = false;
//            traitsTableModel.fireTableDataChanged();
//            dataTableModel.fireTableDataChanged();
//            return false;
//        }
//
//        return isAdd;
    }

    public boolean addTrait(String traitName) {
        return addTrait(null, traitName, false);
    }

    public boolean addTrait(String message, String traitName, boolean isSpeciesTrait) {
        if (createTraitDialog == null) {
            createTraitDialog = new CreateTraitDialog(frame);
        }

        createTraitDialog.setSpeciesTrait(isSpeciesTrait);
        createTraitDialog.setTraitName(traitName);
        createTraitDialog.setMessage(message);

        int result = createTraitDialog.showDialog();
        if (result == JOptionPane.OK_OPTION) {
            frame.tabbedPane.setSelectedComponent(this);

            String name = createTraitDialog.getName();
            TraitData.TraitType type = createTraitDialog.getType();
            TraitData newTrait = new TraitData(options, name, "", type);
            currentTrait = newTrait;

// The createTraitDialog will have already checked if the
            // user is overwriting an existing trait
            addTrait(newTrait);

            if (createTraitDialog.createTraitPartition()) {
                options.createPartitionForTraits(name, newTrait);
            }

            fireTraitsChanged();
//            traitsTableModel.fireTableDataChanged();
//            dataTableModel.fireTableDataChanged();

//            traitSelectionChanged();
            // AR we don't want to guess traits automatically - the user may
            // be planning on typing them in. Also this method may have been
            // called by guessTraits() anyway.
//            guessTrait();
        } else if (result == CreateTraitDialog.OK_IMPORT) {
            boolean done = frame.doImportTraits();
            if (done) {
                if (isSpeciesTrait) {
                // check that we did indeed import a 'species' trait
                    if (!options.traitExists(TraitData.TRAIT_SPECIES)) {
                        JOptionPane.showMessageDialog(this,
                                        "The imported trait file didn't contain a trait\n" +
                                        "called '" + TraitData.TRAIT_SPECIES + "', required for *BEAST.\n" +
                                        "Please edit it or select a different file.",
                                "Reserved trait name",
                                JOptionPane.WARNING_MESSAGE);

                        return false;
                    }
                }
            }
            return done;
        } else if (result == JOptionPane.CANCEL_OPTION) {
            return false;
        }

        return true;
    }

    public void addTrait(TraitData newTrait) {
        int selRow = options.addTrait(newTrait);
        traitsTable.getSelectionModel().setSelectionInterval(selRow, selRow);
    }

    private void removeTrait() {
        int selRow = traitsTable.getSelectedRow();
        removeTrait(traitsTable.getValueAt(selRow, 0).toString());
    }

    public void removeTrait(String traitName) {
        if (options.useStarBEAST && traitName.equalsIgnoreCase(TraitData.TRAIT_SPECIES)) {
            JOptionPane.showMessageDialog(this, "The trait named '" + traitName + "' is being used by *BEAST.\nTurn *BEAST off before deleting this trait.", "Trait in use", JOptionPane.ERROR_MESSAGE);
            return;
        }
        TraitData traitData = options.getTrait(traitName);
        if (options.getAllPartitionData(traitData).size() > 0) {
            JOptionPane.showMessageDialog(this, "The trait named '" + traitName + "' is being used in a partition.\nRemove the partition before deleting this trait.", "Trait in use", JOptionPane.ERROR_MESSAGE);
            return;
        }
        options.removeTrait(traitName);

        fireTraitsChanged();
        traitSelectionChanged();
    }

    public class ClearTraitAction extends AbstractAction {
        private static final long serialVersionUID = -7281309694753868635L;

        public ClearTraitAction() {<commit_msg>trunk: solve Issue 523:	BEAUti Traits panel buttons enable/disable should adapt to data and traits<commit_after>/*
 * TraitsPanel.java
 *
 * Copyright (C) 2002-2009 <NAME> and <NAME>
 *
 * 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.traitspanel;

import dr.app.beauti.BeautiFrame;
import dr.app.beauti.BeautiPanel;
import dr.app.beauti.ComboBoxRenderer;
import dr.app.beauti.datapanel.DataPanel;
import dr.app.beauti.options.BeautiOptions;
import dr.app.beauti.options.TraitData;
import dr.app.beauti.options.TraitGuesser;
import dr.app.beauti.util.PanelUtils;
import dr.app.gui.table.TableEditorStopper;
import dr.app.gui.table.TableSorter;
import dr.evolution.util.Taxa;
import dr.evolution.util.Taxon;
import jam.framework.Exportable;
import jam.panels.ActionPanel;
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.AbstractTableModel;
import javax.swing.table.TableColumn;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.*;

/**
 * @author <NAME>
 * @version $Id: DataPanel.java,v 1.17 2006/09/05 13:29:34 rambaut Exp $
 */
public class TraitsPanel extends BeautiPanel implements Exportable {

    private static final long serialVersionUID = 5283922195494563924L;

    private static final int MINIMUM_TABLE_WIDTH = 140;

    private static final String ADD_TRAITS_TOOLTIP = "<html>Define a new trait for the current taxa</html>";
    private static final String IMPORT_TRAITS_TOOLTIP = "<html>Import one or more traits for these taxa from a tab-delimited<br>" +
            "file. Taxa should be in the first column and the trait names<br>" +
            "in the first row</html>";
    private static final String GUESS_TRAIT_VALUES_TOOLTIP = "<html>This attempts to extract values for this trait that are<br>" +
            "encoded in the names of the selected taxa.</html>";
    private static final String SET_TRAIT_VALUES_TOOLTIP = "<html>This sets values for this trait for all<br>" +
            "the selected taxa.</html>";
    private static final String CLEAR_TRAIT_VALUES_TOOLTIP = "<html>This clears all the values currently assigned to taxa for<br>" +
            "this trait.</html>";
    private static final String CREATE_TRAIT_PARTITIONS_TOOLTIP = "<html>Create a data partition for the selected traits.</html>";

    public final JTable traitsTable;
    private final TraitsTableModel traitsTableModel;

    private final JTable dataTable;
    private final DataTableModel dataTableModel;

    private final BeautiFrame frame;
    private final DataPanel dataPanel;

    private BeautiOptions options = null;

    private TraitData currentTrait = null; // current trait

    private CreateTraitDialog createTraitDialog = null;
    private GuessTraitDialog guessTraitDialog = null;
    private TraitValueDialog traitValueDialog = null;

    AddTraitAction addTraitAction = new AddTraitAction();
    Action importTraitsAction;
    CreateTraitPartitionAction createTraitPartitionAction = new CreateTraitPartitionAction();
    GuessTraitsAction guessTraitsAction = new GuessTraitsAction();
    SetValueAction setValueAction = new SetValueAction();

    public TraitsPanel(BeautiFrame parent, DataPanel dataPanel, Action importTraitsAction) {

        this.frame = parent;
        this.dataPanel = dataPanel;

        traitsTableModel = new TraitsTableModel();
//        TableSorter sorter = new TableSorter(traitsTableModel);
//        traitsTable = new JTable(sorter);
//        sorter.setTableHeader(traitsTable.getTableHeader());
        traitsTable = new JTable(traitsTableModel);

        traitsTable.getTableHeader().setReorderingAllowed(false);
        traitsTable.getTableHeader().setResizingAllowed(false);
//        traitsTable.getTableHeader().setDefaultRenderer(
//                new HeaderRenderer(SwingConstants.LEFT, new Insets(0, 4, 0, 4)));

        TableColumn col = traitsTable.getColumnModel().getColumn(1);
        ComboBoxRenderer comboBoxRenderer = new ComboBoxRenderer(TraitData.TraitType.values());
        comboBoxRenderer.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);
        col.setCellRenderer(comboBoxRenderer);

        TableEditorStopper.ensureEditingStopWhenTableLosesFocus(traitsTable);

        traitsTable.getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
        traitsTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
            public void valueChanged(ListSelectionEvent evt) {
                traitSelectionChanged();
//                dataTableModel.fireTableDataChanged();
            }
        });

        JScrollPane scrollPane1 = new JScrollPane(traitsTable,
                JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
        scrollPane1.setOpaque(false);

        dataTableModel = new DataTableModel();
        TableSorter sorter = new TableSorter(dataTableModel);
        dataTable = new JTable(sorter);
        sorter.setTableHeader(dataTable.getTableHeader());

        dataTable.getTableHeader().setReorderingAllowed(false);
//        dataTable.getTableHeader().setDefaultRenderer(
//                new HeaderRenderer(SwingConstants.LEFT, new Insets(0, 4, 0, 4)));

        dataTable.getColumnModel().getColumn(0).setCellRenderer(
                new TableRenderer(SwingConstants.LEFT, new Insets(0, 4, 0, 4)));
        dataTable.getColumnModel().getColumn(0).setPreferredWidth(80);

        col = dataTable.getColumnModel().getColumn(1);
        comboBoxRenderer = new ComboBoxRenderer();
        comboBoxRenderer.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);
        col.setCellRenderer(comboBoxRenderer);

        TableEditorStopper.ensureEditingStopWhenTableLosesFocus(dataTable);

//        dataTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
//            public void valueChanged(ListSelectionEvent evt) {
//                traitSelectionChanged();
//            }
//        });

        JScrollPane scrollPane2 = new JScrollPane(dataTable,
                JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
        scrollPane2.setOpaque(false);

        JToolBar toolBar1 = new JToolBar();
        toolBar1.setFloatable(false);
        toolBar1.setOpaque(false);

        toolBar1.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));

        JButton button;

        button = new JButton(addTraitAction);
        PanelUtils.setupComponent(button);
        button.setToolTipText(ADD_TRAITS_TOOLTIP);
        toolBar1.add(button);

        this.importTraitsAction = importTraitsAction;
        button = new JButton(importTraitsAction);
        PanelUtils.setupComponent(button);
        button.setToolTipText(IMPORT_TRAITS_TOOLTIP);
        toolBar1.add(button);

        toolBar1.add(new JToolBar.Separator(new Dimension(12, 12)));

        button = new JButton(guessTraitsAction);
        PanelUtils.setupComponent(button);
        button.setToolTipText(GUESS_TRAIT_VALUES_TOOLTIP);
        toolBar1.add(button);

        button = new JButton(setValueAction);
        PanelUtils.setupComponent(button);
        button.setToolTipText(SET_TRAIT_VALUES_TOOLTIP);
        toolBar1.add(button);

        // Don't see the need for a clear values button
//        button = new JButton(new ClearTraitAction());
//        PanelUtils.setupComponent(button);
//        button.setToolTipText(CLEAR_TRAIT_VALUES_TOOLTIP);
//        toolBar1.add(button);
        button = new JButton(createTraitPartitionAction);
        PanelUtils.setupComponent(button);
        button.setToolTipText(CREATE_TRAIT_PARTITIONS_TOOLTIP);
        toolBar1.add(button);

        ActionPanel actionPanel1 = new ActionPanel(false);
        actionPanel1.setAddAction(addTraitAction);
        actionPanel1.setRemoveAction(removeTraitAction);
//        actionPanel1.getAddActionButton().setTooltipText(ADD_TRAITS_TOOLTIP);

        removeTraitAction.setEnabled(false);

        JPanel controlPanel1 = new JPanel(new FlowLayout(FlowLayout.LEFT));
        controlPanel1.setOpaque(false);
        controlPanel1.add(actionPanel1);

        JPanel panel1 = new JPanel(new BorderLayout(0, 0));
        panel1.setOpaque(false);
        panel1.add(scrollPane1, BorderLayout.CENTER);
        panel1.add(controlPanel1, BorderLayout.SOUTH);
        panel1.setMinimumSize(new Dimension(MINIMUM_TABLE_WIDTH, 0));

        JPanel panel2 = new JPanel(new BorderLayout(0, 0));
        panel2.setOpaque(false);
        panel2.add(toolBar1, BorderLayout.NORTH);
        panel2.add(scrollPane2, BorderLayout.CENTER);

        JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
                panel1, panel2);
        splitPane.setDividerLocation(MINIMUM_TABLE_WIDTH);
        splitPane.setContinuousLayout(true);
        splitPane.setBorder(BorderFactory.createEmptyBorder());
        splitPane.setOpaque(false);

        setOpaque(false);
        setBorder(new BorderUIResource.EmptyBorderUIResource(new Insets(12, 12, 12, 12)));
        setLayout(new BorderLayout(0, 0));
        add(splitPane, BorderLayout.CENTER);
        add(toolBar1, BorderLayout.NORTH);

    }

    public void setOptions(BeautiOptions options) {
        this.options = options;

        updateButtons();

//        int selRow = traitsTable.getSelectedRow();
//        traitsTableModel.fireTableDataChanged();
//
//        if (selRow < 0) {
//            selRow = 0;
//        }
//        traitsTable.getSelectionModel().setSelectionInterval(selRow, selRow);


//        if (selectedTrait == null) {
//            traitsTable.getSelectionModel().setSelectionInterval(0, 0);
//        }

        traitsTableModel.fireTableDataChanged();
        dataTableModel.fireTableDataChanged();

        validate();
        repaint();
    }

    public void getOptions(BeautiOptions options) {
//        int selRow = traitsTable.getSelectedRow();
//        if (selRow >= 0 && options.traitsOptions.selecetedTraits.size() > 0) {
//            selectedTrait = options.traitsOptions.selecetedTraits.get(selRow);
//        }
//        options.datesUnits = unitsCombo.getSelectedIndex();
//        options.datesDirection = directionCombo.getSelectedIndex();
    }

    public JComponent getExportableComponent() {
        return dataTable;
    }

    public void fireTraitsChanged() {
        if (currentTrait != null) {
//        if (currentTrait.getName().equalsIgnoreCase(TraitData.Traits.TRAIT_SPECIES.toString())) {
//            frame.setupStarBEAST();
//        } else
            if (currentTrait != null && currentTrait.getTraitType() == TraitData.TraitType.DISCRETE) {
                frame.updateDiscreteTraitAnalysis();
            }

//            if (selRow > 0) {
//                traitsTable.getSelectionModel().setSelectionInterval(selRow-1, selRow-1);
//            } else if (selRow == 0 && options.traitsOptions.traits.size() > 0) { // options.traitsOptions.traits.size() after remove
//                traitsTable.getSelectionModel().setSelectionInterval(0, 0);
//            }

            traitsTableModel.fireTableDataChanged();
            options.updatePartitionAllLinks();
            frame.setDirty();
        }
    }

    private void traitSelectionChanged() {
        int selRow = traitsTable.getSelectedRow();
        if (selRow >= 0) {
            currentTrait = options.traits.get(selRow);
//            traitsTable.getSelectionModel().setSelectionInterval(selRow, selRow);
            removeTraitAction.setEnabled(true);
//        } else {
//            currentTrait = null;
//            removeTraitAction.setEnabled(false);
        }

        if (options.traits.size() <= 0) {
            currentTrait = null;
            removeTraitAction.setEnabled(false);
        }
        dataTableModel.fireTableDataChanged();
//        traitsTableModel.fireTableDataChanged();
    }

    private void updateButtons() { //TODO: better to merge updateButtons() fireTraitsChanged() traitSelectionChanged() into one
        boolean hasData = options.hasData();

        addTraitAction.setEnabled(hasData);
        importTraitsAction.setEnabled(hasData);
        createTraitPartitionAction.setEnabled(hasData && options.traits.size() > 0);
        guessTraitsAction.setEnabled(hasData && options.traits.size() > 0);
        setValueAction.setEnabled(hasData && options.traits.size() > 0);
    }

    public void clearTraitValues(String traitName) {
        options.clearTraitValues(traitName);

        dataTableModel.fireTableDataChanged();
    }

    public void guessTrait() {
        if (options.taxonList == null) { // validation of check empty taxonList
            return;
        }

        if (currentTrait == null) {
            if (!addTrait()) {
                return; // if addTrait() cancel then false
            }
        }
        int result;
        do {
            TraitGuesser currentTraitGuesser = new TraitGuesser(currentTrait);
            if (guessTraitDialog == null) {
                guessTraitDialog = new GuessTraitDialog(frame);
            }
            guessTraitDialog.setDescription("Extract values for trait '" + currentTrait + "' from taxa labels");
            result = guessTraitDialog.showDialog();

            if (result == -1 || result == JOptionPane.CANCEL_OPTION) {
                return;
            }

            guessTraitDialog.setupGuesserFromDialog(currentTraitGuesser);

            try {
                int[] selRows = dataTable.getSelectedRows();
                if (selRows.length > 0) {
                    Taxa selectedTaxa = new Taxa();

                    for (int row : selRows) {
                        Taxon taxon = (Taxon) dataTable.getValueAt(row, 0);
                        selectedTaxa.addTaxon(taxon);
                    }
                    currentTraitGuesser.guessTrait(selectedTaxa);
                } else {
                    currentTraitGuesser.guessTrait(options.taxonList);
                }

            } catch (IllegalArgumentException iae) {
                JOptionPane.showMessageDialog(this, iae.getMessage(), "Unable to guess trait value", JOptionPane.ERROR_MESSAGE);
                result = -1;
            }

            dataTableModel.fireTableDataChanged();
        } while (result < 0);
    }

    public void setTraitValue() {
        if (options.taxonList == null) { // validation of check empty taxonList
            return;
        }

        int result;
        do {
            if (traitValueDialog == null) {
                traitValueDialog = new TraitValueDialog(frame);
            }

            int[] selRows = dataTable.getSelectedRows();

            if (selRows.length > 0) {
                traitValueDialog.setDescription("Set values for trait '" + currentTrait + "' for selected taxa");
            } else {
                traitValueDialog.setDescription("Set values for trait '" + currentTrait + "' for all taxa");
            }

            result = traitValueDialog.showDialog();

            if (result == -1 || result == JOptionPane.CANCEL_OPTION) {
                return;
            }

//            currentTrait.guessTrait = true; // ?? no use?
            String value = traitValueDialog.getTraitValue();

            try {
                if (selRows.length > 0) {
                    for (int row : selRows) {
                        Taxon taxon = (Taxon) dataTable.getValueAt(row, 0);
                        taxon.setAttribute(currentTrait.getName(), value);
                    }
                } else {
                    for (Taxon taxon : options.taxonList) {
                        taxon.setAttribute(currentTrait.getName(), value);
                    }
                }

            } catch (IllegalArgumentException iae) {
                JOptionPane.showMessageDialog(this, iae.getMessage(), "Unable to guess trait value", JOptionPane.ERROR_MESSAGE);
                result = -1;
            }

            dataTableModel.fireTableDataChanged();
        } while (result < 0);
    }

    public boolean addTrait() {
        return addTrait("Untitled");

        // The 'species' trait doesn't need to be a reserved keyword since the StarBEAST button was added
        // http://code.google.com/p/beast-mcmc/issues/detail?id=388
//        if (options.traitExists(TraitData.TRAIT_SPECIES)) {
//            JOptionPane.showMessageDialog(frame, "Keyword \"species\" has been reserved for *BEAST !" +
//                    "\nPlease use a different trait name.", "Illegal Argument Exception", JOptionPane.ERROR_MESSAGE);
//            options.removeTrait(TraitData.TRAIT_SPECIES);
////            options.useStarBEAST = false;
//            traitsTableModel.fireTableDataChanged();
//            dataTableModel.fireTableDataChanged();
//            return false;
//        }
//
//        return isAdd;
    }

    public boolean addTrait(String traitName) {
        return addTrait(null, traitName, false);
    }

    public boolean addTrait(String message, String traitName, boolean isSpeciesTrait) {
        if (createTraitDialog == null) {
            createTraitDialog = new CreateTraitDialog(frame);
        }

        createTraitDialog.setSpeciesTrait(isSpeciesTrait);
        createTraitDialog.setTraitName(traitName);
        createTraitDialog.setMessage(message);

        int result = createTraitDialog.showDialog();
        if (result == JOptionPane.OK_OPTION) {
            frame.tabbedPane.setSelectedComponent(this);

            String name = createTraitDialog.getName();
            TraitData.TraitType type = createTraitDialog.getType();
            TraitData newTrait = new TraitData(options, name, "", type);
            currentTrait = newTrait;

// The createTraitDialog will have already checked if the
            // user is overwriting an existing trait
            addTrait(newTrait);

            if (createTraitDialog.createTraitPartition()) {
                options.createPartitionForTraits(name, newTrait);
            }

            fireTraitsChanged();
            updateButtons();
//            traitsTableModel.fireTableDataChanged();
//            dataTableModel.fireTableDataChanged();

//            traitSelectionChanged();
            // AR we don't want to guess traits automatically - the user may
            // be planning on typing them in. Also this method may have been
            // called by guessTraits() anyway.
//            guessTrait();
        } else if (result == CreateTraitDialog.OK_IMPORT) {
            boolean done = frame.doImportTraits();
            if (done) {
                if (isSpeciesTrait) {
                // check that we did indeed import a 'species' trait
                    if (!options.traitExists(TraitData.TRAIT_SPECIES)) {
                        JOptionPane.showMessageDialog(this,
                                        "The imported trait file didn't contain a trait\n" +
                                        "called '" + TraitData.TRAIT_SPECIES + "', required for *BEAST.\n" +
                                        "Please edit it or select a different file.",
                                "Reserved trait name",
                                JOptionPane.WARNING_MESSAGE);

                        return false;
                    }
                }
                updateButtons();
            }
            return done;
        } else if (result == JOptionPane.CANCEL_OPTION) {
            return false;
        }

        return true;
    }

    public void addTrait(TraitData newTrait) {
        int selRow = options.addTrait(newTrait);
        traitsTable.getSelectionModel().setSelectionInterval(selRow, selRow);
    }

    private void removeTrait() {
        int selRow = traitsTable.getSelectedRow();
        removeTrait(traitsTable.getValueAt(selRow, 0).toString());
    }

    public void removeTrait(String traitName) {
        if (options.useStarBEAST && traitName.equalsIgnoreCase(TraitData.TRAIT_SPECIES)) {
            JOptionPane.showMessageDialog(this, "The trait named '" + traitName + "' is being used by *BEAST.\nTurn *BEAST off before deleting this trait.", "Trait in use", JOptionPane.ERROR_MESSAGE);
            return;
        }
        TraitData traitData = options.getTrait(traitName);
        if (options.getAllPartitionData(traitData).size() > 0) {
            JOptionPane.showMessageDialog(this, "The trait named '" + traitName + "' is being used in a partition.\nRemove the partition before deleting this trait.", "Trait in use", JOptionPane.ERROR_MESSAGE);
            return;
        }
        options.removeTrait(traitName);

        updateButtons();
        fireTraitsChanged();
        traitSelectionChanged();
    }

    public class ClearTraitAction extends AbstractAction {
        private static final long serialVersionUID = -7281309694753868635L;

        public ClearTraitAction() {
{'fraction_non_alphanumeric': 0.09551165288870207, 'fraction_numerical': 0.008873943300172809, 'mean_word_length': 2.458767466278976, 'pattern_counts': {'":': 0, '<': 51, '<?xml version=': 0, '>': 64, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7549931', 'n_tokens_mistral': 12085, 'n_tokens_neox': 11500, 'n_words': 2528}