1 package ch.elca.el4j.services.gui.model.tablemodel;
2 import java.awt.*;
3 import java.awt.event.*;
4 import java.util.*;
5 import java.util.List;
6
7 import javax.swing.*;
8 import javax.swing.event.TableModelEvent;
9 import javax.swing.event.TableModelListener;
10 import javax.swing.table.*;
11
12 import org.jdesktop.swingbinding.validation.ValidatedProperty;
13
14 import ch.elca.el4j.util.codingsupport.annotations.FindBugsSuppressWarnings;
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75 public class TableSorter extends AbstractTableModel {
76
77
78
79 private static final long serialVersionUID = 1L;
80
81 protected TableModel tableModel;
82
83 public static final int DESCENDING = -1;
84 public static final int NOT_SORTED = 0;
85 public static final int ASCENDING = 1;
86
87 private static Directive EMPTY_DIRECTIVE = new Directive(-1, NOT_SORTED);
88
89 public static final Comparator COMPARABLE_COMAPRATOR = new Comparator() {
90 public int compare(Object o1, Object o2) {
91 return ((Comparable) o1).compareTo(o2);
92 }
93 };
94 public static final Comparator LEXICAL_COMPARATOR = new Comparator() {
95 public int compare(Object o1, Object o2) {
96 return o1.toString().compareTo(o2.toString());
97 }
98 };
99
100 private Row[] viewToModel;
101 private int[] modelToView;
102
103 private JTableHeader tableHeader;
104 private MouseListener mouseListener;
105 private TableModelListener tableModelListener;
106 private Map columnComparators = new HashMap();
107 private List sortingColumns = new ArrayList();
108 private boolean suppressChangeEvents = false;
109
110 public TableSorter() {
111 this.mouseListener = new MouseHandler();
112 this.tableModelListener = new TableModelHandler();
113 }
114
115 public TableSorter(TableModel tableModel) {
116 this();
117 setTableModel(tableModel);
118 }
119
120 public TableSorter(TableModel tableModel, JTableHeader tableHeader) {
121 this();
122 setTableHeader(tableHeader);
123 setTableModel(tableModel);
124 }
125
126 private void clearSortingState() {
127 viewToModel = null;
128 modelToView = null;
129 }
130
131 public TableModel getTableModel() {
132 return tableModel;
133 }
134
135 public void setTableModel(TableModel tableModel) {
136 if (this.tableModel != null) {
137 this.tableModel.removeTableModelListener(tableModelListener);
138 }
139
140 this.tableModel = tableModel;
141 if (this.tableModel != null) {
142 this.tableModel.addTableModelListener(tableModelListener);
143 }
144
145 clearSortingState();
146 fireTableStructureChanged();
147 }
148
149 public JTableHeader getTableHeader() {
150 return tableHeader;
151 }
152
153 public void setTableHeader(JTableHeader tableHeader) {
154 if (this.tableHeader != null) {
155 this.tableHeader.removeMouseListener(mouseListener);
156 TableCellRenderer defaultRenderer = this.tableHeader.getDefaultRenderer();
157 if (defaultRenderer instanceof SortableHeaderRenderer) {
158 this.tableHeader.setDefaultRenderer(((SortableHeaderRenderer) defaultRenderer).tableCellRenderer);
159 }
160 }
161 this.tableHeader = tableHeader;
162 if (this.tableHeader != null) {
163 this.tableHeader.addMouseListener(mouseListener);
164 this.tableHeader.setDefaultRenderer(
165 new SortableHeaderRenderer(this.tableHeader.getDefaultRenderer()));
166 }
167 }
168
169 public boolean isSorting() {
170 return sortingColumns.size() != 0;
171 }
172
173 private Directive getDirective(int column) {
174 for (int i = 0; i < sortingColumns.size(); i++) {
175 Directive directive = (Directive)sortingColumns.get(i);
176 if (directive.column == column) {
177 return directive;
178 }
179 }
180 return EMPTY_DIRECTIVE;
181 }
182
183 public int getSortingStatus(int column) {
184 return getDirective(column).direction;
185 }
186
187 private void sortingStatusChanged() {
188 clearSortingState();
189 fireTableDataChanged();
190 if (tableHeader != null) {
191 tableHeader.repaint();
192 }
193 }
194
195 public void setSortingStatus(int column, int status) {
196 Directive directive = getDirective(column);
197 if (directive != EMPTY_DIRECTIVE) {
198 sortingColumns.remove(directive);
199 }
200 if (status != NOT_SORTED) {
201 sortingColumns.add(new Directive(column, status));
202 }
203 sortingStatusChanged();
204 }
205
206 protected Icon getHeaderRendererIcon(int column, int size) {
207 Directive directive = getDirective(column);
208 if (directive == EMPTY_DIRECTIVE) {
209 return null;
210 }
211 return new Arrow(directive.direction == DESCENDING, size, sortingColumns.indexOf(directive));
212 }
213
214 private void cancelSorting() {
215 sortingColumns.clear();
216 sortingStatusChanged();
217 }
218
219 public void setColumnComparator(Class type, Comparator comparator) {
220 if (comparator == null) {
221 columnComparators.remove(type);
222 } else {
223 columnComparators.put(type, comparator);
224 }
225 }
226
227 protected Comparator getComparator(int column) {
228 Class columnType = tableModel.getColumnClass(column);
229 Comparator comparator = (Comparator) columnComparators.get(columnType);
230 if (comparator != null) {
231 return comparator;
232 }
233 if (Comparable.class.isAssignableFrom(columnType)) {
234 return COMPARABLE_COMAPRATOR;
235 }
236 return LEXICAL_COMPARATOR;
237 }
238
239 private Row[] getViewToModel() {
240 if (viewToModel == null) {
241 int tableModelRowCount = tableModel.getRowCount();
242 viewToModel = new Row[tableModelRowCount];
243 for (int row = 0; row < tableModelRowCount; row++) {
244 viewToModel[row] = new Row(row);
245 }
246
247 if (isSorting()) {
248 Arrays.sort(viewToModel);
249 }
250 }
251 return viewToModel;
252 }
253
254 public int modelIndex(int viewIndex) {
255 return getViewToModel()[viewIndex].modelIndex;
256 }
257
258 private int[] getModelToView() {
259 if (modelToView == null) {
260 int n = getViewToModel().length;
261 modelToView = new int[n];
262 for (int i = 0; i < n; i++) {
263 modelToView[modelIndex(i)] = i;
264 }
265 }
266 return modelToView;
267 }
268
269
270
271 public int getRowCount() {
272 return (tableModel == null) ? 0 : tableModel.getRowCount();
273 }
274
275 public int getColumnCount() {
276 return (tableModel == null) ? 0 : tableModel.getColumnCount();
277 }
278
279 public String getColumnName(int column) {
280 return tableModel.getColumnName(column);
281 }
282
283 public Class getColumnClass(int column) {
284 return tableModel.getColumnClass(column);
285 }
286
287 public boolean isCellEditable(int row, int column) {
288 return tableModel.isCellEditable(modelIndex(row), column);
289 }
290
291 public Object getValueAt(int row, int column) {
292 return tableModel.getValueAt(modelIndex(row), column);
293 }
294
295 public void setValueAt(Object aValue, int row, int column) {
296 tableModel.setValueAt(aValue, modelIndex(row), column);
297 }
298
299
300
301 private class Row implements Comparable {
302 private int modelIndex;
303
304 public Row(int index) {
305 this.modelIndex = index;
306 }
307
308 public int compareTo(Object o) {
309 int row1 = modelIndex;
310 int row2 = ((Row) o).modelIndex;
311
312 for (Iterator it = sortingColumns.iterator(); it.hasNext();) {
313 Directive directive = (Directive) it.next();
314 int column = directive.column;
315 Object o1 = tableModel.getValueAt(row1, column);
316 Object o2 = tableModel.getValueAt(row2, column);
317
318
319 if (o1 instanceof ValidatedProperty) {
320 ValidatedProperty vp = (ValidatedProperty) o1;
321 o1 = vp.getValue();
322 }
323 if (o2 instanceof ValidatedProperty) {
324 ValidatedProperty vp = (ValidatedProperty) o2;
325 o2 = vp.getValue();
326 }
327
328 int comparison = 0;
329
330 if (o1 == null && o2 == null) {
331 comparison = 0;
332 } else if (o1 == null) {
333 comparison = -1;
334 } else if (o2 == null) {
335 comparison = 1;
336 } else if (o1 instanceof String && o2 instanceof String) {
337 comparison = ((String) o1).compareToIgnoreCase((String) o2);
338 } else {
339 comparison = getComparator(column).compare(o1, o2);
340 }
341 if (comparison != 0) {
342 return directive.direction == DESCENDING ? -comparison : comparison;
343 }
344 }
345 return 0;
346 }
347 }
348
349 private class TableModelHandler implements TableModelListener {
350 public void tableChanged(TableModelEvent e) {
351 if (suppressChangeEvents) {
352
353 return;
354 }
355
356 if (!isSorting()) {
357 clearSortingState();
358 fireTableChanged(e);
359 return;
360 }
361
362
363
364
365 if (e.getFirstRow() == TableModelEvent.HEADER_ROW) {
366 cancelSorting();
367 fireTableChanged(e);
368 return;
369 }
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389 int column = e.getColumn();
390 if (e.getFirstRow() == e.getLastRow()
391 && column != TableModelEvent.ALL_COLUMNS
392 && getSortingStatus(column) == NOT_SORTED
393 && modelToView != null) {
394 int viewIndex = getModelToView()[e.getFirstRow()];
395 fireTableChanged(new TableModelEvent(TableSorter.this,
396 viewIndex, viewIndex,
397 column, e.getType()));
398 return;
399 }
400
401
402 clearSortingState();
403 fireTableDataChanged();
404 return;
405 }
406 }
407
408 private class MouseHandler extends MouseAdapter {
409 public void mouseClicked(MouseEvent e) {
410 JTableHeader h = (JTableHeader) e.getSource();
411 TableColumnModel columnModel = h.getColumnModel();
412 int viewColumn = columnModel.getColumnIndexAtX(e.getX());
413 int column = columnModel.getColumn(viewColumn).getModelIndex();
414 if (column != -1) {
415 int status = getSortingStatus(column);
416 if (!e.isControlDown()) {
417 cancelSorting();
418 }
419
420
421 status = status + (e.isShiftDown() ? -1 : 1);
422 status = (status + 4) % 3 - 1;
423 setSortingStatus(column, status);
424 }
425 }
426 }
427
428 private static class Arrow implements Icon {
429 private boolean descending;
430 private int size;
431 private int priority;
432
433 public Arrow(boolean descending, int size, int priority) {
434 this.descending = descending;
435 this.size = size;
436 this.priority = priority;
437 }
438
439 @FindBugsSuppressWarnings(value = "ICAST_IDIV_CAST_TO_DOUBLE",
440 justification = "False positive.")
441 public void paintIcon(Component c, Graphics g, int x, int y) {
442 Color color = c == null ? Color.GRAY : c.getBackground();
443
444
445 int dx = (int)(size/2*Math.pow(0.8, priority));
446 int dy = descending ? dx : -dx;
447
448 int yaligned = y + 5*size/6 + (descending ? -dy : 0);
449 int shift = descending ? 1 : -1;
450 g.translate(x, yaligned);
451
452
453 g.setColor(color.darker());
454 g.drawLine(dx / 2, dy, 0, 0);
455 g.drawLine(dx / 2, dy + shift, 0, shift);
456
457
458 g.setColor(color.brighter());
459 g.drawLine(dx / 2, dy, dx, 0);
460 g.drawLine(dx / 2, dy + shift, dx, shift);
461
462
463 if (descending) {
464 g.setColor(color.darker().darker());
465 } else {
466 g.setColor(color.brighter().brighter());
467 }
468 g.drawLine(dx, 0, 0, 0);
469
470 g.setColor(color);
471 g.translate(-x, -yaligned);
472 }
473
474 public int getIconWidth() {
475 return size;
476 }
477
478 public int getIconHeight() {
479 return size;
480 }
481 }
482
483 private class SortableHeaderRenderer implements TableCellRenderer {
484 private TableCellRenderer tableCellRenderer;
485
486 public SortableHeaderRenderer(TableCellRenderer tableCellRenderer) {
487 this.tableCellRenderer = tableCellRenderer;
488 }
489
490 public Component getTableCellRendererComponent(JTable table,
491 Object value,
492 boolean isSelected,
493 boolean hasFocus,
494 int row,
495 int column) {
496
497 Component c = tableCellRenderer.getTableCellRendererComponent(table,
498 value, isSelected, hasFocus, row, column);
499 if (c instanceof JLabel) {
500 JLabel l = (JLabel) c;
501 l.setHorizontalTextPosition(JLabel.LEFT);
502 int modelColumn = table.convertColumnIndexToModel(column);
503 l.setIcon(getHeaderRendererIcon(modelColumn, l.getFont().getSize()));
504 }
505 return c;
506 }
507 }
508
509 private static class Directive {
510 private int column;
511 private int direction;
512
513 public Directive(int column, int direction) {
514 this.column = column;
515 this.direction = direction;
516 }
517 }
518
519 public boolean isSuppressChangeEvents() {
520 return suppressChangeEvents;
521 }
522
523 public void setSuppressChangeEvents(boolean suppress) {
524 suppressChangeEvents = suppress;
525 if (!suppress) {
526 sortingStatusChanged();
527 }
528 }
529 }