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
  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