1 /*
   2  * Copyright (c) 1997, 2014, 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     transient protected 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 enumeration = getColumns();
 273         TableColumn aColumn;
 274         int index = 0;
 275 
 276         while (enumeration.hasMoreElements()) {
 277             aColumn = (TableColumn)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             int iMin = selectionModel.getMinSelectionIndex();
 431             int iMax = selectionModel.getMaxSelectionIndex();
 432 
 433             if ((iMin == -1) || (iMax == -1)) {
 434                 return new int[0];
 435             }
 436 
 437             int[] rvTmp = new int[1+ (iMax - iMin)];
 438             int n = 0;
 439             for(int i = iMin; i <= iMax; i++) {
 440                 if (selectionModel.isSelectedIndex(i)) {
 441                     rvTmp[n++] = i;
 442                 }
 443             }
 444             int[] rv = new int[n];
 445             System.arraycopy(rvTmp, 0, rv, 0, n);
 446             return rv;
 447         }
 448         return  new int[0];
 449     }
 450 
 451     // implements javax.swing.table.TableColumnModel
 452     /**
 453      * Returns the number of columns selected.
 454      * @return the number of columns selected
 455      */
 456     public int getSelectedColumnCount() {
 457         if (selectionModel != null) {
 458             int iMin = selectionModel.getMinSelectionIndex();
 459             int iMax = selectionModel.getMaxSelectionIndex();
 460             int count = 0;
 461 
 462             for(int i = iMin; i <= iMax; i++) {
 463                 if (selectionModel.isSelectedIndex(i)) {
 464                     count++;
 465                 }
 466             }
 467             return count;
 468         }
 469         return 0;
 470     }
 471 
 472 //
 473 // Listener Support Methods
 474 //
 475 
 476     // implements javax.swing.table.TableColumnModel
 477     /**
 478      * Adds a listener for table column model events.
 479      * @param x  a <code>TableColumnModelListener</code> object
 480      */
 481     public void addColumnModelListener(TableColumnModelListener x) {
 482         listenerList.add(TableColumnModelListener.class, x);
 483     }
 484 
 485     // implements javax.swing.table.TableColumnModel
 486     /**
 487      * Removes a listener for table column model events.
 488      * @param x  a <code>TableColumnModelListener</code> object
 489      */
 490     public void removeColumnModelListener(TableColumnModelListener x) {
 491         listenerList.remove(TableColumnModelListener.class, x);
 492     }
 493 
 494     /**
 495      * Returns an array of all the column model listeners
 496      * registered on this model.
 497      *
 498      * @return all of this default table column model's <code>ColumnModelListener</code>s
 499      *         or an empty
 500      *         array if no column model listeners are currently registered
 501      *
 502      * @see #addColumnModelListener
 503      * @see #removeColumnModelListener
 504      *
 505      * @since 1.4
 506      */
 507     public TableColumnModelListener[] getColumnModelListeners() {
 508         return listenerList.getListeners(TableColumnModelListener.class);
 509     }
 510 
 511 //
 512 //   Event firing methods
 513 //
 514 
 515     /**
 516      * Notifies all listeners that have registered interest for
 517      * notification on this event type.  The event instance
 518      * is lazily created using the parameters passed into
 519      * the fire method.
 520      * @param e  the event received
 521      * @see EventListenerList
 522      */
 523     protected void fireColumnAdded(TableColumnModelEvent e) {
 524         // Guaranteed to return a non-null array
 525         Object[] listeners = listenerList.getListenerList();
 526         // Process the listeners last to first, notifying
 527         // those that are interested in this event
 528         for (int i = listeners.length-2; i>=0; i-=2) {
 529             if (listeners[i]==TableColumnModelListener.class) {
 530                 // Lazily create the event:
 531                 // if (e == null)
 532                 //  e = new ChangeEvent(this);
 533                 ((TableColumnModelListener)listeners[i+1]).
 534                     columnAdded(e);
 535             }
 536         }
 537     }
 538 
 539     /**
 540      * Notifies all listeners that have registered interest for
 541      * notification on this event type.  The event instance
 542      * is lazily created using the parameters passed into
 543      * the fire method.
 544      * @param  e  the event received
 545      * @see EventListenerList
 546      */
 547     protected void fireColumnRemoved(TableColumnModelEvent e) {
 548         // Guaranteed to return a non-null array
 549         Object[] listeners = listenerList.getListenerList();
 550         // Process the listeners last to first, notifying
 551         // those that are interested in this event
 552         for (int i = listeners.length-2; i>=0; i-=2) {
 553             if (listeners[i]==TableColumnModelListener.class) {
 554                 // Lazily create the event:
 555                 // if (e == null)
 556                 //  e = new ChangeEvent(this);
 557                 ((TableColumnModelListener)listeners[i+1]).
 558                     columnRemoved(e);
 559             }
 560         }
 561     }
 562 
 563     /**
 564      * Notifies all listeners that have registered interest for
 565      * notification on this event type.  The event instance
 566      * is lazily created using the parameters passed into
 567      * the fire method.
 568      * @param  e the event received
 569      * @see EventListenerList
 570      */
 571     protected void fireColumnMoved(TableColumnModelEvent e) {
 572         // Guaranteed to return a non-null array
 573         Object[] listeners = listenerList.getListenerList();
 574         // Process the listeners last to first, notifying
 575         // those that are interested in this event
 576         for (int i = listeners.length-2; i>=0; i-=2) {
 577             if (listeners[i]==TableColumnModelListener.class) {
 578                 // Lazily create the event:
 579                 // if (e == null)
 580                 //  e = new ChangeEvent(this);
 581                 ((TableColumnModelListener)listeners[i+1]).
 582                     columnMoved(e);
 583             }
 584         }
 585     }
 586 
 587     /**
 588      * Notifies all listeners that have registered interest for
 589      * notification on this event type.  The event instance
 590      * is lazily created using the parameters passed into
 591      * the fire method.
 592      * @param e the event received
 593      * @see EventListenerList
 594      */
 595     protected void fireColumnSelectionChanged(ListSelectionEvent e) {
 596         // Guaranteed to return a non-null array
 597         Object[] listeners = listenerList.getListenerList();
 598         // Process the listeners last to first, notifying
 599         // those that are interested in this event
 600         for (int i = listeners.length-2; i>=0; i-=2) {
 601             if (listeners[i]==TableColumnModelListener.class) {
 602                 // Lazily create the event:
 603                 // if (e == null)
 604                 //  e = new ChangeEvent(this);
 605                 ((TableColumnModelListener)listeners[i+1]).
 606                     columnSelectionChanged(e);
 607             }
 608         }
 609     }
 610 
 611     /**
 612      * Notifies all listeners that have registered interest for
 613      * notification on this event type.  The event instance
 614      * is lazily created using the parameters passed into
 615      * the fire method.
 616      * @see EventListenerList
 617      */
 618     protected void fireColumnMarginChanged() {
 619         // Guaranteed to return a non-null array
 620         Object[] listeners = listenerList.getListenerList();
 621         // Process the listeners last to first, notifying
 622         // those that are interested in this event
 623         for (int i = listeners.length-2; i>=0; i-=2) {
 624             if (listeners[i]==TableColumnModelListener.class) {
 625                 // Lazily create the event:
 626                 if (changeEvent == null)
 627                     changeEvent = new ChangeEvent(this);
 628                 ((TableColumnModelListener)listeners[i+1]).
 629                     columnMarginChanged(changeEvent);
 630             }
 631         }
 632     }
 633 
 634     /**
 635      * Returns an array of all the objects currently registered
 636      * as <code><em>Foo</em>Listener</code>s
 637      * upon this model.
 638      * <code><em>Foo</em>Listener</code>s are registered using the
 639      * <code>add<em>Foo</em>Listener</code> method.
 640      *
 641      * <p>
 642      *
 643      * You can specify the <code>listenerType</code> argument
 644      * with a class literal,
 645      * such as
 646      * <code><em>Foo</em>Listener.class</code>.
 647      * For example, you can query a
 648      * <code>DefaultTableColumnModel</code> <code>m</code>
 649      * for its column model listeners with the following code:
 650      *
 651      * <pre>ColumnModelListener[] cmls = (ColumnModelListener[])(m.getListeners(ColumnModelListener.class));</pre>
 652      *
 653      * If no such listeners exist, this method returns an empty array.
 654      *
 655      * @param listenerType the type of listeners requested; this parameter
 656      *          should specify an interface that descends from
 657      *          <code>java.util.EventListener</code>
 658      * @return an array of all objects registered as
 659      *          <code><em>Foo</em>Listener</code>s on this model,
 660      *          or an empty array if no such
 661      *          listeners have been added
 662      * @exception ClassCastException if <code>listenerType</code>
 663      *          doesn't specify a class or interface that implements
 664      *          <code>java.util.EventListener</code>
 665      *
 666      * @see #getColumnModelListeners
 667      * @since 1.3
 668      */
 669     public <T extends EventListener> T[] getListeners(Class<T> listenerType) {
 670         return listenerList.getListeners(listenerType);
 671     }
 672 
 673 //
 674 // Implementing the PropertyChangeListener interface
 675 //
 676 
 677     // PENDING(alan)
 678     // implements java.beans.PropertyChangeListener
 679     /**
 680      * Property Change Listener change method.  Used to track changes
 681      * to the column width or preferred column width.
 682      *
 683      * @param  evt  <code>PropertyChangeEvent</code>
 684      */
 685     public void propertyChange(PropertyChangeEvent evt) {
 686         String name = evt.getPropertyName();
 687 
 688         if (name == "width" || name == "preferredWidth") {
 689             invalidateWidthCache();
 690             // This is a misnomer, we're using this method
 691             // simply to cause a relayout.
 692             fireColumnMarginChanged();
 693         }
 694 
 695     }
 696 
 697 //
 698 // Implementing ListSelectionListener interface
 699 //
 700 
 701     // implements javax.swing.event.ListSelectionListener
 702     /**
 703      * A <code>ListSelectionListener</code> that forwards
 704      * <code>ListSelectionEvents</code> when there is a column
 705      * selection change.
 706      *
 707      * @param e  the change event
 708      */
 709     public void valueChanged(ListSelectionEvent e) {
 710         fireColumnSelectionChanged(e);
 711     }
 712 
 713 //
 714 // Protected Methods
 715 //
 716 
 717     /**
 718      * Creates a new default list selection model.
 719      */
 720     protected ListSelectionModel createSelectionModel() {
 721         return new DefaultListSelectionModel();
 722     }
 723 
 724     /**
 725      * Recalculates the total combined width of all columns.  Updates the
 726      * <code>totalColumnWidth</code> property.
 727      */
 728     protected void recalcWidthCache() {
 729         Enumeration enumeration = getColumns();
 730         totalColumnWidth = 0;
 731         while (enumeration.hasMoreElements()) {
 732             totalColumnWidth += ((TableColumn)enumeration.nextElement()).getWidth();
 733         }
 734     }
 735 
 736     private void invalidateWidthCache() {
 737         totalColumnWidth = -1;
 738     }
 739 
 740 } // End of class DefaultTableColumnModel