001 /*
002 * Copyright 1997-2005 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025
026 package javax.swing.table;
027
028 import javax.swing.*;
029 import javax.swing.event.*;
030 import java.awt.*;
031 import java.util.Vector;
032 import java.util.Enumeration;
033 import java.util.EventListener;
034 import java.beans.PropertyChangeListener;
035 import java.beans.PropertyChangeEvent;
036 import java.io.Serializable;
037 import sun.swing.SwingUtilities2;
038
039 /**
040 * The standard column-handler for a <code>JTable</code>.
041 * <p>
042 * <strong>Warning:</strong>
043 * Serialized objects of this class will not be compatible with
044 * future Swing releases. The current serialization support is
045 * appropriate for short term storage or RMI between applications running
046 * the same version of Swing. As of 1.4, support for long term storage
047 * of all JavaBeans<sup><font size="-2">TM</font></sup>
048 * has been added to the <code>java.beans</code> package.
049 * Please see {@link java.beans.XMLEncoder}.
050 *
051 * @version 1.58 05/05/07
052 * @author Alan Chung
053 * @author Philip Milne
054 * @see JTable
055 */
056 public class DefaultTableColumnModel implements TableColumnModel,
057 PropertyChangeListener, ListSelectionListener, Serializable {
058 //
059 // Instance Variables
060 //
061
062 /** Array of TableColumn objects in this model */
063 protected Vector<TableColumn> tableColumns;
064
065 /** Model for keeping track of column selections */
066 protected ListSelectionModel selectionModel;
067
068 /** Width margin between each column */
069 protected int columnMargin;
070
071 /** List of TableColumnModelListener */
072 protected EventListenerList listenerList = new EventListenerList();
073
074 /** Change event (only one needed) */
075 transient protected ChangeEvent changeEvent = null;
076
077 /** Column selection allowed in this column model */
078 protected boolean columnSelectionAllowed;
079
080 /** A local cache of the combined width of all columns */
081 protected int totalColumnWidth;
082
083 //
084 // Constructors
085 //
086 /**
087 * Creates a default table column model.
088 */
089 public DefaultTableColumnModel() {
090 super ();
091
092 // Initialize local ivars to default
093 tableColumns = new Vector<TableColumn>();
094 setSelectionModel(createSelectionModel());
095 setColumnMargin(1);
096 invalidateWidthCache();
097 setColumnSelectionAllowed(false);
098 }
099
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,
148 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(
181 "moveColumn() - Index out of range");
182
183 TableColumn aColumn;
184
185 // If the column has not yet moved far enough to change positions
186 // post the event anyway, the "draggedDistance" property of the
187 // tableHeader will say how far the column has been dragged.
188 // Here we are really trying to get the best out of an
189 // API that could do with some rethinking. We preserve backward
190 // compatibility by slightly bending the meaning of these methods.
191 if (columnIndex == newIndex) {
192 fireColumnMoved(new TableColumnModelEvent(this ,
193 columnIndex, newIndex));
194 return;
195 }
196 aColumn = (TableColumn) tableColumns.elementAt(columnIndex);
197
198 tableColumns.removeElementAt(columnIndex);
199 boolean selected = selectionModel.isSelectedIndex(columnIndex);
200 selectionModel.removeIndexInterval(columnIndex, columnIndex);
201
202 tableColumns.insertElementAt(aColumn, newIndex);
203 selectionModel.insertIndexInterval(newIndex, 1, true);
204 if (selected) {
205 selectionModel.addSelectionInterval(newIndex, newIndex);
206 } else {
207 selectionModel.removeSelectionInterval(newIndex, newIndex);
208 }
209
210 fireColumnMoved(new TableColumnModelEvent(this , columnIndex,
211 newIndex));
212 }
213
214 /**
215 * Sets the column margin to <code>newMargin</code>. This method
216 * also posts a <code>columnMarginChanged</code> event to its
217 * listeners.
218 *
219 * @param newMargin the new margin width, in pixels
220 * @see #getColumnMargin
221 * @see #getTotalColumnWidth
222 */
223 public void setColumnMargin(int newMargin) {
224 if (newMargin != columnMargin) {
225 columnMargin = newMargin;
226 // Post columnMarginChanged event notification.
227 fireColumnMarginChanged();
228 }
229 }
230
231 //
232 // Querying the model
233 //
234
235 /**
236 * Returns the number of columns in the <code>tableColumns</code> array.
237 *
238 * @return the number of columns in the <code>tableColumns</code> array
239 * @see #getColumns
240 */
241 public int getColumnCount() {
242 return tableColumns.size();
243 }
244
245 /**
246 * Returns an <code>Enumeration</code> of all the columns in the model.
247 * @return an <code>Enumeration</code> of the columns in the model
248 */
249 public Enumeration<TableColumn> getColumns() {
250 return tableColumns.elements();
251 }
252
253 /**
254 * Returns the index of the first column in the <code>tableColumns</code>
255 * array whose identifier is equal to <code>identifier</code>,
256 * when compared using <code>equals</code>.
257 *
258 * @param identifier the identifier object
259 * @return the index of the first column in the
260 * <code>tableColumns</code> array whose identifier
261 * is equal to <code>identifier</code>
262 * @exception IllegalArgumentException if <code>identifier</code>
263 * is <code>null</code>, or if no
264 * <code>TableColumn</code> has this
265 * <code>identifier</code>
266 * @see #getColumn
267 */
268 public int getColumnIndex(Object identifier) {
269 if (identifier == null) {
270 throw new IllegalArgumentException("Identifier is null");
271 }
272
273 Enumeration enumeration = getColumns();
274 TableColumn aColumn;
275 int index = 0;
276
277 while (enumeration.hasMoreElements()) {
278 aColumn = (TableColumn) enumeration.nextElement();
279 // Compare them this way in case the column's identifier is null.
280 if (identifier.equals(aColumn.getIdentifier()))
281 return index;
282 index++;
283 }
284 throw new IllegalArgumentException("Identifier not found");
285 }
286
287 /**
288 * Returns the <code>TableColumn</code> object for the column
289 * at <code>columnIndex</code>.
290 *
291 * @param columnIndex the index of the column desired
292 * @return the <code>TableColumn</code> object for the column
293 * at <code>columnIndex</code>
294 */
295 public TableColumn getColumn(int columnIndex) {
296 return (TableColumn) tableColumns.elementAt(columnIndex);
297 }
298
299 /**
300 * Returns the width margin for <code>TableColumn</code>.
301 * The default <code>columnMargin</code> is 1.
302 *
303 * @return the maximum width for the <code>TableColumn</code>
304 * @see #setColumnMargin
305 */
306 public int getColumnMargin() {
307 return columnMargin;
308 }
309
310 /**
311 * Returns the index of the column that lies at position <code>x</code>,
312 * or -1 if no column covers this point.
313 *
314 * In keeping with Swing's separable model architecture, a
315 * TableColumnModel does not know how the table columns actually appear on
316 * screen. The visual presentation of the columns is the responsibility
317 * of the view/controller object using this model (typically JTable). The
318 * view/controller need not display the columns sequentially from left to
319 * right. For example, columns could be displayed from right to left to
320 * accomodate a locale preference or some columns might be hidden at the
321 * request of the user. Because the model does not know how the columns
322 * are laid out on screen, the given <code>xPosition</code> should not be
323 * considered to be a coordinate in 2D graphics space. Instead, it should
324 * be considered to be a width from the start of the first column in the
325 * model. If the column index for a given X coordinate in 2D space is
326 * required, <code>JTable.columnAtPoint</code> can be used instead.
327 *
328 * @param x the horizontal location of interest
329 * @return the index of the column or -1 if no column is found
330 * @see javax.swing.JTable#columnAtPoint
331 */
332 public int getColumnIndexAtX(int x) {
333 if (x < 0) {
334 return -1;
335 }
336 int cc = getColumnCount();
337 for (int column = 0; column < cc; column++) {
338 x = x - getColumn(column).getWidth();
339 if (x < 0) {
340 return column;
341 }
342 }
343 return -1;
344 }
345
346 /**
347 * Returns the total combined width of all columns.
348 * @return the <code>totalColumnWidth</code> property
349 */
350 public int getTotalColumnWidth() {
351 if (totalColumnWidth == -1) {
352 recalcWidthCache();
353 }
354 return totalColumnWidth;
355 }
356
357 //
358 // Selection model
359 //
360
361 /**
362 * Sets the selection model for this <code>TableColumnModel</code>
363 * to <code>newModel</code>
364 * and registers for listener notifications from the new selection
365 * model. If <code>newModel</code> is <code>null</code>,
366 * an exception is thrown.
367 *
368 * @param newModel the new selection model
369 * @exception IllegalArgumentException if <code>newModel</code>
370 * is <code>null</code>
371 * @see #getSelectionModel
372 */
373 public void setSelectionModel(ListSelectionModel newModel) {
374 if (newModel == null) {
375 throw new IllegalArgumentException(
376 "Cannot set a null SelectionModel");
377 }
378
379 ListSelectionModel oldModel = selectionModel;
380
381 if (newModel != oldModel) {
382 if (oldModel != null) {
383 oldModel.removeListSelectionListener(this );
384 }
385
386 selectionModel = newModel;
387 newModel.addListSelectionListener(this );
388 }
389 }
390
391 /**
392 * Returns the <code>ListSelectionModel</code> that is used to
393 * maintain column selection state.
394 *
395 * @return the object that provides column selection state. Or
396 * <code>null</code> if row selection is not allowed.
397 * @see #setSelectionModel
398 */
399 public ListSelectionModel getSelectionModel() {
400 return selectionModel;
401 }
402
403 // implements javax.swing.table.TableColumnModel
404 /**
405 * Sets whether column selection is allowed. The default is false.
406 * @param flag true if column selection will be allowed, false otherwise
407 */
408 public void setColumnSelectionAllowed(boolean flag) {
409 columnSelectionAllowed = flag;
410 }
411
412 // implements javax.swing.table.TableColumnModel
413 /**
414 * Returns true if column selection is allowed, otherwise false.
415 * The default is false.
416 * @return the <code>columnSelectionAllowed</code> property
417 */
418 public boolean getColumnSelectionAllowed() {
419 return columnSelectionAllowed;
420 }
421
422 // implements javax.swing.table.TableColumnModel
423 /**
424 * Returns an array of selected columns. If <code>selectionModel</code>
425 * is <code>null</code>, returns an empty array.
426 * @return an array of selected columns or an empty array if nothing
427 * is selected or the <code>selectionModel</code> is
428 * <code>null</code>
429 */
430 public int[] getSelectedColumns() {
431 if (selectionModel != null) {
432 int iMin = selectionModel.getMinSelectionIndex();
433 int iMax = selectionModel.getMaxSelectionIndex();
434
435 if ((iMin == -1) || (iMax == -1)) {
436 return new int[0];
437 }
438
439 int[] rvTmp = new int[1 + (iMax - iMin)];
440 int n = 0;
441 for (int i = iMin; i <= iMax; i++) {
442 if (selectionModel.isSelectedIndex(i)) {
443 rvTmp[n++] = i;
444 }
445 }
446 int[] rv = new int[n];
447 System.arraycopy(rvTmp, 0, rv, 0, n);
448 return rv;
449 }
450 return new int[0];
451 }
452
453 // implements javax.swing.table.TableColumnModel
454 /**
455 * Returns the number of columns selected.
456 * @return the number of columns selected
457 */
458 public int getSelectedColumnCount() {
459 if (selectionModel != null) {
460 int iMin = selectionModel.getMinSelectionIndex();
461 int iMax = selectionModel.getMaxSelectionIndex();
462 int count = 0;
463
464 for (int i = iMin; i <= iMax; i++) {
465 if (selectionModel.isSelectedIndex(i)) {
466 count++;
467 }
468 }
469 return count;
470 }
471 return 0;
472 }
473
474 //
475 // Listener Support Methods
476 //
477
478 // implements javax.swing.table.TableColumnModel
479 /**
480 * Adds a listener for table column model events.
481 * @param x a <code>TableColumnModelListener</code> object
482 */
483 public void addColumnModelListener(TableColumnModelListener x) {
484 listenerList.add(TableColumnModelListener.class, x);
485 }
486
487 // implements javax.swing.table.TableColumnModel
488 /**
489 * Removes a listener for table column model events.
490 * @param x a <code>TableColumnModelListener</code> object
491 */
492 public void removeColumnModelListener(TableColumnModelListener x) {
493 listenerList.remove(TableColumnModelListener.class, x);
494 }
495
496 /**
497 * Returns an array of all the column model listeners
498 * registered on this model.
499 *
500 * @return all of this default table column model's <code>ColumnModelListener</code>s
501 * or an empty
502 * array if no column model listeners are currently registered
503 *
504 * @see #addColumnModelListener
505 * @see #removeColumnModelListener
506 *
507 * @since 1.4
508 */
509 public TableColumnModelListener[] getColumnModelListeners() {
510 return (TableColumnModelListener[]) listenerList
511 .getListeners(TableColumnModelListener.class);
512 }
513
514 //
515 // Event firing methods
516 //
517
518 /**
519 * Notifies all listeners that have registered interest for
520 * notification on this event type. The event instance
521 * is lazily created using the parameters passed into
522 * the fire method.
523 * @param e the event received
524 * @see EventListenerList
525 */
526 protected void fireColumnAdded(TableColumnModelEvent e) {
527 // Guaranteed to return a non-null array
528 Object[] listeners = listenerList.getListenerList();
529 // Process the listeners last to first, notifying
530 // those that are interested in this event
531 for (int i = listeners.length - 2; i >= 0; i -= 2) {
532 if (listeners[i] == TableColumnModelListener.class) {
533 // Lazily create the event:
534 // if (e == null)
535 // e = new ChangeEvent(this);
536 ((TableColumnModelListener) listeners[i + 1])
537 .columnAdded(e);
538 }
539 }
540 }
541
542 /**
543 * Notifies all listeners that have registered interest for
544 * notification on this event type. The event instance
545 * is lazily created using the parameters passed into
546 * the fire method.
547 * @param e the event received
548 * @see EventListenerList
549 */
550 protected void fireColumnRemoved(TableColumnModelEvent e) {
551 // Guaranteed to return a non-null array
552 Object[] listeners = listenerList.getListenerList();
553 // Process the listeners last to first, notifying
554 // those that are interested in this event
555 for (int i = listeners.length - 2; i >= 0; i -= 2) {
556 if (listeners[i] == TableColumnModelListener.class) {
557 // Lazily create the event:
558 // if (e == null)
559 // e = new ChangeEvent(this);
560 ((TableColumnModelListener) listeners[i + 1])
561 .columnRemoved(e);
562 }
563 }
564 }
565
566 /**
567 * Notifies all listeners that have registered interest for
568 * notification on this event type. The event instance
569 * is lazily created using the parameters passed into
570 * the fire method.
571 * @param e the event received
572 * @see EventListenerList
573 */
574 protected void fireColumnMoved(TableColumnModelEvent e) {
575 // Guaranteed to return a non-null array
576 Object[] listeners = listenerList.getListenerList();
577 // Process the listeners last to first, notifying
578 // those that are interested in this event
579 for (int i = listeners.length - 2; i >= 0; i -= 2) {
580 if (listeners[i] == TableColumnModelListener.class) {
581 // Lazily create the event:
582 // if (e == null)
583 // e = new ChangeEvent(this);
584 ((TableColumnModelListener) listeners[i + 1])
585 .columnMoved(e);
586 }
587 }
588 }
589
590 /**
591 * Notifies all listeners that have registered interest for
592 * notification on this event type. The event instance
593 * is lazily created using the parameters passed into
594 * the fire method.
595 * @param e the event received
596 * @see EventListenerList
597 */
598 protected void fireColumnSelectionChanged(ListSelectionEvent e) {
599 // Guaranteed to return a non-null array
600 Object[] listeners = listenerList.getListenerList();
601 // Process the listeners last to first, notifying
602 // those that are interested in this event
603 for (int i = listeners.length - 2; i >= 0; i -= 2) {
604 if (listeners[i] == TableColumnModelListener.class) {
605 // Lazily create the event:
606 // if (e == null)
607 // e = new ChangeEvent(this);
608 ((TableColumnModelListener) listeners[i + 1])
609 .columnSelectionChanged(e);
610 }
611 }
612 }
613
614 /**
615 * Notifies all listeners that have registered interest for
616 * notification on this event type. The event instance
617 * is lazily created using the parameters passed into
618 * the fire method.
619 * @see EventListenerList
620 */
621 protected void fireColumnMarginChanged() {
622 // Guaranteed to return a non-null array
623 Object[] listeners = listenerList.getListenerList();
624 // Process the listeners last to first, notifying
625 // those that are interested in this event
626 for (int i = listeners.length - 2; i >= 0; i -= 2) {
627 if (listeners[i] == TableColumnModelListener.class) {
628 // Lazily create the event:
629 if (changeEvent == null)
630 changeEvent = new ChangeEvent(this );
631 ((TableColumnModelListener) listeners[i + 1])
632 .columnMarginChanged(changeEvent);
633 }
634 }
635 }
636
637 /**
638 * Returns an array of all the objects currently registered
639 * as <code><em>Foo</em>Listener</code>s
640 * upon this model.
641 * <code><em>Foo</em>Listener</code>s are registered using the
642 * <code>add<em>Foo</em>Listener</code> method.
643 *
644 * <p>
645 *
646 * You can specify the <code>listenerType</code> argument
647 * with a class literal,
648 * such as
649 * <code><em>Foo</em>Listener.class</code>.
650 * For example, you can query a
651 * <code>DefaultTableColumnModel</code> <code>m</code>
652 * for its column model listeners with the following code:
653 *
654 * <pre>ColumnModelListener[] cmls = (ColumnModelListener[])(m.getListeners(ColumnModelListener.class));</pre>
655 *
656 * If no such listeners exist, this method returns an empty array.
657 *
658 * @param listenerType the type of listeners requested; this parameter
659 * should specify an interface that descends from
660 * <code>java.util.EventListener</code>
661 * @return an array of all objects registered as
662 * <code><em>Foo</em>Listener</code>s on this model,
663 * or an empty array if no such
664 * listeners have been added
665 * @exception ClassCastException if <code>listenerType</code>
666 * doesn't specify a class or interface that implements
667 * <code>java.util.EventListener</code>
668 *
669 * @see #getColumnModelListeners
670 * @since 1.3
671 */
672 public <T extends EventListener> T[] getListeners(
673 Class<T> listenerType) {
674 return listenerList.getListeners(listenerType);
675 }
676
677 //
678 // Implementing the PropertyChangeListener interface
679 //
680
681 // PENDING(alan)
682 // implements java.beans.PropertyChangeListener
683 /**
684 * Property Change Listener change method. Used to track changes
685 * to the column width or preferred column width.
686 *
687 * @param evt <code>PropertyChangeEvent</code>
688 */
689 public void propertyChange(PropertyChangeEvent evt) {
690 String name = evt.getPropertyName();
691
692 if (name == "width" || name == "preferredWidth") {
693 invalidateWidthCache();
694 // This is a misnomer, we're using this method
695 // simply to cause a relayout.
696 fireColumnMarginChanged();
697 }
698
699 }
700
701 //
702 // Implementing ListSelectionListener interface
703 //
704
705 // implements javax.swing.event.ListSelectionListener
706 /**
707 * A <code>ListSelectionListener</code> that forwards
708 * <code>ListSelectionEvents</code> when there is a column
709 * selection change.
710 *
711 * @param e the change event
712 */
713 public void valueChanged(ListSelectionEvent e) {
714 fireColumnSelectionChanged(e);
715 }
716
717 //
718 // Protected Methods
719 //
720
721 /**
722 * Creates a new default list selection model.
723 */
724 protected ListSelectionModel createSelectionModel() {
725 return new DefaultListSelectionModel();
726 }
727
728 /**
729 * Recalculates the total combined width of all columns. Updates the
730 * <code>totalColumnWidth</code> property.
731 */
732 protected void recalcWidthCache() {
733 Enumeration enumeration = getColumns();
734 totalColumnWidth = 0;
735 while (enumeration.hasMoreElements()) {
736 totalColumnWidth += ((TableColumn) enumeration
737 .nextElement()).getWidth();
738 }
739 }
740
741 private void invalidateWidthCache() {
742 totalColumnWidth = -1;
743 }
744
745 } // End of class DefaultTableColumnModel
|