Source for javax.swing.table.DefaultTableColumnModel

   1: /* DefaultTableColumnModel.java --
   2:    Copyright (C) 2002, 2004, 2005, 2006,  Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package javax.swing.table;
  40: 
  41: import java.beans.PropertyChangeEvent;
  42: import java.beans.PropertyChangeListener;
  43: import java.io.Serializable;
  44: import java.util.Enumeration;
  45: import java.util.EventListener;
  46: import java.util.Vector;
  47: 
  48: import javax.swing.DefaultListSelectionModel;
  49: import javax.swing.JTable;
  50: import javax.swing.ListSelectionModel;
  51: import javax.swing.event.ChangeEvent;
  52: import javax.swing.event.EventListenerList;
  53: import javax.swing.event.ListSelectionEvent;
  54: import javax.swing.event.ListSelectionListener;
  55: import javax.swing.event.TableColumnModelEvent;
  56: import javax.swing.event.TableColumnModelListener;
  57: 
  58: /**
  59:  * A model that stores information about the columns used in a {@link JTable}.
  60:  *
  61:  * @see JTable#setColumnModel(TableColumnModel)
  62:  *
  63:  * @author      Andrew Selkirk
  64:  */
  65: public class DefaultTableColumnModel
  66:   implements TableColumnModel, PropertyChangeListener, ListSelectionListener,
  67:              Serializable
  68: {
  69:   private static final long serialVersionUID = 6580012493508960512L;
  70: 
  71:   /**
  72:    * Storage for the table columns.
  73:    */
  74:   protected Vector<TableColumn> tableColumns;
  75: 
  76:   /**
  77:    * A selection model that keeps track of column selections.
  78:    */
  79:   protected ListSelectionModel selectionModel;
  80: 
  81:   /**
  82:    * The space between the columns (the default value is <code>1</code>).
  83:    */
  84:   protected int columnMargin;
  85: 
  86:   /**
  87:    * Storage for the listeners registered with the model.
  88:    */
  89:   protected EventListenerList listenerList = new EventListenerList();
  90: 
  91:   /**
  92:    * A change event used when notifying listeners of a change to the
  93:    * <code>columnMargin</code> field.  This single event is reused for all
  94:    * notifications (it is lazily instantiated within the
  95:    * {@link #fireColumnMarginChanged()} method).
  96:    */
  97:   protected transient ChangeEvent changeEvent;
  98: 
  99:   /**
 100:    * A flag that indicates whether or not columns can be selected.
 101:    */
 102:   protected boolean columnSelectionAllowed;
 103: 
 104:   /**
 105:    * The total width of all the columns in this model.
 106:    */
 107:   protected int totalColumnWidth;
 108: 
 109:   /**
 110:    * Creates a new table column model with zero columns.  A default column
 111:    * selection model is created by calling {@link #createSelectionModel()}.
 112:    * The default value for <code>columnMargin</code> is <code>1</code> and
 113:    * the default value for <code>columnSelectionAllowed</code> is
 114:    * <code>false</code>.
 115:    */
 116:   public DefaultTableColumnModel()
 117:   {
 118:     tableColumns = new Vector();
 119:     selectionModel = createSelectionModel();
 120:     selectionModel.addListSelectionListener(this);
 121:     columnMargin = 1;
 122:     columnSelectionAllowed = false;
 123:   }
 124: 
 125:   /**
 126:    * Adds a column to the model then calls
 127:    * {@link #fireColumnAdded(TableColumnModelEvent)} to notify the registered
 128:    * listeners.  The model registers itself with the column as a
 129:    * {@link PropertyChangeListener} so that changes to the column width will
 130:    * invalidate the cached {@link #totalColumnWidth} value.
 131:    *
 132:    * @param column  the column (<code>null</code> not permitted).
 133:    *
 134:    * @throws IllegalArgumentException if <code>column</code> is
 135:    *     <code>null</code>.
 136:    *
 137:    * @see #removeColumn(TableColumn)
 138:    */
 139:   public void addColumn(TableColumn column)
 140:   {
 141:     if (column == null)
 142:       throw new IllegalArgumentException("Null 'col' argument.");
 143:     tableColumns.add(column);
 144:     column.addPropertyChangeListener(this);
 145:     invalidateWidthCache();
 146:     fireColumnAdded(new TableColumnModelEvent(this, 0,
 147:                                               tableColumns.size() - 1));
 148:   }
 149: 
 150:   /**
 151:    * Removes a column from the model then calls
 152:    * {@link #fireColumnRemoved(TableColumnModelEvent)} to notify the registered
 153:    * listeners.  If the specified column does not belong to the model, or is
 154:    * <code>null</code>, this method does nothing.
 155:    *
 156:    * @param column the column to be removed (<code>null</code> permitted).
 157:    *
 158:    * @see #addColumn(TableColumn)
 159:    */
 160:   public void removeColumn(TableColumn column)
 161:   {
 162:     int index = this.tableColumns.indexOf(column);
 163:     if (index < 0)
 164:       return;
 165:     tableColumns.remove(column);
 166:     fireColumnRemoved(new TableColumnModelEvent(this, index, 0));
 167:     column.removePropertyChangeListener(this);
 168:     invalidateWidthCache();
 169:   }
 170: 
 171:   /**
 172:    * Moves the column at index i to the position specified by index j, then
 173:    * calls {@link #fireColumnMoved(TableColumnModelEvent)} to notify registered
 174:    * listeners.
 175:    *
 176:    * @param i index of the column that will be moved.
 177:    * @param j index of the column's new location.
 178:    *
 179:    * @throws IllegalArgumentException if <code>i</code> or <code>j</code> are
 180:    *     outside the range <code>0</code> to <code>N-1</code>, where
 181:    *     <code>N</code> is the column count.
 182:    */
 183:   public void moveColumn(int i, int j)
 184:   {
 185:     int columnCount = getColumnCount();
 186:     if (i < 0 || i >= columnCount)
 187:       throw new IllegalArgumentException("Index 'i' out of range.");
 188:     if (j < 0 || j >= columnCount)
 189:       throw new IllegalArgumentException("Index 'j' out of range.");
 190:     TableColumn column = tableColumns.remove(i);
 191:     tableColumns.add(j, column);
 192:     fireColumnMoved(new TableColumnModelEvent(this, i, j));
 193:   }
 194: 
 195:   /**
 196:    * Sets the column margin then calls {@link #fireColumnMarginChanged()} to
 197:    * notify the registered listeners.
 198:    *
 199:    * @param margin  the column margin.
 200:    *
 201:    * @see #getColumnMargin()
 202:    */
 203:   public void setColumnMargin(int margin)
 204:   {
 205:     columnMargin = margin;
 206:     fireColumnMarginChanged();
 207:   }
 208: 
 209:   /**
 210:    * Returns the number of columns in the model.
 211:    *
 212:    * @return The column count.
 213:    */
 214:   public int getColumnCount()
 215:   {
 216:     return tableColumns.size();
 217:   }
 218: 
 219:   /**
 220:    * Returns an enumeration of the columns in the model.
 221:    *
 222:    * @return An enumeration of the columns in the model.
 223:    */
 224:   public Enumeration<TableColumn> getColumns()
 225:   {
 226:     return tableColumns.elements();
 227:   }
 228: 
 229:   /**
 230:    * Returns the index of the {@link TableColumn} with the given identifier.
 231:    *
 232:    * @param identifier  the identifier (<code>null</code> not permitted).
 233:    *
 234:    * @return The index of the {@link TableColumn} with the given identifier.
 235:    *
 236:    * @throws IllegalArgumentException if <code>identifier</code> is
 237:    *         <code>null</code> or there is no column with that identifier.
 238:    */
 239:   public int getColumnIndex(Object identifier)
 240:   {
 241:     if (identifier == null)
 242:       throw new IllegalArgumentException("Null identifier.");
 243:     int columnCount = tableColumns.size();
 244:     for (int i = 0; i < columnCount; i++)
 245:     {
 246:       TableColumn tc = tableColumns.get(i);
 247:       if (identifier.equals(tc.getIdentifier()))
 248:         return i;
 249:     }
 250:     throw new IllegalArgumentException("No TableColumn with that identifier.");
 251:   }
 252: 
 253:   /**
 254:    * Returns the column at the specified index.
 255:    *
 256:    * @param columnIndex  the column index (in the range from <code>0</code> to
 257:    *     <code>N-1</code>, where <code>N</code> is the number of columns in
 258:    *     the model).
 259:    *
 260:    * @return The column at the specified index.
 261:    *
 262:    * @throws ArrayIndexOutOfBoundsException if <code>i</code> is not within
 263:    *     the specified range.
 264:    */
 265:   public TableColumn getColumn(int columnIndex)
 266:   {
 267:     return tableColumns.get(columnIndex);
 268:   }
 269: 
 270:   /**
 271:    * Returns the column margin.
 272:    *
 273:    * @return The column margin.
 274:    *
 275:    * @see #setColumnMargin(int)
 276:    */
 277:   public int getColumnMargin()
 278:   {
 279:     return columnMargin;
 280:   }
 281: 
 282:   /**
 283:    * Returns the index of the column that contains the specified x-coordinate.
 284:    * This method assumes that:
 285:    * <ul>
 286:    * <li>column zero begins at position zero;</li>
 287:    * <li>all columns appear in order;</li>
 288:    * <li>individual column widths are taken into account, but the column margin
 289:    *     is ignored.</li>
 290:    * </ul>
 291:    * If no column contains the specified position, this method returns
 292:    * <code>-1</code>.
 293:    *
 294:    * @param x  the x-position.
 295:    *
 296:    * @return The column index, or <code>-1</code>.
 297:    */
 298:   public int getColumnIndexAtX(int x)
 299:   {
 300:     for (int i = 0; i < tableColumns.size(); ++i)
 301:       {
 302:         int w = (tableColumns.get(i)).getWidth();
 303:         if (0 <= x && x < w)
 304:           return i;
 305:         else
 306:           x -= w;
 307:       }
 308:     return -1;
 309:   }
 310: 
 311:   /**
 312:    * Returns total width of all the columns in the model, ignoring the
 313:    * {@link #columnMargin}.
 314:    *
 315:    * @return The total width of all the columns.
 316:    */
 317:   public int getTotalColumnWidth()
 318:   {
 319:     if (totalColumnWidth == -1)
 320:       recalcWidthCache();
 321:     return totalColumnWidth;
 322:   }
 323: 
 324:   /**
 325:    * Sets the selection model that will be used to keep track of the selected
 326:    * columns.
 327:    *
 328:    * @param model  the selection model (<code>null</code> not permitted).
 329:    *
 330:    * @throws IllegalArgumentException if <code>model</code> is
 331:    *     <code>null</code>.
 332:    *
 333:    * @see #getSelectionModel()
 334:    */
 335:   public void setSelectionModel(ListSelectionModel model)
 336:   {
 337:     if (model == null)
 338:       throw new IllegalArgumentException();
 339: 
 340:     selectionModel.removeListSelectionListener(this);
 341:     selectionModel = model;
 342:     selectionModel.addListSelectionListener(this);
 343:   }
 344: 
 345:   /**
 346:    * Returns the selection model used to track table column selections.
 347:    *
 348:    * @return The selection model.
 349:    *
 350:    * @see #setSelectionModel(ListSelectionModel)
 351:    */
 352:   public ListSelectionModel getSelectionModel()
 353:   {
 354:     return selectionModel;
 355:   }
 356: 
 357:   /**
 358:    * Sets the flag that indicates whether or not column selection is allowed.
 359:    *
 360:    * @param flag  the new flag value.
 361:    *
 362:    * @see #getColumnSelectionAllowed()
 363:    */
 364:   public void setColumnSelectionAllowed(boolean flag)
 365:   {
 366:     columnSelectionAllowed = flag;
 367:   }
 368: 
 369:   /**
 370:    * Returns <code>true</code> if column selection is allowed, and
 371:    * <code>false</code> if column selection is not allowed.
 372:    *
 373:    * @return A boolean.
 374:    *
 375:    * @see #setColumnSelectionAllowed(boolean)
 376:    */
 377:   public boolean getColumnSelectionAllowed()
 378:   {
 379:     return columnSelectionAllowed;
 380:   }
 381: 
 382:   /**
 383:    * Returns an array containing the indices of the selected columns.
 384:    *
 385:    * @return An array containing the indices of the selected columns.
 386:    */
 387:   public int[] getSelectedColumns()
 388:   {
 389:     // FIXME: Implementation of this method was taken from private method
 390:     // JTable.getSelections(), which is used in various places in JTable
 391:     // including selected row calculations and cannot be simply removed.
 392:     // This design should be improved to illuminate duplication of code.
 393: 
 394:     ListSelectionModel lsm = this.selectionModel;
 395:     int sz = getSelectedColumnCount();
 396:     int [] ret = new int[sz];
 397: 
 398:     int lo = lsm.getMinSelectionIndex();
 399:     int hi = lsm.getMaxSelectionIndex();
 400:     int j = 0;
 401:     java.util.ArrayList ls = new java.util.ArrayList();
 402:     if (lo != -1 && hi != -1)
 403:       {
 404:         switch (lsm.getSelectionMode())
 405:           {
 406:           case ListSelectionModel.SINGLE_SELECTION:
 407:             ret[0] = lo;
 408:             break;
 409: 
 410:           case ListSelectionModel.SINGLE_INTERVAL_SELECTION:
 411:             for (int i = lo; i <= hi; ++i)
 412:               ret[j++] = i;
 413:             break;
 414: 
 415:           case ListSelectionModel.MULTIPLE_INTERVAL_SELECTION:
 416:             for (int i = lo; i <= hi; ++i)
 417:               if (lsm.isSelectedIndex(i))
 418:                 ret[j++] = i;
 419:             break;
 420:           }
 421:       }
 422:     return ret;
 423:   }
 424: 
 425:   /**
 426:    * Returns the number of selected columns in the model.
 427:    *
 428:    * @return The selected column count.
 429:    *
 430:    * @see #getSelectionModel()
 431:    */
 432:   public int getSelectedColumnCount()
 433:   {
 434:     // FIXME: Implementation of this method was taken from private method
 435:     // JTable.countSelections(), which is used in various places in JTable
 436:     // including selected row calculations and cannot be simply removed.
 437:     // This design should be improved to illuminate duplication of code.
 438: 
 439:     ListSelectionModel lsm = this.selectionModel;
 440:     int lo = lsm.getMinSelectionIndex();
 441:     int hi = lsm.getMaxSelectionIndex();
 442:     int sum = 0;
 443: 
 444:     if (lo != -1 && hi != -1)
 445:       {
 446:         switch (lsm.getSelectionMode())
 447:           {
 448:           case ListSelectionModel.SINGLE_SELECTION:
 449:             sum = 1;
 450:             break;
 451: 
 452:           case ListSelectionModel.SINGLE_INTERVAL_SELECTION:
 453:             sum = hi - lo + 1;
 454:             break;
 455: 
 456:           case ListSelectionModel.MULTIPLE_INTERVAL_SELECTION:
 457:             for (int i = lo; i <= hi; ++i)
 458:               if (lsm.isSelectedIndex(i))
 459:                 ++sum;
 460:             break;
 461:           }
 462:       }
 463: 
 464:      return sum;
 465:   }
 466: 
 467:   /**
 468:    * Registers a listener with the model, so that it will receive
 469:    * {@link TableColumnModelEvent} notifications.
 470:    *
 471:    * @param listener the listener (<code>null</code> ignored).
 472:    */
 473:   public void addColumnModelListener(TableColumnModelListener listener)
 474:   {
 475:     listenerList.add(TableColumnModelListener.class, listener);
 476:   }
 477: 
 478:   /**
 479:    * Deregisters a listener so that it no longer receives notification of
 480:    * changes to this model.
 481:    *
 482:    * @param listener  the listener to remove
 483:    */
 484:   public void removeColumnModelListener(TableColumnModelListener listener)
 485:   {
 486:     listenerList.remove(TableColumnModelListener.class, listener);
 487:   }
 488: 
 489:   /**
 490:    * Returns an array containing the listeners that are registered with the
 491:    * model.  If there are no listeners, an empty array is returned.
 492:    *
 493:    * @return An array containing the listeners that are registered with the
 494:    *     model.
 495:    *
 496:    * @see #addColumnModelListener(TableColumnModelListener)
 497:    * @since 1.4
 498:    */
 499:   public TableColumnModelListener[] getColumnModelListeners()
 500:   {
 501:     return (TableColumnModelListener[])
 502:       listenerList.getListeners(TableColumnModelListener.class);
 503:   }
 504: 
 505:   /**
 506:    * Sends the specified {@link TableColumnModelEvent} to all registered
 507:    * listeners, to indicate that a column has been added to the model.  The
 508:    * event's <code>toIndex</code> attribute should contain the index of the
 509:    * added column.
 510:    *
 511:    * @param e  the event.
 512:    *
 513:    * @see #addColumn(TableColumn)
 514:    */
 515:   protected void fireColumnAdded(TableColumnModelEvent e)
 516:   {
 517:     TableColumnModelListener[] listeners = getColumnModelListeners();
 518: 
 519:     for (int i = 0; i < listeners.length; i++)
 520:       listeners[i].columnAdded(e);
 521:   }
 522: 
 523:   /**
 524:    * Sends the specified {@link TableColumnModelEvent} to all registered
 525:    * listeners, to indicate that a column has been removed from the model.  The
 526:    * event's <code>fromIndex</code> attribute should contain the index of the
 527:    * removed column.
 528:    *
 529:    * @param e  the event.
 530:    *
 531:    * @see #removeColumn(TableColumn)
 532:    */
 533:   protected void fireColumnRemoved(TableColumnModelEvent e)
 534:   {
 535:     TableColumnModelListener[] listeners = getColumnModelListeners();
 536: 
 537:     for (int i = 0; i < listeners.length; i++)
 538:       listeners[i].columnRemoved(e);
 539:   }
 540: 
 541:   /**
 542:    * Sends the specified {@link TableColumnModelEvent} to all registered
 543:    * listeners, to indicate that a column in the model has been moved.  The
 544:    * event's <code>fromIndex</code> attribute should contain the old column
 545:    * index, and the <code>toIndex</code> attribute should contain the new
 546:    * column index.
 547:    *
 548:    * @param e  the event.
 549:    *
 550:    * @see #moveColumn(int, int)
 551:    */
 552:   protected void fireColumnMoved(TableColumnModelEvent e)
 553:   {
 554:     TableColumnModelListener[] listeners = getColumnModelListeners();
 555: 
 556:     for (int i = 0; i < listeners.length; i++)
 557:       listeners[i].columnMoved(e);
 558:   }
 559: 
 560:   /**
 561:    * Sends the specified {@link ListSelectionEvent} to all registered listeners,
 562:    * to indicate that the column selections have changed.
 563:    *
 564:    * @param e  the event.
 565:    *
 566:    * @see #valueChanged(ListSelectionEvent)
 567:    */
 568:   protected void fireColumnSelectionChanged(ListSelectionEvent e)
 569:   {
 570:     EventListener [] listeners = getListeners(TableColumnModelListener.class);
 571:     for (int i = 0; i < listeners.length; ++i)
 572:       ((TableColumnModelListener) listeners[i]).columnSelectionChanged(e);
 573:   }
 574: 
 575:   /**
 576:    * Sends a {@link ChangeEvent} to the model's registered listeners to
 577:    * indicate that the column margin was changed.
 578:    *
 579:    * @see #setColumnMargin(int)
 580:    */
 581:   protected void fireColumnMarginChanged()
 582:   {
 583:     EventListener[] listeners = getListeners(TableColumnModelListener.class);
 584:     if (changeEvent == null && listeners.length > 0)
 585:       changeEvent = new ChangeEvent(this);
 586:     for (int i = 0; i < listeners.length; ++i)
 587:       ((TableColumnModelListener) listeners[i]).columnMarginChanged(changeEvent);
 588:   }
 589: 
 590:   /**
 591:    * Returns an array containing the listeners (of the specified type) that
 592:    * are registered with this model.
 593:    *
 594:    * @param listenerType  the listener type (must indicate a subclass of
 595:    *     {@link EventListener}, <code>null</code> not permitted).
 596:    *
 597:    * @return An array containing the listeners (of the specified type) that
 598:    *     are registered with this model.
 599:    */
 600:   public <T extends EventListener> T[] getListeners(Class<T> listenerType)
 601:   {
 602:     return listenerList.getListeners(listenerType);
 603:   }
 604: 
 605:   /**
 606:    * Receives notification of property changes for the columns in the model.
 607:    * If the <code>width</code> property for any column changes, we invalidate
 608:    * the {@link #totalColumnWidth} value here.
 609:    *
 610:    * @param event  the event.
 611:    */
 612:   public void propertyChange(PropertyChangeEvent event)
 613:   {
 614:     if (event.getPropertyName().equals("width"))
 615:           invalidateWidthCache();
 616:   }
 617: 
 618:   /**
 619:    * Receives notification of the change to the list selection model, and
 620:    * responds by calling
 621:    * {@link #fireColumnSelectionChanged(ListSelectionEvent)}.
 622:    *
 623:    * @param e  the list selection event.
 624:    *
 625:    * @see #getSelectionModel()
 626:    */
 627:   public void valueChanged(ListSelectionEvent e)
 628:   {
 629:     fireColumnSelectionChanged(e);
 630:   }
 631: 
 632:   /**
 633:    * Creates a default selection model to track the currently selected
 634:    * column(s).  This method is called by the constructor and returns a new
 635:    * instance of {@link DefaultListSelectionModel}.
 636:    *
 637:    * @return A new default column selection model.
 638:    */
 639:   protected ListSelectionModel createSelectionModel()
 640:   {
 641:     return new DefaultListSelectionModel();
 642:   }
 643: 
 644:   /**
 645:    * Recalculates the total width of the columns, if the cached value is
 646:    * <code>-1</code>.  Otherwise this method does nothing.
 647:    *
 648:    * @see #getTotalColumnWidth()
 649:    */
 650:   protected void recalcWidthCache()
 651:   {
 652:     if (totalColumnWidth == -1)
 653:       {
 654:         totalColumnWidth = 0;
 655:         for (int i = 0; i < tableColumns.size(); ++i)
 656:           {
 657:             totalColumnWidth += tableColumns.get(i).getWidth();
 658:           }
 659:       }
 660:   }
 661: 
 662:   /**
 663:    * Sets the {@link #totalColumnWidth} field to <code>-1</code>.
 664:    *
 665:    * @see #recalcWidthCache()
 666:    */
 667:   private void invalidateWidthCache()
 668:   {
 669:     totalColumnWidth = -1;
 670:   }
 671: }