- /*
- * @(#)DefaultTableColumnModel.java 1.48 04/05/05
- *
- * 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 <code>JTable</code>.
- * <p>
- * <strong>Warning:</strong>
- * 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 JavaBeans<sup><font size="-2">TM</font></sup>
- * has been added to the <code>java.beans</code> package.
- * Please see {@link java.beans.XMLEncoder}.
- *
- * @version 1.48 05/05/04
- * @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 Vector<TableColumn> tableColumns;
-
- /** Model for keeping track of column selections */
- protected ListSelectionModel selectionModel;
-
- /** Width margin between each column */
- protected int columnMargin;
-
- /** List of TableColumnModelListener */
- protected EventListenerList listenerList = new EventListenerList();
-
- /** Change event (only one needed) */
- transient protected ChangeEvent changeEvent = null;
-
- /** Column selection allowed in this column model */
- protected boolean columnSelectionAllowed;
-
- /** A local cache of the combined width of all columns */
- protected int totalColumnWidth;
-
- //
- // Constructors
- //
- /**
- * Creates a default table column model.
- */
- public DefaultTableColumnModel() {
- super();
-
- // Initialize local ivars to default
- tableColumns = new Vector<TableColumn>();
- setSelectionModel(createSelectionModel());
- setColumnMargin(1);
- invalidateWidthCache();
- setColumnSelectionAllowed(false);
- }
-
- //
- // Modifying the model
- //
-
- /**
- * Appends <code>aColumn</code> to the end of the
- * <code>tableColumns</code> array.
- * This method also posts the <code>columnAdded</code>
- * event to its listeners.
- *
- * @param aColumn the <code>TableColumn</code> to be added
- * @exception IllegalArgumentException if <code>aColumn</code> is
- * <code>null</code>
- * @see #removeColumn
- */
- public void addColumn(TableColumn aColumn) {
- if (aColumn == null) {
- throw new IllegalArgumentException("Object is null");
- }
-
- tableColumns.addElement(aColumn);
- aColumn.addPropertyChangeListener(this);
- invalidateWidthCache();
-
- checkLeadAnchor();
-
- // Post columnAdded event notification
- fireColumnAdded(new TableColumnModelEvent(this, 0,
- getColumnCount() - 1));
- }
-
- /**
- * Deletes the <code>column</code> from the
- * <code>tableColumns</code> array. This method will do nothing if
- * <code>column</code> is not in the table's columns list.
- * <code>tile</code> is called
- * to resize both the header and table views.
- * This method also posts a <code>columnRemoved</code>
- * event to its listeners.
- *
- * @param column the <code>TableColumn</code> 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);
- }
-
- checkLeadAnchor();
-
- 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 <code>columnIndex</code> to
- * <code>newIndex</code>. The old column at <code>columnIndex</code>
- * will now be found at <code>newIndex</code>. The column
- * that used to be at <code>newIndex</code> is shifted
- * left or right to make room. This will not move any columns if
- * <code>columnIndex</code> equals <code>newIndex</code>. This method
- * also posts a <code>columnMoved</code> event to its listeners.
- *
- * @param columnIndex the index of column to be moved
- * @param newIndex new index to move the column
- * @exception IllegalArgumentException if <code>column</code> or
- * <code>newIndex</code>
- * 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 <code>newMargin</code>. This method
- * also posts a <code>columnMarginChanged</code> 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 <code>tableColumns</code> array.
- *
- * @return the number of columns in the <code>tableColumns</code> array
- * @see #getColumns
- */
- public int getColumnCount() {
- return tableColumns.size();
- }
-
- /**
- * Returns an <code>Enumeration</code> of all the columns in the model.
- * @return an <code>Enumeration</code> of the columns in the model
- */
- public Enumeration<TableColumn> getColumns() {
- return tableColumns.elements();
- }
-
- /**
- * Returns the index of the first column in the <code>tableColumns</code>
- * array whose identifier is equal to <code>identifier</code>,
- * when compared using <code>equals</code>.
- *
- * @param identifier the identifier object
- * @return the index of the first column in the
- * <code>tableColumns</code> array whose identifier
- * is equal to <code>identifier</code>
- * @exception IllegalArgumentException if <code>identifier</code>
- * is <code>null</code>, or if no
- * <code>TableColumn</code> has this
- * <code>identifier</code>
- * @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 <code>TableColumn</code> object for the column
- * at <code>columnIndex</code>.
- *
- * @param columnIndex the index of the column desired
- * @return the <code>TableColumn</code> object for the column
- * at <code>columnIndex</code>
- */
- public TableColumn getColumn(int columnIndex) {
- return (TableColumn)tableColumns.elementAt(columnIndex);
- }
-
- /**
- * Returns the width margin for <code>TableColumn</code>.
- * The default <code>columnMargin</code> is 1.
- *
- * @return the maximum width for the <code>TableColumn</code>
- * @see #setColumnMargin
- */
- public int getColumnMargin() {
- return columnMargin;
- }
-
- /**
- * Returns the index of the column that lies at position <code>x</code>,
- * 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 <code>xPosition</code> 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, <code>JTable.columnAtPoint</code> 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 <code>totalColumnWidth</code> property
- */
- public int getTotalColumnWidth() {
- if (totalColumnWidth == -1) {
- recalcWidthCache();
- }
- return totalColumnWidth;
- }
-
- //
- // Selection model
- //
-
- /**
- * Sets the selection model for this <code>TableColumnModel</code>
- * to <code>newModel</code>
- * and registers for listener notifications from the new selection
- * model. If <code>newModel</code> is <code>null</code>,
- * an exception is thrown.
- *
- * @param newModel the new selection model
- * @exception IllegalArgumentException if <code>newModel</code>
- * is <code>null</code>
- * @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);
-
- checkLeadAnchor();
- }
- }
-
- /**
- * Returns the <code>ListSelectionModel</code> that is used to
- * maintain column selection state.
- *
- * @return the object that provides column selection state. Or
- * <code>null</code> 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 <code>columnSelectionAllowed</code> property
- */
- public boolean getColumnSelectionAllowed() {
- return columnSelectionAllowed;
- }
-
- // implements javax.swing.table.TableColumnModel
- /**
- * Returns an array of selected columns. If <code>selectionModel</code>
- * is <code>null</code>, returns an empty array.
- * @return an array of selected columns or an empty array if nothing
- * is selected or the <code>selectionModel</code> is
- * <code>null</code>
- */
- 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 <code>TableColumnModelListener</code> 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 <code>TableColumnModelListener</code> 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 <code>ColumnModelListener</code>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 <code><em>Foo</em>Listener</code>s
- * upon this model.
- * <code><em>Foo</em>Listener</code>s are registered using the
- * <code>add<em>Foo</em>Listener</code> method.
- *
- * <p>
- *
- * You can specify the <code>listenerType</code> argument
- * with a class literal,
- * such as
- * <code><em>Foo</em>Listener.class</code>.
- * For example, you can query a
- * <code>DefaultTableColumnModel</code> <code>m</code>
- * for its column model listeners with the following code:
- *
- * <pre>ColumnModelListener[] cmls = (ColumnModelListener[])(m.getListeners(ColumnModelListener.class));</pre>
- *
- * 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
- * <code>java.util.EventListener</code>
- * @return an array of all objects registered as
- * <code><em>Foo</em>Listener</code>s on this model,
- * or an empty array if no such
- * listeners have been added
- * @exception ClassCastException if <code>listenerType</code>
- * doesn't specify a class or interface that implements
- * <code>java.util.EventListener</code>
- *
- * @see #getColumnModelListeners
- * @since 1.3
- */
- public <T extends EventListener> T[] getListeners(Class<T> listenerType) {
- return listenerList.getListeners(listenerType);
- }
-
- //
- // Implementing the PropertyChangeListener interface
- //
-
- // PENDING(alan)
- // implements java.beans.PropertyChangeListener
- /**
- * Property Change Listener change method. Used to track changes
- * to the column width or preferred column width.
- *
- * @param evt <code>PropertyChangeEvent</code>
- */
- 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 <code>ListSelectionListener</code> that forwards
- * <code>ListSelectionEvents</code> 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
- * <code>totalColumnWidth</code> 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
-