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