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™ 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