1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/java/JFTable/src/cz/frantovo/gui/tabulky/TableSorterModel.java Sat Feb 28 16:51:54 2009 +0100
1.3 @@ -0,0 +1,533 @@
1.4 +package cz.frantovo.gui.tabulky;
1.5 +
1.6 +
1.7 +import java.awt.Component;
1.8 +import java.awt.event.MouseAdapter;
1.9 +import java.awt.event.MouseEvent;
1.10 +import java.awt.event.MouseListener;
1.11 +import java.util.ArrayList;
1.12 +import java.util.Arrays;
1.13 +import java.util.Comparator;
1.14 +import java.util.HashMap;
1.15 +import java.util.Iterator;
1.16 +import java.util.List;
1.17 +import java.util.Map;
1.18 +
1.19 +import javax.swing.Icon;
1.20 +import javax.swing.ImageIcon;
1.21 +import javax.swing.JLabel;
1.22 +import javax.swing.JTable;
1.23 +import javax.swing.event.TableModelEvent;
1.24 +import javax.swing.event.TableModelListener;
1.25 +import javax.swing.table.AbstractTableModel;
1.26 +import javax.swing.table.JTableHeader;
1.27 +import javax.swing.table.TableCellRenderer;
1.28 +import javax.swing.table.TableColumnModel;
1.29 +import javax.swing.table.TableModel;
1.30 +
1.31 +/**
1.32 + * Tato t��da p�id�v� tabulce funk�nost �azen� podle v�ce sloupc�. Skute�n�
1.33 + * model obsahuj�c� data sta�� obalit touto t��dou + je pot�eba nastavit:
1.34 + * tableSorterModel.setTableHeader(table.getTableHeader());<br>
1.35 + * <br>
1.36 + * Jednodu��� ale je pou��vat t��du cz.ebanka.util.table.JTable, kter� v�echno
1.37 + * �e�� za v�s. <br>
1.38 + * <br>
1.39 + * Tento TableSorterModel je zalo�en na p�vodn�m TableSorter od autor�: Philip
1.40 + * Milne, Brendon McLean, Dan van Enckevort a Parwinder Sekhon. <br>
1.41 + * <br>
1.42 + * J� jsem p�idal hez�� grafick� �ipky v z�hlav� tabulky
1.43 + *
1.44 + *
1.45 + * @author František Kučera
1.46 + */
1.47 +
1.48 +public class TableSorterModel extends AbstractTableModel {
1.49 +
1.50 + private static final long serialVersionUID = 7902301859145867387L;
1.51 +
1.52 + protected TableModel tableModel;
1.53 +
1.54 + public static final int DESCENDING = -1;
1.55 +
1.56 + public static final int NOT_SORTED = 0;
1.57 +
1.58 + public static final int ASCENDING = 1;
1.59 +
1.60 + private static Directive EMPTY_DIRECTIVE = new Directive( -1,
1.61 + NOT_SORTED);
1.62 +
1.63 + public static final Comparator COMPARABLE_COMAPRATOR = new Comparator() {
1.64 +
1.65 + public int compare (Object o1,
1.66 + Object o2)
1.67 + {
1.68 + return ((Comparable) o1).compareTo(o2);
1.69 + }
1.70 + };
1.71 +
1.72 + public static final Comparator LEXICAL_COMPARATOR = new Comparator() {
1.73 +
1.74 + public int compare (Object o1,
1.75 + Object o2)
1.76 + {
1.77 + return o1.toString().compareTo(o2.toString());
1.78 + }
1.79 + };
1.80 +
1.81 + private Row[] viewToModel;
1.82 +
1.83 + private int[] modelToView;
1.84 +
1.85 + private JTableHeader tableHeader;
1.86 +
1.87 + private MouseListener mouseListener;
1.88 +
1.89 + private TableModelListener tableModelListener;
1.90 +
1.91 + private Map columnComparators = new HashMap();
1.92 +
1.93 + private List sortingColumns = new ArrayList();
1.94 +
1.95 + private ImageIcon headerIconDown = new ImageIcon(getClass().getResource("/cz/frantovo/gui/tabulky/dolu.png"));
1.96 +
1.97 + private ImageIcon headerIconUp = new ImageIcon(getClass().getResource("/cz/frantovo/gui/tabulky/nahoru.png"));
1.98 +
1.99 + public TableSorterModel ()
1.100 + {
1.101 + this.mouseListener = new MouseHandler();
1.102 + this.tableModelListener = new TableModelHandler();
1.103 + }
1.104 +
1.105 + public TableSorterModel (TableModel tableModel)
1.106 + {
1.107 + this();
1.108 + setTableModel(tableModel);
1.109 + }
1.110 +
1.111 + public TableSorterModel (TableModel tableModel,
1.112 + JTableHeader tableHeader)
1.113 + {
1.114 + this();
1.115 + setTableHeader(tableHeader);
1.116 + setTableModel(tableModel);
1.117 + }
1.118 +
1.119 + private void clearSortingState ()
1.120 + {
1.121 + viewToModel = null;
1.122 + modelToView = null;
1.123 + }
1.124 +
1.125 + public TableModel getTableModel ()
1.126 + {
1.127 + return tableModel;
1.128 + }
1.129 +
1.130 + public void setTableModel (TableModel tableModel)
1.131 + {
1.132 + if (this.tableModel != null) {
1.133 + this.tableModel.removeTableModelListener(tableModelListener);
1.134 + }
1.135 +
1.136 + this.tableModel = tableModel;
1.137 + if (this.tableModel != null) {
1.138 + this.tableModel.addTableModelListener(tableModelListener);
1.139 + }
1.140 +
1.141 + clearSortingState();
1.142 + fireTableStructureChanged();
1.143 + }
1.144 +
1.145 + public JTableHeader getTableHeader ()
1.146 + {
1.147 + return tableHeader;
1.148 + }
1.149 +
1.150 + public void setTableHeader (JTableHeader tableHeader)
1.151 + {
1.152 + if (this.tableHeader != null) {
1.153 + this.tableHeader.removeMouseListener(mouseListener);
1.154 + TableCellRenderer defaultRenderer = this.tableHeader.getDefaultRenderer();
1.155 + if (defaultRenderer instanceof SortableHeaderRenderer) {
1.156 + this.tableHeader.setDefaultRenderer(((SortableHeaderRenderer) defaultRenderer).tableCellRenderer);
1.157 + }
1.158 + }
1.159 + this.tableHeader = tableHeader;
1.160 + if (this.tableHeader != null) {
1.161 + this.tableHeader.addMouseListener(mouseListener);
1.162 + this.tableHeader.setDefaultRenderer(new SortableHeaderRenderer(this.tableHeader.getDefaultRenderer()));
1.163 + }
1.164 + }
1.165 +
1.166 + public boolean isSorting ()
1.167 + {
1.168 + return sortingColumns.size() != 0;
1.169 + }
1.170 +
1.171 + private Directive getDirective (int column)
1.172 + {
1.173 + for (int i = 0; i < sortingColumns.size(); i++) {
1.174 + Directive directive = (Directive) sortingColumns.get(i);
1.175 + if (directive.column == column) {
1.176 + return directive;
1.177 + }
1.178 + }
1.179 + return EMPTY_DIRECTIVE;
1.180 + }
1.181 +
1.182 + public int getSortingStatus (int column)
1.183 + {
1.184 + return getDirective(column).direction;
1.185 + }
1.186 +
1.187 + private void sortingStatusChanged ()
1.188 + {
1.189 + clearSortingState();
1.190 + fireTableDataChanged();
1.191 + if (tableHeader != null) {
1.192 + tableHeader.repaint();
1.193 + }
1.194 + }
1.195 +
1.196 + public void setSortingStatus (int column,
1.197 + int status)
1.198 + {
1.199 + Directive directive = getDirective(column);
1.200 + if (directive != EMPTY_DIRECTIVE) {
1.201 + sortingColumns.remove(directive);
1.202 + }
1.203 + if (status != NOT_SORTED) {
1.204 + sortingColumns.add(new Directive(column,
1.205 + status));
1.206 + }
1.207 + sortingStatusChanged();
1.208 + }
1.209 +
1.210 + protected Icon getHeaderRendererIcon (int column,
1.211 + int size)
1.212 + {
1.213 + Directive directive = getDirective(column);
1.214 + if (directive == EMPTY_DIRECTIVE) {
1.215 + return null;
1.216 + }
1.217 +
1.218 + if (directive.direction == DESCENDING) {
1.219 + return headerIconDown;
1.220 + } else {
1.221 + return headerIconUp;
1.222 + }
1.223 + }
1.224 +
1.225 + private void cancelSorting ()
1.226 + {
1.227 + sortingColumns.clear();
1.228 + sortingStatusChanged();
1.229 + }
1.230 +
1.231 + public void setColumnComparator (Class type,
1.232 + Comparator comparator)
1.233 + {
1.234 + if (comparator == null) {
1.235 + columnComparators.remove(type);
1.236 + } else {
1.237 + columnComparators.put(type,
1.238 + comparator);
1.239 + }
1.240 + }
1.241 +
1.242 + protected Comparator getComparator (int column)
1.243 + {
1.244 + Class columnType = tableModel.getColumnClass(column);
1.245 + Comparator comparator = (Comparator) columnComparators.get(columnType);
1.246 + if (comparator != null) {
1.247 + return comparator;
1.248 + }
1.249 + if (Comparable.class.isAssignableFrom(columnType)) {
1.250 + return COMPARABLE_COMAPRATOR;
1.251 + }
1.252 + return LEXICAL_COMPARATOR;
1.253 + }
1.254 +
1.255 + private Row[] getViewToModel ()
1.256 + {
1.257 + if (viewToModel == null) {
1.258 + int tableModelRowCount = tableModel.getRowCount();
1.259 + viewToModel = new Row[tableModelRowCount];
1.260 + for (int row = 0; row < tableModelRowCount; row++) {
1.261 + viewToModel[row] = new Row(row);
1.262 + }
1.263 +
1.264 + if (isSorting()) {
1.265 + Arrays.sort(viewToModel);
1.266 + }
1.267 + }
1.268 + return viewToModel;
1.269 + }
1.270 +
1.271 + public int modelIndex (int viewIndex)
1.272 + {
1.273 + if (viewIndex > -1 && viewIndex < getViewToModel().length) {
1.274 + return getViewToModel()[viewIndex].modelIndex;
1.275 + } else {
1.276 + return -1;
1.277 + }
1.278 + }
1.279 +
1.280 + private int[] getModelToView ()
1.281 + {
1.282 + if (modelToView == null) {
1.283 + int n = getViewToModel().length;
1.284 + modelToView = new int[n];
1.285 + for (int i = 0; i < n; i++) {
1.286 + modelToView[modelIndex(i)] = i;
1.287 + }
1.288 + }
1.289 + return modelToView;
1.290 + }
1.291 +
1.292 + // Metody rozhran� TableModel
1.293 +
1.294 + public int getRowCount ()
1.295 + {
1.296 + return (tableModel == null) ? 0 : tableModel.getRowCount();
1.297 + }
1.298 +
1.299 + public int getColumnCount ()
1.300 + {
1.301 + return (tableModel == null) ? 0 : tableModel.getColumnCount();
1.302 + }
1.303 +
1.304 + public String getColumnName (int column)
1.305 + {
1.306 + return tableModel.getColumnName(column);
1.307 + }
1.308 +
1.309 + public Class getColumnClass (int column)
1.310 + {
1.311 + return tableModel.getColumnClass(column);
1.312 + }
1.313 +
1.314 + public boolean isCellEditable (int row,
1.315 + int column)
1.316 + {
1.317 + return tableModel.isCellEditable(modelIndex(row),
1.318 + column);
1.319 + }
1.320 +
1.321 + public Object getValueAt (int row,
1.322 + int column)
1.323 + {
1.324 + return tableModel.getValueAt(modelIndex(row),
1.325 + column);
1.326 + }
1.327 +
1.328 + public void setValueAt (Object aValue,
1.329 + int row,
1.330 + int column)
1.331 + {
1.332 + tableModel.setValueAt(aValue,
1.333 + modelIndex(row),
1.334 + column);
1.335 + }
1.336 +
1.337 + // Pomocn� t��dy
1.338 +
1.339 + private class Row implements Comparable {
1.340 +
1.341 + private int modelIndex;
1.342 +
1.343 + public Row (int index)
1.344 + {
1.345 + this.modelIndex = index;
1.346 + }
1.347 +
1.348 + public int compareTo (Object o)
1.349 + {
1.350 + int row1 = modelIndex;
1.351 + int row2 = ((Row) o).modelIndex;
1.352 +
1.353 + for (Iterator it = sortingColumns.iterator(); it.hasNext();) {
1.354 + Directive directive = (Directive) it.next();
1.355 + int column = directive.column;
1.356 + Object o1 = tableModel.getValueAt(row1,
1.357 + column);
1.358 + Object o2 = tableModel.getValueAt(row2,
1.359 + column);
1.360 +
1.361 + int comparison = 0;
1.362 + // Define null less than everything, except
1.363 + // null.
1.364 + if (o1 == null && o2 == null) {
1.365 + comparison = 0;
1.366 + } else if (o1 == null) {
1.367 + comparison = -1;
1.368 + } else if (o2 == null) {
1.369 + comparison = 1;
1.370 + } else {
1.371 + comparison = getComparator(column).compare(o1,
1.372 + o2);
1.373 + }
1.374 + if (comparison != 0) {
1.375 + return directive.direction == DESCENDING ? -comparison : comparison;
1.376 + }
1.377 + }
1.378 + return 0;
1.379 + }
1.380 + }
1.381 +
1.382 + private class TableModelHandler implements TableModelListener {
1.383 +
1.384 + public void tableChanged (TableModelEvent e)
1.385 + {
1.386 + // If we're not sorting by anything, just pass the event
1.387 + // along.
1.388 + if ( !isSorting()) {
1.389 + clearSortingState();
1.390 + fireTableChanged(e);
1.391 + return;
1.392 + }
1.393 +
1.394 + // If the table structure has changed, cancel the
1.395 + // sorting; the
1.396 + // sorting columns may have been either moved or deleted
1.397 + // from
1.398 + // the model.
1.399 + if (e.getFirstRow() == TableModelEvent.HEADER_ROW) {
1.400 + cancelSorting();
1.401 + fireTableChanged(e);
1.402 + return;
1.403 + }
1.404 +
1.405 + // We can map a cell event through to the view without
1.406 + // widening
1.407 + // when the following conditions apply:
1.408 + //
1.409 + // a) all the changes are on one row (e.getFirstRow() ==
1.410 + // e.getLastRow()) and,
1.411 + // b) all the changes are in one column (column !=
1.412 + // TableModelEvent.ALL_COLUMNS) and,
1.413 + // c) we are not sorting on that column
1.414 + // (getSortingStatus(column) == NOT_SORTED) and,
1.415 + // d) a reverse lookup will not trigger a sort
1.416 + // (modelToView != null)
1.417 + //
1.418 + // Note: INSERT and DELETE events fail this test as they
1.419 + // have column == ALL_COLUMNS.
1.420 + //
1.421 + // The last check, for (modelToView != null) is to see
1.422 + // if modelToView
1.423 + // is already allocated. If we don't do this check;
1.424 + // sorting can become
1.425 + // a performance bottleneck for applications where cells
1.426 + // change rapidly in different parts of the table. If
1.427 + // cells
1.428 + // change alternately in the sorting column and then
1.429 + // outside of
1.430 + // it this class can end up re-sorting on alternate cell
1.431 + // updates -
1.432 + // which can be a performance problem for large tables.
1.433 + // The last
1.434 + // clause avoids this problem.
1.435 + int column = e.getColumn();
1.436 + if (e.getFirstRow() == e.getLastRow() && column != TableModelEvent.ALL_COLUMNS && getSortingStatus(column) == NOT_SORTED
1.437 + && modelToView != null) {
1.438 + int viewIndex = getModelToView()[e.getFirstRow()];
1.439 + fireTableChanged(new TableModelEvent(TableSorterModel.this,
1.440 + viewIndex,
1.441 + viewIndex,
1.442 + column,
1.443 + e.getType()));
1.444 + return;
1.445 + }
1.446 +
1.447 + // Something has happened to the data that may have
1.448 + // invalidated the row order.
1.449 + clearSortingState();
1.450 + fireTableDataChanged();
1.451 + return;
1.452 + }
1.453 + }
1.454 +
1.455 + private class MouseHandler extends MouseAdapter {
1.456 +
1.457 + public void mouseClicked (MouseEvent e)
1.458 + {
1.459 + JTableHeader h = (JTableHeader) e.getSource();
1.460 + TableColumnModel columnModel = h.getColumnModel();
1.461 + int viewColumn = columnModel.getColumnIndexAtX(e.getX());
1.462 + int column = columnModel.getColumn(viewColumn).getModelIndex();
1.463 + if (column != -1) {
1.464 + int status = getSortingStatus(column);
1.465 + if ( !e.isControlDown()) {
1.466 + cancelSorting();
1.467 + }
1.468 + // Cycle the sorting states through {NOT_SORTED,
1.469 + // ASCENDING, DESCENDING} or
1.470 + // {NOT_SORTED, DESCENDING, ASCENDING} depending
1.471 + // on whether shift is pressed.
1.472 + status = status + (e.isShiftDown() ? -1 : 1);
1.473 + status = (status + 4) % 3 - 1; // signed mod,
1.474 + // returning
1.475 + // {-1, 0, 1}
1.476 + setSortingStatus(column,
1.477 + status);
1.478 + }
1.479 + }
1.480 + }
1.481 +
1.482 + private class SortableHeaderRenderer implements TableCellRenderer {
1.483 +
1.484 + private TableCellRenderer tableCellRenderer;
1.485 +
1.486 + public SortableHeaderRenderer (TableCellRenderer tableCellRenderer)
1.487 + {
1.488 + this.tableCellRenderer = tableCellRenderer;
1.489 + }
1.490 +
1.491 + public Component getTableCellRendererComponent (JTable table,
1.492 + Object value,
1.493 + boolean isSelected,
1.494 + boolean hasFocus,
1.495 + int row,
1.496 + int column)
1.497 + {
1.498 + Component c = tableCellRenderer.getTableCellRendererComponent(table,
1.499 + value,
1.500 + isSelected,
1.501 + hasFocus,
1.502 + row,
1.503 + column);
1.504 + if (c instanceof JLabel) {
1.505 + JLabel l = (JLabel) c;
1.506 + l.setHorizontalTextPosition(JLabel.LEFT);
1.507 + int modelColumn = table.convertColumnIndexToModel(column);
1.508 + l.setIcon(getHeaderRendererIcon(modelColumn,
1.509 + l.getFont().getSize()));
1.510 + }
1.511 + return c;
1.512 + }
1.513 + }
1.514 +
1.515 + private static class Directive {
1.516 +
1.517 + private int column;
1.518 +
1.519 + private int direction;
1.520 +
1.521 + public Directive (int column,
1.522 + int direction)
1.523 + {
1.524 + this.column = column;
1.525 + this.direction = direction;
1.526 + }
1.527 + }
1.528 +
1.529 + public void fireTableColumnUpdated (int index)
1.530 + {
1.531 + for (int i = 0; i < getRowCount(); i++) {
1.532 + fireTableCellUpdated(i,
1.533 + index);
1.534 + }
1.535 + }
1.536 +}