1 /* 2 * Copyright (c) 1997, 2015, 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 @SuppressWarnings("serial") // Same-version serialization only 56 public class DefaultTableColumnModel implements TableColumnModel, 57 PropertyChangeListener, ListSelectionListener, Serializable 58 { 59 // 60 // Instance Variables 61 // 62 63 /** Array of TableColumn objects in this model */ 64 protected Vector<TableColumn> tableColumns; 65 66 /** Model for keeping track of column selections */ 67 protected ListSelectionModel selectionModel; 68 69 /** Width margin between each column */ 70 protected int columnMargin; 71 72 /** List of TableColumnModelListener */ 73 protected EventListenerList listenerList = new EventListenerList(); 74 75 /** Change event (only one needed) */ 76 protected transient ChangeEvent changeEvent = null; 77 78 /** Column selection allowed in this column model */ 79 protected boolean columnSelectionAllowed; 80 81 /** A local cache of the combined width of all columns */ 82 protected int totalColumnWidth; 83 84 // 85 // Constructors 86 // 87 /** 88 * Creates a default table column model. 89 */ 90 public DefaultTableColumnModel() { 91 super(); 92 93 // Initialize local ivars to default 94 tableColumns = new Vector<TableColumn>(); 95 setSelectionModel(createSelectionModel()); 96 setColumnMargin(1); 97 invalidateWidthCache(); 98 setColumnSelectionAllowed(false); 99 } 100 101 // 102 // Modifying the model 103 // 104 105 /** 106 * Appends <code>aColumn</code> to the end of the 107 * <code>tableColumns</code> array. 108 * This method also posts the <code>columnAdded</code> 109 * event to its listeners. 110 * 111 * @param aColumn the <code>TableColumn</code> to be added 112 * @exception IllegalArgumentException if <code>aColumn</code> is 113 * <code>null</code> 114 * @see #removeColumn 115 */ 116 public void addColumn(TableColumn aColumn) { 117 if (aColumn == null) { 118 throw new IllegalArgumentException("Object is null"); 119 } 120 121 tableColumns.addElement(aColumn); 122 aColumn.addPropertyChangeListener(this); 123 invalidateWidthCache(); 124 125 // Post columnAdded event notification 126 fireColumnAdded(new TableColumnModelEvent(this, 0, 127 getColumnCount() - 1)); 128 } 129 130 /** 131 * Deletes the <code>column</code> from the 132 * <code>tableColumns</code> array. This method will do nothing if 133 * <code>column</code> is not in the table's columns list. 134 * <code>tile</code> is called 135 * to resize both the header and table views. 136 * This method also posts a <code>columnRemoved</code> 137 * event to its listeners. 138 * 139 * @param column the <code>TableColumn</code> to be removed 140 * @see #addColumn 141 */ 142 public void removeColumn(TableColumn column) { 143 int columnIndex = tableColumns.indexOf(column); 144 145 if (columnIndex != -1) { 146 // Adjust for the selection 147 if (selectionModel != null) { 148 selectionModel.removeIndexInterval(columnIndex,columnIndex); 149 } 150 151 column.removePropertyChangeListener(this); 152 tableColumns.removeElementAt(columnIndex); 153 invalidateWidthCache(); 154 155 // Post columnAdded event notification. (JTable and JTableHeader 156 // listens so they can adjust size and redraw) 157 fireColumnRemoved(new TableColumnModelEvent(this, 158 columnIndex, 0)); 159 } 160 } 161 162 /** 163 * Moves the column and heading at <code>columnIndex</code> to 164 * <code>newIndex</code>. The old column at <code>columnIndex</code> 165 * will now be found at <code>newIndex</code>. The column 166 * that used to be at <code>newIndex</code> is shifted 167 * left or right to make room. This will not move any columns if 168 * <code>columnIndex</code> equals <code>newIndex</code>. This method 169 * also posts a <code>columnMoved</code> event to its listeners. 170 * 171 * @param columnIndex the index of column to be moved 172 * @param newIndex new index to move the column 173 * @exception IllegalArgumentException if <code>column</code> or 174 * <code>newIndex</code> 175 * are not in the valid range 176 */ 177 public void moveColumn(int columnIndex, int newIndex) { 178 if ((columnIndex < 0) || (columnIndex >= getColumnCount()) || 179 (newIndex < 0) || (newIndex >= getColumnCount())) 180 throw new IllegalArgumentException("moveColumn() - Index out of range"); 181 182 TableColumn aColumn; 183 184 // If the column has not yet moved far enough to change positions 185 // post the event anyway, the "draggedDistance" property of the 186 // tableHeader will say how far the column has been dragged. 187 // Here we are really trying to get the best out of an 188 // API that could do with some rethinking. We preserve backward 189 // compatibility by slightly bending the meaning of these methods. 190 if (columnIndex == newIndex) { 191 fireColumnMoved(new TableColumnModelEvent(this, columnIndex, newIndex)); 192 return; 193 } 194 aColumn = tableColumns.elementAt(columnIndex); 195 196 tableColumns.removeElementAt(columnIndex); 197 boolean selected = selectionModel.isSelectedIndex(columnIndex); 198 selectionModel.removeIndexInterval(columnIndex,columnIndex); 199 200 tableColumns.insertElementAt(aColumn, newIndex); 201 selectionModel.insertIndexInterval(newIndex, 1, true); 202 if (selected) { 203 selectionModel.addSelectionInterval(newIndex, newIndex); 204 } 205 else { 206 selectionModel.removeSelectionInterval(newIndex, newIndex); 207 } 208 209 fireColumnMoved(new TableColumnModelEvent(this, columnIndex, 210 newIndex)); 211 } 212 213 /** 214 * Sets the column margin to <code>newMargin</code>. This method 215 * also posts a <code>columnMarginChanged</code> event to its 216 * listeners. 217 * 218 * @param newMargin the new margin width, in pixels 219 * @see #getColumnMargin 220 * @see #getTotalColumnWidth 221 */ 222 public void setColumnMargin(int newMargin) { 223 if (newMargin != columnMargin) { 224 columnMargin = newMargin; 225 // Post columnMarginChanged event notification. 226 fireColumnMarginChanged(); 227 } 228 } 229 230 // 231 // Querying the model 232 // 233 234 /** 235 * Returns the number of columns in the <code>tableColumns</code> array. 236 * 237 * @return the number of columns in the <code>tableColumns</code> array 238 * @see #getColumns 239 */ 240 public int getColumnCount() { 241 return tableColumns.size(); 242 } 243 244 /** 245 * Returns an <code>Enumeration</code> of all the columns in the model. 246 * @return an <code>Enumeration</code> of the columns in the model 247 */ 248 public Enumeration<TableColumn> getColumns() { 249 return tableColumns.elements(); 250 } 251 252 /** 253 * Returns the index of the first column in the <code>tableColumns</code> 254 * array whose identifier is equal to <code>identifier</code>, 255 * when compared using <code>equals</code>. 256 * 257 * @param identifier the identifier object 258 * @return the index of the first column in the 259 * <code>tableColumns</code> array whose identifier 260 * is equal to <code>identifier</code> 261 * @exception IllegalArgumentException if <code>identifier</code> 262 * is <code>null</code>, or if no 263 * <code>TableColumn</code> has this 264 * <code>identifier</code> 265 * @see #getColumn 266 */ 267 public int getColumnIndex(Object identifier) { 268 if (identifier == null) { 269 throw new IllegalArgumentException("Identifier is null"); 270 } 271 272 Enumeration<TableColumn> enumeration = getColumns(); 273 TableColumn aColumn; 274 int index = 0; 275 276 while (enumeration.hasMoreElements()) { 277 aColumn = enumeration.nextElement(); 278 // Compare them this way in case the column's identifier is null. 279 if (identifier.equals(aColumn.getIdentifier())) 280 return index; 281 index++; 282 } 283 throw new IllegalArgumentException("Identifier not found"); 284 } 285 286 /** 287 * Returns the <code>TableColumn</code> object for the column 288 * at <code>columnIndex</code>. 289 * 290 * @param columnIndex the index of the column desired 291 * @return the <code>TableColumn</code> object for the column 292 * at <code>columnIndex</code> 293 */ 294 public TableColumn getColumn(int columnIndex) { 295 return tableColumns.elementAt(columnIndex); 296 } 297 298 /** 299 * Returns the width margin for <code>TableColumn</code>. 300 * The default <code>columnMargin</code> is 1. 301 * 302 * @return the maximum width for the <code>TableColumn</code> 303 * @see #setColumnMargin 304 */ 305 public int getColumnMargin() { 306 return columnMargin; 307 } 308 309 /** 310 * Returns the index of the column that lies at position <code>x</code>, 311 * or -1 if no column covers this point. 312 * 313 * In keeping with Swing's separable model architecture, a 314 * TableColumnModel does not know how the table columns actually appear on 315 * screen. The visual presentation of the columns is the responsibility 316 * of the view/controller object using this model (typically JTable). The 317 * view/controller need not display the columns sequentially from left to 318 * right. For example, columns could be displayed from right to left to 319 * accommodate a locale preference or some columns might be hidden at the 320 * request of the user. Because the model does not know how the columns 321 * are laid out on screen, the given <code>xPosition</code> should not be 322 * considered to be a coordinate in 2D graphics space. Instead, it should 323 * be considered to be a width from the start of the first column in the 324 * model. If the column index for a given X coordinate in 2D space is 325 * required, <code>JTable.columnAtPoint</code> can be used instead. 326 * 327 * @param x the horizontal location of interest 328 * @return the index of the column or -1 if no column is found 329 * @see javax.swing.JTable#columnAtPoint 330 */ 331 public int getColumnIndexAtX(int x) { 332 if (x < 0) { 333 return -1; 334 } 335 int cc = getColumnCount(); 336 for(int column = 0; column < cc; column++) { 337 x = x - getColumn(column).getWidth(); 338 if (x < 0) { 339 return column; 340 } 341 } 342 return -1; 343 } 344 345 /** 346 * Returns the total combined width of all columns. 347 * @return the <code>totalColumnWidth</code> property 348 */ 349 public int getTotalColumnWidth() { 350 if (totalColumnWidth == -1) { 351 recalcWidthCache(); 352 } 353 return totalColumnWidth; 354 } 355 356 // 357 // Selection model 358 // 359 360 /** 361 * Sets the selection model for this <code>TableColumnModel</code> 362 * to <code>newModel</code> 363 * and registers for listener notifications from the new selection 364 * model. If <code>newModel</code> is <code>null</code>, 365 * an exception is thrown. 366 * 367 * @param newModel the new selection model 368 * @exception IllegalArgumentException if <code>newModel</code> 369 * is <code>null</code> 370 * @see #getSelectionModel 371 */ 372 public void setSelectionModel(ListSelectionModel newModel) { 373 if (newModel == null) { 374 throw new IllegalArgumentException("Cannot set a null SelectionModel"); 375 } 376 377 ListSelectionModel oldModel = selectionModel; 378 379 if (newModel != oldModel) { 380 if (oldModel != null) { 381 oldModel.removeListSelectionListener(this); 382 } 383 384 selectionModel= newModel; 385 newModel.addListSelectionListener(this); 386 } 387 } 388 389 /** 390 * Returns the <code>ListSelectionModel</code> that is used to 391 * maintain column selection state. 392 * 393 * @return the object that provides column selection state. Or 394 * <code>null</code> if row selection is not allowed. 395 * @see #setSelectionModel 396 */ 397 public ListSelectionModel getSelectionModel() { 398 return selectionModel; 399 } 400 401 // implements javax.swing.table.TableColumnModel 402 /** 403 * Sets whether column selection is allowed. The default is false. 404 * @param flag true if column selection will be allowed, false otherwise 405 */ 406 public void setColumnSelectionAllowed(boolean flag) { 407 columnSelectionAllowed = flag; 408 } 409 410 // implements javax.swing.table.TableColumnModel 411 /** 412 * Returns true if column selection is allowed, otherwise false. 413 * The default is false. 414 * @return the <code>columnSelectionAllowed</code> property 415 */ 416 public boolean getColumnSelectionAllowed() { 417 return columnSelectionAllowed; 418 } 419 420 // implements javax.swing.table.TableColumnModel 421 /** 422 * Returns an array of selected columns. If <code>selectionModel</code> 423 * is <code>null</code>, returns an empty array. 424 * @return an array of selected columns or an empty array if nothing 425 * is selected or the <code>selectionModel</code> is 426 * <code>null</code> 427 */ 428 public int[] getSelectedColumns() { 429 if (selectionModel != null) { 430 return selectionModel.getSelectedIndices(); 431 } 432 return new int[0]; 433 } 434 435 // implements javax.swing.table.TableColumnModel 436 /** 437 * Returns the number of columns selected. 438 * @return the number of columns selected 439 */ 440 public int getSelectedColumnCount() { 441 if (selectionModel != null) { 442 return selectionModel.getSelectedItemsCount(); 443 } 444 return 0; 445 } 446 447 // 448 // Listener Support Methods 449 // 450 451 // implements javax.swing.table.TableColumnModel 452 /** 453 * Adds a listener for table column model events. 454 * @param x a <code>TableColumnModelListener</code> object 455 */ 456 public void addColumnModelListener(TableColumnModelListener x) { 457 listenerList.add(TableColumnModelListener.class, x); 458 } 459 460 // implements javax.swing.table.TableColumnModel 461 /** 462 * Removes a listener for table column model events. 463 * @param x a <code>TableColumnModelListener</code> object 464 */ 465 public void removeColumnModelListener(TableColumnModelListener x) { 466 listenerList.remove(TableColumnModelListener.class, x); 467 } 468 469 /** 470 * Returns an array of all the column model listeners 471 * registered on this model. 472 * 473 * @return all of this default table column model's <code>ColumnModelListener</code>s 474 * or an empty 475 * array if no column model listeners are currently registered 476 * 477 * @see #addColumnModelListener 478 * @see #removeColumnModelListener 479 * 480 * @since 1.4 481 */ 482 public TableColumnModelListener[] getColumnModelListeners() { 483 return listenerList.getListeners(TableColumnModelListener.class); 484 } 485 486 // 487 // Event firing methods 488 // 489 490 /** 491 * Notifies all listeners that have registered interest for 492 * notification on this event type. The event instance 493 * is lazily created using the parameters passed into 494 * the fire method. 495 * @param e the event received 496 * @see EventListenerList 497 */ 498 protected void fireColumnAdded(TableColumnModelEvent e) { 499 // Guaranteed to return a non-null array 500 Object[] listeners = listenerList.getListenerList(); 501 // Process the listeners last to first, notifying 502 // those that are interested in this event 503 for (int i = listeners.length-2; i>=0; i-=2) { 504 if (listeners[i]==TableColumnModelListener.class) { 505 // Lazily create the event: 506 // if (e == null) 507 // e = new ChangeEvent(this); 508 ((TableColumnModelListener)listeners[i+1]). 509 columnAdded(e); 510 } 511 } 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 fireColumnRemoved(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 columnRemoved(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 fireColumnMoved(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 columnMoved(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 fireColumnSelectionChanged(ListSelectionEvent 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 columnSelectionChanged(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 * @see EventListenerList 592 */ 593 protected void fireColumnMarginChanged() { 594 // Guaranteed to return a non-null array 595 Object[] listeners = listenerList.getListenerList(); 596 // Process the listeners last to first, notifying 597 // those that are interested in this event 598 for (int i = listeners.length-2; i>=0; i-=2) { 599 if (listeners[i]==TableColumnModelListener.class) { 600 // Lazily create the event: 601 if (changeEvent == null) 602 changeEvent = new ChangeEvent(this); 603 ((TableColumnModelListener)listeners[i+1]). 604 columnMarginChanged(changeEvent); 605 } 606 } 607 } 608 609 /** 610 * Returns an array of all the objects currently registered 611 * as <code><em>Foo</em>Listener</code>s 612 * upon this model. 613 * <code><em>Foo</em>Listener</code>s are registered using the 614 * <code>add<em>Foo</em>Listener</code> method. 615 * 616 * <p> 617 * 618 * You can specify the <code>listenerType</code> argument 619 * with a class literal, 620 * such as 621 * <code><em>Foo</em>Listener.class</code>. 622 * For example, you can query a 623 * <code>DefaultTableColumnModel</code> <code>m</code> 624 * for its column model listeners with the following code: 625 * 626 * <pre>ColumnModelListener[] cmls = (ColumnModelListener[])(m.getListeners(ColumnModelListener.class));</pre> 627 * 628 * If no such listeners exist, this method returns an empty array. 629 * 630 * @param <T> the listener type 631 * @param listenerType the type of listeners requested 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 * @return a newly created default list selection model. 695 */ 696 protected ListSelectionModel createSelectionModel() { 697 return new DefaultListSelectionModel(); 698 } 699 700 /** 701 * Recalculates the total combined width of all columns. Updates the 702 * <code>totalColumnWidth</code> property. 703 */ 704 protected void recalcWidthCache() { 705 Enumeration<TableColumn> enumeration = getColumns(); 706 totalColumnWidth = 0; 707 while (enumeration.hasMoreElements()) { 708 totalColumnWidth += enumeration.nextElement().getWidth(); 709 } 710 } 711 712 private void invalidateWidthCache() { 713 totalColumnWidth = -1; 714 } 715 716 } // End of class DefaultTableColumnModel