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