/*
Copyright (C) 2006 EBI
This library 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.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the itmplied 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 this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.biomart.builder.view.gui.panels;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.swing.DefaultCellEditor;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import org.biomart.builder.model.Column;
/**
* This panel represents a two-column table which can contain the entries of a
* map. The keys go in the left column and the values in the right. It includes
* methods to update the contents of the table and to obtain the current
* contents, including stripping out rows with blank keys.
*
* @author Richard Holland <holland@ebi.ac.uk>
* @version $Revision: 1.3 $, $Date: 2007-10-03 10:41:02 $, modified by
* $Author: rh4 $
* @since 0.6
*/
public abstract class TwoColumnTablePanel extends JPanel {
private static final long serialVersionUID = 1;
private TwoColumnTableModel tableModel;
private JButton insert;
private JButton remove;
/**
* Creates a two-columned table that displays an initial set of values. It
* optionally restricts input on either column to two further sets of
* values.
*
* @param values
* initial values to display in the two columns of the table.
* @param firstColValues
* the values to restrict entry in the first (left) column with.
* If <tt>null</tt> then no restriction is made.
* @param secondColValues
* the values to restrict entry in the second (right) column
* with. If <tt>null</tt> then no restriction is made.
*/
public TwoColumnTablePanel(final Map values,
final Collection firstColValues, final Collection secondColValues) {
// Create the basic panel.
super();
// Create the layout to display the rest of the panel.
this.setLayout(new GridBagLayout());
// Create constraints for labels that are not in the last row.
final GridBagConstraints labelConstraints = new GridBagConstraints();
labelConstraints.gridwidth = GridBagConstraints.RELATIVE;
labelConstraints.fill = GridBagConstraints.HORIZONTAL;
labelConstraints.anchor = GridBagConstraints.LINE_END;
labelConstraints.insets = new Insets(0, 2, 0, 0);
// Create constraints for fields that are not in the last row.
final GridBagConstraints fieldConstraints = new GridBagConstraints();
fieldConstraints.gridwidth = GridBagConstraints.REMAINDER;
fieldConstraints.fill = GridBagConstraints.NONE;
fieldConstraints.anchor = GridBagConstraints.LINE_START;
fieldConstraints.insets = new Insets(0, 1, 0, 2);
// Create constraints for labels that are in the last row.
final GridBagConstraints labelLastRowConstraints = (GridBagConstraints) labelConstraints
.clone();
labelLastRowConstraints.gridheight = GridBagConstraints.REMAINDER;
// Create constraints for fields that are in the last row.
final GridBagConstraints fieldLastRowConstraints = (GridBagConstraints) fieldConstraints
.clone();
fieldLastRowConstraints.gridheight = GridBagConstraints.REMAINDER;
// Set up the data model.
this.tableModel = new TwoColumnTableModel(values, this
.getFirstColumnHeader(), this.getSecondColumnHeader(), this
.getFirstColumnType(), this.getSecondColumnType());
final JTable table = new JTable(this.tableModel);
table.setGridColor(Color.LIGHT_GRAY); // Mac OSX.
// First column.
final JComboBox firstEd = this
.getFirstColumnEditor(firstColValues == null ? Collections.EMPTY_SET
: firstColValues);
if (firstEd != null) {
table.getColumnModel().getColumn(0).setCellEditor(
new DefaultCellEditor(firstEd));
table.getColumnModel().getColumn(0).setPreferredWidth(
firstEd.getPreferredSize().width);
} else
table.getColumnModel().getColumn(0).setPreferredWidth(
Math.max(250, table.getTableHeader().getDefaultRenderer()
.getTableCellRendererComponent(
null,
table.getColumnModel().getColumn(0)
.getHeaderValue(), false, false, 0,
0).getPreferredSize().width));
final TableCellRenderer firstRend = this.getFirstColumnRenderer();
if (firstRend != null)
table.getColumnModel().getColumn(0).setCellRenderer(firstRend);
// Second column.
final JComboBox secondEd = this
.getSecondColumnEditor(secondColValues == null ? Collections.EMPTY_SET
: secondColValues);
if (secondEd != null) {
table.getColumnModel().getColumn(1).setCellEditor(
new DefaultCellEditor(secondEd));
table.getColumnModel().getColumn(1).setPreferredWidth(
secondEd.getPreferredSize().width);
} else
table.getColumnModel().getColumn(1).setPreferredWidth(
Math.max(250, table.getTableHeader().getDefaultRenderer()
.getTableCellRendererComponent(
null,
table.getColumnModel().getColumn(1)
.getHeaderValue(), false, false, 0,
0).getPreferredSize().width));
final TableCellRenderer secondRend = this.getSecondColumnRenderer();
if (secondRend != null)
table.getColumnModel().getColumn(1).setCellRenderer(secondRend);
// Buttons.
table
.setPreferredScrollableViewportSize(new Dimension(table
.getColumnModel().getColumn(0).getPreferredWidth()
+ table.getColumnModel().getColumn(1)
.getPreferredWidth(), 150));
if (this.getInsertButtonText() != null) {
this.insert = new JButton(this.getInsertButtonText());
this.insert.addActionListener(new ActionListener() {
public void actionPerformed(final ActionEvent e) {
TwoColumnTablePanel.this.tableModel.insertRow(
TwoColumnTablePanel.this.tableModel.getRowCount(),
new Object[] {
TwoColumnTablePanel.this
.getNewRowFirstColumn(),
TwoColumnTablePanel.this
.getNewRowSecondColumn() });
}
});
}
if (this.getRemoveButtonText() != null) {
this.remove = new JButton(this.getRemoveButtonText());
this.remove.addActionListener(new ActionListener() {
public void actionPerformed(final ActionEvent e) {
final int rows[] = table.getSelectedRows();
// Reverse order, so we don't end up with changing
// indices along the way.
for (int i = rows.length - 1; i >= 0; i--)
TwoColumnTablePanel.this.tableModel.removeRow(rows[i]);
}
});
}
// Display the table and buttons as two parts of a single panel.
final JPanel tableField = new JPanel();
tableField.add(new JScrollPane(table));
if (this.insert != null || this.remove != null) {
final JPanel field = new JPanel();
if (this.insert != null)
field.add(this.insert);
if (this.remove != null)
field.add(this.remove);
this.add(tableField, fieldConstraints);
this.add(field, fieldLastRowConstraints);
}
// If cannot insert/remove then also cannot change.
else {
this.add(tableField, fieldLastRowConstraints);
table.setEnabled(false);
}
}
/**
* Retrieve the text to display on the 'insert row' button. Return
* <tt>null</tt> if this button is to be disabled.
*
* @return the text to display on the 'insert row' button.
*/
public String getInsertButtonText() {
return null;
}
/**
* Retrieve the text to display on the 'remove row' button. Return
* <tt>null</tt> if this button is to be disabled.
*
* @return the text to display on the 'remove row' button.
*/
public String getRemoveButtonText() {
return null;
}
/**
* Retrieve the header text for the first column.
*
* @return the header text for the first column.
*/
public abstract String getFirstColumnHeader();
/**
* Retrieve the header text for the second column.
*
* @return the header text for the second column.
*/
public abstract String getSecondColumnHeader();
/**
* Retrieve the data type for the first column.
*
* @return the data type for the first column.
*/
public abstract Class getFirstColumnType();
/**
* Retrieve the data type for the second column.
*
* @return the data type for the second column.
*/
public abstract Class getSecondColumnType();
/**
* Retrieve the value to populate the first column for every new row
* created.
*
* @return the value to use.
*/
public abstract Object getNewRowFirstColumn();
/**
* Retrieve the value to populate the second column for every new row
* created.
*
* @return the value to use.
*/
public abstract Object getNewRowSecondColumn();
/**
* Retrieve the editor to use to edit values in the first column.
*
* @param values
* the values that can be chosen from. This will never be
* <tt>null</tt> but may be empty.
* @return the editor to use for this column. Return <tt>null</tt> if the
* default editor should be used.
*/
public abstract JComboBox getFirstColumnEditor(final Collection values);
/**
* Retrieve the editor to use to edit values in the second column.
*
* @param values
* the values that can be chosen from. This will never be
* <tt>null</tt> but may be empty.
* @return the editor to use for this column. Return <tt>null</tt> if the
* default editor should be used.
*/
public abstract JComboBox getSecondColumnEditor(final Collection values);
/**
* Retrieve the renderer to use for the first column.
*
* @return the renderer to use. Return <tt>null</tt> if the default
* renderer should be used.
*/
public abstract TableCellRenderer getFirstColumnRenderer();
/**
* Retrieve the renderer to use for the second column.
*
* @return the renderer to use. Return <tt>null</tt> if the default
* renderer should be used.
*/
public abstract TableCellRenderer getSecondColumnRenderer();
/**
* Replace the contents of the displayed table with the given set of values.
* Keys of the map will appear in the left (first) column and values in the
* second (right) column. All existing contents will be removed before these
* new contents are inserted.
*
* @param values
* the map of values to display.
*/
public void setValues(final Map values) {
this.tableModel.setValues(values);
}
/**
* Retrieve the currently displayed set of values, with any that have empty
* left columns removed first.
*
* @return the set of current values. Keys of the map are the left (first)
* column and values of the map are the right (second) column.
*/
public Map getValues() {
return this.tableModel.getValues();
}
/**
* This internal class represents the data model upon which the two column
* table is based.
*/
private static class TwoColumnTableModel extends DefaultTableModel {
private final Class[] colClasses;
private static final long serialVersionUID = 1;
/**
* Construct a model of data from the given information.
*
* @param values
* the initial values to display. If <tt>null</tt>, no
* initial values are displayed. Keys of the map go in the
* left column (first), values in the right (second).
* @param firstColHeader
* the header to give the first column.
* @param secondColHeader
* the header to give the second column.
* @param firstColType
* the type of data displayed in the first column.
* @param secondColType
* the type of data displayed in the second column.
*/
public TwoColumnTableModel(final Map values,
final String firstColHeader, final String secondColHeader,
final Class firstColType, final Class secondColType) {
super(new Object[] { firstColHeader, secondColHeader }, 0);
this.colClasses = new Class[] { firstColType, secondColType };
this.setValues(values);
}
/**
* Overwrite (clear) the existing contents of the table and replace with
* values from the given map.
*
* @param values
* the new values to use. If <tt>null</tt> then all
* existing data is dropped and no new data inserted. Keys of
* the map go in the left column, values in the right.
*/
public void setValues(final Map values) {
while (this.getRowCount() > 0)
this.removeRow(0);
if (values != null)
for (final Iterator i = values.entrySet().iterator(); i
.hasNext();) {
final Map.Entry entry = (Map.Entry) i.next();
this.insertRow(this.getRowCount(), new Object[] {
entry.getKey(), entry.getValue() });
}
}
/**
* Obtain the currently displayed set of values. Any which have empty or
* all-whitespace strings in the left (first) column are not included in
* the results.
*
* @return a map containing the results. The keys of the map are the
* entries in the first (left) column and the values are those
* in the second (right) column.
*/
public Map getValues() {
final HashMap aliases = new HashMap();
for (int i = 0; i < this.getRowCount(); i++) {
final Object alias = this.getValueAt(i, 0);
final Object expr = this.getValueAt(i, 1);
if (alias != null && alias.toString().trim().length() > 0)
aliases.put(alias,
expr != null ? expr.toString().length() == 0 ? null
: expr : null);
}
return aliases;
}
public Class getColumnClass(final int column) {
return this.colClasses[column];
}
}
/**
* This is a simple two-column base table which allows any string in both
* columns.
*/
public abstract static class StringStringTablePanel extends
TwoColumnTablePanel {
/**
* Constructs a two-column table which displays simple strings in both
* columns.
*
* @param values
* the values to display initially, or <tt>null</tt> if
* none.
* @param firstColValues
* the values to restrict the first column to, or
* <tt>null</tt> if none required.
* @param secondColValues
* the values to restrict the second column to, or
* <tt>null</tt> if none required.
*/
protected StringStringTablePanel(final Map values,
final Collection firstColValues,
final Collection secondColValues) {
super(values, firstColValues, secondColValues);
}
/**
* Constructs a two-column table which displays simple strings in both
* columns.
*
* @param values
* the values to display initially, or <tt>null</tt> if
* none.
*/
public StringStringTablePanel(final Map values) {
this(values, null, null);
}
public Class getFirstColumnType() {
return String.class;
}
public Class getSecondColumnType() {
return String.class;
}
public Object getNewRowFirstColumn() {
return "";
}
public Object getNewRowSecondColumn() {
return "";
}
public JComboBox getFirstColumnEditor(final Collection values) {
return null;
}
public JComboBox getSecondColumnEditor(final Collection values) {
return null;
}
public TableCellRenderer getFirstColumnRenderer() {
return null;
}
public TableCellRenderer getSecondColumnRenderer() {
return null;
}
}
/**
* This class displays a database column lookup in the first column and a
* simple string in the second column.
*/
public abstract static class ColumnStringTablePanel extends
StringStringTablePanel {
private JComboBox editor;
/**
* Constructs a table with a drop-down for database columns in the first
* column and allows simple string entry in the second column.
*
* @param values
* the initial values to display, or <tt>null</tt> if none.
* @param cols
* the columns to show in the drop-down.
*/
public ColumnStringTablePanel(final Map values, final Collection cols) {
super(values, cols, null);
}
/**
* Given a bunch of columns, sort them into a user-pleasing order. This
* implementation uses the {@link Column#getName()} method to get a name
* for each one then uses {@link String#compareTo(Object)} method to
* sort them.
*
* @param columns
* the columns to sort.
* @return the sorted columns.
*/
protected Collection getSortedColumns(final Collection columns) {
final List cols = new ArrayList(columns);
Collections.sort(cols);
return cols;
}
/**
* Gets a reference to the editor used to display the drop-down in the
* first column.
*
* @return the editor used.
*/
protected JComboBox getFirstColumnEditor() {
return this.editor;
}
public Class getFirstColumnType() {
return Column.class;
}
public Object getNewRowFirstColumn() {
return this.editor.getItemAt(0);
}
public JComboBox getFirstColumnEditor(final Collection values) {
if (this.editor == null) {
// Create and store the editor for future reference.
this.editor = new JComboBox();
for (final Iterator i = this.getSortedColumns(values)
.iterator(); i.hasNext();)
this.editor.addItem(i.next());
}
return this.editor;
}
}
}