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