/*
* TraitsPanel.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.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.ArrayList;
/**
* @author Andrew Rambaut
* @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.setAddToolTipText(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");
}
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();
} 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 createTraitPartition() {
int[] selRows = traitsTable.getSelectedRows();
java.util.List<TraitData> traits = new ArrayList<TraitData>();
int discreteCount = 0;
int continuousCount = 0;
for (int row : selRows) {
TraitData trait = options.traits.get(row);
traits.add(trait);
if (trait.getTraitType() == TraitData.TraitType.DISCRETE) {
discreteCount ++;
}
if (trait.getTraitType() == TraitData.TraitType.CONTINUOUS) {
continuousCount ++;
}
}
boolean success = false;
if (discreteCount > 0) {
if (continuousCount > 0) {
JOptionPane.showMessageDialog(TraitsPanel.this, "Don't mix discrete and continuous traits when creating partition(s).", "Mixed Trait Types", JOptionPane.ERROR_MESSAGE);
return;
}
// with discrete traits, create a separate partition for each
for (TraitData trait : traits) {
java.util.List<TraitData> singleTrait = new ArrayList<TraitData>();
singleTrait.add(trait);
if (dataPanel.createFromTraits(singleTrait)) {
success = true;
}
}
} else {
// with
success = dataPanel.createFromTraits(traits);
}
if (success) {
frame.switchToPanel(BeautiFrame.DATA_PARTITIONS);
}
}
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.getTraitPartitions(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() {
super("Clear trait values");
}
public void actionPerformed(ActionEvent ae) {
if (currentTrait != null) clearTraitValues(currentTrait.getName()); // Clear trait values
}
}
public class GuessTraitsAction extends AbstractAction {
private static final long serialVersionUID = 8514706149822252033L;
public GuessTraitsAction() {
super("Guess trait values");
}
public void actionPerformed(ActionEvent ae) {
guessTrait();
}
}
public class AddTraitAction extends AbstractAction {
public AddTraitAction() {
super("Add trait");
}
public void actionPerformed(ActionEvent ae) {
addTrait();
}
}
AbstractAction removeTraitAction = new AbstractAction() {
public void actionPerformed(ActionEvent ae) {
removeTrait();
}
};
public class SetValueAction extends AbstractAction {
public SetValueAction() {
super("Set trait values");
setToolTipText("Use this button to set the trait values of selected taxa");
}
public void actionPerformed(ActionEvent ae) {
setTraitValue();
}
}
public class CreateTraitPartitionAction extends AbstractAction {
public CreateTraitPartitionAction() {
super("Create partition from trait ...");
}
public void actionPerformed(ActionEvent ae) {
createTraitPartition();
}
}
class TraitsTableModel extends AbstractTableModel {
private static final long serialVersionUID = -6707994233020715574L;
String[] columnNames = {"Trait", "Type"};
public TraitsTableModel() {
}
public int getColumnCount() {
return columnNames.length;
}
public int getRowCount() {
if (options == null) return 0;
return options.traits.size();
}
public Object getValueAt(int row, int col) {
switch (col) {
case 0:
return options.traits.get(row).getName();
case 1:
return options.traits.get(row).getTraitType();
}
return null;
}
public void setValueAt(Object aValue, int row, int col) {
switch (col) {
case 0:
String oldName = options.traits.get(row).getName();
options.traits.get(row).setName(aValue.toString());
Object value;
for (Taxon t : options.taxonList) {
value = t.getAttribute(oldName);
t.setAttribute(aValue.toString(), value);
// cannot remvoe attribute in Attributable inteface
}
fireTraitsChanged();
break;
case 1:
options.traits.get(row).setTraitType((TraitData.TraitType) aValue);
break;
}
}
public boolean isCellEditable(int row, int col) {
return !options.useStarBEAST || !options.traits.get(row).getName().equalsIgnoreCase(TraitData.TRAIT_SPECIES.toString());
}
public String getColumnName(int column) {
return columnNames[column];
}
public Class getColumnClass(int c) {
return getValueAt(0, c).getClass();
}
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append(getColumnName(0));
for (int j = 1; j < getColumnCount(); j++) {
buffer.append("\t");
buffer.append(getColumnName(j));
}
buffer.append("\n");
for (int i = 0; i < getRowCount(); i++) {
buffer.append(getValueAt(i, 0));
for (int j = 1; j < getColumnCount(); j++) {
buffer.append("\t");
buffer.append(getValueAt(i, j));
}
buffer.append("\n");
}
return buffer.toString();
}
}
class DataTableModel extends AbstractTableModel {
private static final long serialVersionUID = -6707994233020715574L;
String[] columnNames = {"Taxon", "Value"};
public DataTableModel() {
}
public int getColumnCount() {
return columnNames.length;
}
public int getRowCount() {
if (options == null) return 0;
if (options.taxonList == null) return 0;
if (currentTrait == null) return 0;
return options.taxonList.getTaxonCount();
}
public Object getValueAt(int row, int col) {
switch (col) {
case 0:
return options.taxonList.getTaxon(row);
case 1:
Object value = null;
if (currentTrait != null) {
value = options.taxonList.getTaxon(row).getAttribute(currentTrait.getName());
}
if (value != null) {
return value;
} else {
return "";
}
}
return null;
}
public void setValueAt(Object aValue, int row, int col) {
if (col == 1) {
// Location location = options.taxonList.getTaxon(row).getLocation();
// if (location != null) {
// options.taxonList.getTaxon(row).setLocation(location);
// }
if (currentTrait != null) {
options.taxonList.getTaxon(row).setAttribute(currentTrait.getName(), aValue);
}
}
}
public boolean isCellEditable(int row, int col) {
if (col == 1) {
// Object t = options.taxonList.getTaxon(row).getAttribute(currentTrait.getName());
// return (t != null);
return true;
} else {
return false;
}
}
public String getColumnName(int column) {
return columnNames[column];
}
public Class getColumnClass(int c) {
return getValueAt(0, c).getClass();
}
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append(getColumnName(0));
for (int j = 1; j < getColumnCount(); j++) {
buffer.append("\t");
buffer.append(getColumnName(j));
}
buffer.append("\n");
for (int i = 0; i < getRowCount(); i++) {
buffer.append(getValueAt(i, 0));
for (int j = 1; j < getColumnCount(); j++) {
buffer.append("\t");
buffer.append(getValueAt(i, j));
}
buffer.append("\n");
}
return buffer.toString();
}
}
}