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 * 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());<br>
33 * Jednodu��� ale je pou��vat t��du cz.ebanka.util.table.JTable, kter� v�echno
36 * Tento TableSorterModel je zalo�en na p�vodn�m TableSorter od autor�: Philip
37 * Milne, Brendon McLean, Dan van Enckevort a Parwinder Sekhon. <br>
39 * J� jsem p�idal hez�� grafick� �ipky v z�hlav� tabulky
42 * @author František Kučera
45 public class TableSorterModel extends AbstractTableModel {
47 private static final long serialVersionUID = 7902301859145867387L;
49 protected TableModel tableModel;
51 public static final int DESCENDING = -1;
53 public static final int NOT_SORTED = 0;
55 public static final int ASCENDING = 1;
57 private static Directive EMPTY_DIRECTIVE = new Directive( -1,
60 public static final Comparator COMPARABLE_COMAPRATOR = new Comparator() {
62 public int compare (Object o1,
65 return ((Comparable) o1).compareTo(o2);
69 public static final Comparator LEXICAL_COMPARATOR = new Comparator() {
71 public int compare (Object o1,
74 return o1.toString().compareTo(o2.toString());
78 private Row[] viewToModel;
80 private int[] modelToView;
82 private JTableHeader tableHeader;
84 private MouseListener mouseListener;
86 private TableModelListener tableModelListener;
88 private Map columnComparators = new HashMap();
90 private List sortingColumns = new ArrayList();
92 private ImageIcon headerIconDown = new ImageIcon(getClass().getResource("/cz/frantovo/gui/tabulky/dolu.png"));
94 private ImageIcon headerIconUp = new ImageIcon(getClass().getResource("/cz/frantovo/gui/tabulky/nahoru.png"));
96 public TableSorterModel ()
98 this.mouseListener = new MouseHandler();
99 this.tableModelListener = new TableModelHandler();
102 public TableSorterModel (TableModel tableModel)
105 setTableModel(tableModel);
108 public TableSorterModel (TableModel tableModel,
109 JTableHeader tableHeader)
112 setTableHeader(tableHeader);
113 setTableModel(tableModel);
116 private void clearSortingState ()
122 public TableModel getTableModel ()
127 public void setTableModel (TableModel tableModel)
129 if (this.tableModel != null) {
130 this.tableModel.removeTableModelListener(tableModelListener);
133 this.tableModel = tableModel;
134 if (this.tableModel != null) {
135 this.tableModel.addTableModelListener(tableModelListener);
139 fireTableStructureChanged();
142 public JTableHeader getTableHeader ()
147 public void setTableHeader (JTableHeader tableHeader)
149 if (this.tableHeader != null) {
150 this.tableHeader.removeMouseListener(mouseListener);
151 TableCellRenderer defaultRenderer = this.tableHeader.getDefaultRenderer();
152 if (defaultRenderer instanceof SortableHeaderRenderer) {
153 this.tableHeader.setDefaultRenderer(((SortableHeaderRenderer) defaultRenderer).tableCellRenderer);
156 this.tableHeader = tableHeader;
157 if (this.tableHeader != null) {
158 this.tableHeader.addMouseListener(mouseListener);
159 this.tableHeader.setDefaultRenderer(new SortableHeaderRenderer(this.tableHeader.getDefaultRenderer()));
163 public boolean isSorting ()
165 return sortingColumns.size() != 0;
168 private Directive getDirective (int column)
170 for (int i = 0; i < sortingColumns.size(); i++) {
171 Directive directive = (Directive) sortingColumns.get(i);
172 if (directive.column == column) {
176 return EMPTY_DIRECTIVE;
179 public int getSortingStatus (int column)
181 return getDirective(column).direction;
184 private void sortingStatusChanged ()
187 fireTableDataChanged();
188 if (tableHeader != null) {
189 tableHeader.repaint();
193 public void setSortingStatus (int column,
196 Directive directive = getDirective(column);
197 if (directive != EMPTY_DIRECTIVE) {
198 sortingColumns.remove(directive);
200 if (status != NOT_SORTED) {
201 sortingColumns.add(new Directive(column,
204 sortingStatusChanged();
207 protected Icon getHeaderRendererIcon (int column,
210 Directive directive = getDirective(column);
211 if (directive == EMPTY_DIRECTIVE) {
215 if (directive.direction == DESCENDING) {
216 return headerIconDown;
222 private void cancelSorting ()
224 sortingColumns.clear();
225 sortingStatusChanged();
228 public void setColumnComparator (Class type,
229 Comparator comparator)
231 if (comparator == null) {
232 columnComparators.remove(type);
234 columnComparators.put(type,
239 protected Comparator getComparator (int column)
241 Class columnType = tableModel.getColumnClass(column);
242 Comparator comparator = (Comparator) columnComparators.get(columnType);
243 if (comparator != null) {
246 if (Comparable.class.isAssignableFrom(columnType)) {
247 return COMPARABLE_COMAPRATOR;
249 return LEXICAL_COMPARATOR;
252 private Row[] getViewToModel ()
254 if (viewToModel == null) {
255 int tableModelRowCount = tableModel.getRowCount();
256 viewToModel = new Row[tableModelRowCount];
257 for (int row = 0; row < tableModelRowCount; row++) {
258 viewToModel[row] = new Row(row);
262 Arrays.sort(viewToModel);
268 public int modelIndex (int viewIndex)
270 if (viewIndex > -1 && viewIndex < getViewToModel().length) {
271 return getViewToModel()[viewIndex].modelIndex;
277 private int[] getModelToView ()
279 if (modelToView == null) {
280 int n = getViewToModel().length;
281 modelToView = new int[n];
282 for (int i = 0; i < n; i++) {
283 modelToView[modelIndex(i)] = i;
289 // Metody rozhran� TableModel
291 public int getRowCount ()
293 return (tableModel == null) ? 0 : tableModel.getRowCount();
296 public int getColumnCount ()
298 return (tableModel == null) ? 0 : tableModel.getColumnCount();
301 public String getColumnName (int column)
303 return tableModel.getColumnName(column);
306 public Class getColumnClass (int column)
308 return tableModel.getColumnClass(column);
311 public boolean isCellEditable (int row,
314 return tableModel.isCellEditable(modelIndex(row),
318 public Object getValueAt (int row,
321 return tableModel.getValueAt(modelIndex(row),
325 public void setValueAt (Object aValue,
329 tableModel.setValueAt(aValue,
336 private class Row implements Comparable {
338 private int modelIndex;
340 public Row (int index)
342 this.modelIndex = index;
345 public int compareTo (Object o)
347 int row1 = modelIndex;
348 int row2 = ((Row) o).modelIndex;
350 for (Iterator it = sortingColumns.iterator(); it.hasNext();) {
351 Directive directive = (Directive) it.next();
352 int column = directive.column;
353 Object o1 = tableModel.getValueAt(row1,
355 Object o2 = tableModel.getValueAt(row2,
359 // Define null less than everything, except
361 if (o1 == null && o2 == null) {
363 } else if (o1 == null) {
365 } else if (o2 == null) {
368 comparison = getComparator(column).compare(o1,
371 if (comparison != 0) {
372 return directive.direction == DESCENDING ? -comparison : comparison;
379 private class TableModelHandler implements TableModelListener {
381 public void tableChanged (TableModelEvent e)
383 // If we're not sorting by anything, just pass the event
391 // If the table structure has changed, cancel the
393 // sorting columns may have been either moved or deleted
396 if (e.getFirstRow() == TableModelEvent.HEADER_ROW) {
402 // We can map a cell event through to the view without
404 // when the following conditions apply:
406 // a) all the changes are on one row (e.getFirstRow() ==
407 // e.getLastRow()) and,
408 // b) all the changes are in one column (column !=
409 // TableModelEvent.ALL_COLUMNS) and,
410 // c) we are not sorting on that column
411 // (getSortingStatus(column) == NOT_SORTED) and,
412 // d) a reverse lookup will not trigger a sort
413 // (modelToView != null)
415 // Note: INSERT and DELETE events fail this test as they
416 // have column == ALL_COLUMNS.
418 // The last check, for (modelToView != null) is to see
420 // is already allocated. If we don't do this check;
421 // sorting can become
422 // a performance bottleneck for applications where cells
423 // change rapidly in different parts of the table. If
425 // change alternately in the sorting column and then
427 // it this class can end up re-sorting on alternate cell
429 // which can be a performance problem for large tables.
431 // clause avoids this problem.
432 int column = e.getColumn();
433 if (e.getFirstRow() == e.getLastRow() && column != TableModelEvent.ALL_COLUMNS && getSortingStatus(column) == NOT_SORTED
434 && modelToView != null) {
435 int viewIndex = getModelToView()[e.getFirstRow()];
436 fireTableChanged(new TableModelEvent(TableSorterModel.this,
444 // Something has happened to the data that may have
445 // invalidated the row order.
447 fireTableDataChanged();
452 private class MouseHandler extends MouseAdapter {
454 public void mouseClicked (MouseEvent e)
456 JTableHeader h = (JTableHeader) e.getSource();
457 TableColumnModel columnModel = h.getColumnModel();
458 int viewColumn = columnModel.getColumnIndexAtX(e.getX());
459 int column = columnModel.getColumn(viewColumn).getModelIndex();
461 int status = getSortingStatus(column);
462 if ( !e.isControlDown()) {
465 // Cycle the sorting states through {NOT_SORTED,
466 // ASCENDING, DESCENDING} or
467 // {NOT_SORTED, DESCENDING, ASCENDING} depending
468 // on whether shift is pressed.
469 status = status + (e.isShiftDown() ? -1 : 1);
470 status = (status + 4) % 3 - 1; // signed mod,
473 setSortingStatus(column,
479 private class SortableHeaderRenderer implements TableCellRenderer {
481 private TableCellRenderer tableCellRenderer;
483 public SortableHeaderRenderer (TableCellRenderer tableCellRenderer)
485 this.tableCellRenderer = tableCellRenderer;
488 public Component getTableCellRendererComponent (JTable table,
495 Component c = tableCellRenderer.getTableCellRendererComponent(table,
501 if (c instanceof JLabel) {
502 JLabel l = (JLabel) c;
503 l.setHorizontalTextPosition(JLabel.LEFT);
504 int modelColumn = table.convertColumnIndexToModel(column);
505 l.setIcon(getHeaderRendererIcon(modelColumn,
506 l.getFont().getSize()));
512 private static class Directive {
516 private int direction;
518 public Directive (int column,
521 this.column = column;
522 this.direction = direction;
526 public void fireTableColumnUpdated (int index)
528 for (int i = 0; i < getRowCount(); i++) {
529 fireTableCellUpdated(i,