View Javadoc

1   // %1126721045932:hoplugins.commons.ui.sorter%
2   package hoplugins.commons.ui.sorter;
3   
4   import javax.swing.Icon;
5   import javax.swing.event.TableModelListener;
6   import javax.swing.table.AbstractTableModel;
7   import javax.swing.table.JTableHeader;
8   import javax.swing.table.TableCellRenderer;
9   import javax.swing.table.TableModel;
10  
11  import java.awt.event.MouseListener;
12  
13  import java.util.ArrayList;
14  import java.util.Arrays;
15  import java.util.Comparator;
16  import java.util.HashMap;
17  import java.util.List;
18  import java.util.Map;
19  
20  /***
21   * TableSorter is a decorator for TableModels; adding sorting functionality to a supplied
22   * TableModel. TableSorter does not store or copy the data in its TableModel; instead it maintains
23   * a map from the row indexes of the view to the row indexes of the model. As requests are made of
24   * the sorter (like getValueAt(row, col)) they are passed to the underlying model after the row
25   * numbers have been translated via the internal mapping array. This way, the TableSorter appears
26   * to hold another copy of the table with the rows in a different order.  TableSorter registers
27   * itself as a listener to the underlying model, just as the JTable itself would. Events recieved
28   * from the model are examined, sometimes manipulated (typically widened), and then passed on to
29   * the TableSorter's listeners (typically the JTable). If a change to the model has invalidated
30   * the order of TableSorter's rows, a note of this is made and the sorter will resort the rows the
31   * next time a value is requested.  When the tableHeader property is set, either by using the
32   * setTableHeader() method or the two argument constructor, the table header may be used as a
33   * complete UI for TableSorter. The default renderer of the tableHeader is decorated with a
34   * renderer that indicates the sorting status of each column. In addition, a mouse listener is
35   * installed with the following behavior:   Mouse-click: Clears the sorting status of all other
36   * columns and advances the sorting status of that column through three values: {NOT_SORTED,
37   * ASCENDING, DESCENDING} (then back to NOT_SORTED again).  SHIFT-mouse-click: Clears the sorting
38   * status of all other columns and cycles the sorting status of the column through the same three
39   * values, in the opposite order: {NOT_SORTED, DESCENDING, ASCENDING}.  CONTROL-mouse-click and
40   * CONTROL-SHIFT-mouse-click: as above except that the changes to the column do not cancel the
41   * statuses of columns that are already sorting - giving a way to initiate a compound sort.   This
42   * is a long overdue rewrite of a class of the same name that first appeared in the swing table
43   * demos in 1997.
44   *
45   * @author Philip Milne
46   * @author Brendon McLean
47   * @author Dan van Enckevort
48   * @author Parwinder Sekhon
49   * @version 2.0 02/27/04
50   */
51  public abstract class AbstractTableSorter extends AbstractTableModel {
52      /***  */
53      /*** TODO Missing Parameter Documentation */
54      public static final int DESCENDING = -1;
55  
56      /***  */
57      /*** TODO Missing Parameter Documentation */
58      public static final int NOT_SORTED = 0;
59  
60      /***  */
61      /*** TODO Missing Parameter Documentation */
62      public static final int ASCENDING = 1;
63      private static Directive EMPTY_DIRECTIVE = new Directive(-1, NOT_SORTED);
64  
65      /***  */
66      /*** TODO Missing Parameter Documentation */
67      public static final Comparator COMPARABLE_COMAPRATOR = new Comparator() {
68              public int compare(Object o1, Object o2) {
69                  return ((Comparable) o1).compareTo(o2);
70              }
71          };
72  
73      /***  */
74      /*** TODO Missing Parameter Documentation */
75      public static final Comparator LEXICAL_COMPARATOR = new Comparator() {
76              public int compare(Object o1, Object o2) {
77                  return o1.toString().compareTo(o2.toString());
78              }
79          };
80  
81      /***  */
82      /*** TODO Missing Parameter Documentation */
83      protected TableModel tableModel;
84      private JTableHeader tableHeader;
85      private List sortingColumns = new ArrayList();
86      private Map columnComparators = new HashMap();
87      private MouseListener mouseListener;
88      private TableModelListener tableModelListener;
89      private int[] modelToView;
90      private Row[] viewToModel;
91  
92      /***
93       * Creates a new TableSorter object.
94       */
95      public AbstractTableSorter() {
96          this.mouseListener = new MouseHandler(this);
97          this.tableModelListener = new TableModelHandler(this);
98      }
99  
100     /***
101      * Creates a new TableSorter object.
102      *
103      * @param tableModel
104      */
105     public AbstractTableSorter(TableModel tableModel) {
106         this();
107         setTableModel(tableModel);
108     }
109 
110     /***
111      * Creates a new TableSorter object.
112      *
113      * @param tableModel
114      * @param tableHeader
115      */
116     public AbstractTableSorter(TableModel tableModel, JTableHeader tableHeader) {
117         this();
118         setTableHeader(tableHeader);
119         setTableModel(tableModel);
120     }
121 
122     /***
123      * DOCUMENT ME!
124      *
125      * @param row
126      * @param column
127      *
128      * @return
129      */
130     public boolean isCellEditable(int row, int column) {
131         return tableModel.isCellEditable(modelIndex(row), column);
132     }
133 
134     /***
135      * DOCUMENT ME!
136      *
137      * @param column
138      *
139      * @return
140      */
141     public Class getColumnClass(int column) {
142         return tableModel.getColumnClass(column);
143     }
144 
145     /***
146      * DOCUMENT ME!
147      *
148      * @param type
149      * @param comparator
150      */
151     public void setColumnComparator(Class type, Comparator comparator) {
152         if (comparator == null) {
153             columnComparators.remove(type);
154         }
155         else {
156             columnComparators.put(type, comparator);
157         }
158     }
159 
160     /***
161      * DOCUMENT ME!
162      *
163      * @return
164      */
165     public int getColumnCount() {
166         return (tableModel == null) ? 0 : tableModel.getColumnCount();
167     }
168 
169     /***
170      * DOCUMENT ME!
171      *
172      * @param column
173      *
174      * @return
175      */
176     public String getColumnName(int column) {
177         return tableModel.getColumnName(column);
178     }
179 
180     /***
181      * DOCUMENT ME!
182      *
183      * @param column
184      *
185      * @return
186      */
187     public abstract Comparator getCustomComparator(int column);
188 
189     // TableModel interface methods 
190     public int getRowCount() {
191         return (tableModel == null) ? 0 : tableModel.getRowCount();
192     }
193 
194     /***
195      * DOCUMENT ME!
196      *
197      * @return
198      */
199     public boolean isSorting() {
200         return sortingColumns.size() != 0;
201     }
202 
203     /***
204      * DOCUMENT ME!
205      *
206      * @return
207      */
208     public List getSortingColumns() {
209         return sortingColumns;
210     }
211 
212     /***
213      * DOCUMENT ME!
214      *
215      * @param column
216      * @param status
217      */
218     public void setSortingStatus(int column, int status) {
219         Directive directive = getDirective(column);
220 
221         if (directive != EMPTY_DIRECTIVE) {
222             sortingColumns.remove(directive);
223         }
224 
225         if (status != NOT_SORTED) {
226             sortingColumns.add(new Directive(column, status));
227         }
228 
229         sortingStatusChanged();
230     }
231 
232     /***
233      * DOCUMENT ME!
234      *
235      * @param column
236      *
237      * @return
238      */
239     public int getSortingStatus(int column) {
240         return getDirective(column).getDirection();
241     }
242 
243     /***
244      * DOCUMENT ME!
245      *
246      * @param tableHeader
247      */
248     public void setTableHeader(JTableHeader tableHeader) {
249         if (this.tableHeader != null) {
250             this.tableHeader.removeMouseListener(mouseListener);
251 
252             TableCellRenderer defaultRenderer = this.tableHeader
253                 .getDefaultRenderer();
254 
255             if (defaultRenderer instanceof SortableHeaderRenderer) {
256                 this.tableHeader.setDefaultRenderer(((SortableHeaderRenderer) defaultRenderer)
257                     .getTableCellRenderer());
258             }
259         }
260 
261         this.tableHeader = tableHeader;
262 
263         if (this.tableHeader != null) {
264             this.tableHeader.addMouseListener(mouseListener);
265             this.tableHeader.setDefaultRenderer(new SortableHeaderRenderer(
266                     this, this.tableHeader.getDefaultRenderer()));
267         }
268     }
269 
270     /***
271      * DOCUMENT ME!
272      *
273      * @return
274      */
275     public JTableHeader getTableHeader() {
276         return tableHeader;
277     }
278 
279     /***
280      * DOCUMENT ME!
281      *
282      * @param tableModel
283      */
284     public void setTableModel(TableModel tableModel) {
285         if (this.tableModel != null) {
286             this.tableModel.removeTableModelListener(tableModelListener);
287         }
288 
289         this.tableModel = tableModel;
290 
291         if (this.tableModel != null) {
292             this.tableModel.addTableModelListener(tableModelListener);
293         }
294 
295         clearSortingState();
296         fireTableStructureChanged();
297     }
298 
299     /***
300      * DOCUMENT ME!
301      *
302      * @return
303      */
304     public TableModel getTableModel() {
305         return tableModel;
306     }
307 
308     /***
309      * DOCUMENT ME!
310      *
311      * @param aValue
312      * @param row
313      * @param column
314      */
315     public void setValueAt(Object aValue, int row, int column) {
316         tableModel.setValueAt(aValue, modelIndex(row), column);
317     }
318 
319     /***
320      * DOCUMENT ME!
321      *
322      * @param row
323      * @param column
324      *
325      * @return
326      */
327     public Object getValueAt(int row, int column) {
328         return tableModel.getValueAt(modelIndex(row), column);
329     }
330 
331     /***
332      * DOCUMENT ME!
333      *
334      * @return
335      */
336     public abstract boolean hasHeaderLine();
337 
338     /***
339      * DOCUMENT ME!
340      *
341      * @return
342      */
343     public abstract int minSortableColumn();
344 
345     /***
346      * DOCUMENT ME!
347      *
348      * @param viewIndex
349      *
350      * @return
351      */
352     public int modelIndex(int viewIndex) {
353         return getViewToModel()[viewIndex].getModelIndex();
354     }
355 
356     /***
357      * DOCUMENT ME!
358      *
359      * @param column
360      *
361      * @return
362      */
363     protected Comparator getComparator(int column) {
364         Comparator comparator = getCustomComparator(column);
365 
366         if (comparator != null) {
367             return comparator;
368         }
369 
370         Class columnType = tableModel.getColumnClass(column);
371 
372         comparator = (Comparator) columnComparators.get(columnType);
373 
374         if (comparator != null) {
375             return comparator;
376         }
377 
378         if (Comparable.class.isAssignableFrom(columnType)) {
379             return COMPARABLE_COMAPRATOR;
380         }
381 
382         return LEXICAL_COMPARATOR;
383     }
384 
385     /***
386      * DOCUMENT ME!
387      *
388      * @param column
389      * @param size
390      *
391      * @return
392      */
393     protected Icon getHeaderRendererIcon(int column, int size) {
394         Directive directive = getDirective(column);
395 
396         if (directive == EMPTY_DIRECTIVE) {
397             return null;
398         }
399 
400         return new Arrow(directive.getDirection() == DESCENDING, size,
401             sortingColumns.indexOf(directive));
402     }
403 
404     /***
405      *
406      */
407     protected void cancelSorting() {
408         sortingColumns.clear();
409         sortingStatusChanged();
410     }
411 
412     /***
413      * DOCUMENT ME!
414      *
415      * @return
416      */
417     int[] getModelToView() {
418         if (modelToView == null) {
419             int n = getViewToModel().length;
420 
421             modelToView = new int[n];
422 
423             for (int i = 0; i < n; i++) {
424                 modelToView[modelIndex(i)] = i;
425             }
426         }
427 
428         return modelToView;
429     }
430 
431     /***
432      *
433      */
434     void clearSortingState() {
435         viewToModel = null;
436         modelToView = null;
437     }
438 
439     /***
440      * DOCUMENT ME!
441      *
442      * @param column
443      *
444      * @return
445      */
446     private Directive getDirective(int column) {
447         for (int i = 0; i < sortingColumns.size(); i++) {
448             Directive directive = (Directive) sortingColumns.get(i);
449 
450             if (directive.getColumn() == column) {
451                 return directive;
452             }
453         }
454 
455         return EMPTY_DIRECTIVE;
456     }
457 
458     /***
459      * DOCUMENT ME!
460      *
461      * @return
462      */
463     private Row[] getViewToModel() {
464         if (viewToModel == null) {
465             int tableModelRowCount = tableModel.getRowCount();
466 
467             viewToModel = new Row[tableModelRowCount];
468 
469             for (int row = 0; row < tableModelRowCount; row++) {
470                 viewToModel[row] = new Row(this, row);
471             }
472 
473             if (isSorting()) {
474                 Arrays.sort(viewToModel);
475             }
476         }
477 
478         return viewToModel;
479     }
480 
481     /***
482      *
483      */
484     private void sortingStatusChanged() {
485         clearSortingState();
486         fireTableDataChanged();
487 
488         if (tableHeader != null) {
489             tableHeader.repaint();
490         }
491     }
492 }