blob: 846001b35b1e80c6a75db700625daf894f3cf1f4 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1997-2005 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package javax.swing.table;
27
28import javax.swing.*;
29import javax.swing.event.*;
30import java.awt.*;
31import java.util.Vector;
32import java.util.Enumeration;
33import java.util.EventListener;
34import java.beans.PropertyChangeListener;
35import java.beans.PropertyChangeEvent;
36import java.io.Serializable;
37import 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<sup><font size="-2">TM</font></sup>
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 */
55public 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 = (TableColumn)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 (TableColumn)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 * accomodate a locale preference or some columns might be hidden at the
319 * request of the user. Because the model does not know how the columns
320 * are laid out on screen, the given <code>xPosition</code> should not be
321 * considered to be a coordinate in 2D graphics space. Instead, it should
322 * be considered to be a width from the start of the first column in the
323 * model. If the column index for a given X coordinate in 2D space is
324 * required, <code>JTable.columnAtPoint</code> can be used instead.
325 *
326 * @param x the horizontal location of interest
327 * @return the index of the column or -1 if no column is found
328 * @see javax.swing.JTable#columnAtPoint
329 */
330 public int getColumnIndexAtX(int x) {
331 if (x < 0) {
332 return -1;
333 }
334 int cc = getColumnCount();
335 for(int column = 0; column < cc; column++) {
336 x = x - getColumn(column).getWidth();
337 if (x < 0) {
338 return column;
339 }
340 }
341 return -1;
342 }
343
344 /**
345 * Returns the total combined width of all columns.
346 * @return the <code>totalColumnWidth</code> property
347 */
348 public int getTotalColumnWidth() {
349 if (totalColumnWidth == -1) {
350 recalcWidthCache();
351 }
352 return totalColumnWidth;
353 }
354
355//
356// Selection model
357//
358
359 /**
360 * Sets the selection model for this <code>TableColumnModel</code>
361 * to <code>newModel</code>
362 * and registers for listener notifications from the new selection
363 * model. If <code>newModel</code> is <code>null</code>,
364 * an exception is thrown.
365 *
366 * @param newModel the new selection model
367 * @exception IllegalArgumentException if <code>newModel</code>
368 * is <code>null</code>
369 * @see #getSelectionModel
370 */
371 public void setSelectionModel(ListSelectionModel newModel) {
372 if (newModel == null) {
373 throw new IllegalArgumentException("Cannot set a null SelectionModel");
374 }
375
376 ListSelectionModel oldModel = selectionModel;
377
378 if (newModel != oldModel) {
379 if (oldModel != null) {
380 oldModel.removeListSelectionListener(this);
381 }
382
383 selectionModel= newModel;
384 newModel.addListSelectionListener(this);
385 }
386 }
387
388 /**
389 * Returns the <code>ListSelectionModel</code> that is used to
390 * maintain column selection state.
391 *
392 * @return the object that provides column selection state. Or
393 * <code>null</code> if row selection is not allowed.
394 * @see #setSelectionModel
395 */
396 public ListSelectionModel getSelectionModel() {
397 return selectionModel;
398 }
399
400 // implements javax.swing.table.TableColumnModel
401 /**
402 * Sets whether column selection is allowed. The default is false.
403 * @param flag true if column selection will be allowed, false otherwise
404 */
405 public void setColumnSelectionAllowed(boolean flag) {
406 columnSelectionAllowed = flag;
407 }
408
409 // implements javax.swing.table.TableColumnModel
410 /**
411 * Returns true if column selection is allowed, otherwise false.
412 * The default is false.
413 * @return the <code>columnSelectionAllowed</code> property
414 */
415 public boolean getColumnSelectionAllowed() {
416 return columnSelectionAllowed;
417 }
418
419 // implements javax.swing.table.TableColumnModel
420 /**
421 * Returns an array of selected columns. If <code>selectionModel</code>
422 * is <code>null</code>, returns an empty array.
423 * @return an array of selected columns or an empty array if nothing
424 * is selected or the <code>selectionModel</code> is
425 * <code>null</code>
426 */
427 public int[] getSelectedColumns() {
428 if (selectionModel != null) {
429 int iMin = selectionModel.getMinSelectionIndex();
430 int iMax = selectionModel.getMaxSelectionIndex();
431
432 if ((iMin == -1) || (iMax == -1)) {
433 return new int[0];
434 }
435
436 int[] rvTmp = new int[1+ (iMax - iMin)];
437 int n = 0;
438 for(int i = iMin; i <= iMax; i++) {
439 if (selectionModel.isSelectedIndex(i)) {
440 rvTmp[n++] = i;
441 }
442 }
443 int[] rv = new int[n];
444 System.arraycopy(rvTmp, 0, rv, 0, n);
445 return rv;
446 }
447 return new int[0];
448 }
449
450 // implements javax.swing.table.TableColumnModel
451 /**
452 * Returns the number of columns selected.
453 * @return the number of columns selected
454 */
455 public int getSelectedColumnCount() {
456 if (selectionModel != null) {
457 int iMin = selectionModel.getMinSelectionIndex();
458 int iMax = selectionModel.getMaxSelectionIndex();
459 int count = 0;
460
461 for(int i = iMin; i <= iMax; i++) {
462 if (selectionModel.isSelectedIndex(i)) {
463 count++;
464 }
465 }
466 return count;
467 }
468 return 0;
469 }
470
471//
472// Listener Support Methods
473//
474
475 // implements javax.swing.table.TableColumnModel
476 /**
477 * Adds a listener for table column model events.
478 * @param x a <code>TableColumnModelListener</code> object
479 */
480 public void addColumnModelListener(TableColumnModelListener x) {
481 listenerList.add(TableColumnModelListener.class, x);
482 }
483
484 // implements javax.swing.table.TableColumnModel
485 /**
486 * Removes a listener for table column model events.
487 * @param x a <code>TableColumnModelListener</code> object
488 */
489 public void removeColumnModelListener(TableColumnModelListener x) {
490 listenerList.remove(TableColumnModelListener.class, x);
491 }
492
493 /**
494 * Returns an array of all the column model listeners
495 * registered on this model.
496 *
497 * @return all of this default table column model's <code>ColumnModelListener</code>s
498 * or an empty
499 * array if no column model listeners are currently registered
500 *
501 * @see #addColumnModelListener
502 * @see #removeColumnModelListener
503 *
504 * @since 1.4
505 */
506 public TableColumnModelListener[] getColumnModelListeners() {
507 return (TableColumnModelListener[])listenerList.getListeners(
508 TableColumnModelListener.class);
509 }
510
511//
512// Event firing methods
513//
514
515 /**
516 * Notifies all listeners that have registered interest for
517 * notification on this event type. The event instance
518 * is lazily created using the parameters passed into
519 * the fire method.
520 * @param e the event received
521 * @see EventListenerList
522 */
523 protected void fireColumnAdded(TableColumnModelEvent e) {
524 // Guaranteed to return a non-null array
525 Object[] listeners = listenerList.getListenerList();
526 // Process the listeners last to first, notifying
527 // those that are interested in this event
528 for (int i = listeners.length-2; i>=0; i-=2) {
529 if (listeners[i]==TableColumnModelListener.class) {
530 // Lazily create the event:
531 // if (e == null)
532 // e = new ChangeEvent(this);
533 ((TableColumnModelListener)listeners[i+1]).
534 columnAdded(e);
535 }
536 }
537 }
538
539 /**
540 * Notifies all listeners that have registered interest for
541 * notification on this event type. The event instance
542 * is lazily created using the parameters passed into
543 * the fire method.
544 * @param e the event received
545 * @see EventListenerList
546 */
547 protected void fireColumnRemoved(TableColumnModelEvent e) {
548 // Guaranteed to return a non-null array
549 Object[] listeners = listenerList.getListenerList();
550 // Process the listeners last to first, notifying
551 // those that are interested in this event
552 for (int i = listeners.length-2; i>=0; i-=2) {
553 if (listeners[i]==TableColumnModelListener.class) {
554 // Lazily create the event:
555 // if (e == null)
556 // e = new ChangeEvent(this);
557 ((TableColumnModelListener)listeners[i+1]).
558 columnRemoved(e);
559 }
560 }
561 }
562
563 /**
564 * Notifies all listeners that have registered interest for
565 * notification on this event type. The event instance
566 * is lazily created using the parameters passed into
567 * the fire method.
568 * @param e the event received
569 * @see EventListenerList
570 */
571 protected void fireColumnMoved(TableColumnModelEvent e) {
572 // Guaranteed to return a non-null array
573 Object[] listeners = listenerList.getListenerList();
574 // Process the listeners last to first, notifying
575 // those that are interested in this event
576 for (int i = listeners.length-2; i>=0; i-=2) {
577 if (listeners[i]==TableColumnModelListener.class) {
578 // Lazily create the event:
579 // if (e == null)
580 // e = new ChangeEvent(this);
581 ((TableColumnModelListener)listeners[i+1]).
582 columnMoved(e);
583 }
584 }
585 }
586
587 /**
588 * Notifies all listeners that have registered interest for
589 * notification on this event type. The event instance
590 * is lazily created using the parameters passed into
591 * the fire method.
592 * @param e the event received
593 * @see EventListenerList
594 */
595 protected void fireColumnSelectionChanged(ListSelectionEvent e) {
596 // Guaranteed to return a non-null array
597 Object[] listeners = listenerList.getListenerList();
598 // Process the listeners last to first, notifying
599 // those that are interested in this event
600 for (int i = listeners.length-2; i>=0; i-=2) {
601 if (listeners[i]==TableColumnModelListener.class) {
602 // Lazily create the event:
603 // if (e == null)
604 // e = new ChangeEvent(this);
605 ((TableColumnModelListener)listeners[i+1]).
606 columnSelectionChanged(e);
607 }
608 }
609 }
610
611 /**
612 * Notifies all listeners that have registered interest for
613 * notification on this event type. The event instance
614 * is lazily created using the parameters passed into
615 * the fire method.
616 * @see EventListenerList
617 */
618 protected void fireColumnMarginChanged() {
619 // Guaranteed to return a non-null array
620 Object[] listeners = listenerList.getListenerList();
621 // Process the listeners last to first, notifying
622 // those that are interested in this event
623 for (int i = listeners.length-2; i>=0; i-=2) {
624 if (listeners[i]==TableColumnModelListener.class) {
625 // Lazily create the event:
626 if (changeEvent == null)
627 changeEvent = new ChangeEvent(this);
628 ((TableColumnModelListener)listeners[i+1]).
629 columnMarginChanged(changeEvent);
630 }
631 }
632 }
633
634 /**
635 * Returns an array of all the objects currently registered
636 * as <code><em>Foo</em>Listener</code>s
637 * upon this model.
638 * <code><em>Foo</em>Listener</code>s are registered using the
639 * <code>add<em>Foo</em>Listener</code> method.
640 *
641 * <p>
642 *
643 * You can specify the <code>listenerType</code> argument
644 * with a class literal,
645 * such as
646 * <code><em>Foo</em>Listener.class</code>.
647 * For example, you can query a
648 * <code>DefaultTableColumnModel</code> <code>m</code>
649 * for its column model listeners with the following code:
650 *
651 * <pre>ColumnModelListener[] cmls = (ColumnModelListener[])(m.getListeners(ColumnModelListener.class));</pre>
652 *
653 * If no such listeners exist, this method returns an empty array.
654 *
655 * @param listenerType the type of listeners requested; this parameter
656 * should specify an interface that descends from
657 * <code>java.util.EventListener</code>
658 * @return an array of all objects registered as
659 * <code><em>Foo</em>Listener</code>s on this model,
660 * or an empty array if no such
661 * listeners have been added
662 * @exception ClassCastException if <code>listenerType</code>
663 * doesn't specify a class or interface that implements
664 * <code>java.util.EventListener</code>
665 *
666 * @see #getColumnModelListeners
667 * @since 1.3
668 */
669 public <T extends EventListener> T[] getListeners(Class<T> listenerType) {
670 return listenerList.getListeners(listenerType);
671 }
672
673//
674// Implementing the PropertyChangeListener interface
675//
676
677 // PENDING(alan)
678 // implements java.beans.PropertyChangeListener
679 /**
680 * Property Change Listener change method. Used to track changes
681 * to the column width or preferred column width.
682 *
683 * @param evt <code>PropertyChangeEvent</code>
684 */
685 public void propertyChange(PropertyChangeEvent evt) {
686 String name = evt.getPropertyName();
687
688 if (name == "width" || name == "preferredWidth") {
689 invalidateWidthCache();
690 // This is a misnomer, we're using this method
691 // simply to cause a relayout.
692 fireColumnMarginChanged();
693 }
694
695 }
696
697//
698// Implementing ListSelectionListener interface
699//
700
701 // implements javax.swing.event.ListSelectionListener
702 /**
703 * A <code>ListSelectionListener</code> that forwards
704 * <code>ListSelectionEvents</code> when there is a column
705 * selection change.
706 *
707 * @param e the change event
708 */
709 public void valueChanged(ListSelectionEvent e) {
710 fireColumnSelectionChanged(e);
711 }
712
713//
714// Protected Methods
715//
716
717 /**
718 * Creates a new default list selection model.
719 */
720 protected ListSelectionModel createSelectionModel() {
721 return new DefaultListSelectionModel();
722 }
723
724 /**
725 * Recalculates the total combined width of all columns. Updates the
726 * <code>totalColumnWidth</code> property.
727 */
728 protected void recalcWidthCache() {
729 Enumeration enumeration = getColumns();
730 totalColumnWidth = 0;
731 while (enumeration.hasMoreElements()) {
732 totalColumnWidth += ((TableColumn)enumeration.nextElement()).getWidth();
733 }
734 }
735
736 private void invalidateWidthCache() {
737 totalColumnWidth = -1;
738 }
739
740} // End of class DefaultTableColumnModel