001 /* DefaultTableColumnModel.java -- 002 Copyright (C) 2002, 2004, 2005, 2006, Free Software Foundation, Inc. 003 004 This file is part of GNU Classpath. 005 006 GNU Classpath is free software; you can redistribute it and/or modify 007 it under the terms of the GNU General Public License as published by 008 the Free Software Foundation; either version 2, or (at your option) 009 any later version. 010 011 GNU Classpath is distributed in the hope that it will be useful, but 012 WITHOUT ANY WARRANTY; without even the implied warranty of 013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 General Public License for more details. 015 016 You should have received a copy of the GNU General Public License 017 along with GNU Classpath; see the file COPYING. If not, write to the 018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 019 02110-1301 USA. 020 021 Linking this library statically or dynamically with other modules is 022 making a combined work based on this library. Thus, the terms and 023 conditions of the GNU General Public License cover the whole 024 combination. 025 026 As a special exception, the copyright holders of this library give you 027 permission to link this library with independent modules to produce an 028 executable, regardless of the license terms of these independent 029 modules, and to copy and distribute the resulting executable under 030 terms of your choice, provided that you also meet, for each linked 031 independent module, the terms and conditions of the license of that 032 module. An independent module is a module which is not derived from 033 or based on this library. If you modify this library, you may extend 034 this exception to your version of the library, but you are not 035 obligated to do so. If you do not wish to do so, delete this 036 exception statement from your version. */ 037 038 039 package javax.swing.table; 040 041 import java.beans.PropertyChangeEvent; 042 import java.beans.PropertyChangeListener; 043 import java.io.Serializable; 044 import java.util.Enumeration; 045 import java.util.EventListener; 046 import java.util.Vector; 047 048 import javax.swing.DefaultListSelectionModel; 049 import javax.swing.JTable; 050 import javax.swing.ListSelectionModel; 051 import javax.swing.event.ChangeEvent; 052 import javax.swing.event.EventListenerList; 053 import javax.swing.event.ListSelectionEvent; 054 import javax.swing.event.ListSelectionListener; 055 import javax.swing.event.TableColumnModelEvent; 056 import javax.swing.event.TableColumnModelListener; 057 058 /** 059 * A model that stores information about the columns used in a {@link JTable}. 060 * 061 * @see JTable#setColumnModel(TableColumnModel) 062 * 063 * @author Andrew Selkirk 064 */ 065 public class DefaultTableColumnModel 066 implements TableColumnModel, PropertyChangeListener, ListSelectionListener, 067 Serializable 068 { 069 private static final long serialVersionUID = 6580012493508960512L; 070 071 /** 072 * Storage for the table columns. 073 */ 074 protected Vector<TableColumn> tableColumns; 075 076 /** 077 * A selection model that keeps track of column selections. 078 */ 079 protected ListSelectionModel selectionModel; 080 081 /** 082 * The space between the columns (the default value is <code>1</code>). 083 */ 084 protected int columnMargin; 085 086 /** 087 * Storage for the listeners registered with the model. 088 */ 089 protected EventListenerList listenerList = new EventListenerList(); 090 091 /** 092 * A change event used when notifying listeners of a change to the 093 * <code>columnMargin</code> field. This single event is reused for all 094 * notifications (it is lazily instantiated within the 095 * {@link #fireColumnMarginChanged()} method). 096 */ 097 protected transient ChangeEvent changeEvent; 098 099 /** 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 = (TableColumn) 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 (TableColumn) 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 = ((TableColumn) 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 += ((TableColumn) 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 }