jdk/src/share/classes/javax/swing/table/DefaultTableColumnModel.java
changeset 2 90ce3da70b43
child 1843 267cc4de4221
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/swing/table/DefaultTableColumnModel.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,740 @@
+/*
+ * Copyright 1997-2005 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+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;
+import sun.swing.SwingUtilities2;
+
+/**
+ * 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}.
+ *
+ * @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();
+
+        // 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);
+            }
+
+            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);
+        }
+    }
+
+    /**
+     * 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;
+    }
+
+    // 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