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