java/JFTable/src/cz/frantovo/gui/tabulky/TableSorterModel.java
author František Kučera <franta-hg@frantovo.cz>
Sat Feb 28 17:10:53 2009 +0100 (2009-02-28)
changeset 2 29fb34084b12
parent 0 0359f2c3b3ba
child 3 74211841e25c
permissions -rwxr-xr-x
Oprava kódování češtiny v komentářích
     1 package cz.frantovo.gui.tabulky;
     2 
     3 
     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;
    14 import java.util.Map;
    15 
    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;
    27 
    28 /**
    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>
    32  * 
    33  * <p>Jednodušší ale je používat třídu cz.frantovo.gui.tabulky.JFTable, která všechno
    34  * udělá za vás. </p>
    35  * 
    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>
    38  *
    39  * <p>Já jsem přidal hezčí grafické šipky v záhlaví tabulky</p>
    40  * 
    41  * @author František Kučera
    42  */
    43 
    44 public class TableSorterModel extends AbstractTableModel {
    45 
    46         private static final long serialVersionUID = 7902301859145867387L;
    47 
    48         protected TableModel tableModel;
    49 
    50         public static final int DESCENDING = -1;
    51 
    52         public static final int NOT_SORTED = 0;
    53 
    54         public static final int ASCENDING = 1;
    55 
    56         private static Directive EMPTY_DIRECTIVE = new Directive( -1,
    57                                                                  NOT_SORTED);
    58 
    59         public static final Comparator COMPARABLE_COMAPRATOR = new Comparator() {
    60 
    61                 public int compare (Object o1,
    62                                     Object o2)
    63                 {
    64                         return ((Comparable) o1).compareTo(o2);
    65                 }
    66         };
    67 
    68         public static final Comparator LEXICAL_COMPARATOR = new Comparator() {
    69 
    70                 public int compare (Object o1,
    71                                     Object o2)
    72                 {
    73                         return o1.toString().compareTo(o2.toString());
    74                 }
    75         };
    76 
    77         private Row[] viewToModel;
    78 
    79         private int[] modelToView;
    80 
    81         private JTableHeader tableHeader;
    82 
    83         private MouseListener mouseListener;
    84 
    85         private TableModelListener tableModelListener;
    86 
    87         private Map columnComparators = new HashMap();
    88 
    89         private List sortingColumns = new ArrayList();
    90 
    91         private ImageIcon headerIconDown = new ImageIcon(getClass().getResource("/cz/frantovo/gui/tabulky/dolu.png"));
    92 
    93         private ImageIcon headerIconUp = new ImageIcon(getClass().getResource("/cz/frantovo/gui/tabulky/nahoru.png"));
    94 
    95         public TableSorterModel ()
    96         {
    97                 this.mouseListener = new MouseHandler();
    98                 this.tableModelListener = new TableModelHandler();
    99         }
   100 
   101         public TableSorterModel (TableModel tableModel)
   102         {
   103                 this();
   104                 setTableModel(tableModel);
   105         }
   106 
   107         public TableSorterModel (TableModel tableModel,
   108                                  JTableHeader tableHeader)
   109         {
   110                 this();
   111                 setTableHeader(tableHeader);
   112                 setTableModel(tableModel);
   113         }
   114 
   115         private void clearSortingState ()
   116         {
   117                 viewToModel = null;
   118                 modelToView = null;
   119         }
   120 
   121         public TableModel getTableModel ()
   122         {
   123                 return tableModel;
   124         }
   125 
   126         public void setTableModel (TableModel tableModel)
   127         {
   128                 if (this.tableModel != null) {
   129                         this.tableModel.removeTableModelListener(tableModelListener);
   130                 }
   131 
   132                 this.tableModel = tableModel;
   133                 if (this.tableModel != null) {
   134                         this.tableModel.addTableModelListener(tableModelListener);
   135                 }
   136 
   137                 clearSortingState();
   138                 fireTableStructureChanged();
   139         }
   140 
   141         public JTableHeader getTableHeader ()
   142         {
   143                 return tableHeader;
   144         }
   145 
   146         public void setTableHeader (JTableHeader tableHeader)
   147         {
   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);
   153                         }
   154                 }
   155                 this.tableHeader = tableHeader;
   156                 if (this.tableHeader != null) {
   157                         this.tableHeader.addMouseListener(mouseListener);
   158                         this.tableHeader.setDefaultRenderer(new SortableHeaderRenderer(this.tableHeader.getDefaultRenderer()));
   159                 }
   160         }
   161 
   162         public boolean isSorting ()
   163         {
   164                 return sortingColumns.size() != 0;
   165         }
   166 
   167         private Directive getDirective (int column)
   168         {
   169                 for (int i = 0; i < sortingColumns.size(); i++) {
   170                         Directive directive = (Directive) sortingColumns.get(i);
   171                         if (directive.column == column) {
   172                                 return directive;
   173                         }
   174                 }
   175                 return EMPTY_DIRECTIVE;
   176         }
   177 
   178         public int getSortingStatus (int column)
   179         {
   180                 return getDirective(column).direction;
   181         }
   182 
   183         private void sortingStatusChanged ()
   184         {
   185                 clearSortingState();
   186                 fireTableDataChanged();
   187                 if (tableHeader != null) {
   188                         tableHeader.repaint();
   189                 }
   190         }
   191 
   192         public void setSortingStatus (int column,
   193                                       int status)
   194         {
   195                 Directive directive = getDirective(column);
   196                 if (directive != EMPTY_DIRECTIVE) {
   197                         sortingColumns.remove(directive);
   198                 }
   199                 if (status != NOT_SORTED) {
   200                         sortingColumns.add(new Directive(column,
   201                                                          status));
   202                 }
   203                 sortingStatusChanged();
   204         }
   205 
   206         protected Icon getHeaderRendererIcon (int column,
   207                                               int size)
   208         {
   209                 Directive directive = getDirective(column);
   210                 if (directive == EMPTY_DIRECTIVE) {
   211                         return null;
   212                 }
   213 
   214                 if (directive.direction == DESCENDING) {
   215                         return headerIconDown;
   216                 } else {
   217                         return headerIconUp;
   218                 }
   219         }
   220 
   221         private void cancelSorting ()
   222         {
   223                 sortingColumns.clear();
   224                 sortingStatusChanged();
   225         }
   226 
   227         public void setColumnComparator (Class type,
   228                                          Comparator comparator)
   229         {
   230                 if (comparator == null) {
   231                         columnComparators.remove(type);
   232                 } else {
   233                         columnComparators.put(type,
   234                                               comparator);
   235                 }
   236         }
   237 
   238         protected Comparator getComparator (int column)
   239         {
   240                 Class columnType = tableModel.getColumnClass(column);
   241                 Comparator comparator = (Comparator) columnComparators.get(columnType);
   242                 if (comparator != null) {
   243                         return comparator;
   244                 }
   245                 if (Comparable.class.isAssignableFrom(columnType)) {
   246                         return COMPARABLE_COMAPRATOR;
   247                 }
   248                 return LEXICAL_COMPARATOR;
   249         }
   250 
   251         private Row[] getViewToModel ()
   252         {
   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);
   258                         }
   259 
   260                         if (isSorting()) {
   261                                 Arrays.sort(viewToModel);
   262                         }
   263                 }
   264                 return viewToModel;
   265         }
   266 
   267         public int modelIndex (int viewIndex)
   268         {
   269                 if (viewIndex > -1 && viewIndex < getViewToModel().length) {
   270                         return getViewToModel()[viewIndex].modelIndex;
   271                 } else {
   272                         return -1;
   273                 }
   274         }
   275 
   276         private int[] getModelToView ()
   277         {
   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;
   283                         }
   284                 }
   285                 return modelToView;
   286         }
   287 
   288         // Metody rozhran� TableModel
   289 
   290         public int getRowCount ()
   291         {
   292                 return (tableModel == null) ? 0 : tableModel.getRowCount();
   293         }
   294 
   295         public int getColumnCount ()
   296         {
   297                 return (tableModel == null) ? 0 : tableModel.getColumnCount();
   298         }
   299 
   300         public String getColumnName (int column)
   301         {
   302                 return tableModel.getColumnName(column);
   303         }
   304 
   305         public Class getColumnClass (int column)
   306         {
   307                 return tableModel.getColumnClass(column);
   308         }
   309 
   310         public boolean isCellEditable (int row,
   311                                        int column)
   312         {
   313                 return tableModel.isCellEditable(modelIndex(row),
   314                                                  column);
   315         }
   316 
   317         public Object getValueAt (int row,
   318                                   int column)
   319         {
   320                 return tableModel.getValueAt(modelIndex(row),
   321                                              column);
   322         }
   323 
   324         public void setValueAt (Object aValue,
   325                                 int row,
   326                                 int column)
   327         {
   328                 tableModel.setValueAt(aValue,
   329                                       modelIndex(row),
   330                                       column);
   331         }
   332 
   333         // Pomocn� t��dy
   334 
   335         private class Row implements Comparable {
   336 
   337                 private int modelIndex;
   338 
   339                 public Row (int index)
   340                 {
   341                         this.modelIndex = index;
   342                 }
   343 
   344                 public int compareTo (Object o)
   345                 {
   346                         int row1 = modelIndex;
   347                         int row2 = ((Row) o).modelIndex;
   348 
   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,
   353                                                                   column);
   354                                 Object o2 = tableModel.getValueAt(row2,
   355                                                                   column);
   356 
   357                                 int comparison = 0;
   358                                 // Define null less than everything, except
   359                                 // null.
   360                                 if (o1 == null && o2 == null) {
   361                                         comparison = 0;
   362                                 } else if (o1 == null) {
   363                                         comparison = -1;
   364                                 } else if (o2 == null) {
   365                                         comparison = 1;
   366                                 } else {
   367                                         comparison = getComparator(column).compare(o1,
   368                                                                                    o2);
   369                                 }
   370                                 if (comparison != 0) {
   371                                         return directive.direction == DESCENDING ? -comparison : comparison;
   372                                 }
   373                         }
   374                         return 0;
   375                 }
   376         }
   377 
   378         private class TableModelHandler implements TableModelListener {
   379 
   380                 public void tableChanged (TableModelEvent e)
   381                 {
   382                         // If we're not sorting by anything, just pass the event
   383                         // along.
   384                         if ( !isSorting()) {
   385                                 clearSortingState();
   386                                 fireTableChanged(e);
   387                                 return;
   388                         }
   389 
   390                         // If the table structure has changed, cancel the
   391                         // sorting; the
   392                         // sorting columns may have been either moved or deleted
   393                         // from
   394                         // the model.
   395                         if (e.getFirstRow() == TableModelEvent.HEADER_ROW) {
   396                                 cancelSorting();
   397                                 fireTableChanged(e);
   398                                 return;
   399                         }
   400 
   401                         // We can map a cell event through to the view without
   402                         // widening
   403                         // when the following conditions apply:
   404                         // 
   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)
   413                         //
   414                         // Note: INSERT and DELETE events fail this test as they
   415                         // have column == ALL_COLUMNS.
   416                         // 
   417                         // The last check, for (modelToView != null) is to see
   418                         // if modelToView
   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
   423                         // cells
   424                         // change alternately in the sorting column and then
   425                         // outside of
   426                         // it this class can end up re-sorting on alternate cell
   427                         // updates -
   428                         // which can be a performance problem for large tables.
   429                         // The last
   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,
   436                                                                      viewIndex,
   437                                                                      viewIndex,
   438                                                                      column,
   439                                                                      e.getType()));
   440                                 return;
   441                         }
   442 
   443                         // Something has happened to the data that may have
   444                         // invalidated the row order.
   445                         clearSortingState();
   446                         fireTableDataChanged();
   447                         return;
   448                 }
   449         }
   450 
   451         private class MouseHandler extends MouseAdapter {
   452 
   453                 public void mouseClicked (MouseEvent e)
   454                 {
   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();
   459                         if (column != -1) {
   460                                 int status = getSortingStatus(column);
   461                                 if ( !e.isControlDown()) {
   462                                         cancelSorting();
   463                                 }
   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,
   470                                 // returning
   471                                 // {-1, 0, 1}
   472                                 setSortingStatus(column,
   473                                                  status);
   474                         }
   475                 }
   476         }
   477 
   478         private class SortableHeaderRenderer implements TableCellRenderer {
   479 
   480                 private TableCellRenderer tableCellRenderer;
   481 
   482                 public SortableHeaderRenderer (TableCellRenderer tableCellRenderer)
   483                 {
   484                         this.tableCellRenderer = tableCellRenderer;
   485                 }
   486 
   487                 public Component getTableCellRendererComponent (JTable table,
   488                                                                 Object value,
   489                                                                 boolean isSelected,
   490                                                                 boolean hasFocus,
   491                                                                 int row,
   492                                                                 int column)
   493                 {
   494                         Component c = tableCellRenderer.getTableCellRendererComponent(table,
   495                                                                                       value,
   496                                                                                       isSelected,
   497                                                                                       hasFocus,
   498                                                                                       row,
   499                                                                                       column);
   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()));
   506                         }
   507                         return c;
   508                 }
   509         }
   510 
   511         private static class Directive {
   512 
   513                 private int column;
   514 
   515                 private int direction;
   516 
   517                 public Directive (int column,
   518                                   int direction)
   519                 {
   520                         this.column = column;
   521                         this.direction = direction;
   522                 }
   523         }
   524 
   525         public void fireTableColumnUpdated (int index)
   526         {
   527                 for (int i = 0; i < getRowCount(); i++) {
   528                         fireTableCellUpdated(i,
   529                                              index);
   530                 }
   531         }
   532 }