franta-hg@0: package cz.frantovo.gui.tabulky; franta-hg@0: franta-hg@0: franta-hg@0: import java.awt.Component; franta-hg@0: import java.awt.event.MouseAdapter; franta-hg@0: import java.awt.event.MouseEvent; franta-hg@0: import java.awt.event.MouseListener; franta-hg@0: import java.util.ArrayList; franta-hg@0: import java.util.Arrays; franta-hg@0: import java.util.Comparator; franta-hg@0: import java.util.HashMap; franta-hg@0: import java.util.Iterator; franta-hg@0: import java.util.List; franta-hg@0: import java.util.Map; franta-hg@0: franta-hg@0: import javax.swing.Icon; franta-hg@0: import javax.swing.ImageIcon; franta-hg@0: import javax.swing.JLabel; franta-hg@0: import javax.swing.JTable; franta-hg@0: import javax.swing.event.TableModelEvent; franta-hg@0: import javax.swing.event.TableModelListener; franta-hg@0: import javax.swing.table.AbstractTableModel; franta-hg@0: import javax.swing.table.JTableHeader; franta-hg@0: import javax.swing.table.TableCellRenderer; franta-hg@0: import javax.swing.table.TableColumnModel; franta-hg@0: import javax.swing.table.TableModel; franta-hg@0: franta-hg@0: /** franta-hg@2: *

Tato třída přidává tabulce funkčnost řazení podle více sloupců. Skutečný franta-hg@2: * model obsahující data stačí obalit touto třídou + je potřeba nastavit: franta-hg@2: * tableSorterModel.setTableHeader(table.getTableHeader());

franta-hg@0: * franta-hg@2: *

Jednodušší ale je používat třídu cz.frantovo.gui.tabulky.JFTable, která všechno franta-hg@2: * udělá za vás.

franta-hg@2: * franta-hg@2: *

Tento TableSorterModel je založen na původním TableSorter od autorů: Philip franta-hg@2: * Milne, Brendon McLean, Dan van Enckevort a Parwinder Sekhon.

franta-hg@2: * franta-hg@2: *

Já jsem přidal hezčí grafické šipky v záhlaví tabulky

franta-hg@0: * franta-hg@0: * @author František Kučera franta-hg@0: */ franta-hg@0: franta-hg@0: public class TableSorterModel extends AbstractTableModel { franta-hg@0: franta-hg@0: private static final long serialVersionUID = 7902301859145867387L; franta-hg@0: franta-hg@0: protected TableModel tableModel; franta-hg@0: franta-hg@0: public static final int DESCENDING = -1; franta-hg@0: franta-hg@0: public static final int NOT_SORTED = 0; franta-hg@0: franta-hg@0: public static final int ASCENDING = 1; franta-hg@0: franta-hg@0: private static Directive EMPTY_DIRECTIVE = new Directive( -1, franta-hg@0: NOT_SORTED); franta-hg@0: franta-hg@0: public static final Comparator COMPARABLE_COMAPRATOR = new Comparator() { franta-hg@0: franta-hg@0: public int compare (Object o1, franta-hg@0: Object o2) franta-hg@0: { franta-hg@0: return ((Comparable) o1).compareTo(o2); franta-hg@0: } franta-hg@0: }; franta-hg@0: franta-hg@0: public static final Comparator LEXICAL_COMPARATOR = new Comparator() { franta-hg@0: franta-hg@0: public int compare (Object o1, franta-hg@0: Object o2) franta-hg@0: { franta-hg@0: return o1.toString().compareTo(o2.toString()); franta-hg@0: } franta-hg@0: }; franta-hg@0: franta-hg@0: private Row[] viewToModel; franta-hg@0: franta-hg@0: private int[] modelToView; franta-hg@0: franta-hg@0: private JTableHeader tableHeader; franta-hg@0: franta-hg@0: private MouseListener mouseListener; franta-hg@0: franta-hg@0: private TableModelListener tableModelListener; franta-hg@0: franta-hg@0: private Map columnComparators = new HashMap(); franta-hg@0: franta-hg@0: private List sortingColumns = new ArrayList(); franta-hg@0: franta-hg@0: private ImageIcon headerIconDown = new ImageIcon(getClass().getResource("/cz/frantovo/gui/tabulky/dolu.png")); franta-hg@0: franta-hg@0: private ImageIcon headerIconUp = new ImageIcon(getClass().getResource("/cz/frantovo/gui/tabulky/nahoru.png")); franta-hg@0: franta-hg@0: public TableSorterModel () franta-hg@0: { franta-hg@0: this.mouseListener = new MouseHandler(); franta-hg@0: this.tableModelListener = new TableModelHandler(); franta-hg@0: } franta-hg@0: franta-hg@0: public TableSorterModel (TableModel tableModel) franta-hg@0: { franta-hg@0: this(); franta-hg@0: setTableModel(tableModel); franta-hg@0: } franta-hg@0: franta-hg@0: public TableSorterModel (TableModel tableModel, franta-hg@0: JTableHeader tableHeader) franta-hg@0: { franta-hg@0: this(); franta-hg@0: setTableHeader(tableHeader); franta-hg@0: setTableModel(tableModel); franta-hg@0: } franta-hg@0: franta-hg@0: private void clearSortingState () franta-hg@0: { franta-hg@0: viewToModel = null; franta-hg@0: modelToView = null; franta-hg@0: } franta-hg@0: franta-hg@0: public TableModel getTableModel () franta-hg@0: { franta-hg@0: return tableModel; franta-hg@0: } franta-hg@0: franta-hg@0: public void setTableModel (TableModel tableModel) franta-hg@0: { franta-hg@0: if (this.tableModel != null) { franta-hg@0: this.tableModel.removeTableModelListener(tableModelListener); franta-hg@0: } franta-hg@0: franta-hg@0: this.tableModel = tableModel; franta-hg@0: if (this.tableModel != null) { franta-hg@0: this.tableModel.addTableModelListener(tableModelListener); franta-hg@0: } franta-hg@0: franta-hg@0: clearSortingState(); franta-hg@0: fireTableStructureChanged(); franta-hg@0: } franta-hg@0: franta-hg@0: public JTableHeader getTableHeader () franta-hg@0: { franta-hg@0: return tableHeader; franta-hg@0: } franta-hg@0: franta-hg@0: public void setTableHeader (JTableHeader tableHeader) franta-hg@0: { franta-hg@0: if (this.tableHeader != null) { franta-hg@0: this.tableHeader.removeMouseListener(mouseListener); franta-hg@0: TableCellRenderer defaultRenderer = this.tableHeader.getDefaultRenderer(); franta-hg@0: if (defaultRenderer instanceof SortableHeaderRenderer) { franta-hg@0: this.tableHeader.setDefaultRenderer(((SortableHeaderRenderer) defaultRenderer).tableCellRenderer); franta-hg@0: } franta-hg@0: } franta-hg@0: this.tableHeader = tableHeader; franta-hg@0: if (this.tableHeader != null) { franta-hg@0: this.tableHeader.addMouseListener(mouseListener); franta-hg@0: this.tableHeader.setDefaultRenderer(new SortableHeaderRenderer(this.tableHeader.getDefaultRenderer())); franta-hg@0: } franta-hg@0: } franta-hg@0: franta-hg@0: public boolean isSorting () franta-hg@0: { franta-hg@0: return sortingColumns.size() != 0; franta-hg@0: } franta-hg@0: franta-hg@0: private Directive getDirective (int column) franta-hg@0: { franta-hg@0: for (int i = 0; i < sortingColumns.size(); i++) { franta-hg@0: Directive directive = (Directive) sortingColumns.get(i); franta-hg@0: if (directive.column == column) { franta-hg@0: return directive; franta-hg@0: } franta-hg@0: } franta-hg@0: return EMPTY_DIRECTIVE; franta-hg@0: } franta-hg@0: franta-hg@0: public int getSortingStatus (int column) franta-hg@0: { franta-hg@0: return getDirective(column).direction; franta-hg@0: } franta-hg@0: franta-hg@0: private void sortingStatusChanged () franta-hg@0: { franta-hg@0: clearSortingState(); franta-hg@0: fireTableDataChanged(); franta-hg@0: if (tableHeader != null) { franta-hg@0: tableHeader.repaint(); franta-hg@0: } franta-hg@0: } franta-hg@0: franta-hg@0: public void setSortingStatus (int column, franta-hg@0: int status) franta-hg@0: { franta-hg@0: Directive directive = getDirective(column); franta-hg@0: if (directive != EMPTY_DIRECTIVE) { franta-hg@0: sortingColumns.remove(directive); franta-hg@0: } franta-hg@0: if (status != NOT_SORTED) { franta-hg@0: sortingColumns.add(new Directive(column, franta-hg@0: status)); franta-hg@0: } franta-hg@0: sortingStatusChanged(); franta-hg@0: } franta-hg@0: franta-hg@0: protected Icon getHeaderRendererIcon (int column, franta-hg@0: int size) franta-hg@0: { franta-hg@0: Directive directive = getDirective(column); franta-hg@0: if (directive == EMPTY_DIRECTIVE) { franta-hg@0: return null; franta-hg@0: } franta-hg@0: franta-hg@0: if (directive.direction == DESCENDING) { franta-hg@0: return headerIconDown; franta-hg@0: } else { franta-hg@0: return headerIconUp; franta-hg@0: } franta-hg@0: } franta-hg@0: franta-hg@0: private void cancelSorting () franta-hg@0: { franta-hg@0: sortingColumns.clear(); franta-hg@0: sortingStatusChanged(); franta-hg@0: } franta-hg@0: franta-hg@0: public void setColumnComparator (Class type, franta-hg@0: Comparator comparator) franta-hg@0: { franta-hg@0: if (comparator == null) { franta-hg@0: columnComparators.remove(type); franta-hg@0: } else { franta-hg@0: columnComparators.put(type, franta-hg@0: comparator); franta-hg@0: } franta-hg@0: } franta-hg@0: franta-hg@0: protected Comparator getComparator (int column) franta-hg@0: { franta-hg@0: Class columnType = tableModel.getColumnClass(column); franta-hg@0: Comparator comparator = (Comparator) columnComparators.get(columnType); franta-hg@0: if (comparator != null) { franta-hg@0: return comparator; franta-hg@0: } franta-hg@0: if (Comparable.class.isAssignableFrom(columnType)) { franta-hg@0: return COMPARABLE_COMAPRATOR; franta-hg@0: } franta-hg@0: return LEXICAL_COMPARATOR; franta-hg@0: } franta-hg@0: franta-hg@0: private Row[] getViewToModel () franta-hg@0: { franta-hg@0: if (viewToModel == null) { franta-hg@0: int tableModelRowCount = tableModel.getRowCount(); franta-hg@0: viewToModel = new Row[tableModelRowCount]; franta-hg@0: for (int row = 0; row < tableModelRowCount; row++) { franta-hg@0: viewToModel[row] = new Row(row); franta-hg@0: } franta-hg@0: franta-hg@0: if (isSorting()) { franta-hg@0: Arrays.sort(viewToModel); franta-hg@0: } franta-hg@0: } franta-hg@0: return viewToModel; franta-hg@0: } franta-hg@0: franta-hg@0: public int modelIndex (int viewIndex) franta-hg@0: { franta-hg@0: if (viewIndex > -1 && viewIndex < getViewToModel().length) { franta-hg@0: return getViewToModel()[viewIndex].modelIndex; franta-hg@0: } else { franta-hg@0: return -1; franta-hg@0: } franta-hg@0: } franta-hg@0: franta-hg@0: private int[] getModelToView () franta-hg@0: { franta-hg@0: if (modelToView == null) { franta-hg@0: int n = getViewToModel().length; franta-hg@0: modelToView = new int[n]; franta-hg@0: for (int i = 0; i < n; i++) { franta-hg@0: modelToView[modelIndex(i)] = i; franta-hg@0: } franta-hg@0: } franta-hg@0: return modelToView; franta-hg@0: } franta-hg@0: franta-hg@0: // Metody rozhran� TableModel franta-hg@0: franta-hg@0: public int getRowCount () franta-hg@0: { franta-hg@0: return (tableModel == null) ? 0 : tableModel.getRowCount(); franta-hg@0: } franta-hg@0: franta-hg@0: public int getColumnCount () franta-hg@0: { franta-hg@0: return (tableModel == null) ? 0 : tableModel.getColumnCount(); franta-hg@0: } franta-hg@0: franta-hg@0: public String getColumnName (int column) franta-hg@0: { franta-hg@0: return tableModel.getColumnName(column); franta-hg@0: } franta-hg@0: franta-hg@0: public Class getColumnClass (int column) franta-hg@0: { franta-hg@0: return tableModel.getColumnClass(column); franta-hg@0: } franta-hg@0: franta-hg@0: public boolean isCellEditable (int row, franta-hg@0: int column) franta-hg@0: { franta-hg@0: return tableModel.isCellEditable(modelIndex(row), franta-hg@0: column); franta-hg@0: } franta-hg@0: franta-hg@0: public Object getValueAt (int row, franta-hg@0: int column) franta-hg@0: { franta-hg@0: return tableModel.getValueAt(modelIndex(row), franta-hg@0: column); franta-hg@0: } franta-hg@0: franta-hg@0: public void setValueAt (Object aValue, franta-hg@0: int row, franta-hg@0: int column) franta-hg@0: { franta-hg@0: tableModel.setValueAt(aValue, franta-hg@0: modelIndex(row), franta-hg@0: column); franta-hg@0: } franta-hg@0: franta-hg@0: // Pomocn� t��dy franta-hg@0: franta-hg@0: private class Row implements Comparable { franta-hg@0: franta-hg@0: private int modelIndex; franta-hg@0: franta-hg@0: public Row (int index) franta-hg@0: { franta-hg@0: this.modelIndex = index; franta-hg@0: } franta-hg@0: franta-hg@0: public int compareTo (Object o) franta-hg@0: { franta-hg@0: int row1 = modelIndex; franta-hg@0: int row2 = ((Row) o).modelIndex; franta-hg@0: franta-hg@0: for (Iterator it = sortingColumns.iterator(); it.hasNext();) { franta-hg@0: Directive directive = (Directive) it.next(); franta-hg@0: int column = directive.column; franta-hg@0: Object o1 = tableModel.getValueAt(row1, franta-hg@0: column); franta-hg@0: Object o2 = tableModel.getValueAt(row2, franta-hg@0: column); franta-hg@0: franta-hg@0: int comparison = 0; franta-hg@0: // Define null less than everything, except franta-hg@0: // null. franta-hg@0: if (o1 == null && o2 == null) { franta-hg@0: comparison = 0; franta-hg@0: } else if (o1 == null) { franta-hg@0: comparison = -1; franta-hg@0: } else if (o2 == null) { franta-hg@0: comparison = 1; franta-hg@0: } else { franta-hg@0: comparison = getComparator(column).compare(o1, franta-hg@0: o2); franta-hg@0: } franta-hg@0: if (comparison != 0) { franta-hg@0: return directive.direction == DESCENDING ? -comparison : comparison; franta-hg@0: } franta-hg@0: } franta-hg@0: return 0; franta-hg@0: } franta-hg@0: } franta-hg@0: franta-hg@0: private class TableModelHandler implements TableModelListener { franta-hg@0: franta-hg@0: public void tableChanged (TableModelEvent e) franta-hg@0: { franta-hg@0: // If we're not sorting by anything, just pass the event franta-hg@0: // along. franta-hg@0: if ( !isSorting()) { franta-hg@0: clearSortingState(); franta-hg@0: fireTableChanged(e); franta-hg@0: return; franta-hg@0: } franta-hg@0: franta-hg@0: // If the table structure has changed, cancel the franta-hg@0: // sorting; the franta-hg@0: // sorting columns may have been either moved or deleted franta-hg@0: // from franta-hg@0: // the model. franta-hg@0: if (e.getFirstRow() == TableModelEvent.HEADER_ROW) { franta-hg@0: cancelSorting(); franta-hg@0: fireTableChanged(e); franta-hg@0: return; franta-hg@0: } franta-hg@0: franta-hg@0: // We can map a cell event through to the view without franta-hg@0: // widening franta-hg@0: // when the following conditions apply: franta-hg@0: // franta-hg@0: // a) all the changes are on one row (e.getFirstRow() == franta-hg@0: // e.getLastRow()) and, franta-hg@0: // b) all the changes are in one column (column != franta-hg@0: // TableModelEvent.ALL_COLUMNS) and, franta-hg@0: // c) we are not sorting on that column franta-hg@0: // (getSortingStatus(column) == NOT_SORTED) and, franta-hg@0: // d) a reverse lookup will not trigger a sort franta-hg@0: // (modelToView != null) franta-hg@0: // franta-hg@0: // Note: INSERT and DELETE events fail this test as they franta-hg@0: // have column == ALL_COLUMNS. franta-hg@0: // franta-hg@0: // The last check, for (modelToView != null) is to see franta-hg@0: // if modelToView franta-hg@0: // is already allocated. If we don't do this check; franta-hg@0: // sorting can become franta-hg@0: // a performance bottleneck for applications where cells franta-hg@0: // change rapidly in different parts of the table. If franta-hg@0: // cells franta-hg@0: // change alternately in the sorting column and then franta-hg@0: // outside of franta-hg@0: // it this class can end up re-sorting on alternate cell franta-hg@0: // updates - franta-hg@0: // which can be a performance problem for large tables. franta-hg@0: // The last franta-hg@0: // clause avoids this problem. franta-hg@0: int column = e.getColumn(); franta-hg@0: if (e.getFirstRow() == e.getLastRow() && column != TableModelEvent.ALL_COLUMNS && getSortingStatus(column) == NOT_SORTED franta-hg@0: && modelToView != null) { franta-hg@0: int viewIndex = getModelToView()[e.getFirstRow()]; franta-hg@0: fireTableChanged(new TableModelEvent(TableSorterModel.this, franta-hg@0: viewIndex, franta-hg@0: viewIndex, franta-hg@0: column, franta-hg@0: e.getType())); franta-hg@0: return; franta-hg@0: } franta-hg@0: franta-hg@0: // Something has happened to the data that may have franta-hg@0: // invalidated the row order. franta-hg@0: clearSortingState(); franta-hg@0: fireTableDataChanged(); franta-hg@0: return; franta-hg@0: } franta-hg@0: } franta-hg@0: franta-hg@0: private class MouseHandler extends MouseAdapter { franta-hg@0: franta-hg@0: public void mouseClicked (MouseEvent e) franta-hg@0: { franta-hg@0: JTableHeader h = (JTableHeader) e.getSource(); franta-hg@0: TableColumnModel columnModel = h.getColumnModel(); franta-hg@0: int viewColumn = columnModel.getColumnIndexAtX(e.getX()); franta-hg@0: int column = columnModel.getColumn(viewColumn).getModelIndex(); franta-hg@0: if (column != -1) { franta-hg@0: int status = getSortingStatus(column); franta-hg@0: if ( !e.isControlDown()) { franta-hg@0: cancelSorting(); franta-hg@0: } franta-hg@0: // Cycle the sorting states through {NOT_SORTED, franta-hg@0: // ASCENDING, DESCENDING} or franta-hg@0: // {NOT_SORTED, DESCENDING, ASCENDING} depending franta-hg@0: // on whether shift is pressed. franta-hg@0: status = status + (e.isShiftDown() ? -1 : 1); franta-hg@0: status = (status + 4) % 3 - 1; // signed mod, franta-hg@0: // returning franta-hg@0: // {-1, 0, 1} franta-hg@0: setSortingStatus(column, franta-hg@0: status); franta-hg@0: } franta-hg@0: } franta-hg@0: } franta-hg@0: franta-hg@0: private class SortableHeaderRenderer implements TableCellRenderer { franta-hg@0: franta-hg@0: private TableCellRenderer tableCellRenderer; franta-hg@0: franta-hg@0: public SortableHeaderRenderer (TableCellRenderer tableCellRenderer) franta-hg@0: { franta-hg@0: this.tableCellRenderer = tableCellRenderer; franta-hg@0: } franta-hg@0: franta-hg@0: public Component getTableCellRendererComponent (JTable table, franta-hg@0: Object value, franta-hg@0: boolean isSelected, franta-hg@0: boolean hasFocus, franta-hg@0: int row, franta-hg@0: int column) franta-hg@0: { franta-hg@0: Component c = tableCellRenderer.getTableCellRendererComponent(table, franta-hg@0: value, franta-hg@0: isSelected, franta-hg@0: hasFocus, franta-hg@0: row, franta-hg@0: column); franta-hg@0: if (c instanceof JLabel) { franta-hg@0: JLabel l = (JLabel) c; franta-hg@0: l.setHorizontalTextPosition(JLabel.LEFT); franta-hg@0: int modelColumn = table.convertColumnIndexToModel(column); franta-hg@0: l.setIcon(getHeaderRendererIcon(modelColumn, franta-hg@0: l.getFont().getSize())); franta-hg@0: } franta-hg@0: return c; franta-hg@0: } franta-hg@0: } franta-hg@0: franta-hg@0: private static class Directive { franta-hg@0: franta-hg@0: private int column; franta-hg@0: franta-hg@0: private int direction; franta-hg@0: franta-hg@0: public Directive (int column, franta-hg@0: int direction) franta-hg@0: { franta-hg@0: this.column = column; franta-hg@0: this.direction = direction; franta-hg@0: } franta-hg@0: } franta-hg@0: franta-hg@0: public void fireTableColumnUpdated (int index) franta-hg@0: { franta-hg@0: for (int i = 0; i < getRowCount(); i++) { franta-hg@0: fireTableCellUpdated(i, franta-hg@0: index); franta-hg@0: } franta-hg@0: } franta-hg@0: }