/* class JTable
*
* Copyright (C) 2001 R M Pitman
*
* 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 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 this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package charvax.swing;
import java.util.Enumeration;
import java.util.Vector;
import charva.awt.Dimension;
import charva.awt.EventQueue;
import charva.awt.Point;
import charva.awt.Scrollable;
import charva.awt.Toolkit;
import charva.awt.event.KeyEvent;
import charva.awt.event.ScrollEvent;
import charva.awt.event.ScrollListener;
import charvax.swing.event.ListSelectionEvent;
import charvax.swing.event.ListSelectionListener;
import charvax.swing.event.TableModelEvent;
import charvax.swing.event.TableModelListener;
import charvax.swing.table.DefaultTableModel;
import charvax.swing.table.TableModel;
/**
* JTable is a user-interface component that displays data in a two-
* dimensional table format.<p>
* The user-interface works as follows:<p>
* The user can select a column by pressing the left or right arrow keys
* to move to the desired column, and then pressing ENTER.<br>
* He/she can select a row by pressing the up and down arrow keys to move to
* the desired row, then pressing ENTER.<br>
* Depending on the value of the selection mode, multiple rows and/or columns
* may be selected. By default the selection mode is set to SINGLE_SELECTION
* so that only a single row or column can be selected at a time. Selection
* of rows and/or columns can be enabled/disabled by means of the
* setRowSelectionAllowed() and setColumnSelectionAllowed() methods.
*/
public class JTable
extends JComponent
implements TableModelListener, Scrollable, ListSelectionListener
{
/** Default constructor
*/
public JTable() {
this(new DefaultTableModel(0, 0));
}
/** Constructs a table of numRows_ and numColumns_ of empty cells
* using a DefaultTableModel.
*/
public JTable(int numRows_, int numColumns_) {
this(new DefaultTableModel(numRows_, numColumns_));
}
/**
* Construct a JTable from the specified data and column names, using
* a DefaultTableModel.
*/
public JTable(Object[][] data_, Object[] columnNames_) {
this(new DefaultTableModel(data_, columnNames_));
}
/** Construct a JTable with the specified data model.
*/
public JTable(TableModel model_) {
setModel(model_);
_rowSelectionModel.addListSelectionListener(this);
}
/**
* Sets the data model to the specified TableModel and registers with it
* as a listener for events from the model.
*/
public void setModel(TableModel model_) {
_model = model_;
_model.addTableModelListener(this);
}
public TableModel getModel() { return _model; }
public void setValueAt(Object object_, int row_, int column_) {
_model.setValueAt(object_, row_, column_);
}
public Object getValueAt(int row_, int column_) {
return _model.getValueAt(row_, column_);
}
/** This method implements the TableModelListener interface;
* it is invoked when this table's TableModel generates a
* TableModelEvent.
*/
public void tableChanged(TableModelEvent evt_) {
/* For now, we'll just post a PaintEvent onto the queue.
*/
repaint();
}
public void requestFocus() {
/* Generate the FOCUS_GAINED event.
*/
super.requestFocus();
/* Get the absolute origin of this component
*/
Point origin = getLocationOnScreen();
/* Calculate the x position of the cursor
*/
int x=1;
for (int i=0; i<_currentColumn; i++) {
x += getColumnWidth(i) + 1;
}
/* Ensure that the new cursor position is not off the screen (which
* it can be if the JTable is in a JViewport).
*/
Point newCursor = origin.addOffset(x, _currentRow+1);
if (newCursor.x < 0)
newCursor.x = 0;
if (newCursor.y < 0)
newCursor.y = 0;
Toolkit.getDefaultToolkit().setCursor(newCursor);
}
public void draw(Toolkit toolkit) {
/* Get the absolute origin of this component.
*/
Point origin = getLocationOnScreen();
int rows = _model.getRowCount();
int columns = _model.getColumnCount();
int colorpair = getCursesColor();
/* Start by blanking out the table area and drawing the box
* around the table.
*/
toolkit.blankBox(origin, getSize(), colorpair);
toolkit.drawBox(origin, getSize(), colorpair);
/* Now fill in the table headings
*/
int x = 1;
int attr = Toolkit.A_BOLD;
for (int i=0; i<columns; i++) {
toolkit.setCursor(origin.addOffset(x, 0));
toolkit.addChar(' ', attr, colorpair);
toolkit.addString(_model.getColumnName(i), attr, colorpair);
toolkit.addChar(' ', attr, colorpair);
x += getColumnWidth(i) + 1;
}
/* Now draw the vertical lines that divide the columns.
*/
if (_model.getColumnCount() != 0) {
x = getColumnWidth(0) + 1;
for (int i=0; i<columns-1; i++) {
toolkit.setCursor(origin.addOffset(x, 0));
toolkit.addChar(Toolkit.ACS_TTEE, 0, colorpair); // top tee
toolkit.setCursor(origin.addOffset(x, 1));
toolkit.addVerticalLine(rows, 0, colorpair);
toolkit.setCursor(origin.addOffset(x, rows+1));
toolkit.addChar(Toolkit.ACS_BTEE, 0, colorpair); // bottom tee
x += getColumnWidth(i+1) + 1;
}
}
/* Now draw the contents of the cells.
*/
x = 1;
for (int column = 0; column<columns; column++) {
for (int row=0; row<rows; row++) {
toolkit.setCursor(origin.addOffset(x, row+1));
Object value = _model.getValueAt(row, column);
/* Show the currently SELECTED rows and columns in reverse video
*/
int attrib =
(isRowSelected(row) || isColumnSelected(column)) ?
Toolkit.A_REVERSE : Toolkit.A_NORMAL;
// Highlight the current row and column
if (_currentRow == row || _currentColumn == column)
attrib += Toolkit.A_BOLD;
if (value == null)
toolkit.addString("", attrib, colorpair);
else
toolkit.addString(value.toString(), attrib, colorpair);
}
x += getColumnWidth(column) + 1;
}
}
/**
* Processes key events occurring on this object
*/
public void processKeyEvent(KeyEvent ke_) {
/* First call all KeyListener objects that may have been registered
* for this component.
*/
super.processKeyEvent(ke_);
/* Check if any of the KeyListeners consumed the KeyEvent.
*/
if (ke_.isConsumed())
return;
Toolkit term = Toolkit.getDefaultToolkit();
EventQueue evtqueue = term.getSystemEventQueue();
int key = ke_.getKeyCode();
if (key == '\t') {
getParent().nextFocus();
return;
}
else if (key == KeyEvent.VK_BACK_TAB) {
getParent().previousFocus();
return;
}
else if (key == KeyEvent.VK_UP) {
if (_currentRow == 0)
term.beep();
else {
_currentRow--;
int x=0;
for (int i=0; i<_currentColumn; i++)
x += getColumnWidth(i) + 1;
evtqueue.postEvent(
new ScrollEvent(this, ScrollEvent.DOWN,
new Point(x, _currentRow+1)));
}
}
else if (key == KeyEvent.VK_DOWN) {
if (_currentRow == _model.getRowCount() - 1)
term.beep();
else {
_currentRow++;
int x=0;
for (int i=0; i<_currentColumn; i++)
x += getColumnWidth(i) + 1;
evtqueue.postEvent(
new ScrollEvent(this, ScrollEvent.UP,
new Point(x, _currentRow+2)));
}
}
else if (key == KeyEvent.VK_LEFT) {
if (_currentColumn == 0)
term.beep();
else {
_currentColumn--;
int x=0;
for (int i=0; i<_currentColumn; i++)
x += getColumnWidth(i) + 1;
evtqueue.postEvent(
new ScrollEvent(this, ScrollEvent.RIGHT,
new Point(x, _currentRow)));
}
}
else if (key == KeyEvent.VK_RIGHT) {
if (_currentColumn == _model.getColumnCount() - 1)
term.beep();
else {
_currentColumn++;
int x=0;
for (int i=0; i<=_currentColumn; i++)
x += getColumnWidth(i) + 1;
evtqueue.postEvent(
new ScrollEvent(this, ScrollEvent.LEFT,
new Point(x, _currentRow)));
}
}
else if (key == KeyEvent.VK_HOME) {
int x=0;
for (int i=0; i<_currentColumn; i++)
x += getColumnWidth(i) + 1;
evtqueue.postEvent(
new ScrollEvent(this, ScrollEvent.RIGHT,
new Point(x, _currentRow)));
}
else if (key == KeyEvent.VK_END) {
int x=0;
for (int i=0; i<=_currentColumn; i++)
x += getColumnWidth(i) + 1;
evtqueue.postEvent(
new ScrollEvent(this, ScrollEvent.LEFT,
new Point(x, _currentRow)));
}
else if (key == KeyEvent.VK_ENTER) {
if (getColumnSelectionAllowed())
selectCurrentColumn();
if (getRowSelectionAllowed())
selectCurrentRow();
repaint();
}
if ((getParent() instanceof JViewport) == false) {
draw(Toolkit.getDefaultToolkit());
requestFocus();
super.requestSync();
}
}
/**
* Register a ScrollListener object for this table.
*/
public void addScrollListener(ScrollListener sl_) {
if (_scrollListeners == null)
_scrollListeners = new Vector<ScrollListener>();
_scrollListeners.add(sl_);
}
/**
* Remove a ScrollListener object that is registered for this table.
*/
public void removeScrollListener(ScrollListener sl_) {
if (_scrollListeners == null)
return;
_scrollListeners.remove(sl_);
}
/** Process scroll events generated by this JTable.
*/
public void processScrollEvent(ScrollEvent e_) {
if (_scrollListeners != null) {
for (Enumeration<ScrollListener> e = _scrollListeners.elements();
e.hasMoreElements(); ) {
ScrollListener sl = (ScrollListener) e.nextElement();
sl.scroll(e_);
}
}
}
public Dimension getSize() {
return new Dimension(this.getWidth(), this.getHeight());
}
public Dimension minimumSize() {
return this.getSize();
}
public int getWidth() {
int columns = _model.getColumnCount();
int width = 1;
for (int i=0; i<columns; i++) {
width += getColumnWidth(i) + 1;
}
return width;
}
public int getHeight() {
int rows = _model.getRowCount();
return rows + 2;
}
public void setPreferredScrollableViewportSize(Dimension size_) {
_viewportSize = size_;
_viewportSizeSet = true;
}
public Dimension getPreferredScrollableViewportSize() {
if (_viewportSizeSet)
return new Dimension(_viewportSize);
else
return minimumSize();
}
/** Sets the table's row selection model and registers for notifications
* from the new selection model.
*/
public void setSelectionModel(ListSelectionModel model_) {
_rowSelectionModel = model_;
_rowSelectionModel.addListSelectionListener(this);
}
/** Returns the table's row selection model.
*/
public ListSelectionModel getSelectionModel() {
return _rowSelectionModel;
}
/** Sets the table's selection mode to allow selection of either single
* rows and/or columns, or multiple rows and/or columns.
* @param mode_ the selection mode. Allowable values are
* ListSelectionModel.SINGLE_SELECTION and
* ListSelectionModel.MULTIPLE_INTERVAL_SELECTION.
*/
public void setSelectionMode(int mode_) {
_rowSelectionModel.setSelectionMode(mode_);
_columnSelectionModel.setSelectionMode(mode_);
}
/** Returns the table's row/column selection mode.
*/
public int getSelectionMode() {
return _rowSelectionModel.getSelectionMode();
}
/** Set whether selection of columns is allowed.
*/
public void setColumnSelectionAllowed(boolean allowed_) {
_columnSelectionAllowed = allowed_;
}
/** Returns true if columns can be selected; otherwise false.
*/
public boolean getColumnSelectionAllowed() {
return _columnSelectionAllowed;
}
/** Set whether selection of rows is allowed.
*/
public void setRowSelectionAllowed(boolean allowed_) {
_rowSelectionAllowed = allowed_;
}
/** Returns true if rows can be selected; otherwise false.
*/
public boolean getRowSelectionAllowed() {
return _rowSelectionAllowed;
}
/** Adds the columns from <code>index0_</code> to <code>index1_</code>,
* inclusive, to the current selection.
*/
public void addColumnSelectionInterval(int index0_, int index1_) {
_columnSelectionModel.addSelectionInterval(index0_, index1_);
}
/** Adds the rows from <code>index0_</code> to <code>index1_</code>,
* inclusive, to the current selection.
*/
public void addRowSelectionInterval(int index0_, int index1_) {
_rowSelectionModel.addSelectionInterval(index0_, index1_);
}
/** Selects the columns from <code>index0_</code> to <code>index1_</code>,
* inclusive.
*/
public void setColumnSelectionInterval(int index0_, int index1_) {
_columnSelectionModel.setSelectionInterval(index0_, index1_);
}
/** Selects the rows from <code>index0_</code> to <code>index1_</code>,
* inclusive.
*/
public void setRowSelectionInterval(int index0_, int index1_) {
_rowSelectionModel.setSelectionInterval(index0_, index1_);
}
/** Returns the index of the first selected row, or -1 if
* no row is selected.
*/
public int getSelectedRow() {
return _rowSelectionModel.getMinSelectionIndex();
}
/** Returns the number of selected rows.
*/
public int getSelectedRowCount() {
int min = _rowSelectionModel.getMinSelectionIndex();
if (min == -1)
return 0;
int max = _rowSelectionModel.getMaxSelectionIndex();
int j = 0;
for (int i=min; i<=max; i++) {
if (_rowSelectionModel.isSelectedIndex(i))
j++;
}
return j;
}
/** Returns an array of the indices of all selected rows.
*/
public int[] getSelectedRows() {
int rowCount = getSelectedRowCount();
if (rowCount == 0)
return new int[0];
int[] array = new int[rowCount];
int min = _rowSelectionModel.getMinSelectionIndex();
int max = _rowSelectionModel.getMaxSelectionIndex();
int j = 0;
for (int i=min; i<=max; i++) {
if (_rowSelectionModel.isSelectedIndex(i))
array[j++] = i;
}
return array;
}
/** Returns the index of the first selected column, or -1 if
* no column is selected.
*/
public int getSelectedColumn() {
return _columnSelectionModel.getMinSelectionIndex();
}
/** Returns the number of selected columns.
*/
public int getSelectedColumnCount() {
int min = _columnSelectionModel.getMinSelectionIndex();
if (min == -1)
return 0;
int max = _columnSelectionModel.getMaxSelectionIndex();
int j = 0;
for (int i=min; i<=max; i++) {
if (_columnSelectionModel.isSelectedIndex(i))
j++;
}
return j;
}
/** Returns an array of the indices of all selected columns.
*/
public int[] getSelectedColumns() {
int columnCount = getSelectedColumnCount();
if (columnCount == 0)
return new int[0];
int[] array = new int[columnCount];
int min = _columnSelectionModel.getMinSelectionIndex();
int max = _columnSelectionModel.getMaxSelectionIndex();
int j = 0;
for (int i=min; i<=max; i++) {
if (_columnSelectionModel.isSelectedIndex(i))
array[j++] = i;
}
return array;
}
/** Returns true if the row with the specified index is selected.
*/
public boolean isRowSelected(int row_) {
return _rowSelectionModel.isSelectedIndex(row_);
}
/** Returns true if the column with the specified index is selected.
*/
public boolean isColumnSelected(int column_) {
return _columnSelectionModel.isSelectedIndex(column_);
}
/** This method is invoked when the row selection changes.
*/
public void valueChanged(ListSelectionEvent e_) {
repaint();
}
public void debug(int level_) {
for (int i=0; i<level_; i++)
System.err.print(" ");
System.err.println("JTable origin=" + _origin +
" size=" + getSize());
}
private int getColumnWidth(int column_) {
/* Calculate the column width for the specified column.
*/
int columnwidth = _model.getColumnName(column_).length() + 2;
for (int j=0; j<_model.getRowCount(); j++) {
Object value = getValueAt(j, column_);
if (value != null) {
int width = value.toString().length();
if (width > columnwidth)
columnwidth = width;
}
}
return columnwidth;
}
private void selectCurrentColumn() {
if (_columnSelectionModel.isSelectedIndex(_currentColumn)) {
_columnSelectionModel.removeSelectionInterval(
_currentColumn, _currentColumn);
}
else {
int selectionMode = _rowSelectionModel.getSelectionMode();
// the column is not currently selected; select it.
if (selectionMode == ListSelectionModel.SINGLE_SELECTION) {
_columnSelectionModel.setSelectionInterval(
_currentColumn, _currentColumn);
}
else {
_columnSelectionModel.addSelectionInterval(
_currentColumn, _currentColumn);
}
}
}
private void selectCurrentRow() {
if (_rowSelectionModel.isSelectedIndex(_currentRow)) {
_rowSelectionModel.removeSelectionInterval(
_currentRow, _currentRow);
}
else {
int selectionMode = _rowSelectionModel.getSelectionMode();
// the row is not currently selected; select it.
if (selectionMode == ListSelectionModel.SINGLE_SELECTION) {
_rowSelectionModel.setSelectionInterval(
_currentRow, _currentRow);
}
else {
_rowSelectionModel.addSelectionInterval(
_currentRow, _currentRow);
}
}
}
//--------------------------------------------------------------------
// INSTANCE VARIABLES
private TableModel _model = null;
private Dimension _viewportSize;
private boolean _viewportSizeSet = false;
/** This instance variable determines the row that will
* be highlighted when the table has input focus.
*/
private int _currentRow = 0;
private int _currentColumn = 0;
private boolean _columnSelectionAllowed = true;
private boolean _rowSelectionAllowed = true;
/** A list of ScrollListeners registered for this JTable.
*/
private Vector<ScrollListener> _scrollListeners = null;
protected ListSelectionModel _rowSelectionModel =
new DefaultListSelectionModel();
protected ListSelectionModel _columnSelectionModel =
new DefaultListSelectionModel();
}