1
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
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 }