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