1 package cz.frantovo.gui.tabulky;
4 import java.awt.Component;
5 import java.awt.event.MouseAdapter;
6 import java.awt.event.MouseEvent;
7 import java.awt.event.MouseListener;
8 import java.util.ArrayList;
9 import java.util.Arrays;
10 import java.util.Comparator;
11 import java.util.HashMap;
12 import java.util.Iterator;
13 import java.util.List;
16 import javax.swing.Icon;
17 import javax.swing.ImageIcon;
18 import javax.swing.JLabel;
19 import javax.swing.JTable;
20 import javax.swing.event.TableModelEvent;
21 import javax.swing.event.TableModelListener;
22 import javax.swing.table.AbstractTableModel;
23 import javax.swing.table.JTableHeader;
24 import javax.swing.table.TableCellRenderer;
25 import javax.swing.table.TableColumnModel;
26 import javax.swing.table.TableModel;
29 * <p>Tato třída přidává tabulce funkčnost řazení podle více sloupců. Skutečný
30 * model obsahující data stačí obalit touto třídou + je potřeba nastavit:
31 * tableSorterModel.setTableHeader(table.getTableHeader());</p>
33 * <p>Jednodušší ale je používat třídu cz.frantovo.gui.tabulky.JFTable, která všechno
36 * <p>Tento TableSorterModel je založen na původním TableSorter od autorů: Philip
37 * Milne, Brendon McLean, Dan van Enckevort a Parwinder Sekhon.</p>
39 * <p>Já jsem přidal hezčí grafické šipky v záhlaví tabulky</p>
41 * @author František Kučera
44 public class TableSorterModel extends AbstractTableModel {
46 private static final long serialVersionUID = 7902301859145867387L;
48 protected TableModel tableModel;
50 public static final int DESCENDING = -1;
52 public static final int NOT_SORTED = 0;
54 public static final int ASCENDING = 1;
56 private static Directive EMPTY_DIRECTIVE = new Directive( -1,
59 public static final Comparator COMPARABLE_COMAPRATOR = new Comparator() {
61 public int compare (Object o1,
64 return ((Comparable) o1).compareTo(o2);
68 public static final Comparator LEXICAL_COMPARATOR = new Comparator() {
70 public int compare (Object o1,
73 return o1.toString().compareTo(o2.toString());
77 private Row[] viewToModel;
79 private int[] modelToView;
81 private JTableHeader tableHeader;
83 private MouseListener mouseListener;
85 private TableModelListener tableModelListener;
87 private Map columnComparators = new HashMap();
89 private List sortingColumns = new ArrayList();
91 private ImageIcon headerIconDown = new ImageIcon(getClass().getResource("/cz/frantovo/gui/tabulky/dolu.png"));
93 private ImageIcon headerIconUp = new ImageIcon(getClass().getResource("/cz/frantovo/gui/tabulky/nahoru.png"));
95 public TableSorterModel ()
97 this.mouseListener = new MouseHandler();
98 this.tableModelListener = new TableModelHandler();
101 public TableSorterModel (TableModel tableModel)
104 setTableModel(tableModel);
107 public TableSorterModel (TableModel tableModel,
108 JTableHeader tableHeader)
111 setTableHeader(tableHeader);
112 setTableModel(tableModel);
115 private void clearSortingState ()
121 public TableModel getTableModel ()
126 public void setTableModel (TableModel tableModel)
128 if (this.tableModel != null) {
129 this.tableModel.removeTableModelListener(tableModelListener);
132 this.tableModel = tableModel;
133 if (this.tableModel != null) {
134 this.tableModel.addTableModelListener(tableModelListener);
138 fireTableStructureChanged();
141 public JTableHeader getTableHeader ()
146 public void setTableHeader (JTableHeader tableHeader)
148 if (this.tableHeader != null) {
149 this.tableHeader.removeMouseListener(mouseListener);
150 TableCellRenderer defaultRenderer = this.tableHeader.getDefaultRenderer();
151 if (defaultRenderer instanceof SortableHeaderRenderer) {
152 this.tableHeader.setDefaultRenderer(((SortableHeaderRenderer) defaultRenderer).tableCellRenderer);
155 this.tableHeader = tableHeader;
156 if (this.tableHeader != null) {
157 this.tableHeader.addMouseListener(mouseListener);
158 this.tableHeader.setDefaultRenderer(new SortableHeaderRenderer(this.tableHeader.getDefaultRenderer()));
162 public boolean isSorting ()
164 return sortingColumns.size() != 0;
167 private Directive getDirective (int column)
169 for (int i = 0; i < sortingColumns.size(); i++) {
170 Directive directive = (Directive) sortingColumns.get(i);
171 if (directive.column == column) {
175 return EMPTY_DIRECTIVE;
178 public int getSortingStatus (int column)
180 return getDirective(column).direction;
183 private void sortingStatusChanged ()
186 fireTableDataChanged();
187 if (tableHeader != null) {
188 tableHeader.repaint();
192 public void setSortingStatus (int column,
195 Directive directive = getDirective(column);
196 if (directive != EMPTY_DIRECTIVE) {
197 sortingColumns.remove(directive);
199 if (status != NOT_SORTED) {
200 sortingColumns.add(new Directive(column,
203 sortingStatusChanged();
206 protected Icon getHeaderRendererIcon (int column,
209 Directive directive = getDirective(column);
210 if (directive == EMPTY_DIRECTIVE) {
214 if (directive.direction == DESCENDING) {
215 return headerIconDown;
221 private void cancelSorting ()
223 sortingColumns.clear();
224 sortingStatusChanged();
227 public void setColumnComparator (Class type,
228 Comparator comparator)
230 if (comparator == null) {
231 columnComparators.remove(type);
233 columnComparators.put(type,
238 protected Comparator getComparator (int column)
240 Class columnType = tableModel.getColumnClass(column);
241 Comparator comparator = (Comparator) columnComparators.get(columnType);
242 if (comparator != null) {
245 if (Comparable.class.isAssignableFrom(columnType)) {
246 return COMPARABLE_COMAPRATOR;
248 return LEXICAL_COMPARATOR;
251 private Row[] getViewToModel ()
253 if (viewToModel == null) {
254 int tableModelRowCount = tableModel.getRowCount();
255 viewToModel = new Row[tableModelRowCount];
256 for (int row = 0; row < tableModelRowCount; row++) {
257 viewToModel[row] = new Row(row);
261 Arrays.sort(viewToModel);
267 public int modelIndex (int viewIndex)
269 if (viewIndex > -1 && viewIndex < getViewToModel().length) {
270 return getViewToModel()[viewIndex].modelIndex;
276 private int[] getModelToView ()
278 if (modelToView == null) {
279 int n = getViewToModel().length;
280 modelToView = new int[n];
281 for (int i = 0; i < n; i++) {
282 modelToView[modelIndex(i)] = i;
288 // Metody rozhran� TableModel
290 public int getRowCount ()
292 return (tableModel == null) ? 0 : tableModel.getRowCount();
295 public int getColumnCount ()
297 return (tableModel == null) ? 0 : tableModel.getColumnCount();
300 public String getColumnName (int column)
302 return tableModel.getColumnName(column);
305 public Class getColumnClass (int column)
307 return tableModel.getColumnClass(column);
310 public boolean isCellEditable (int row,
313 return tableModel.isCellEditable(modelIndex(row),
317 public Object getValueAt (int row,
320 return tableModel.getValueAt(modelIndex(row),
324 public void setValueAt (Object aValue,
328 tableModel.setValueAt(aValue,
335 private class Row implements Comparable {
337 private int modelIndex;
339 public Row (int index)
341 this.modelIndex = index;
344 public int compareTo (Object o)
346 int row1 = modelIndex;
347 int row2 = ((Row) o).modelIndex;
349 for (Iterator it = sortingColumns.iterator(); it.hasNext();) {
350 Directive directive = (Directive) it.next();
351 int column = directive.column;
352 Object o1 = tableModel.getValueAt(row1,
354 Object o2 = tableModel.getValueAt(row2,
358 // Define null less than everything, except
360 if (o1 == null && o2 == null) {
362 } else if (o1 == null) {
364 } else if (o2 == null) {
367 comparison = getComparator(column).compare(o1,
370 if (comparison != 0) {
371 return directive.direction == DESCENDING ? -comparison : comparison;
378 private class TableModelHandler implements TableModelListener {
380 public void tableChanged (TableModelEvent e)
382 // If we're not sorting by anything, just pass the event
390 // If the table structure has changed, cancel the
392 // sorting columns may have been either moved or deleted
395 if (e.getFirstRow() == TableModelEvent.HEADER_ROW) {
401 // We can map a cell event through to the view without
403 // when the following conditions apply:
405 // a) all the changes are on one row (e.getFirstRow() ==
406 // e.getLastRow()) and,
407 // b) all the changes are in one column (column !=
408 // TableModelEvent.ALL_COLUMNS) and,
409 // c) we are not sorting on that column
410 // (getSortingStatus(column) == NOT_SORTED) and,
411 // d) a reverse lookup will not trigger a sort
412 // (modelToView != null)
414 // Note: INSERT and DELETE events fail this test as they
415 // have column == ALL_COLUMNS.
417 // The last check, for (modelToView != null) is to see
419 // is already allocated. If we don't do this check;
420 // sorting can become
421 // a performance bottleneck for applications where cells
422 // change rapidly in different parts of the table. If
424 // change alternately in the sorting column and then
426 // it this class can end up re-sorting on alternate cell
428 // which can be a performance problem for large tables.
430 // clause avoids this problem.
431 int column = e.getColumn();
432 if (e.getFirstRow() == e.getLastRow() && column != TableModelEvent.ALL_COLUMNS && getSortingStatus(column) == NOT_SORTED
433 && modelToView != null) {
434 int viewIndex = getModelToView()[e.getFirstRow()];
435 fireTableChanged(new TableModelEvent(TableSorterModel.this,
443 // Something has happened to the data that may have
444 // invalidated the row order.
446 fireTableDataChanged();
451 private class MouseHandler extends MouseAdapter {
453 public void mouseClicked (MouseEvent e)
455 JTableHeader h = (JTableHeader) e.getSource();
456 TableColumnModel columnModel = h.getColumnModel();
457 int viewColumn = columnModel.getColumnIndexAtX(e.getX());
458 int column = columnModel.getColumn(viewColumn).getModelIndex();
460 int status = getSortingStatus(column);
461 if ( !e.isControlDown()) {
464 // Cycle the sorting states through {NOT_SORTED,
465 // ASCENDING, DESCENDING} or
466 // {NOT_SORTED, DESCENDING, ASCENDING} depending
467 // on whether shift is pressed.
468 status = status + (e.isShiftDown() ? -1 : 1);
469 status = (status + 4) % 3 - 1; // signed mod,
472 setSortingStatus(column,
478 private class SortableHeaderRenderer implements TableCellRenderer {
480 private TableCellRenderer tableCellRenderer;
482 public SortableHeaderRenderer (TableCellRenderer tableCellRenderer)
484 this.tableCellRenderer = tableCellRenderer;
487 public Component getTableCellRendererComponent (JTable table,
494 Component c = tableCellRenderer.getTableCellRendererComponent(table,
500 if (c instanceof JLabel) {
501 JLabel l = (JLabel) c;
502 l.setHorizontalTextPosition(JLabel.LEFT);
503 int modelColumn = table.convertColumnIndexToModel(column);
504 l.setIcon(getHeaderRendererIcon(modelColumn,
505 l.getFont().getSize()));
511 private static class Directive {
515 private int direction;
517 public Directive (int column,
520 this.column = column;
521 this.direction = direction;
525 public void fireTableColumnUpdated (int index)
527 for (int i = 0; i < getRowCount(); i++) {
528 fireTableCellUpdated(i,