java/JFTable/src/cz/frantovo/gui/tabulky/TableSorterModel.java
author František Kučera <franta-hg@frantovo.cz>
Sat Feb 28 17:11:20 2009 +0100 (2009-02-28)
changeset 3 74211841e25c
parent 2 29fb34084b12
child 4 44c9171272a0
permissions -rwxr-xr-x
Naformátování zdrojového kódu
     1 package cz.frantovo.gui.tabulky;
     2 
     3 import java.awt.Component;
     4 import java.awt.event.MouseAdapter;
     5 import java.awt.event.MouseEvent;
     6 import java.awt.event.MouseListener;
     7 import java.util.ArrayList;
     8 import java.util.Arrays;
     9 import java.util.Comparator;
    10 import java.util.HashMap;
    11 import java.util.Iterator;
    12 import java.util.List;
    13 import java.util.Map;
    14 
    15 import javax.swing.Icon;
    16 import javax.swing.ImageIcon;
    17 import javax.swing.JLabel;
    18 import javax.swing.JTable;
    19 import javax.swing.event.TableModelEvent;
    20 import javax.swing.event.TableModelListener;
    21 import javax.swing.table.AbstractTableModel;
    22 import javax.swing.table.JTableHeader;
    23 import javax.swing.table.TableCellRenderer;
    24 import javax.swing.table.TableColumnModel;
    25 import javax.swing.table.TableModel;
    26 
    27 /**
    28  * <p>Tato třída přidává tabulce funkčnost řazení podle více sloupců. Skutečný
    29  * model obsahující data stačí obalit touto třídou + je potřeba nastavit:
    30  * tableSorterModel.setTableHeader(table.getTableHeader());</p>
    31  * 
    32  * <p>Jednodušší ale je používat třídu cz.frantovo.gui.tabulky.JFTable, která všechno
    33  * udělá za vás. </p>
    34  * 
    35  * <p>Tento TableSorterModel je založen na původním TableSorter od autorů: Philip
    36  * Milne, Brendon McLean, Dan van Enckevort a Parwinder Sekhon.</p>
    37  *
    38  * <p>Já jsem přidal hezčí grafické šipky v záhlaví tabulky</p>
    39  * 
    40  * @author František Kučera
    41  */
    42 public class TableSorterModel extends AbstractTableModel {
    43 
    44     private static final long serialVersionUID = 7902301859145867387L;
    45     protected TableModel tableModel;
    46     public static final int DESCENDING = -1;
    47     public static final int NOT_SORTED = 0;
    48     public static final int ASCENDING = 1;
    49     private static Directive EMPTY_DIRECTIVE = new Directive(-1,
    50             NOT_SORTED);
    51     public static final Comparator COMPARABLE_COMAPRATOR = new Comparator() {
    52 
    53         public int compare(Object o1,
    54                 Object o2) {
    55             return ((Comparable) o1).compareTo(o2);
    56         }
    57     };
    58     public static final Comparator LEXICAL_COMPARATOR = new Comparator() {
    59 
    60         public int compare(Object o1,
    61                 Object o2) {
    62             return o1.toString().compareTo(o2.toString());
    63         }
    64     };
    65     private Row[] viewToModel;
    66     private int[] modelToView;
    67     private JTableHeader tableHeader;
    68     private MouseListener mouseListener;
    69     private TableModelListener tableModelListener;
    70     private Map columnComparators = new HashMap();
    71     private List sortingColumns = new ArrayList();
    72     private ImageIcon headerIconDown = new ImageIcon(getClass().getResource("/cz/frantovo/gui/tabulky/dolu.png"));
    73     private ImageIcon headerIconUp = new ImageIcon(getClass().getResource("/cz/frantovo/gui/tabulky/nahoru.png"));
    74 
    75     public TableSorterModel() {
    76         this.mouseListener = new MouseHandler();
    77         this.tableModelListener = new TableModelHandler();
    78     }
    79 
    80     public TableSorterModel(TableModel tableModel) {
    81         this();
    82         setTableModel(tableModel);
    83     }
    84 
    85     public TableSorterModel(TableModel tableModel,
    86             JTableHeader tableHeader) {
    87         this();
    88         setTableHeader(tableHeader);
    89         setTableModel(tableModel);
    90     }
    91 
    92     private void clearSortingState() {
    93         viewToModel = null;
    94         modelToView = null;
    95     }
    96 
    97     public TableModel getTableModel() {
    98         return tableModel;
    99     }
   100 
   101     public void setTableModel(TableModel tableModel) {
   102         if (this.tableModel != null) {
   103             this.tableModel.removeTableModelListener(tableModelListener);
   104         }
   105 
   106         this.tableModel = tableModel;
   107         if (this.tableModel != null) {
   108             this.tableModel.addTableModelListener(tableModelListener);
   109         }
   110 
   111         clearSortingState();
   112         fireTableStructureChanged();
   113     }
   114 
   115     public JTableHeader getTableHeader() {
   116         return tableHeader;
   117     }
   118 
   119     public void setTableHeader(JTableHeader tableHeader) {
   120         if (this.tableHeader != null) {
   121             this.tableHeader.removeMouseListener(mouseListener);
   122             TableCellRenderer defaultRenderer = this.tableHeader.getDefaultRenderer();
   123             if (defaultRenderer instanceof SortableHeaderRenderer) {
   124                 this.tableHeader.setDefaultRenderer(((SortableHeaderRenderer) defaultRenderer).tableCellRenderer);
   125             }
   126         }
   127         this.tableHeader = tableHeader;
   128         if (this.tableHeader != null) {
   129             this.tableHeader.addMouseListener(mouseListener);
   130             this.tableHeader.setDefaultRenderer(new SortableHeaderRenderer(this.tableHeader.getDefaultRenderer()));
   131         }
   132     }
   133 
   134     public boolean isSorting() {
   135         return sortingColumns.size() != 0;
   136     }
   137 
   138     private Directive getDirective(int column) {
   139         for (int i = 0; i < sortingColumns.size(); i++) {
   140             Directive directive = (Directive) sortingColumns.get(i);
   141             if (directive.column == column) {
   142                 return directive;
   143             }
   144         }
   145         return EMPTY_DIRECTIVE;
   146     }
   147 
   148     public int getSortingStatus(int column) {
   149         return getDirective(column).direction;
   150     }
   151 
   152     private void sortingStatusChanged() {
   153         clearSortingState();
   154         fireTableDataChanged();
   155         if (tableHeader != null) {
   156             tableHeader.repaint();
   157         }
   158     }
   159 
   160     public void setSortingStatus(int column,
   161             int status) {
   162         Directive directive = getDirective(column);
   163         if (directive != EMPTY_DIRECTIVE) {
   164             sortingColumns.remove(directive);
   165         }
   166         if (status != NOT_SORTED) {
   167             sortingColumns.add(new Directive(column,
   168                     status));
   169         }
   170         sortingStatusChanged();
   171     }
   172 
   173     protected Icon getHeaderRendererIcon(int column,
   174             int size) {
   175         Directive directive = getDirective(column);
   176         if (directive == EMPTY_DIRECTIVE) {
   177             return null;
   178         }
   179 
   180         if (directive.direction == DESCENDING) {
   181             return headerIconDown;
   182         } else {
   183             return headerIconUp;
   184         }
   185     }
   186 
   187     private void cancelSorting() {
   188         sortingColumns.clear();
   189         sortingStatusChanged();
   190     }
   191 
   192     public void setColumnComparator(Class type,
   193             Comparator comparator) {
   194         if (comparator == null) {
   195             columnComparators.remove(type);
   196         } else {
   197             columnComparators.put(type,
   198                     comparator);
   199         }
   200     }
   201 
   202     protected Comparator getComparator(int column) {
   203         Class columnType = tableModel.getColumnClass(column);
   204         Comparator comparator = (Comparator) columnComparators.get(columnType);
   205         if (comparator != null) {
   206             return comparator;
   207         }
   208         if (Comparable.class.isAssignableFrom(columnType)) {
   209             return COMPARABLE_COMAPRATOR;
   210         }
   211         return LEXICAL_COMPARATOR;
   212     }
   213 
   214     private Row[] getViewToModel() {
   215         if (viewToModel == null) {
   216             int tableModelRowCount = tableModel.getRowCount();
   217             viewToModel = new Row[tableModelRowCount];
   218             for (int row = 0; row < tableModelRowCount; row++) {
   219                 viewToModel[row] = new Row(row);
   220             }
   221 
   222             if (isSorting()) {
   223                 Arrays.sort(viewToModel);
   224             }
   225         }
   226         return viewToModel;
   227     }
   228 
   229     public int modelIndex(int viewIndex) {
   230         if (viewIndex > -1 && viewIndex < getViewToModel().length) {
   231             return getViewToModel()[viewIndex].modelIndex;
   232         } else {
   233             return -1;
   234         }
   235     }
   236 
   237     private int[] getModelToView() {
   238         if (modelToView == null) {
   239             int n = getViewToModel().length;
   240             modelToView = new int[n];
   241             for (int i = 0; i < n; i++) {
   242                 modelToView[modelIndex(i)] = i;
   243             }
   244         }
   245         return modelToView;
   246     }
   247 
   248     // Metody rozhran� TableModel
   249     public int getRowCount() {
   250         return (tableModel == null) ? 0 : tableModel.getRowCount();
   251     }
   252 
   253     public int getColumnCount() {
   254         return (tableModel == null) ? 0 : tableModel.getColumnCount();
   255     }
   256 
   257     public String getColumnName(int column) {
   258         return tableModel.getColumnName(column);
   259     }
   260 
   261     public Class getColumnClass(int column) {
   262         return tableModel.getColumnClass(column);
   263     }
   264 
   265     public boolean isCellEditable(int row,
   266             int column) {
   267         return tableModel.isCellEditable(modelIndex(row),
   268                 column);
   269     }
   270 
   271     public Object getValueAt(int row,
   272             int column) {
   273         return tableModel.getValueAt(modelIndex(row),
   274                 column);
   275     }
   276 
   277     public void setValueAt(Object aValue,
   278             int row,
   279             int column) {
   280         tableModel.setValueAt(aValue,
   281                 modelIndex(row),
   282                 column);
   283     }
   284 
   285     // Pomocn� t��dy
   286     private class Row implements Comparable {
   287 
   288         private int modelIndex;
   289 
   290         public Row(int index) {
   291             this.modelIndex = index;
   292         }
   293 
   294         public int compareTo(Object o) {
   295             int row1 = modelIndex;
   296             int row2 = ((Row) o).modelIndex;
   297 
   298             for (Iterator it = sortingColumns.iterator(); it.hasNext();) {
   299                 Directive directive = (Directive) it.next();
   300                 int column = directive.column;
   301                 Object o1 = tableModel.getValueAt(row1,
   302                         column);
   303                 Object o2 = tableModel.getValueAt(row2,
   304                         column);
   305 
   306                 int comparison = 0;
   307                 // Define null less than everything, except
   308                 // null.
   309                 if (o1 == null && o2 == null) {
   310                     comparison = 0;
   311                 } else if (o1 == null) {
   312                     comparison = -1;
   313                 } else if (o2 == null) {
   314                     comparison = 1;
   315                 } else {
   316                     comparison = getComparator(column).compare(o1,
   317                             o2);
   318                 }
   319                 if (comparison != 0) {
   320                     return directive.direction == DESCENDING ? -comparison : comparison;
   321                 }
   322             }
   323             return 0;
   324         }
   325     }
   326 
   327     private class TableModelHandler implements TableModelListener {
   328 
   329         public void tableChanged(TableModelEvent e) {
   330             // If we're not sorting by anything, just pass the event
   331             // along.
   332             if (!isSorting()) {
   333                 clearSortingState();
   334                 fireTableChanged(e);
   335                 return;
   336             }
   337 
   338             // If the table structure has changed, cancel the
   339             // sorting; the
   340             // sorting columns may have been either moved or deleted
   341             // from
   342             // the model.
   343             if (e.getFirstRow() == TableModelEvent.HEADER_ROW) {
   344                 cancelSorting();
   345                 fireTableChanged(e);
   346                 return;
   347             }
   348 
   349             // We can map a cell event through to the view without
   350             // widening
   351             // when the following conditions apply:
   352             //
   353             // a) all the changes are on one row (e.getFirstRow() ==
   354             // e.getLastRow()) and,
   355             // b) all the changes are in one column (column !=
   356             // TableModelEvent.ALL_COLUMNS) and,
   357             // c) we are not sorting on that column
   358             // (getSortingStatus(column) == NOT_SORTED) and,
   359             // d) a reverse lookup will not trigger a sort
   360             // (modelToView != null)
   361             //
   362             // Note: INSERT and DELETE events fail this test as they
   363             // have column == ALL_COLUMNS.
   364             //
   365             // The last check, for (modelToView != null) is to see
   366             // if modelToView
   367             // is already allocated. If we don't do this check;
   368             // sorting can become
   369             // a performance bottleneck for applications where cells
   370             // change rapidly in different parts of the table. If
   371             // cells
   372             // change alternately in the sorting column and then
   373             // outside of
   374             // it this class can end up re-sorting on alternate cell
   375             // updates -
   376             // which can be a performance problem for large tables.
   377             // The last
   378             // clause avoids this problem.
   379             int column = e.getColumn();
   380             if (e.getFirstRow() == e.getLastRow() && column != TableModelEvent.ALL_COLUMNS && getSortingStatus(column) == NOT_SORTED && modelToView != null) {
   381                 int viewIndex = getModelToView()[e.getFirstRow()];
   382                 fireTableChanged(new TableModelEvent(TableSorterModel.this,
   383                         viewIndex,
   384                         viewIndex,
   385                         column,
   386                         e.getType()));
   387                 return;
   388             }
   389 
   390             // Something has happened to the data that may have
   391             // invalidated the row order.
   392             clearSortingState();
   393             fireTableDataChanged();
   394             return;
   395         }
   396     }
   397 
   398     private class MouseHandler extends MouseAdapter {
   399 
   400         public void mouseClicked(MouseEvent e) {
   401             JTableHeader h = (JTableHeader) e.getSource();
   402             TableColumnModel columnModel = h.getColumnModel();
   403             int viewColumn = columnModel.getColumnIndexAtX(e.getX());
   404             int column = columnModel.getColumn(viewColumn).getModelIndex();
   405             if (column != -1) {
   406                 int status = getSortingStatus(column);
   407                 if (!e.isControlDown()) {
   408                     cancelSorting();
   409                 }
   410                 // Cycle the sorting states through {NOT_SORTED,
   411                 // ASCENDING, DESCENDING} or
   412                 // {NOT_SORTED, DESCENDING, ASCENDING} depending
   413                 // on whether shift is pressed.
   414                 status = status + (e.isShiftDown() ? -1 : 1);
   415                 status = (status + 4) % 3 - 1; // signed mod,
   416                 // returning
   417                 // {-1, 0, 1}
   418                 setSortingStatus(column,
   419                         status);
   420             }
   421         }
   422     }
   423 
   424     private class SortableHeaderRenderer implements TableCellRenderer {
   425 
   426         private TableCellRenderer tableCellRenderer;
   427 
   428         public SortableHeaderRenderer(TableCellRenderer tableCellRenderer) {
   429             this.tableCellRenderer = tableCellRenderer;
   430         }
   431 
   432         public Component getTableCellRendererComponent(JTable table,
   433                 Object value,
   434                 boolean isSelected,
   435                 boolean hasFocus,
   436                 int row,
   437                 int column) {
   438             Component c = tableCellRenderer.getTableCellRendererComponent(table,
   439                     value,
   440                     isSelected,
   441                     hasFocus,
   442                     row,
   443                     column);
   444             if (c instanceof JLabel) {
   445                 JLabel l = (JLabel) c;
   446                 l.setHorizontalTextPosition(JLabel.LEFT);
   447                 int modelColumn = table.convertColumnIndexToModel(column);
   448                 l.setIcon(getHeaderRendererIcon(modelColumn,
   449                         l.getFont().getSize()));
   450             }
   451             return c;
   452         }
   453     }
   454 
   455     private static class Directive {
   456 
   457         private int column;
   458         private int direction;
   459 
   460         public Directive(int column,
   461                 int direction) {
   462             this.column = column;
   463             this.direction = direction;
   464         }
   465     }
   466 
   467     public void fireTableColumnUpdated(int index) {
   468         for (int i = 0; i < getRowCount(); i++) {
   469             fireTableCellUpdated(i,
   470                     index);
   471         }
   472     }
   473 }