1 /*
   2  * Copyright (c) 1997, 2013, 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 public class DefaultTableColumnModel implements TableColumnModel,
  56                         PropertyChangeListener, ListSelectionListener, Serializable
  57 {
  58 //
  59 // Instance Variables
  60 //
  61 
  62     /** Array of TableColumn objects in this model */
  63     protected Vector<TableColumn> tableColumns;
  64 
  65     /** Model for keeping track of column selections */
  66     protected ListSelectionModel selectionModel;
  67 
  68     /** Width margin between each column */
  69     protected int columnMargin;
  70 
  71     /** List of TableColumnModelListener */
  72     protected EventListenerList listenerList = new EventListenerList();
  73 
  74     /** Change event (only one needed) */
  75     transient protected ChangeEvent changeEvent = null;
  76 
  77     /** Column selection allowed in this column model */
  78     protected boolean columnSelectionAllowed;
  79 
  80     /** A local cache of the combined width of all columns */
  81     protected int totalColumnWidth;
  82 
  83 //
  84 // Constructors
  85 //
  86     /**
  87      * Creates a default table column model.
  88      */
  89     public DefaultTableColumnModel() {
  90         super();
  91 
  92         // Initialize local ivars to default
  93         tableColumns = new Vector<TableColumn>();
  94         setSelectionModel(createSelectionModel());
  95         setColumnMargin(1);
  96         invalidateWidthCache();
  97         setColumnSelectionAllowed(false);
  98     }
  99 
 100 //
 101 // Modifying the model
 102 //
 103 
 104     /**
 105      *  Appends <code>aColumn</code> to the end of the
 106      *  <code>tableColumns</code> array.
 107      *  This method also posts the <code>columnAdded</code>
 108      *  event to its listeners.
 109      *
 110      * @param   aColumn         the <code>TableColumn</code> to be added
 111      * @exception IllegalArgumentException      if <code>aColumn</code> is
 112      *                          <code>null</code>
 113      * @see     #removeColumn
 114      */
 115     public void addColumn(TableColumn aColumn) {
 116         if (aColumn == null) {
 117             throw new IllegalArgumentException("Object is null");
 118         }
 119 
 120         tableColumns.addElement(aColumn);
 121         aColumn.addPropertyChangeListener(this);
 122         invalidateWidthCache();
 123 
 124         // Post columnAdded event notification
 125         fireColumnAdded(new TableColumnModelEvent(this, 0,
 126                                                   getColumnCount() - 1));
 127     }
 128 
 129     /**
 130      *  Deletes the <code>column</code> from the
 131      *  <code>tableColumns</code> array.  This method will do nothing if
 132      *  <code>column</code> is not in the table's columns list.
 133      *  <code>tile</code> is called
 134      *  to resize both the header and table views.
 135      *  This method also posts a <code>columnRemoved</code>
 136      *  event to its listeners.
 137      *
 138      * @param   column          the <code>TableColumn</code> to be removed
 139      * @see     #addColumn
 140      */
 141     public void removeColumn(TableColumn column) {
 142         int columnIndex = tableColumns.indexOf(column);
 143 
 144         if (columnIndex != -1) {
 145             // Adjust for the selection
 146             if (selectionModel != null) {
 147                 selectionModel.removeIndexInterval(columnIndex,columnIndex);
 148             }
 149 
 150             column.removePropertyChangeListener(this);
 151             tableColumns.removeElementAt(columnIndex);
 152             invalidateWidthCache();
 153 
 154             // Post columnAdded event notification.  (JTable and JTableHeader
 155             // listens so they can adjust size and redraw)
 156             fireColumnRemoved(new TableColumnModelEvent(this,
 157                                            columnIndex, 0));
 158         }
 159     }
 160 
 161     /**
 162      * Moves the column and heading at <code>columnIndex</code> to
 163      * <code>newIndex</code>.  The old column at <code>columnIndex</code>
 164      * will now be found at <code>newIndex</code>.  The column
 165      * that used to be at <code>newIndex</code> is shifted
 166      * left or right to make room.  This will not move any columns if
 167      * <code>columnIndex</code> equals <code>newIndex</code>.  This method
 168      * also posts a <code>columnMoved</code> event to its listeners.
 169      *
 170      * @param   columnIndex                     the index of column to be moved
 171      * @param   newIndex                        new index to move the column
 172      * @exception IllegalArgumentException      if <code>column</code> or
 173      *                                          <code>newIndex</code>
 174      *                                          are not in the valid range
 175      */
 176     public void moveColumn(int columnIndex, int newIndex) {
 177         if ((columnIndex < 0) || (columnIndex >= getColumnCount()) ||
 178             (newIndex < 0) || (newIndex >= getColumnCount()))
 179             throw new IllegalArgumentException("moveColumn() - Index out of range");
 180 
 181         TableColumn aColumn;
 182 
 183         // If the column has not yet moved far enough to change positions
 184         // post the event anyway, the "draggedDistance" property of the
 185         // tableHeader will say how far the column has been dragged.
 186         // Here we are really trying to get the best out of an
 187         // API that could do with some rethinking. We preserve backward
 188         // compatibility by slightly bending the meaning of these methods.
 189         if (columnIndex == newIndex) {
 190             fireColumnMoved(new TableColumnModelEvent(this, columnIndex, newIndex));
 191             return;
 192         }
 193         aColumn = tableColumns.elementAt(columnIndex);
 194 
 195         tableColumns.removeElementAt(columnIndex);
 196         boolean selected = selectionModel.isSelectedIndex(columnIndex);
 197         selectionModel.removeIndexInterval(columnIndex,columnIndex);
 198 
 199         tableColumns.insertElementAt(aColumn, newIndex);
 200         selectionModel.insertIndexInterval(newIndex, 1, true);
 201         if (selected) {
 202             selectionModel.addSelectionInterval(newIndex, newIndex);
 203         }
 204         else {
 205             selectionModel.removeSelectionInterval(newIndex, newIndex);
 206         }
 207 
 208         fireColumnMoved(new TableColumnModelEvent(this, columnIndex,
 209                                                                newIndex));
 210     }
 211 
 212     /**
 213      * Sets the column margin to <code>newMargin</code>.  This method
 214      * also posts a <code>columnMarginChanged</code> event to its
 215      * listeners.
 216      *
 217      * @param   newMargin               the new margin width, in pixels
 218      * @see     #getColumnMargin
 219      * @see     #getTotalColumnWidth
 220      */
 221     public void setColumnMargin(int newMargin) {
 222         if (newMargin != columnMargin) {
 223             columnMargin = newMargin;
 224             // Post columnMarginChanged event notification.
 225             fireColumnMarginChanged();
 226         }
 227     }
 228 
 229 //
 230 // Querying the model
 231 //
 232 
 233     /**
 234      * Returns the number of columns in the <code>tableColumns</code> array.
 235      *
 236      * @return  the number of columns in the <code>tableColumns</code> array
 237      * @see     #getColumns
 238      */
 239     public int getColumnCount() {
 240         return tableColumns.size();
 241     }
 242 
 243     /**
 244      * Returns an <code>Enumeration</code> of all the columns in the model.
 245      * @return an <code>Enumeration</code> of the columns in the model
 246      */
 247     public Enumeration<TableColumn> getColumns() {
 248         return tableColumns.elements();
 249     }
 250 
 251     /**
 252      * Returns the index of the first column in the <code>tableColumns</code>
 253      * array whose identifier is equal to <code>identifier</code>,
 254      * when compared using <code>equals</code>.
 255      *
 256      * @param           identifier              the identifier object
 257      * @return          the index of the first column in the
 258      *                  <code>tableColumns</code> array whose identifier
 259      *                  is equal to <code>identifier</code>
 260      * @exception       IllegalArgumentException  if <code>identifier</code>
 261      *                          is <code>null</code>, or if no
 262      *                          <code>TableColumn</code> has this
 263      *                          <code>identifier</code>
 264      * @see             #getColumn
 265      */
 266     public int getColumnIndex(Object identifier) {
 267         if (identifier == null) {
 268             throw new IllegalArgumentException("Identifier is null");
 269         }
 270 
 271         Enumeration enumeration = getColumns();
 272         TableColumn aColumn;
 273         int index = 0;
 274 
 275         while (enumeration.hasMoreElements()) {
 276             aColumn = (TableColumn)enumeration.nextElement();
 277             // Compare them this way in case the column's identifier is null.
 278             if (identifier.equals(aColumn.getIdentifier()))
 279                 return index;
 280             index++;
 281         }
 282         throw new IllegalArgumentException("Identifier not found");
 283     }
 284 
 285     /**
 286      * Returns the <code>TableColumn</code> object for the column
 287      * at <code>columnIndex</code>.
 288      *
 289      * @param   columnIndex     the index of the column desired
 290      * @return  the <code>TableColumn</code> object for the column
 291      *                          at <code>columnIndex</code>
 292      */
 293     public TableColumn getColumn(int columnIndex) {
 294         return tableColumns.elementAt(columnIndex);
 295     }
 296 
 297     /**
 298      * Returns the width margin for <code>TableColumn</code>.
 299      * The default <code>columnMargin</code> is 1.
 300      *
 301      * @return  the maximum width for the <code>TableColumn</code>
 302      * @see     #setColumnMargin
 303      */
 304     public int getColumnMargin() {
 305         return columnMargin;
 306     }
 307 
 308     /**
 309      * Returns the index of the column that lies at position <code>x</code>,
 310      * or -1 if no column covers this point.
 311      *
 312      * In keeping with Swing's separable model architecture, a
 313      * TableColumnModel does not know how the table columns actually appear on
 314      * screen.  The visual presentation of the columns is the responsibility
 315      * of the view/controller object using this model (typically JTable).  The
 316      * view/controller need not display the columns sequentially from left to
 317      * right.  For example, columns could be displayed from right to left to
 318      * accommodate a locale preference or some columns might be hidden at the
 319      * request of the user.  Because the model does not know how the columns
 320      * are laid out on screen, the given <code>xPosition</code> should not be
 321      * considered to be a coordinate in 2D graphics space.  Instead, it should
 322      * be considered to be a width from the start of the first column in the
 323      * model.  If the column index for a given X coordinate in 2D space is
 324      * required, <code>JTable.columnAtPoint</code> can be used instead.
 325      *
 326      * @param  x  the horizontal location of interest
 327      * @return  the index of the column or -1 if no column is found
 328      * @see javax.swing.JTable#columnAtPoint
 329      */
 330     public int getColumnIndexAtX(int x) {
 331         if (x < 0) {
 332             return -1;
 333         }
 334         int cc = getColumnCount();
 335         for(int column = 0; column < cc; column++) {
 336             x = x - getColumn(column).getWidth();
 337             if (x < 0) {
 338                 return column;
 339             }
 340         }
 341         return -1;
 342     }
 343 
 344     /**
 345      * Returns the total combined width of all columns.
 346      * @return the <code>totalColumnWidth</code> property
 347      */
 348     public int getTotalColumnWidth() {
 349         if (totalColumnWidth == -1) {
 350             recalcWidthCache();
 351         }
 352         return totalColumnWidth;
 353     }
 354 
 355 //
 356 // Selection model
 357 //
 358 
 359     /**
 360      *  Sets the selection model for this <code>TableColumnModel</code>
 361      *  to <code>newModel</code>
 362      *  and registers for listener notifications from the new selection
 363      *  model.  If <code>newModel</code> is <code>null</code>,
 364      *  an exception is thrown.
 365      *
 366      * @param   newModel        the new selection model
 367      * @exception IllegalArgumentException      if <code>newModel</code>
 368      *                                          is <code>null</code>
 369      * @see     #getSelectionModel
 370      */
 371     public void setSelectionModel(ListSelectionModel newModel) {
 372         if (newModel == null) {
 373             throw new IllegalArgumentException("Cannot set a null SelectionModel");
 374         }
 375 
 376         ListSelectionModel oldModel = selectionModel;
 377 
 378         if (newModel != oldModel) {
 379             if (oldModel != null) {
 380                 oldModel.removeListSelectionListener(this);
 381             }
 382 
 383             selectionModel= newModel;
 384             newModel.addListSelectionListener(this);
 385         }
 386     }
 387 
 388     /**
 389      * Returns the <code>ListSelectionModel</code> that is used to
 390      * maintain column selection state.
 391      *
 392      * @return  the object that provides column selection state.  Or
 393      *          <code>null</code> if row selection is not allowed.
 394      * @see     #setSelectionModel
 395      */
 396     public ListSelectionModel getSelectionModel() {
 397         return selectionModel;
 398     }
 399 
 400     // implements javax.swing.table.TableColumnModel
 401     /**
 402      * Sets whether column selection is allowed.  The default is false.
 403      * @param  flag true if column selection will be allowed, false otherwise
 404      */
 405     public void setColumnSelectionAllowed(boolean flag) {
 406         columnSelectionAllowed = flag;
 407     }
 408 
 409     // implements javax.swing.table.TableColumnModel
 410     /**
 411      * Returns true if column selection is allowed, otherwise false.
 412      * The default is false.
 413      * @return the <code>columnSelectionAllowed</code> property
 414      */
 415     public boolean getColumnSelectionAllowed() {
 416         return columnSelectionAllowed;
 417     }
 418 
 419     // implements javax.swing.table.TableColumnModel
 420     /**
 421      * Returns an array of selected columns.  If <code>selectionModel</code>
 422      * is <code>null</code>, returns an empty array.
 423      * @return an array of selected columns or an empty array if nothing
 424      *                  is selected or the <code>selectionModel</code> is
 425      *                  <code>null</code>
 426      */
 427     public int[] getSelectedColumns() {
 428         if (selectionModel != null) {
 429             return selectionModel.getSelectedIndices();
 430         }
 431         return  new int[0];
 432     }
 433 
 434     // implements javax.swing.table.TableColumnModel
 435     /**
 436      * Returns the number of columns selected.
 437      * @return the number of columns selected
 438      */
 439     public int getSelectedColumnCount() {
 440         if (selectionModel != null) {
 441             return selectionModel.getSelectedItemsCount();
 442         }
 443         return 0;
 444     }
 445 
 446 //
 447 // Listener Support Methods
 448 //
 449 
 450     // implements javax.swing.table.TableColumnModel
 451     /**
 452      * Adds a listener for table column model events.
 453      * @param x  a <code>TableColumnModelListener</code> object
 454      */
 455     public void addColumnModelListener(TableColumnModelListener x) {
 456         listenerList.add(TableColumnModelListener.class, x);
 457     }
 458 
 459     // implements javax.swing.table.TableColumnModel
 460     /**
 461      * Removes a listener for table column model events.
 462      * @param x  a <code>TableColumnModelListener</code> object
 463      */
 464     public void removeColumnModelListener(TableColumnModelListener x) {
 465         listenerList.remove(TableColumnModelListener.class, x);
 466     }
 467 
 468     /**
 469      * Returns an array of all the column model listeners
 470      * registered on this model.
 471      *
 472      * @return all of this default table column model's <code>ColumnModelListener</code>s
 473      *         or an empty
 474      *         array if no column model listeners are currently registered
 475      *
 476      * @see #addColumnModelListener
 477      * @see #removeColumnModelListener
 478      *
 479      * @since 1.4
 480      */
 481     public TableColumnModelListener[] getColumnModelListeners() {
 482         return listenerList.getListeners(TableColumnModelListener.class);
 483     }
 484 
 485 //
 486 //   Event firing methods
 487 //
 488 
 489     /**
 490      * Notifies all listeners that have registered interest for
 491      * notification on this event type.  The event instance
 492      * is lazily created using the parameters passed into
 493      * the fire method.
 494      * @param e  the event received
 495      * @see EventListenerList
 496      */
 497     protected void fireColumnAdded(TableColumnModelEvent e) {
 498         // Guaranteed to return a non-null array
 499         Object[] listeners = listenerList.getListenerList();
 500         // Process the listeners last to first, notifying
 501         // those that are interested in this event
 502         for (int i = listeners.length-2; i>=0; i-=2) {
 503             if (listeners[i]==TableColumnModelListener.class) {
 504                 // Lazily create the event:
 505                 // if (e == null)
 506                 //  e = new ChangeEvent(this);
 507                 ((TableColumnModelListener)listeners[i+1]).
 508                     columnAdded(e);
 509             }
 510         }
 511     }
 512 
 513     /**
 514      * Notifies all listeners that have registered interest for
 515      * notification on this event type.  The event instance
 516      * is lazily created using the parameters passed into
 517      * the fire method.
 518      * @param  e  the event received
 519      * @see EventListenerList
 520      */
 521     protected void fireColumnRemoved(TableColumnModelEvent e) {
 522         // Guaranteed to return a non-null array
 523         Object[] listeners = listenerList.getListenerList();
 524         // Process the listeners last to first, notifying
 525         // those that are interested in this event
 526         for (int i = listeners.length-2; i>=0; i-=2) {
 527             if (listeners[i]==TableColumnModelListener.class) {
 528                 // Lazily create the event:
 529                 // if (e == null)
 530                 //  e = new ChangeEvent(this);
 531                 ((TableColumnModelListener)listeners[i+1]).
 532                     columnRemoved(e);
 533             }
 534         }
 535     }
 536 
 537     /**
 538      * Notifies all listeners that have registered interest for
 539      * notification on this event type.  The event instance
 540      * is lazily created using the parameters passed into
 541      * the fire method.
 542      * @param  e the event received
 543      * @see EventListenerList
 544      */
 545     protected void fireColumnMoved(TableColumnModelEvent e) {
 546         // Guaranteed to return a non-null array
 547         Object[] listeners = listenerList.getListenerList();
 548         // Process the listeners last to first, notifying
 549         // those that are interested in this event
 550         for (int i = listeners.length-2; i>=0; i-=2) {
 551             if (listeners[i]==TableColumnModelListener.class) {
 552                 // Lazily create the event:
 553                 // if (e == null)
 554                 //  e = new ChangeEvent(this);
 555                 ((TableColumnModelListener)listeners[i+1]).
 556                     columnMoved(e);
 557             }
 558         }
 559     }
 560 
 561     /**
 562      * Notifies all listeners that have registered interest for
 563      * notification on this event type.  The event instance
 564      * is lazily created using the parameters passed into
 565      * the fire method.
 566      * @param e the event received
 567      * @see EventListenerList
 568      */
 569     protected void fireColumnSelectionChanged(ListSelectionEvent e) {
 570         // Guaranteed to return a non-null array
 571         Object[] listeners = listenerList.getListenerList();
 572         // Process the listeners last to first, notifying
 573         // those that are interested in this event
 574         for (int i = listeners.length-2; i>=0; i-=2) {
 575             if (listeners[i]==TableColumnModelListener.class) {
 576                 // Lazily create the event:
 577                 // if (e == null)
 578                 //  e = new ChangeEvent(this);
 579                 ((TableColumnModelListener)listeners[i+1]).
 580                     columnSelectionChanged(e);
 581             }
 582         }
 583     }
 584 
 585     /**
 586      * Notifies all listeners that have registered interest for
 587      * notification on this event type.  The event instance
 588      * is lazily created using the parameters passed into
 589      * the fire method.
 590      * @see EventListenerList
 591      */
 592     protected void fireColumnMarginChanged() {
 593         // Guaranteed to return a non-null array
 594         Object[] listeners = listenerList.getListenerList();
 595         // Process the listeners last to first, notifying
 596         // those that are interested in this event
 597         for (int i = listeners.length-2; i>=0; i-=2) {
 598             if (listeners[i]==TableColumnModelListener.class) {
 599                 // Lazily create the event:
 600                 if (changeEvent == null)
 601                     changeEvent = new ChangeEvent(this);
 602                 ((TableColumnModelListener)listeners[i+1]).
 603                     columnMarginChanged(changeEvent);
 604             }
 605         }
 606     }
 607 
 608     /**
 609      * Returns an array of all the objects currently registered
 610      * as <code><em>Foo</em>Listener</code>s
 611      * upon this model.
 612      * <code><em>Foo</em>Listener</code>s are registered using the
 613      * <code>add<em>Foo</em>Listener</code> method.
 614      *
 615      * <p>
 616      *
 617      * You can specify the <code>listenerType</code> argument
 618      * with a class literal,
 619      * such as
 620      * <code><em>Foo</em>Listener.class</code>.
 621      * For example, you can query a
 622      * <code>DefaultTableColumnModel</code> <code>m</code>
 623      * for its column model listeners with the following code:
 624      *
 625      * <pre>ColumnModelListener[] cmls = (ColumnModelListener[])(m.getListeners(ColumnModelListener.class));</pre>
 626      *
 627      * If no such listeners exist, this method returns an empty array.
 628      *
 629      * @param listenerType the type of listeners requested; this parameter
 630      *          should specify an interface that descends from
 631      *          <code>java.util.EventListener</code>
 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     protected ListSelectionModel createSelectionModel() {
 695         return new DefaultListSelectionModel();
 696     }
 697 
 698     /**
 699      * Recalculates the total combined width of all columns.  Updates the
 700      * <code>totalColumnWidth</code> property.
 701      */
 702     protected void recalcWidthCache() {
 703         Enumeration enumeration = getColumns();
 704         totalColumnWidth = 0;
 705         while (enumeration.hasMoreElements()) {
 706             totalColumnWidth += ((TableColumn)enumeration.nextElement()).getWidth();
 707         }
 708     }
 709 
 710     private void invalidateWidthCache() {
 711         totalColumnWidth = -1;
 712     }
 713 
 714 } // End of class DefaultTableColumnModel