/*
* @(#)DefaultTableColumnModel.java 1.49 05/08/23
*
* Copyright 2004 Sun Microsystems, Inc. All rights reserved.
* SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package javax.swing.table;
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.util.Vector;
import java.util.Enumeration;
import java.util.EventListener;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import java.io.Serializable;
/**
* The standard column-handler for a JTable
.
*
* Warning:
* Serialized objects of this class will not be compatible with
* future Swing releases. The current serialization support is
* appropriate for short term storage or RMI between applications running
* the same version of Swing. As of 1.4, support for long term storage
* of all JavaBeansTM
* has been added to the
*
* You can specify the java.beans
package.
* Please see {@link java.beans.XMLEncoder}.
*
* @version 1.49 08/23/05
* @author Alan Chung
* @author Philip Milne
* @see JTable
*/
public class DefaultTableColumnModel implements TableColumnModel,
PropertyChangeListener, ListSelectionListener, Serializable
{
//
// Instance Variables
//
/** Array of TableColumn objects in this model */
protected VectoraColumn
to the end of the
* tableColumns
array.
* This method also posts the columnAdded
* event to its listeners.
*
* @param aColumn the TableColumn
to be added
* @exception IllegalArgumentException if aColumn
is
* null
* @see #removeColumn
*/
public void addColumn(TableColumn aColumn) {
if (aColumn == null) {
throw new IllegalArgumentException("Object is null");
}
tableColumns.addElement(aColumn);
aColumn.addPropertyChangeListener(this);
invalidateWidthCache();
// Post columnAdded event notification
fireColumnAdded(new TableColumnModelEvent(this, 0,
getColumnCount() - 1));
}
/**
* Deletes the column
from the
* tableColumns
array. This method will do nothing if
* column
is not in the table's columns list.
* tile
is called
* to resize both the header and table views.
* This method also posts a columnRemoved
* event to its listeners.
*
* @param column the TableColumn
to be removed
* @see #addColumn
*/
public void removeColumn(TableColumn column) {
int columnIndex = tableColumns.indexOf(column);
if (columnIndex != -1) {
// Adjust for the selection
if (selectionModel != null) {
selectionModel.removeIndexInterval(columnIndex,columnIndex);
}
column.removePropertyChangeListener(this);
tableColumns.removeElementAt(columnIndex);
invalidateWidthCache();
// Post columnAdded event notification. (JTable and JTableHeader
// listens so they can adjust size and redraw)
fireColumnRemoved(new TableColumnModelEvent(this,
columnIndex, 0));
}
}
/**
* Moves the column and heading at columnIndex
to
* newIndex
. The old column at columnIndex
* will now be found at newIndex
. The column
* that used to be at newIndex
is shifted
* left or right to make room. This will not move any columns if
* columnIndex
equals newIndex
. This method
* also posts a columnMoved
event to its listeners.
*
* @param columnIndex the index of column to be moved
* @param newIndex new index to move the column
* @exception IllegalArgumentException if column
or
* newIndex
* are not in the valid range
*/
public void moveColumn(int columnIndex, int newIndex) {
if ((columnIndex < 0) || (columnIndex >= getColumnCount()) ||
(newIndex < 0) || (newIndex >= getColumnCount()))
throw new IllegalArgumentException("moveColumn() - Index out of range");
TableColumn aColumn;
// If the column has not yet moved far enough to change positions
// post the event anyway, the "draggedDistance" property of the
// tableHeader will say how far the column has been dragged.
// Here we are really trying to get the best out of an
// API that could do with some rethinking. We preserve backward
// compatibility by slightly bending the meaning of these methods.
if (columnIndex == newIndex) {
fireColumnMoved(new TableColumnModelEvent(this, columnIndex, newIndex));
return;
}
aColumn = (TableColumn)tableColumns.elementAt(columnIndex);
tableColumns.removeElementAt(columnIndex);
boolean selected = selectionModel.isSelectedIndex(columnIndex);
selectionModel.removeIndexInterval(columnIndex,columnIndex);
tableColumns.insertElementAt(aColumn, newIndex);
selectionModel.insertIndexInterval(newIndex, 1, true);
if (selected) {
selectionModel.addSelectionInterval(newIndex, newIndex);
}
else {
selectionModel.removeSelectionInterval(newIndex, newIndex);
}
fireColumnMoved(new TableColumnModelEvent(this, columnIndex,
newIndex));
}
/**
* Sets the column margin to newMargin
. This method
* also posts a columnMarginChanged
event to its
* listeners.
*
* @param newMargin the new margin width, in pixels
* @see #getColumnMargin
* @see #getTotalColumnWidth
*/
public void setColumnMargin(int newMargin) {
if (newMargin != columnMargin) {
columnMargin = newMargin;
// Post columnMarginChanged event notification.
fireColumnMarginChanged();
}
}
//
// Querying the model
//
/**
* Returns the number of columns in the tableColumns
array.
*
* @return the number of columns in the tableColumns
array
* @see #getColumns
*/
public int getColumnCount() {
return tableColumns.size();
}
/**
* Returns an Enumeration
of all the columns in the model.
* @return an Enumeration
of the columns in the model
*/
public EnumerationtableColumns
* array whose identifier is equal to identifier
,
* when compared using equals
.
*
* @param identifier the identifier object
* @return the index of the first column in the
* tableColumns
array whose identifier
* is equal to identifier
* @exception IllegalArgumentException if identifier
* is null
, or if no
* TableColumn
has this
* identifier
* @see #getColumn
*/
public int getColumnIndex(Object identifier) {
if (identifier == null) {
throw new IllegalArgumentException("Identifier is null");
}
Enumeration enumeration = getColumns();
TableColumn aColumn;
int index = 0;
while (enumeration.hasMoreElements()) {
aColumn = (TableColumn)enumeration.nextElement();
// Compare them this way in case the column's identifier is null.
if (identifier.equals(aColumn.getIdentifier()))
return index;
index++;
}
throw new IllegalArgumentException("Identifier not found");
}
/**
* Returns the TableColumn
object for the column
* at columnIndex
.
*
* @param columnIndex the index of the column desired
* @return the TableColumn
object for the column
* at columnIndex
*/
public TableColumn getColumn(int columnIndex) {
return (TableColumn)tableColumns.elementAt(columnIndex);
}
/**
* Returns the width margin for TableColumn
.
* The default columnMargin
is 1.
*
* @return the maximum width for the TableColumn
* @see #setColumnMargin
*/
public int getColumnMargin() {
return columnMargin;
}
/**
* Returns the index of the column that lies at position x
,
* or -1 if no column covers this point.
*
* In keeping with Swing's separable model architecture, a
* TableColumnModel does not know how the table columns actually appear on
* screen. The visual presentation of the columns is the responsibility
* of the view/controller object using this model (typically JTable). The
* view/controller need not display the columns sequentially from left to
* right. For example, columns could be displayed from right to left to
* accomodate a locale preference or some columns might be hidden at the
* request of the user. Because the model does not know how the columns
* are laid out on screen, the given xPosition
should not be
* considered to be a coordinate in 2D graphics space. Instead, it should
* be considered to be a width from the start of the first column in the
* model. If the column index for a given X coordinate in 2D space is
* required, JTable.columnAtPoint
can be used instead.
*
* @param x the horizontal location of interest
* @return the index of the column or -1 if no column is found
* @see javax.swing.JTable#columnAtPoint
*/
public int getColumnIndexAtX(int x) {
if (x < 0) {
return -1;
}
int cc = getColumnCount();
for(int column = 0; column < cc; column++) {
x = x - getColumn(column).getWidth();
if (x < 0) {
return column;
}
}
return -1;
}
/**
* Returns the total combined width of all columns.
* @return the totalColumnWidth
property
*/
public int getTotalColumnWidth() {
if (totalColumnWidth == -1) {
recalcWidthCache();
}
return totalColumnWidth;
}
//
// Selection model
//
/**
* Sets the selection model for this TableColumnModel
* to newModel
* and registers for listener notifications from the new selection
* model. If newModel
is null
,
* an exception is thrown.
*
* @param newModel the new selection model
* @exception IllegalArgumentException if newModel
* is null
* @see #getSelectionModel
*/
public void setSelectionModel(ListSelectionModel newModel) {
if (newModel == null) {
throw new IllegalArgumentException("Cannot set a null SelectionModel");
}
ListSelectionModel oldModel = selectionModel;
if (newModel != oldModel) {
if (oldModel != null) {
oldModel.removeListSelectionListener(this);
}
selectionModel= newModel;
newModel.addListSelectionListener(this);
}
}
/**
* Returns the ListSelectionModel
that is used to
* maintain column selection state.
*
* @return the object that provides column selection state. Or
* null
if row selection is not allowed.
* @see #setSelectionModel
*/
public ListSelectionModel getSelectionModel() {
return selectionModel;
}
/**
* Initialize the lead and anchor of the selection model
* based on what the column model contains.
*/
private void checkLeadAnchor() {
int lead = selectionModel.getLeadSelectionIndex();
int count = tableColumns.size();
if (count == 0) {
if (lead != -1) {
// no columns left, set the lead and anchor to -1
selectionModel.setValueIsAdjusting(true);
selectionModel.setAnchorSelectionIndex(-1);
selectionModel.setLeadSelectionIndex(-1);
selectionModel.setValueIsAdjusting(false);
}
} else {
if (lead == -1) {
// set the lead and anchor to the first column
// (without changing the selection)
if (selectionModel.isSelectedIndex(0)) {
selectionModel.addSelectionInterval(0, 0);
} else {
selectionModel.removeSelectionInterval(0, 0);
}
}
}
}
// implements javax.swing.table.TableColumnModel
/**
* Sets whether column selection is allowed. The default is false.
* @param flag true if column selection will be allowed, false otherwise
*/
public void setColumnSelectionAllowed(boolean flag) {
columnSelectionAllowed = flag;
}
// implements javax.swing.table.TableColumnModel
/**
* Returns true if column selection is allowed, otherwise false.
* The default is false.
* @return the columnSelectionAllowed
property
*/
public boolean getColumnSelectionAllowed() {
return columnSelectionAllowed;
}
// implements javax.swing.table.TableColumnModel
/**
* Returns an array of selected columns. If selectionModel
* is null
, returns an empty array.
* @return an array of selected columns or an empty array if nothing
* is selected or the selectionModel
is
* null
*/
public int[] getSelectedColumns() {
if (selectionModel != null) {
int iMin = selectionModel.getMinSelectionIndex();
int iMax = selectionModel.getMaxSelectionIndex();
if ((iMin == -1) || (iMax == -1)) {
return new int[0];
}
int[] rvTmp = new int[1+ (iMax - iMin)];
int n = 0;
for(int i = iMin; i <= iMax; i++) {
if (selectionModel.isSelectedIndex(i)) {
rvTmp[n++] = i;
}
}
int[] rv = new int[n];
System.arraycopy(rvTmp, 0, rv, 0, n);
return rv;
}
return new int[0];
}
// implements javax.swing.table.TableColumnModel
/**
* Returns the number of columns selected.
* @return the number of columns selected
*/
public int getSelectedColumnCount() {
if (selectionModel != null) {
int iMin = selectionModel.getMinSelectionIndex();
int iMax = selectionModel.getMaxSelectionIndex();
int count = 0;
for(int i = iMin; i <= iMax; i++) {
if (selectionModel.isSelectedIndex(i)) {
count++;
}
}
return count;
}
return 0;
}
//
// Listener Support Methods
//
// implements javax.swing.table.TableColumnModel
/**
* Adds a listener for table column model events.
* @param x a TableColumnModelListener
object
*/
public void addColumnModelListener(TableColumnModelListener x) {
listenerList.add(TableColumnModelListener.class, x);
}
// implements javax.swing.table.TableColumnModel
/**
* Removes a listener for table column model events.
* @param x a TableColumnModelListener
object
*/
public void removeColumnModelListener(TableColumnModelListener x) {
listenerList.remove(TableColumnModelListener.class, x);
}
/**
* Returns an array of all the column model listeners
* registered on this model.
*
* @return all of this default table column model's ColumnModelListener
s
* or an empty
* array if no column model listeners are currently registered
*
* @see #addColumnModelListener
* @see #removeColumnModelListener
*
* @since 1.4
*/
public TableColumnModelListener[] getColumnModelListeners() {
return (TableColumnModelListener[])listenerList.getListeners(
TableColumnModelListener.class);
}
//
// Event firing methods
//
/**
* Notifies all listeners that have registered interest for
* notification on this event type. The event instance
* is lazily created using the parameters passed into
* the fire method.
* @param e the event received
* @see EventListenerList
*/
protected void fireColumnAdded(TableColumnModelEvent e) {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length-2; i>=0; i-=2) {
if (listeners[i]==TableColumnModelListener.class) {
// Lazily create the event:
// if (e == null)
// e = new ChangeEvent(this);
((TableColumnModelListener)listeners[i+1]).
columnAdded(e);
}
}
}
/**
* Notifies all listeners that have registered interest for
* notification on this event type. The event instance
* is lazily created using the parameters passed into
* the fire method.
* @param e the event received
* @see EventListenerList
*/
protected void fireColumnRemoved(TableColumnModelEvent e) {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length-2; i>=0; i-=2) {
if (listeners[i]==TableColumnModelListener.class) {
// Lazily create the event:
// if (e == null)
// e = new ChangeEvent(this);
((TableColumnModelListener)listeners[i+1]).
columnRemoved(e);
}
}
}
/**
* Notifies all listeners that have registered interest for
* notification on this event type. The event instance
* is lazily created using the parameters passed into
* the fire method.
* @param e the event received
* @see EventListenerList
*/
protected void fireColumnMoved(TableColumnModelEvent e) {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length-2; i>=0; i-=2) {
if (listeners[i]==TableColumnModelListener.class) {
// Lazily create the event:
// if (e == null)
// e = new ChangeEvent(this);
((TableColumnModelListener)listeners[i+1]).
columnMoved(e);
}
}
}
/**
* Notifies all listeners that have registered interest for
* notification on this event type. The event instance
* is lazily created using the parameters passed into
* the fire method.
* @param e the event received
* @see EventListenerList
*/
protected void fireColumnSelectionChanged(ListSelectionEvent e) {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length-2; i>=0; i-=2) {
if (listeners[i]==TableColumnModelListener.class) {
// Lazily create the event:
// if (e == null)
// e = new ChangeEvent(this);
((TableColumnModelListener)listeners[i+1]).
columnSelectionChanged(e);
}
}
}
/**
* Notifies all listeners that have registered interest for
* notification on this event type. The event instance
* is lazily created using the parameters passed into
* the fire method.
* @see EventListenerList
*/
protected void fireColumnMarginChanged() {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length-2; i>=0; i-=2) {
if (listeners[i]==TableColumnModelListener.class) {
// Lazily create the event:
if (changeEvent == null)
changeEvent = new ChangeEvent(this);
((TableColumnModelListener)listeners[i+1]).
columnMarginChanged(changeEvent);
}
}
}
/**
* Returns an array of all the objects currently registered
* as FooListener
s
* upon this model.
* FooListener
s are registered using the
* addFooListener
method.
*
* listenerType
argument
* with a class literal,
* such as
* FooListener.class
.
* For example, you can query a
* DefaultTableColumnModel
m
* for its column model listeners with the following code:
*
* ColumnModelListener[] cmls = (ColumnModelListener[])(m.getListeners(ColumnModelListener.class));
*
* If no such listeners exist, this method returns an empty array.
*
* @param listenerType the type of listeners requested; this parameter
* should specify an interface that descends from
* java.util.EventListener
* @return an array of all objects registered as
* FooListener
s on this model,
* or an empty array if no such
* listeners have been added
* @exception ClassCastException if listenerType
* doesn't specify a class or interface that implements
* java.util.EventListener
*
* @see #getColumnModelListeners
* @since 1.3
*/
public PropertyChangeEvent
*/
public void propertyChange(PropertyChangeEvent evt) {
String name = evt.getPropertyName();
if (name == "width" || name == "preferredWidth") {
invalidateWidthCache();
// This is a misnomer, we're using this method
// simply to cause a relayout.
fireColumnMarginChanged();
}
}
//
// Implementing ListSelectionListener interface
//
// implements javax.swing.event.ListSelectionListener
/**
* A ListSelectionListener
that forwards
* ListSelectionEvents
when there is a column
* selection change.
*
* @param e the change event
*/
public void valueChanged(ListSelectionEvent e) {
fireColumnSelectionChanged(e);
}
//
// Protected Methods
//
/**
* Creates a new default list selection model.
*/
protected ListSelectionModel createSelectionModel() {
return new DefaultListSelectionModel();
}
/**
* Recalculates the total combined width of all columns. Updates the
* totalColumnWidth
property.
*/
protected void recalcWidthCache() {
Enumeration enumeration = getColumns();
totalColumnWidth = 0;
while (enumeration.hasMoreElements()) {
totalColumnWidth += ((TableColumn)enumeration.nextElement()).getWidth();
}
}
private void invalidateWidthCache() {
totalColumnWidth = -1;
}
} // End of class DefaultTableColumnModel