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