KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > javax > swing > table > DefaultTableColumnModel


1 /*
2  * @(#)DefaultTableColumnModel.java 1.49 05/08/23
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 package javax.swing.table;
9
10 import javax.swing.*;
11 import javax.swing.event.*;
12 import java.awt.*;
13 import java.util.Vector JavaDoc;
14 import java.util.Enumeration JavaDoc;
15 import java.util.EventListener JavaDoc;
16 import java.beans.PropertyChangeListener JavaDoc;
17 import java.beans.PropertyChangeEvent JavaDoc;
18 import java.io.Serializable JavaDoc;
19
20 /**
21  * The standard column-handler for a <code>JTable</code>.
22  * <p>
23  * <strong>Warning:</strong>
24  * Serialized objects of this class will not be compatible with
25  * future Swing releases. The current serialization support is
26  * appropriate for short term storage or RMI between applications running
27  * the same version of Swing. As of 1.4, support for long term storage
28  * of all JavaBeans<sup><font size="-2">TM</font></sup>
29  * has been added to the <code>java.beans</code> package.
30  * Please see {@link java.beans.XMLEncoder}.
31  *
32  * @version 1.49 08/23/05
33  * @author Alan Chung
34  * @author Philip Milne
35  * @see JTable
36  */

37 public class DefaultTableColumnModel implements TableColumnModel JavaDoc,
38             PropertyChangeListener JavaDoc, ListSelectionListener, Serializable JavaDoc
39 {
40 //
41
// Instance Variables
42
//
43

44     /** Array of TableColumn objects in this model */
45     protected Vector JavaDoc<TableColumn JavaDoc> tableColumns;
46
47     /** Model for keeping track of column selections */
48     protected ListSelectionModel selectionModel;
49
50     /** Width margin between each column */
51     protected int columnMargin;
52
53     /** List of TableColumnModelListener */
54     protected EventListenerList listenerList = new EventListenerList();
55
56     /** Change event (only one needed) */
57     transient protected ChangeEvent JavaDoc changeEvent = null;
58
59     /** Column selection allowed in this column model */
60     protected boolean columnSelectionAllowed;
61
62     /** A local cache of the combined width of all columns */
63     protected int totalColumnWidth;
64
65 //
66
// Constructors
67
//
68
/**
69      * Creates a default table column model.
70      */

71     public DefaultTableColumnModel() {
72     super();
73
74     // Initialize local ivars to default
75
tableColumns = new Vector JavaDoc<TableColumn JavaDoc>();
76     setSelectionModel(createSelectionModel());
77     setColumnMargin(1);
78     invalidateWidthCache();
79     setColumnSelectionAllowed(false);
80     }
81
82 //
83
// Modifying the model
84
//
85

86     /**
87      * Appends <code>aColumn</code> to the end of the
88      * <code>tableColumns</code> array.
89      * This method also posts the <code>columnAdded</code>
90      * event to its listeners.
91      *
92      * @param aColumn the <code>TableColumn</code> to be added
93      * @exception IllegalArgumentException if <code>aColumn</code> is
94      * <code>null</code>
95      * @see #removeColumn
96      */

97     public void addColumn(TableColumn JavaDoc aColumn) {
98     if (aColumn == null) {
99         throw new IllegalArgumentException JavaDoc("Object is null");
100     }
101
102     tableColumns.addElement(aColumn);
103     aColumn.addPropertyChangeListener(this);
104     invalidateWidthCache();
105
106     // Post columnAdded event notification
107
fireColumnAdded(new TableColumnModelEvent(this, 0,
108                           getColumnCount() - 1));
109     }
110
111     /**
112      * Deletes the <code>column</code> from the
113      * <code>tableColumns</code> array. This method will do nothing if
114      * <code>column</code> is not in the table's columns list.
115      * <code>tile</code> is called
116      * to resize both the header and table views.
117      * This method also posts a <code>columnRemoved</code>
118      * event to its listeners.
119      *
120      * @param column the <code>TableColumn</code> to be removed
121      * @see #addColumn
122      */

123     public void removeColumn(TableColumn JavaDoc column) {
124     int columnIndex = tableColumns.indexOf(column);
125
126     if (columnIndex != -1) {
127         // Adjust for the selection
128
if (selectionModel != null) {
129         selectionModel.removeIndexInterval(columnIndex,columnIndex);
130         }
131
132         column.removePropertyChangeListener(this);
133         tableColumns.removeElementAt(columnIndex);
134         invalidateWidthCache();
135
136         // Post columnAdded event notification. (JTable and JTableHeader
137
// listens so they can adjust size and redraw)
138
fireColumnRemoved(new TableColumnModelEvent(this,
139                        columnIndex, 0));
140     }
141     }
142
143     /**
144      * Moves the column and heading at <code>columnIndex</code> to
145      * <code>newIndex</code>. The old column at <code>columnIndex</code>
146      * will now be found at <code>newIndex</code>. The column
147      * that used to be at <code>newIndex</code> is shifted
148      * left or right to make room. This will not move any columns if
149      * <code>columnIndex</code> equals <code>newIndex</code>. This method
150      * also posts a <code>columnMoved</code> event to its listeners.
151      *
152      * @param columnIndex the index of column to be moved
153      * @param newIndex new index to move the column
154      * @exception IllegalArgumentException if <code>column</code> or
155      * <code>newIndex</code>
156      * are not in the valid range
157      */

158     public void moveColumn(int columnIndex, int newIndex) {
159     if ((columnIndex < 0) || (columnIndex >= getColumnCount()) ||
160         (newIndex < 0) || (newIndex >= getColumnCount()))
161         throw new IllegalArgumentException JavaDoc("moveColumn() - Index out of range");
162
163     TableColumn JavaDoc aColumn;
164
165     // If the column has not yet moved far enough to change positions
166
// post the event anyway, the "draggedDistance" property of the
167
// tableHeader will say how far the column has been dragged.
168
// Here we are really trying to get the best out of an
169
// API that could do with some rethinking. We preserve backward
170
// compatibility by slightly bending the meaning of these methods.
171
if (columnIndex == newIndex) {
172         fireColumnMoved(new TableColumnModelEvent(this, columnIndex, newIndex));
173         return;
174     }
175     aColumn = (TableColumn JavaDoc)tableColumns.elementAt(columnIndex);
176
177     tableColumns.removeElementAt(columnIndex);
178     boolean selected = selectionModel.isSelectedIndex(columnIndex);
179     selectionModel.removeIndexInterval(columnIndex,columnIndex);
180
181     tableColumns.insertElementAt(aColumn, newIndex);
182     selectionModel.insertIndexInterval(newIndex, 1, true);
183     if (selected) {
184         selectionModel.addSelectionInterval(newIndex, newIndex);
185     }
186     else {
187         selectionModel.removeSelectionInterval(newIndex, newIndex);
188     }
189
190     fireColumnMoved(new TableColumnModelEvent(this, columnIndex,
191                                    newIndex));
192     }
193
194     /**
195      * Sets the column margin to <code>newMargin</code>. This method
196      * also posts a <code>columnMarginChanged</code> event to its
197      * listeners.
198      *
199      * @param newMargin the new margin width, in pixels
200      * @see #getColumnMargin
201      * @see #getTotalColumnWidth
202      */

203     public void setColumnMargin(int newMargin) {
204     if (newMargin != columnMargin) {
205         columnMargin = newMargin;
206         // Post columnMarginChanged event notification.
207
fireColumnMarginChanged();
208     }
209     }
210
211 //
212
// Querying the model
213
//
214

215     /**
216      * Returns the number of columns in the <code>tableColumns</code> array.
217      *
218      * @return the number of columns in the <code>tableColumns</code> array
219      * @see #getColumns
220      */

221     public int getColumnCount() {
222     return tableColumns.size();
223     }
224
225     /**
226      * Returns an <code>Enumeration</code> of all the columns in the model.
227      * @return an <code>Enumeration</code> of the columns in the model
228      */

229     public Enumeration JavaDoc<TableColumn JavaDoc> getColumns() {
230     return tableColumns.elements();
231     }
232
233     /**
234      * Returns the index of the first column in the <code>tableColumns</code>
235      * array whose identifier is equal to <code>identifier</code>,
236      * when compared using <code>equals</code>.
237      *
238      * @param identifier the identifier object
239      * @return the index of the first column in the
240      * <code>tableColumns</code> array whose identifier
241      * is equal to <code>identifier</code>
242      * @exception IllegalArgumentException if <code>identifier</code>
243      * is <code>null</code>, or if no
244      * <code>TableColumn</code> has this
245      * <code>identifier</code>
246      * @see #getColumn
247      */

248     public int getColumnIndex(Object JavaDoc identifier) {
249     if (identifier == null) {
250         throw new IllegalArgumentException JavaDoc("Identifier is null");
251     }
252
253     Enumeration JavaDoc enumeration = getColumns();
254     TableColumn JavaDoc aColumn;
255     int index = 0;
256
257     while (enumeration.hasMoreElements()) {
258         aColumn = (TableColumn JavaDoc)enumeration.nextElement();
259         // Compare them this way in case the column's identifier is null.
260
if (identifier.equals(aColumn.getIdentifier()))
261         return index;
262         index++;
263     }
264     throw new IllegalArgumentException JavaDoc("Identifier not found");
265     }
266
267     /**
268      * Returns the <code>TableColumn</code> object for the column
269      * at <code>columnIndex</code>.
270      *
271      * @param columnIndex the index of the column desired
272      * @return the <code>TableColumn</code> object for the column
273      * at <code>columnIndex</code>
274      */

275     public TableColumn JavaDoc getColumn(int columnIndex) {
276     return (TableColumn JavaDoc)tableColumns.elementAt(columnIndex);
277     }
278
279     /**
280      * Returns the width margin for <code>TableColumn</code>.
281      * The default <code>columnMargin</code> is 1.
282      *
283      * @return the maximum width for the <code>TableColumn</code>
284      * @see #setColumnMargin
285      */

286     public int getColumnMargin() {
287     return columnMargin;
288     }
289
290     /**
291      * Returns the index of the column that lies at position <code>x</code>,
292      * or -1 if no column covers this point.
293      *
294      * In keeping with Swing's separable model architecture, a
295      * TableColumnModel does not know how the table columns actually appear on
296      * screen. The visual presentation of the columns is the responsibility
297      * of the view/controller object using this model (typically JTable). The
298      * view/controller need not display the columns sequentially from left to
299      * right. For example, columns could be displayed from right to left to
300      * accomodate a locale preference or some columns might be hidden at the
301      * request of the user. Because the model does not know how the columns
302      * are laid out on screen, the given <code>xPosition</code> should not be
303      * considered to be a coordinate in 2D graphics space. Instead, it should
304      * be considered to be a width from the start of the first column in the
305      * model. If the column index for a given X coordinate in 2D space is
306      * required, <code>JTable.columnAtPoint</code> can be used instead.
307      *
308      * @param x the horizontal location of interest
309      * @return the index of the column or -1 if no column is found
310      * @see javax.swing.JTable#columnAtPoint
311      */

312     public int getColumnIndexAtX(int x) {
313     if (x < 0) {
314         return -1;
315     }
316     int cc = getColumnCount();
317     for(int column = 0; column < cc; column++) {
318         x = x - getColumn(column).getWidth();
319         if (x < 0) {
320         return column;
321         }
322     }
323     return -1;
324     }
325
326     /**
327      * Returns the total combined width of all columns.
328      * @return the <code>totalColumnWidth</code> property
329      */

330     public int getTotalColumnWidth() {
331     if (totalColumnWidth == -1) {
332         recalcWidthCache();
333     }
334     return totalColumnWidth;
335     }
336
337 //
338
// Selection model
339
//
340

341     /**
342      * Sets the selection model for this <code>TableColumnModel</code>
343      * to <code>newModel</code>
344      * and registers for listener notifications from the new selection
345      * model. If <code>newModel</code> is <code>null</code>,
346      * an exception is thrown.
347      *
348      * @param newModel the new selection model
349      * @exception IllegalArgumentException if <code>newModel</code>
350      * is <code>null</code>
351      * @see #getSelectionModel
352      */

353     public void setSelectionModel(ListSelectionModel newModel) {
354         if (newModel == null) {
355             throw new IllegalArgumentException JavaDoc("Cannot set a null SelectionModel");
356         }
357
358     ListSelectionModel oldModel = selectionModel;
359
360     if (newModel != oldModel) {
361         if (oldModel != null) {
362         oldModel.removeListSelectionListener(this);
363         }
364
365         selectionModel= newModel;
366             newModel.addListSelectionListener(this);
367     }
368     }
369
370     /**
371      * Returns the <code>ListSelectionModel</code> that is used to
372      * maintain column selection state.
373      *
374      * @return the object that provides column selection state. Or
375      * <code>null</code> if row selection is not allowed.
376      * @see #setSelectionModel
377      */

378     public ListSelectionModel getSelectionModel() {
379     return selectionModel;
380     }
381
382     /**
383      * Initialize the lead and anchor of the selection model
384      * based on what the column model contains.
385      */

386     private void checkLeadAnchor() {
387         int lead = selectionModel.getLeadSelectionIndex();
388         int count = tableColumns.size();
389         if (count == 0) {
390             if (lead != -1) {
391                 // no columns left, set the lead and anchor to -1
392
selectionModel.setValueIsAdjusting(true);
393                 selectionModel.setAnchorSelectionIndex(-1);
394                 selectionModel.setLeadSelectionIndex(-1);
395                 selectionModel.setValueIsAdjusting(false);
396             }
397         } else {
398             if (lead == -1) {
399                 // set the lead and anchor to the first column
400
// (without changing the selection)
401
if (selectionModel.isSelectedIndex(0)) {
402                     selectionModel.addSelectionInterval(0, 0);
403                 } else {
404                     selectionModel.removeSelectionInterval(0, 0);
405                 }
406             }
407         }
408     }
409
410     // implements javax.swing.table.TableColumnModel
411
/**
412      * Sets whether column selection is allowed. The default is false.
413      * @param flag true if column selection will be allowed, false otherwise
414      */

415     public void setColumnSelectionAllowed(boolean flag) {
416     columnSelectionAllowed = flag;
417     }
418
419     // implements javax.swing.table.TableColumnModel
420
/**
421      * Returns true if column selection is allowed, otherwise false.
422      * The default is false.
423      * @return the <code>columnSelectionAllowed</code> property
424      */

425     public boolean getColumnSelectionAllowed() {
426     return columnSelectionAllowed;
427     }
428
429     // implements javax.swing.table.TableColumnModel
430
/**
431      * Returns an array of selected columns. If <code>selectionModel</code>
432      * is <code>null</code>, returns an empty array.
433      * @return an array of selected columns or an empty array if nothing
434      * is selected or the <code>selectionModel</code> is
435      * <code>null</code>
436      */

437     public int[] getSelectedColumns() {
438     if (selectionModel != null) {
439         int iMin = selectionModel.getMinSelectionIndex();
440         int iMax = selectionModel.getMaxSelectionIndex();
441
442         if ((iMin == -1) || (iMax == -1)) {
443         return new int[0];
444         }
445
446         int[] rvTmp = new int[1+ (iMax - iMin)];
447         int n = 0;
448         for(int i = iMin; i <= iMax; i++) {
449         if (selectionModel.isSelectedIndex(i)) {
450             rvTmp[n++] = i;
451         }
452         }
453         int[] rv = new int[n];
454         System.arraycopy(rvTmp, 0, rv, 0, n);
455         return rv;
456     }
457     return new int[0];
458     }
459
460     // implements javax.swing.table.TableColumnModel
461
/**
462      * Returns the number of columns selected.
463      * @return the number of columns selected
464      */

465     public int getSelectedColumnCount() {
466     if (selectionModel != null) {
467         int iMin = selectionModel.getMinSelectionIndex();
468         int iMax = selectionModel.getMaxSelectionIndex();
469         int count = 0;
470
471         for(int i = iMin; i <= iMax; i++) {
472         if (selectionModel.isSelectedIndex(i)) {
473             count++;
474         }
475         }
476         return count;
477     }
478     return 0;
479     }
480
481 //
482
// Listener Support Methods
483
//
484

485     // implements javax.swing.table.TableColumnModel
486
/**
487      * Adds a listener for table column model events.
488      * @param x a <code>TableColumnModelListener</code> object
489      */

490     public void addColumnModelListener(TableColumnModelListener x) {
491     listenerList.add(TableColumnModelListener.class, x);
492     }
493
494     // implements javax.swing.table.TableColumnModel
495
/**
496      * Removes a listener for table column model events.
497      * @param x a <code>TableColumnModelListener</code> object
498      */

499     public void removeColumnModelListener(TableColumnModelListener x) {
500     listenerList.remove(TableColumnModelListener.class, x);
501     }
502
503     /**
504      * Returns an array of all the column model listeners
505      * registered on this model.
506      *
507      * @return all of this default table column model's <code>ColumnModelListener</code>s
508      * or an empty
509      * array if no column model listeners are currently registered
510      *
511      * @see #addColumnModelListener
512      * @see #removeColumnModelListener
513      *
514      * @since 1.4
515      */

516     public TableColumnModelListener[] getColumnModelListeners() {
517         return (TableColumnModelListener[])listenerList.getListeners(
518                 TableColumnModelListener.class);
519     }
520
521 //
522
// Event firing methods
523
//
524

525     /**
526      * Notifies all listeners that have registered interest for
527      * notification on this event type. The event instance
528      * is lazily created using the parameters passed into
529      * the fire method.
530      * @param e the event received
531      * @see EventListenerList
532      */

533     protected void fireColumnAdded(TableColumnModelEvent e) {
534     // Guaranteed to return a non-null array
535
Object JavaDoc[] listeners = listenerList.getListenerList();
536     // Process the listeners last to first, notifying
537
// those that are interested in this event
538
for (int i = listeners.length-2; i>=0; i-=2) {
539         if (listeners[i]==TableColumnModelListener.class) {
540         // Lazily create the event:
541
// if (e == null)
542
// e = new ChangeEvent(this);
543
((TableColumnModelListener)listeners[i+1]).
544             columnAdded(e);
545         }
546     }
547     }
548
549     /**
550      * Notifies all listeners that have registered interest for
551      * notification on this event type. The event instance
552      * is lazily created using the parameters passed into
553      * the fire method.
554      * @param e the event received
555      * @see EventListenerList
556      */

557     protected void fireColumnRemoved(TableColumnModelEvent e) {
558     // Guaranteed to return a non-null array
559
Object JavaDoc[] listeners = listenerList.getListenerList();
560     // Process the listeners last to first, notifying
561
// those that are interested in this event
562
for (int i = listeners.length-2; i>=0; i-=2) {
563         if (listeners[i]==TableColumnModelListener.class) {
564         // Lazily create the event:
565
// if (e == null)
566
// e = new ChangeEvent(this);
567
((TableColumnModelListener)listeners[i+1]).
568             columnRemoved(e);
569         }
570     }
571     }
572
573     /**
574      * Notifies all listeners that have registered interest for
575      * notification on this event type. The event instance
576      * is lazily created using the parameters passed into
577      * the fire method.
578      * @param e the event received
579      * @see EventListenerList
580      */

581     protected void fireColumnMoved(TableColumnModelEvent e) {
582     // Guaranteed to return a non-null array
583
Object JavaDoc[] listeners = listenerList.getListenerList();
584     // Process the listeners last to first, notifying
585
// those that are interested in this event
586
for (int i = listeners.length-2; i>=0; i-=2) {
587         if (listeners[i]==TableColumnModelListener.class) {
588         // Lazily create the event:
589
// if (e == null)
590
// e = new ChangeEvent(this);
591
((TableColumnModelListener)listeners[i+1]).
592             columnMoved(e);
593         }
594     }
595     }
596
597     /**
598      * Notifies all listeners that have registered interest for
599      * notification on this event type. The event instance
600      * is lazily created using the parameters passed into
601      * the fire method.
602      * @param e the event received
603      * @see EventListenerList
604      */

605     protected void fireColumnSelectionChanged(ListSelectionEvent e) {
606     // Guaranteed to return a non-null array
607
Object JavaDoc[] listeners = listenerList.getListenerList();
608     // Process the listeners last to first, notifying
609
// those that are interested in this event
610
for (int i = listeners.length-2; i>=0; i-=2) {
611         if (listeners[i]==TableColumnModelListener.class) {
612         // Lazily create the event:
613
// if (e == null)
614
// e = new ChangeEvent(this);
615
((TableColumnModelListener)listeners[i+1]).
616             columnSelectionChanged(e);
617         }
618     }
619     }
620
621     /**
622      * Notifies all listeners that have registered interest for
623      * notification on this event type. The event instance
624      * is lazily created using the parameters passed into
625      * the fire method.
626      * @see EventListenerList
627      */

628     protected void fireColumnMarginChanged() {
629     // Guaranteed to return a non-null array
630
Object JavaDoc[] listeners = listenerList.getListenerList();
631     // Process the listeners last to first, notifying
632
// those that are interested in this event
633
for (int i = listeners.length-2; i>=0; i-=2) {
634         if (listeners[i]==TableColumnModelListener.class) {
635         // Lazily create the event:
636
if (changeEvent == null)
637             changeEvent = new ChangeEvent JavaDoc(this);
638         ((TableColumnModelListener)listeners[i+1]).
639             columnMarginChanged(changeEvent);
640         }
641     }
642     }
643
644     /**
645      * Returns an array of all the objects currently registered
646      * as <code><em>Foo</em>Listener</code>s
647      * upon this model.
648      * <code><em>Foo</em>Listener</code>s are registered using the
649      * <code>add<em>Foo</em>Listener</code> method.
650      *
651      * <p>
652      *
653      * You can specify the <code>listenerType</code> argument
654      * with a class literal,
655      * such as
656      * <code><em>Foo</em>Listener.class</code>.
657      * For example, you can query a
658      * <code>DefaultTableColumnModel</code> <code>m</code>
659      * for its column model listeners with the following code:
660      *
661      * <pre>ColumnModelListener[] cmls = (ColumnModelListener[])(m.getListeners(ColumnModelListener.class));</pre>
662      *
663      * If no such listeners exist, this method returns an empty array.
664      *
665      * @param listenerType the type of listeners requested; this parameter
666      * should specify an interface that descends from
667      * <code>java.util.EventListener</code>
668      * @return an array of all objects registered as
669      * <code><em>Foo</em>Listener</code>s on this model,
670      * or an empty array if no such
671      * listeners have been added
672      * @exception ClassCastException if <code>listenerType</code>
673      * doesn't specify a class or interface that implements
674      * <code>java.util.EventListener</code>
675      *
676      * @see #getColumnModelListeners
677      * @since 1.3
678      */

679     public <T extends EventListener JavaDoc> T[] getListeners(Class JavaDoc<T> listenerType) {
680     return listenerList.getListeners(listenerType);
681     }
682
683 //
684
// Implementing the PropertyChangeListener interface
685
//
686

687     // PENDING(alan)
688
// implements java.beans.PropertyChangeListener
689
/**
690      * Property Change Listener change method. Used to track changes
691      * to the column width or preferred column width.
692      *
693      * @param evt <code>PropertyChangeEvent</code>
694      */

695     public void propertyChange(PropertyChangeEvent JavaDoc evt) {
696     String JavaDoc name = evt.getPropertyName();
697
698     if (name == "width" || name == "preferredWidth") {
699         invalidateWidthCache();
700         // This is a misnomer, we're using this method
701
// simply to cause a relayout.
702
fireColumnMarginChanged();
703     }
704
705     }
706
707 //
708
// Implementing ListSelectionListener interface
709
//
710

711     // implements javax.swing.event.ListSelectionListener
712
/**
713      * A <code>ListSelectionListener</code> that forwards
714      * <code>ListSelectionEvents</code> when there is a column
715      * selection change.
716      *
717      * @param e the change event
718      */

719     public void valueChanged(ListSelectionEvent e) {
720     fireColumnSelectionChanged(e);
721     }
722
723 //
724
// Protected Methods
725
//
726

727     /**
728      * Creates a new default list selection model.
729      */

730     protected ListSelectionModel createSelectionModel() {
731         return new DefaultListSelectionModel();
732     }
733
734     /**
735      * Recalculates the total combined width of all columns. Updates the
736      * <code>totalColumnWidth</code> property.
737      */

738     protected void recalcWidthCache() {
739     Enumeration JavaDoc enumeration = getColumns();
740     totalColumnWidth = 0;
741     while (enumeration.hasMoreElements()) {
742         totalColumnWidth += ((TableColumn JavaDoc)enumeration.nextElement()).getWidth();
743     }
744     }
745
746     private void invalidateWidthCache() {
747     totalColumnWidth = -1;
748     }
749
750 } // End of class DefaultTableColumnModel
751

752
Popular Tags