1 package cz.frantovo.gui.tabulky;
3 import java.awt.Component;
4 import java.awt.event.MouseAdapter;
5 import java.awt.event.MouseEvent;
6 import java.awt.event.MouseListener;
7 import java.util.ArrayList;
8 import java.util.Arrays;
9 import java.util.Comparator;
10 import java.util.HashMap;
11 import java.util.Iterator;
12 import java.util.List;
15 import javax.swing.Icon;
16 import javax.swing.ImageIcon;
17 import javax.swing.JLabel;
18 import javax.swing.JTable;
19 import javax.swing.event.TableModelEvent;
20 import javax.swing.event.TableModelListener;
21 import javax.swing.table.AbstractTableModel;
22 import javax.swing.table.JTableHeader;
23 import javax.swing.table.TableCellRenderer;
24 import javax.swing.table.TableColumnModel;
25 import javax.swing.table.TableModel;
28 * <p>Tato třída přidává tabulce funkčnost řazení podle více sloupců. Skutečný
29 * model obsahující data stačí obalit touto třídou + je potřeba nastavit:
30 * tableSorterModel.setTableHeader(table.getTableHeader());</p>
32 * <p>Jednodušší ale je používat třídu cz.frantovo.gui.tabulky.JFTable, která všechno
35 * <p>Tento TableSorterModel je založen na původním TableSorter od autorů: Philip
36 * Milne, Brendon McLean, Dan van Enckevort a Parwinder Sekhon.</p>
38 * <p>Já jsem přidal hezčí grafické šipky v záhlaví tabulky</p>
40 * @author František Kučera
42 public class TableSorterModel extends AbstractTableModel {
44 private static final long serialVersionUID = 7902301859145867387L;
45 protected TableModel tableModel;
46 public static final int DESCENDING = -1;
47 public static final int NOT_SORTED = 0;
48 public static final int ASCENDING = 1;
49 private static Directive EMPTY_DIRECTIVE = new Directive(-1,
51 public static final Comparator COMPARABLE_COMAPRATOR = new Comparator() {
53 public int compare(Object o1,
55 return ((Comparable) o1).compareTo(o2);
58 public static final Comparator LEXICAL_COMPARATOR = new Comparator() {
60 public int compare(Object o1,
62 return o1.toString().compareTo(o2.toString());
65 private Row[] viewToModel;
66 private int[] modelToView;
67 private JTableHeader tableHeader;
68 private MouseListener mouseListener;
69 private TableModelListener tableModelListener;
70 private Map columnComparators = new HashMap();
71 private List sortingColumns = new ArrayList();
72 private ImageIcon headerIconDown = new ImageIcon(getClass().getResource("/cz/frantovo/gui/tabulky/dolu.png"));
73 private ImageIcon headerIconUp = new ImageIcon(getClass().getResource("/cz/frantovo/gui/tabulky/nahoru.png"));
75 public TableSorterModel() {
76 this.mouseListener = new MouseHandler();
77 this.tableModelListener = new TableModelHandler();
80 public TableSorterModel(TableModel tableModel) {
82 setTableModel(tableModel);
85 public TableSorterModel(TableModel tableModel,
86 JTableHeader tableHeader) {
88 setTableHeader(tableHeader);
89 setTableModel(tableModel);
92 private void clearSortingState() {
97 public TableModel getTableModel() {
101 public void setTableModel(TableModel tableModel) {
102 if (this.tableModel != null) {
103 this.tableModel.removeTableModelListener(tableModelListener);
106 this.tableModel = tableModel;
107 if (this.tableModel != null) {
108 this.tableModel.addTableModelListener(tableModelListener);
112 fireTableStructureChanged();
115 public JTableHeader getTableHeader() {
119 public void setTableHeader(JTableHeader tableHeader) {
120 if (this.tableHeader != null) {
121 this.tableHeader.removeMouseListener(mouseListener);
122 TableCellRenderer defaultRenderer = this.tableHeader.getDefaultRenderer();
123 if (defaultRenderer instanceof SortableHeaderRenderer) {
124 this.tableHeader.setDefaultRenderer(((SortableHeaderRenderer) defaultRenderer).tableCellRenderer);
127 this.tableHeader = tableHeader;
128 if (this.tableHeader != null) {
129 this.tableHeader.addMouseListener(mouseListener);
130 this.tableHeader.setDefaultRenderer(new SortableHeaderRenderer(this.tableHeader.getDefaultRenderer()));
134 public boolean isSorting() {
135 return sortingColumns.size() != 0;
138 private Directive getDirective(int column) {
139 for (int i = 0; i < sortingColumns.size(); i++) {
140 Directive directive = (Directive) sortingColumns.get(i);
141 if (directive.column == column) {
145 return EMPTY_DIRECTIVE;
148 public int getSortingStatus(int column) {
149 return getDirective(column).direction;
152 private void sortingStatusChanged() {
154 fireTableDataChanged();
155 if (tableHeader != null) {
156 tableHeader.repaint();
160 public void setSortingStatus(int column,
162 Directive directive = getDirective(column);
163 if (directive != EMPTY_DIRECTIVE) {
164 sortingColumns.remove(directive);
166 if (status != NOT_SORTED) {
167 sortingColumns.add(new Directive(column,
170 sortingStatusChanged();
173 protected Icon getHeaderRendererIcon(int column,
175 Directive directive = getDirective(column);
176 if (directive == EMPTY_DIRECTIVE) {
180 if (directive.direction == DESCENDING) {
181 return headerIconDown;
187 private void cancelSorting() {
188 sortingColumns.clear();
189 sortingStatusChanged();
192 public void setColumnComparator(Class type,
193 Comparator comparator) {
194 if (comparator == null) {
195 columnComparators.remove(type);
197 columnComparators.put(type,
202 protected Comparator getComparator(int column) {
203 Class columnType = tableModel.getColumnClass(column);
204 Comparator comparator = (Comparator) columnComparators.get(columnType);
205 if (comparator != null) {
208 if (Comparable.class.isAssignableFrom(columnType)) {
209 return COMPARABLE_COMAPRATOR;
211 return LEXICAL_COMPARATOR;
214 private Row[] getViewToModel() {
215 if (viewToModel == null) {
216 int tableModelRowCount = tableModel.getRowCount();
217 viewToModel = new Row[tableModelRowCount];
218 for (int row = 0; row < tableModelRowCount; row++) {
219 viewToModel[row] = new Row(row);
223 Arrays.sort(viewToModel);
229 public int modelIndex(int viewIndex) {
230 if (viewIndex > -1 && viewIndex < getViewToModel().length) {
231 return getViewToModel()[viewIndex].modelIndex;
237 private int[] getModelToView() {
238 if (modelToView == null) {
239 int n = getViewToModel().length;
240 modelToView = new int[n];
241 for (int i = 0; i < n; i++) {
242 modelToView[modelIndex(i)] = i;
248 // Metody rozhran� TableModel
249 public int getRowCount() {
250 return (tableModel == null) ? 0 : tableModel.getRowCount();
253 public int getColumnCount() {
254 return (tableModel == null) ? 0 : tableModel.getColumnCount();
258 public String getColumnName(int column) {
259 return tableModel.getColumnName(column);
263 public Class getColumnClass(int column) {
264 return tableModel.getColumnClass(column);
268 public boolean isCellEditable(int row,
270 return tableModel.isCellEditable(modelIndex(row),
275 public Object getValueAt(int row,
277 return tableModel.getValueAt(modelIndex(row),
282 public void setValueAt(Object aValue,
285 tableModel.setValueAt(aValue,
291 private class Row implements Comparable {
293 private int modelIndex;
295 public Row(int index) {
296 this.modelIndex = index;
299 public int compareTo(Object o) {
300 int row1 = modelIndex;
301 int row2 = ((Row) o).modelIndex;
303 for (Iterator it = sortingColumns.iterator(); it.hasNext();) {
304 Directive directive = (Directive) it.next();
305 int column = directive.column;
306 Object o1 = tableModel.getValueAt(row1,
308 Object o2 = tableModel.getValueAt(row2,
312 // Define null less than everything, except
314 if (o1 == null && o2 == null) {
316 } else if (o1 == null) {
318 } else if (o2 == null) {
321 comparison = getComparator(column).compare(o1,
324 if (comparison != 0) {
325 return directive.direction == DESCENDING ? -comparison : comparison;
333 private class TableModelHandler implements TableModelListener {
335 public void tableChanged(TableModelEvent e) {
336 // If we're not sorting by anything, just pass the event
344 // If the table structure has changed, cancel the
346 // sorting columns may have been either moved or deleted
349 if (e.getFirstRow() == TableModelEvent.HEADER_ROW) {
355 // We can map a cell event through to the view without
357 // when the following conditions apply:
359 // a) all the changes are on one row (e.getFirstRow() ==
360 // e.getLastRow()) and,
361 // b) all the changes are in one column (column !=
362 // TableModelEvent.ALL_COLUMNS) and,
363 // c) we are not sorting on that column
364 // (getSortingStatus(column) == NOT_SORTED) and,
365 // d) a reverse lookup will not trigger a sort
366 // (modelToView != null)
368 // Note: INSERT and DELETE events fail this test as they
369 // have column == ALL_COLUMNS.
371 // The last check, for (modelToView != null) is to see
373 // is already allocated. If we don't do this check;
374 // sorting can become
375 // a performance bottleneck for applications where cells
376 // change rapidly in different parts of the table. If
378 // change alternately in the sorting column and then
380 // it this class can end up re-sorting on alternate cell
382 // which can be a performance problem for large tables.
384 // clause avoids this problem.
385 int column = e.getColumn();
386 if (e.getFirstRow() == e.getLastRow() && column != TableModelEvent.ALL_COLUMNS && getSortingStatus(column) == NOT_SORTED && modelToView != null) {
387 int viewIndex = getModelToView()[e.getFirstRow()];
388 fireTableChanged(new TableModelEvent(TableSorterModel.this,
396 // Something has happened to the data that may have
397 // invalidated the row order.
399 fireTableDataChanged();
404 private class MouseHandler extends MouseAdapter {
407 public void mouseClicked(MouseEvent e) {
408 JTableHeader h = (JTableHeader) e.getSource();
409 TableColumnModel columnModel = h.getColumnModel();
410 int viewColumn = columnModel.getColumnIndexAtX(e.getX());
411 int column = columnModel.getColumn(viewColumn).getModelIndex();
413 int status = getSortingStatus(column);
414 if (!e.isControlDown()) {
417 // Cycle the sorting states through {NOT_SORTED,
418 // ASCENDING, DESCENDING} or
419 // {NOT_SORTED, DESCENDING, ASCENDING} depending
420 // on whether shift is pressed.
421 status = status + (e.isShiftDown() ? -1 : 1);
422 status = (status + 4) % 3 - 1; // signed mod,
425 setSortingStatus(column,
431 private class SortableHeaderRenderer implements TableCellRenderer {
433 private TableCellRenderer tableCellRenderer;
435 public SortableHeaderRenderer(TableCellRenderer tableCellRenderer) {
436 this.tableCellRenderer = tableCellRenderer;
439 public Component getTableCellRendererComponent(JTable table,
445 Component c = tableCellRenderer.getTableCellRendererComponent(table,
451 if (c instanceof JLabel) {
452 JLabel l = (JLabel) c;
453 l.setHorizontalTextPosition(JLabel.LEFT);
454 int modelColumn = table.convertColumnIndexToModel(column);
455 l.setIcon(getHeaderRendererIcon(modelColumn,
456 l.getFont().getSize()));
462 private static class Directive {
465 private int direction;
467 public Directive(int column,
469 this.column = column;
470 this.direction = direction;
474 public void fireTableColumnUpdated(int index) {
475 for (int i = 0; i < getRowCount(); i++) {
476 fireTableCellUpdated(i,