       1 /*
       2  * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
       3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       4  *
       5  * This code is free software; you can redistribute it and/or modify it
       6  * under the terms of the GNU General Public License version 2 only, as
       7  * published by the Free Software Foundation.  Oracle designates this
       8  * particular file as subject to the "Classpath" exception as provided
       9  * by Oracle in the LICENSE file that accompanied this code.
      10  *
      11  * This code is distributed in the hope that it will be useful, but WITHOUT
      12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
      13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
      14  * version 2 for more details (a copy is included in the LICENSE file that
      15  * accompanied this code).
      16  *
      17  * You should have received a copy of the GNU General Public License version
      18  * 2 along with this work; if not, write to the Free Software Foundation,
      19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
      20  *
      21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
      22  * or visit www.oracle.com if you need additional information or have any
      23  * questions.
      24  */
      25
      26 package javax.swing.table;
      27
      28 import javax.swing.*;
      29 import javax.swing.event.*;
      30 import java.awt.*;
      31 import java.util.Vector;
      32 import java.util.Enumeration;
      33 import java.util.EventListener;
      34 import java.beans.PropertyChangeListener;
      35 import java.beans.PropertyChangeEvent;
      36 import java.io.Serializable;
      37 import sun.swing.SwingUtilities2;
      38
      39 /**
      40  * The standard column-handler for a <code>JTable</code>.
      41  * <p>
      42  * <strong>Warning:</strong>
      43  * Serialized objects of this class will not be compatible with
      44  * future Swing releases. The current serialization support is
      45  * appropriate for short term storage or RMI between applications running
      46  * the same version of Swing.  As of 1.4, support for long term storage
      47  * of all JavaBeans&trade;
      48  * has been added to the <code>java.beans</code> package.
      49  * Please see {@link java.beans.XMLEncoder}.
      50  *
      51  * @author Alan Chung
      52  * @author Philip Milne
      53  * @see JTable
      54  */
      55 @SuppressWarnings("serial") // Same-version serialization only
      56 public class DefaultTableColumnModel implements TableColumnModel,
      57                         PropertyChangeListener, ListSelectionListener, Serializable
      58 {
      59 //
      60 // Instance Variables
      61 //
      62
      63     /** Array of TableColumn objects in this model */
      64     protected Vector<TableColumn> tableColumns;
      65
      66     /** Model for keeping track of column selections */
      67     protected ListSelectionModel selectionModel;
      68
      69     /** Width margin between each column */
      70     protected int columnMargin;
      71
      72     /** List of TableColumnModelListener */
      73     protected EventListenerList listenerList = new EventListenerList();
      74
      75     /** Change event (only one needed) */
      76     protected transient ChangeEvent changeEvent = null;
      77
      78     /** Column selection allowed in this column model */
      79     protected boolean columnSelectionAllowed;
      80
      81     /** A local cache of the combined width of all columns */
      82     protected int totalColumnWidth;
      83
      84 //
      85 // Constructors
      86 //
      87     /**
      88      * Creates a default table column model.
      89      */
      90     public DefaultTableColumnModel() {
      91         super();
      92
      93         // Initialize local ivars to default
      94         tableColumns = new Vector<TableColumn>();
      95         setSelectionModel(createSelectionModel());
      96         setColumnMargin(1);
      97         invalidateWidthCache();
      98         setColumnSelectionAllowed(false);
      99     }
     100
     101 //
     102 // Modifying the model
     103 //
     104
     105     /**
     106      *  Appends <code>aColumn</code> to the end of the
     107      *  <code>tableColumns</code> array.
     108      *  This method also posts the <code>columnAdded</code>
     109      *  event to its listeners.
     110      *
     111      * @param   aColumn         the <code>TableColumn</code> to be added
     112      * @exception IllegalArgumentException      if <code>aColumn</code> is
     113      *                          <code>null</code>
     114      * @see     #removeColumn
     115      */
     116     public void addColumn(TableColumn aColumn) {
     117         if (aColumn == null) {
     118             throw new IllegalArgumentException("Object is null");
     119         }
     120
     121         tableColumns.addElement(aColumn);
     122         aColumn.addPropertyChangeListener(this);
     123         invalidateWidthCache();
     124
     125         // Post columnAdded event notification
     126         fireColumnAdded(new TableColumnModelEvent(this, 0,
     127                                                   getColumnCount() - 1));
     128     }
     129
     130     /**
     131      *  Deletes the <code>column</code> from the
     132      *  <code>tableColumns</code> array.  This method will do nothing if
     133      *  <code>column</code> is not in the table's columns list.
     134      *  <code>tile</code> is called
     135      *  to resize both the header and table views.
     136      *  This method also posts a <code>columnRemoved</code>
     137      *  event to its listeners.
     138      *
     139      * @param   column          the <code>TableColumn</code> to be removed
     140      * @see     #addColumn
     141      */
     142     public void removeColumn(TableColumn column) {
     143         int columnIndex = tableColumns.indexOf(column);
     144
     145         if (columnIndex != -1) {
     146             // Adjust for the selection
     147             if (selectionModel != null) {
     148                 selectionModel.removeIndexInterval(columnIndex,columnIndex);
     149             }
     150
     151             column.removePropertyChangeListener(this);
     152             tableColumns.removeElementAt(columnIndex);
     153             invalidateWidthCache();
     154
     155             // Post columnAdded event notification.  (JTable and JTableHeader
     156             // listens so they can adjust size and redraw)
     157             fireColumnRemoved(new TableColumnModelEvent(this,
     158                                            columnIndex, 0));
     159         }
     160     }
     161
     162     /**
     163      * Moves the column and heading at <code>columnIndex</code> to
     164      * <code>newIndex</code>.  The old column at <code>columnIndex</code>
     165      * will now be found at <code>newIndex</code>.  The column
     166      * that used to be at <code>newIndex</code> is shifted
     167      * left or right to make room.  This will not move any columns if
     168      * <code>columnIndex</code> equals <code>newIndex</code>.  This method
     169      * also posts a <code>columnMoved</code> event to its listeners.
     170      *
     171      * @param   columnIndex                     the index of column to be moved
     172      * @param   newIndex                        new index to move the column
     173      * @exception IllegalArgumentException      if <code>column</code> or
     174      *                                          <code>newIndex</code>
     175      *                                          are not in the valid range
     176      */
     177     public void moveColumn(int columnIndex, int newIndex) {
     178         if ((columnIndex < 0) || (columnIndex >= getColumnCount()) ||
     179             (newIndex < 0) || (newIndex >= getColumnCount()))
     180             throw new IllegalArgumentException("moveColumn() - Index out of range");
     181
     182         TableColumn aColumn;
     183
     184         // If the column has not yet moved far enough to change positions
     185         // post the event anyway, the "draggedDistance" property of the
     186         // tableHeader will say how far the column has been dragged.
     187         // Here we are really trying to get the best out of an
     188         // API that could do with some rethinking. We preserve backward
     189         // compatibility by slightly bending the meaning of these methods.
     190         if (columnIndex == newIndex) {
     191             fireColumnMoved(new TableColumnModelEvent(this, columnIndex, newIndex));
     192             return;
     193         }
     194         aColumn = tableColumns.elementAt(columnIndex);
     195
     196         tableColumns.removeElementAt(columnIndex);
     197         boolean selected = selectionModel.isSelectedIndex(columnIndex);
     198         selectionModel.removeIndexInterval(columnIndex,columnIndex);
     199
     200         tableColumns.insertElementAt(aColumn, newIndex);
     201         selectionModel.insertIndexInterval(newIndex, 1, true);
     202         if (selected) {
     203             selectionModel.addSelectionInterval(newIndex, newIndex);
     204         }
     205         else {
     206             selectionModel.removeSelectionInterval(newIndex, newIndex);
     207         }
     208
     209         fireColumnMoved(new TableColumnModelEvent(this, columnIndex,
     210                                                                newIndex));
     211     }
     212
     213     /**
     214      * Sets the column margin to <code>newMargin</code>.  This method
     215      * also posts a <code>columnMarginChanged</code> event to its
     216      * listeners.
     217      *
     218      * @param   newMargin               the new margin width, in pixels
     219      * @see     #getColumnMargin
     220      * @see     #getTotalColumnWidth
     221      */
     222     public void setColumnMargin(int newMargin) {
     223         if (newMargin != columnMargin) {
     224             columnMargin = newMargin;
     225             // Post columnMarginChanged event notification.
     226             fireColumnMarginChanged();
     227         }
     228     }
     229
     230 //
     231 // Querying the model
     232 //
     233
     234     /**
     235      * Returns the number of columns in the <code>tableColumns</code> array.
     236      *
     237      * @return  the number of columns in the <code>tableColumns</code> array
     238      * @see     #getColumns
     239      */
     240     public int getColumnCount() {
     241         return tableColumns.size();
     242     }
     243
     244     /**
     245      * Returns an <code>Enumeration</code> of all the columns in the model.
     246      * @return an <code>Enumeration</code> of the columns in the model
     247      */
     248     public Enumeration<TableColumn> getColumns() {
     249         return tableColumns.elements();
     250     }
     251
     252     /**
     253      * Returns the index of the first column in the <code>tableColumns</code>
     254      * array whose identifier is equal to <code>identifier</code>,
     255      * when compared using <code>equals</code>.
     256      *
     257      * @param           identifier              the identifier object
     258      * @return          the index of the first column in the
     259      *                  <code>tableColumns</code> array whose identifier
     260      *                  is equal to <code>identifier</code>
     261      * @exception       IllegalArgumentException  if <code>identifier</code>
     262      *                          is <code>null</code>, or if no
     263      *                          <code>TableColumn</code> has this
     264      *                          <code>identifier</code>
     265      * @see             #getColumn
     266      */
     267     public int getColumnIndex(Object identifier) {
     268         if (identifier == null) {
     269             throw new IllegalArgumentException("Identifier is null");
     270         }
     271
     272         Enumeration<TableColumn> enumeration = getColumns();
     273         TableColumn aColumn;
     274         int index = 0;
     275
     276         while (enumeration.hasMoreElements()) {
     277             aColumn = enumeration.nextElement();
     278             // Compare them this way in case the column's identifier is null.
     279             if (identifier.equals(aColumn.getIdentifier()))
     280                 return index;
     281             index++;
     282         }
     283         throw new IllegalArgumentException("Identifier not found");
     284     }
     285
     286     /**
     287      * Returns the <code>TableColumn</code> object for the column
     288      * at <code>columnIndex</code>.
     289      *
     290      * @param   columnIndex     the index of the column desired
     291      * @return  the <code>TableColumn</code> object for the column
     292      *                          at <code>columnIndex</code>
     293      */
     294     public TableColumn getColumn(int columnIndex) {
     295         return tableColumns.elementAt(columnIndex);
     296     }
     297
     298     /**
     299      * Returns the width margin for <code>TableColumn</code>.
     300      * The default <code>columnMargin</code> is 1.
     301      *
     302      * @return  the maximum width for the <code>TableColumn</code>
     303      * @see     #setColumnMargin
     304      */
     305     public int getColumnMargin() {
     306         return columnMargin;
     307     }
     308
     309     /**
     310      * Returns the index of the column that lies at position <code>x</code>,
     311      * or -1 if no column covers this point.
     312      *
     313      * In keeping with Swing's separable model architecture, a
     314      * TableColumnModel does not know how the table columns actually appear on
     315      * screen.  The visual presentation of the columns is the responsibility
     316      * of the view/controller object using this model (typically JTable).  The
     317      * view/controller need not display the columns sequentially from left to
     318      * right.  For example, columns could be displayed from right to left to
     319      * accommodate a locale preference or some columns might be hidden at the
     320      * request of the user.  Because the model does not know how the columns
     321      * are laid out on screen, the given <code>xPosition</code> should not be
     322      * considered to be a coordinate in 2D graphics space.  Instead, it should
     323      * be considered to be a width from the start of the first column in the
     324      * model.  If the column index for a given X coordinate in 2D space is
     325      * required, <code>JTable.columnAtPoint</code> can be used instead.
     326      *
     327      * @param  x  the horizontal location of interest
     328      * @return  the index of the column or -1 if no column is found
     329      * @see javax.swing.JTable#columnAtPoint
     330      */
     331     public int getColumnIndexAtX(int x) {
     332         if (x < 0) {
     333             return -1;
     334         }
     335         int cc = getColumnCount();
     336         for(int column = 0; column < cc; column++) {
     337             x = x - getColumn(column).getWidth();
     338             if (x < 0) {
     339                 return column;
     340             }
     341         }
     342         return -1;
     343     }
     344
     345     /**
     346      * Returns the total combined width of all columns.
     347      * @return the <code>totalColumnWidth</code> property
     348      */
     349     public int getTotalColumnWidth() {
     350         if (totalColumnWidth == -1) {
     351             recalcWidthCache();
     352         }
     353         return totalColumnWidth;
     354     }
     355
     356 //
     357 // Selection model
     358 //
     359
     360     /**
     361      *  Sets the selection model for this <code>TableColumnModel</code>
     362      *  to <code>newModel</code>
     363      *  and registers for listener notifications from the new selection
     364      *  model.  If <code>newModel</code> is <code>null</code>,
     365      *  an exception is thrown.
     366      *
     367      * @param   newModel        the new selection model
     368      * @exception IllegalArgumentException      if <code>newModel</code>
     369      *                                          is <code>null</code>
     370      * @see     #getSelectionModel
     371      */
     372     public void setSelectionModel(ListSelectionModel newModel) {
     373         if (newModel == null) {
     374             throw new IllegalArgumentException("Cannot set a null SelectionModel");
     375         }
     376
     377         ListSelectionModel oldModel = selectionModel;
     378
     379         if (newModel != oldModel) {
     380             if (oldModel != null) {
     381                 oldModel.removeListSelectionListener(this);
     382             }
     383
     384             selectionModel= newModel;
     385             newModel.addListSelectionListener(this);
     386         }
     387     }
     388
     389     /**
     390      * Returns the <code>ListSelectionModel</code> that is used to
     391      * maintain column selection state.
     392      *
     393      * @return  the object that provides column selection state.  Or
     394      *          <code>null</code> if row selection is not allowed.
     395      * @see     #setSelectionModel
     396      */
     397     public ListSelectionModel getSelectionModel() {
     398         return selectionModel;
     399     }
     400
     401     // implements javax.swing.table.TableColumnModel
     402     /**
     403      * Sets whether column selection is allowed.  The default is false.
     404      * @param  flag true if column selection will be allowed, false otherwise
     405      */
     406     public void setColumnSelectionAllowed(boolean flag) {
     407         columnSelectionAllowed = flag;
     408     }
     409
     410     // implements javax.swing.table.TableColumnModel
     411     /**
     412      * Returns true if column selection is allowed, otherwise false.
     413      * The default is false.
     414      * @return the <code>columnSelectionAllowed</code> property
     415      */
     416     public boolean getColumnSelectionAllowed() {
     417         return columnSelectionAllowed;
     418     }
     419
     420     // implements javax.swing.table.TableColumnModel
     421     /**
     422      * Returns an array of selected columns.  If <code>selectionModel</code>
     423      * is <code>null</code>, returns an empty array.
     424      * @return an array of selected columns or an empty array if nothing
     425      *                  is selected or the <code>selectionModel</code> is
     426      *                  <code>null</code>
     427      */
     428     public int[] getSelectedColumns() {
     429         if (selectionModel != null) {
     430             return selectionModel.getSelectedIndices();
     431         }
     432         return  new int[0];
     433     }
     434
     435     // implements javax.swing.table.TableColumnModel
     436     /**
     437      * Returns the number of columns selected.
     438      * @return the number of columns selected
     439      */
     440     public int getSelectedColumnCount() {
     441         if (selectionModel != null) {
     442             return selectionModel.getSelectedItemsCount();
     443         }
     444         return 0;
     445     }
     446
     447 //
     448 // Listener Support Methods
     449 //
     450
     451     // implements javax.swing.table.TableColumnModel
     452     /**
     453      * Adds a listener for table column model events.
     454      * @param x  a <code>TableColumnModelListener</code> object
     455      */
     456     public void addColumnModelListener(TableColumnModelListener x) {
     457         listenerList.add(TableColumnModelListener.class, x);
     458     }
     459
     460     // implements javax.swing.table.TableColumnModel
     461     /**
     462      * Removes a listener for table column model events.
     463      * @param x  a <code>TableColumnModelListener</code> object
     464      */
     465     public void removeColumnModelListener(TableColumnModelListener x) {
     466         listenerList.remove(TableColumnModelListener.class, x);
     467     }
     468
     469     /**
     470      * Returns an array of all the column model listeners
     471      * registered on this model.
     472      *
     473      * @return all of this default table column model's <code>ColumnModelListener</code>s
     474      *         or an empty
     475      *         array if no column model listeners are currently registered
     476      *
     477      * @see #addColumnModelListener
     478      * @see #removeColumnModelListener
     479      *
     480      * @since 1.4
     481      */
     482     public TableColumnModelListener[] getColumnModelListeners() {
     483         return listenerList.getListeners(TableColumnModelListener.class);
     484     }
     485
     486 //
     487 //   Event firing methods
     488 //
     489
     490     /**
     491      * Notifies all listeners that have registered interest for
     492      * notification on this event type.  The event instance
     493      * is lazily created using the parameters passed into
     494      * the fire method.
     495      * @param e  the event received
     496      * @see EventListenerList
     497      */
     498     protected void fireColumnAdded(TableColumnModelEvent e) {
     499         // Guaranteed to return a non-null array
     500         Object[] listeners = listenerList.getListenerList();
     501         // Process the listeners last to first, notifying
     502         // those that are interested in this event
     503         for (int i = listeners.length-2; i>=0; i-=2) {
     504             if (listeners[i]==TableColumnModelListener.class) {
     505                 // Lazily create the event:
     506                 // if (e == null)
     507                 //  e = new ChangeEvent(this);
     508                 ((TableColumnModelListener)listeners[i+1]).
     509                     columnAdded(e);
     510             }
     511         }
     512     }
     513
     514     /**
     515      * Notifies all listeners that have registered interest for
     516      * notification on this event type.  The event instance
     517      * is lazily created using the parameters passed into
     518      * the fire method.
     519      * @param  e  the event received
     520      * @see EventListenerList
     521      */
     522     protected void fireColumnRemoved(TableColumnModelEvent e) {
     523         // Guaranteed to return a non-null array
     524         Object[] listeners = listenerList.getListenerList();
     525         // Process the listeners last to first, notifying
     526         // those that are interested in this event
     527         for (int i = listeners.length-2; i>=0; i-=2) {
     528             if (listeners[i]==TableColumnModelListener.class) {
     529                 // Lazily create the event:
     530                 // if (e == null)
     531                 //  e = new ChangeEvent(this);
     532                 ((TableColumnModelListener)listeners[i+1]).
     533                     columnRemoved(e);
     534             }
     535         }
     536     }
     537
     538     /**
     539      * Notifies all listeners that have registered interest for
     540      * notification on this event type.  The event instance
     541      * is lazily created using the parameters passed into
     542      * the fire method.
     543      * @param  e the event received
     544      * @see EventListenerList
     545      */
     546     protected void fireColumnMoved(TableColumnModelEvent e) {
     547         // Guaranteed to return a non-null array
     548         Object[] listeners = listenerList.getListenerList();
     549         // Process the listeners last to first, notifying
     550         // those that are interested in this event
     551         for (int i = listeners.length-2; i>=0; i-=2) {
     552             if (listeners[i]==TableColumnModelListener.class) {
     553                 // Lazily create the event:
     554                 // if (e == null)
     555                 //  e = new ChangeEvent(this);
     556                 ((TableColumnModelListener)listeners[i+1]).
     557                     columnMoved(e);
     558             }
     559         }
     560     }
     561
     562     /**
     563      * Notifies all listeners that have registered interest for
     564      * notification on this event type.  The event instance
     565      * is lazily created using the parameters passed into
     566      * the fire method.
     567      * @param e the event received
     568      * @see EventListenerList
     569      */
     570     protected void fireColumnSelectionChanged(ListSelectionEvent e) {
     571         // Guaranteed to return a non-null array
     572         Object[] listeners = listenerList.getListenerList();
     573         // Process the listeners last to first, notifying
     574         // those that are interested in this event
     575         for (int i = listeners.length-2; i>=0; i-=2) {
     576             if (listeners[i]==TableColumnModelListener.class) {
     577                 // Lazily create the event:
     578                 // if (e == null)
     579                 //  e = new ChangeEvent(this);
     580                 ((TableColumnModelListener)listeners[i+1]).
     581                     columnSelectionChanged(e);
     582             }
     583         }
     584     }
     585
     586     /**
     587      * Notifies all listeners that have registered interest for
     588      * notification on this event type.  The event instance
     589      * is lazily created using the parameters passed into
     590      * the fire method.
     591      * @see EventListenerList
     592      */
     593     protected void fireColumnMarginChanged() {
     594         // Guaranteed to return a non-null array
     595         Object[] listeners = listenerList.getListenerList();
     596         // Process the listeners last to first, notifying
     597         // those that are interested in this event
     598         for (int i = listeners.length-2; i>=0; i-=2) {
     599             if (listeners[i]==TableColumnModelListener.class) {
     600                 // Lazily create the event:
     601                 if (changeEvent == null)
     602                     changeEvent = new ChangeEvent(this);
     603                 ((TableColumnModelListener)listeners[i+1]).
     604                     columnMarginChanged(changeEvent);
     605             }
     606         }
     607     }
     608
     609     /**
     610      * Returns an array of all the objects currently registered
     611      * as <code><em>Foo</em>Listener</code>s
     612      * upon this model.
     613      * <code><em>Foo</em>Listener</code>s are registered using the
     614      * <code>add<em>Foo</em>Listener</code> method.
     615      *
     616      * <p>
     617      *
     618      * You can specify the <code>listenerType</code> argument
     619      * with a class literal,
     620      * such as
     621      * <code><em>Foo</em>Listener.class</code>.
     622      * For example, you can query a
     623      * <code>DefaultTableColumnModel</code> <code>m</code>
     624      * for its column model listeners with the following code:
     625      *
     626      * <pre>ColumnModelListener[] cmls = (ColumnModelListener[])(m.getListeners(ColumnModelListener.class));</pre>
     627      *
     628      * If no such listeners exist, this method returns an empty array.
     629      *
     630      * @param <T> the listener type
     631      * @param listenerType the type of listeners requested
     632      * @return an array of all objects registered as
     633      *          <code><em>Foo</em>Listener</code>s on this model,
     634      *          or an empty array if no such
     635      *          listeners have been added
     636      * @exception ClassCastException if <code>listenerType</code>
     637      *          doesn't specify a class or interface that implements
     638      *          <code>java.util.EventListener</code>
     639      *
     640      * @see #getColumnModelListeners
     641      * @since 1.3
     642      */
     643     public <T extends EventListener> T[] getListeners(Class<T> listenerType) {
     644         return listenerList.getListeners(listenerType);
     645     }
     646
     647 //
     648 // Implementing the PropertyChangeListener interface
     649 //
     650
     651     // PENDING(alan)
     652     // implements java.beans.PropertyChangeListener
     653     /**
     654      * Property Change Listener change method.  Used to track changes
     655      * to the column width or preferred column width.
     656      *
     657      * @param  evt  <code>PropertyChangeEvent</code>
     658      */
     659     public void propertyChange(PropertyChangeEvent evt) {
     660         String name = evt.getPropertyName();
     661
     662         if (name == "width" || name == "preferredWidth") {
     663             invalidateWidthCache();
     664             // This is a misnomer, we're using this method
     665             // simply to cause a relayout.
     666             fireColumnMarginChanged();
     667         }
     668
     669     }
     670
     671 //
     672 // Implementing ListSelectionListener interface
     673 //
     674
     675     // implements javax.swing.event.ListSelectionListener
     676     /**
     677      * A <code>ListSelectionListener</code> that forwards
     678      * <code>ListSelectionEvents</code> when there is a column
     679      * selection change.
     680      *
     681      * @param e  the change event
     682      */
     683     public void valueChanged(ListSelectionEvent e) {
     684         fireColumnSelectionChanged(e);
     685     }
     686
     687 //
     688 // Protected Methods
     689 //
     690
     691     /**
     692      * Creates a new default list selection model.
     693      *
     694      * @return a newly created default list selection model.
     695      */
     696     protected ListSelectionModel createSelectionModel() {
     697         return new DefaultListSelectionModel();
     698     }
     699
     700     /**
     701      * Recalculates the total combined width of all columns.  Updates the
     702      * <code>totalColumnWidth</code> property.
     703      */
     704     protected void recalcWidthCache() {
     705         Enumeration<TableColumn> enumeration = getColumns();
     706         totalColumnWidth = 0;
     707         while (enumeration.hasMoreElements()) {
     708             totalColumnWidth += enumeration.nextElement().getWidth();
     709         }
     710     }
     711
     712     private void invalidateWidthCache() {
     713         totalColumnWidth = -1;
     714     }
     715
     716 } // End of class DefaultTableColumnModel