java/JFTable/src/cz/frantovo/gui/tabulky/TableSorterModel.java
author František Kučera <franta-hg@frantovo.cz>
Sat Feb 28 18:07:55 2009 +0100 (2009-02-28)
changeset 5 9a3a229198b4
parent 3 74211841e25c
permissions -rwxr-xr-x
nbprojekt – drobnosti
     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     @Override
   258     public String getColumnName(int column) {
   259         return tableModel.getColumnName(column);
   260     }
   261 
   262     @Override
   263     public Class getColumnClass(int column) {
   264         return tableModel.getColumnClass(column);
   265     }
   266 
   267     @Override
   268     public boolean isCellEditable(int row,
   269             int column) {
   270         return tableModel.isCellEditable(modelIndex(row),
   271                 column);
   272     }
   273 
   274     @Override
   275     public Object getValueAt(int row,
   276             int column) {
   277         return tableModel.getValueAt(modelIndex(row),
   278                 column);
   279     }
   280 
   281     @Override
   282     public void setValueAt(Object aValue,
   283             int row,
   284             int column) {
   285         tableModel.setValueAt(aValue,
   286                 modelIndex(row),
   287                 column);
   288     }
   289 
   290     /** Pomocné třídy */
   291     private class Row implements Comparable {
   292 
   293         private int modelIndex;
   294 
   295         public Row(int index) {
   296             this.modelIndex = index;
   297         }
   298 
   299         public int compareTo(Object o) {
   300             int row1 = modelIndex;
   301             int row2 = ((Row) o).modelIndex;
   302 
   303             for (Iterator it = sortingColumns.iterator(); it.hasNext();) {
   304                 Directive directive = (Directive) it.next();
   305                 int column = directive.column;
   306                 Object o1 = tableModel.getValueAt(row1,
   307                         column);
   308                 Object o2 = tableModel.getValueAt(row2,
   309                         column);
   310 
   311                 int comparison = 0;
   312                 // Define null less than everything, except
   313                 // null.
   314                 if (o1 == null && o2 == null) {
   315                     comparison = 0;
   316                 } else if (o1 == null) {
   317                     comparison = -1;
   318                 } else if (o2 == null) {
   319                     comparison = 1;
   320                 } else {
   321                     comparison = getComparator(column).compare(o1,
   322                             o2);
   323                 }
   324                 if (comparison != 0) {
   325                     return directive.direction == DESCENDING ? -comparison : comparison;
   326                 }
   327             }
   328             return 0;
   329         }
   330     }
   331 
   332     /** Pomocné třídy */
   333     private class TableModelHandler implements TableModelListener {
   334 
   335         public void tableChanged(TableModelEvent e) {
   336             // If we're not sorting by anything, just pass the event
   337             // along.
   338             if (!isSorting()) {
   339                 clearSortingState();
   340                 fireTableChanged(e);
   341                 return;
   342             }
   343 
   344             // If the table structure has changed, cancel the
   345             // sorting; the
   346             // sorting columns may have been either moved or deleted
   347             // from
   348             // the model.
   349             if (e.getFirstRow() == TableModelEvent.HEADER_ROW) {
   350                 cancelSorting();
   351                 fireTableChanged(e);
   352                 return;
   353             }
   354 
   355             // We can map a cell event through to the view without
   356             // widening
   357             // when the following conditions apply:
   358             //
   359             // a) all the changes are on one row (e.getFirstRow() ==
   360             // e.getLastRow()) and,
   361             // b) all the changes are in one column (column !=
   362             // TableModelEvent.ALL_COLUMNS) and,
   363             // c) we are not sorting on that column
   364             // (getSortingStatus(column) == NOT_SORTED) and,
   365             // d) a reverse lookup will not trigger a sort
   366             // (modelToView != null)
   367             //
   368             // Note: INSERT and DELETE events fail this test as they
   369             // have column == ALL_COLUMNS.
   370             //
   371             // The last check, for (modelToView != null) is to see
   372             // if modelToView
   373             // is already allocated. If we don't do this check;
   374             // sorting can become
   375             // a performance bottleneck for applications where cells
   376             // change rapidly in different parts of the table. If
   377             // cells
   378             // change alternately in the sorting column and then
   379             // outside of
   380             // it this class can end up re-sorting on alternate cell
   381             // updates -
   382             // which can be a performance problem for large tables.
   383             // The last
   384             // clause avoids this problem.
   385             int column = e.getColumn();
   386             if (e.getFirstRow() == e.getLastRow() && column != TableModelEvent.ALL_COLUMNS && getSortingStatus(column) == NOT_SORTED && modelToView != null) {
   387                 int viewIndex = getModelToView()[e.getFirstRow()];
   388                 fireTableChanged(new TableModelEvent(TableSorterModel.this,
   389                         viewIndex,
   390                         viewIndex,
   391                         column,
   392                         e.getType()));
   393                 return;
   394             }
   395 
   396             // Something has happened to the data that may have
   397             // invalidated the row order.
   398             clearSortingState();
   399             fireTableDataChanged();
   400             return;
   401         }
   402     }
   403 
   404     private class MouseHandler extends MouseAdapter {
   405 
   406         @Override
   407         public void mouseClicked(MouseEvent e) {
   408             JTableHeader h = (JTableHeader) e.getSource();
   409             TableColumnModel columnModel = h.getColumnModel();
   410             int viewColumn = columnModel.getColumnIndexAtX(e.getX());
   411             int column = columnModel.getColumn(viewColumn).getModelIndex();
   412             if (column != -1) {
   413                 int status = getSortingStatus(column);
   414                 if (!e.isControlDown()) {
   415                     cancelSorting();
   416                 }
   417                 // Cycle the sorting states through {NOT_SORTED,
   418                 // ASCENDING, DESCENDING} or
   419                 // {NOT_SORTED, DESCENDING, ASCENDING} depending
   420                 // on whether shift is pressed.
   421                 status = status + (e.isShiftDown() ? -1 : 1);
   422                 status = (status + 4) % 3 - 1; // signed mod,
   423                 // returning
   424                 // {-1, 0, 1}
   425                 setSortingStatus(column,
   426                         status);
   427             }
   428         }
   429     }
   430 
   431     private class SortableHeaderRenderer implements TableCellRenderer {
   432 
   433         private TableCellRenderer tableCellRenderer;
   434 
   435         public SortableHeaderRenderer(TableCellRenderer tableCellRenderer) {
   436             this.tableCellRenderer = tableCellRenderer;
   437         }
   438 
   439         public Component getTableCellRendererComponent(JTable table,
   440                 Object value,
   441                 boolean isSelected,
   442                 boolean hasFocus,
   443                 int row,
   444                 int column) {
   445             Component c = tableCellRenderer.getTableCellRendererComponent(table,
   446                     value,
   447                     isSelected,
   448                     hasFocus,
   449                     row,
   450                     column);
   451             if (c instanceof JLabel) {
   452                 JLabel l = (JLabel) c;
   453                 l.setHorizontalTextPosition(JLabel.LEFT);
   454                 int modelColumn = table.convertColumnIndexToModel(column);
   455                 l.setIcon(getHeaderRendererIcon(modelColumn,
   456                         l.getFont().getSize()));
   457             }
   458             return c;
   459         }
   460     }
   461 
   462     private static class Directive {
   463 
   464         private int column;
   465         private int direction;
   466 
   467         public Directive(int column,
   468                 int direction) {
   469             this.column = column;
   470             this.direction = direction;
   471         }
   472     }
   473 
   474     public void fireTableColumnUpdated(int index) {
   475         for (int i = 0; i < getRowCount(); i++) {
   476             fireTableCellUpdated(i,
   477                     index);
   478         }
   479     }
   480 }