KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > versioning > util > TableSorter


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.modules.versioning.util;
21
22 import java.awt.*;
23 import java.awt.event.*;
24 import java.util.*;
25 import java.util.List JavaDoc;
26
27 import javax.swing.*;
28 import javax.swing.event.TableModelEvent JavaDoc;
29 import javax.swing.event.TableModelListener JavaDoc;
30 import javax.swing.table.*;
31
32 /**
33  * TableSorter is a decorator for TableModels; adding sorting
34  * functionality to a supplied TableModel. TableSorter does
35  * not store or copy the data in its TableModel; instead it maintains
36  * a map from the row indexes of the view to the row indexes of the
37  * model. As requests are made of the sorter (like getValueAt(row, col))
38  * they are passed to the underlying model after the row numbers
39  * have been translated via the internal mapping array. This way,
40  * the TableSorter appears to hold another copy of the table
41  * with the rows in a different order.
42  * <p/>
43  * TableSorter registers itself as a listener to the underlying model,
44  * just as the JTable itself would. Events recieved from the model
45  * are examined, sometimes manipulated (typically widened), and then
46  * passed on to the TableSorter's listeners (typically the JTable).
47  * If a change to the model has invalidated the order of TableSorter's
48  * rows, a note of this is made and the sorter will resort the
49  * rows the next time a value is requested.
50  * <p/>
51  * When the tableHeader property is set, either by using the
52  * setTableHeader() method or the two argument constructor, the
53  * table header may be used as a complete UI for TableSorter.
54  * The default renderer of the tableHeader is decorated with a renderer
55  * that indicates the sorting status of each column. In addition,
56  * a mouse listener is installed with the following behavior:
57  * <ul>
58  * <li>
59  * Mouse-click: Clears the sorting status of all other columns
60  * and advances the sorting status of that column through three
61  * values: {NOT_SORTED, ASCENDING, DESCENDING} (then back to
62  * NOT_SORTED again).
63  * <li>
64  * SHIFT-mouse-click: Clears the sorting status of all other columns
65  * and cycles the sorting status of the column through the same
66  * three values, in the opposite order: {NOT_SORTED, DESCENDING, ASCENDING}.
67  * <li>
68  * CONTROL-mouse-click and CONTROL-SHIFT-mouse-click: as above except
69  * that the changes to the column do not cancel the statuses of columns
70  * that are already sorting - giving a way to initiate a compound
71  * sort.
72  * </ul>
73  * <p/>
74  * This is a long overdue rewrite of a class of the same name that
75  * first appeared in the swing table demos in 1997.
76  *
77  * @author Philip Milne
78  * @author Brendon McLean
79  * @author Dan van Enckevort
80  * @author Parwinder Sekhon
81  * @version 2.0 02/27/04
82  */

83
84 public final class TableSorter extends AbstractTableModel {
85     protected TableModel tableModel;
86
87     public static final int DESCENDING = -1;
88     public static final int NOT_SORTED = 0;
89     public static final int ASCENDING = 1;
90
91     private static Directive EMPTY_DIRECTIVE = new Directive(-1, NOT_SORTED);
92
93     public static final Comparator COMPARABLE_COMAPRATOR = new Comparator<Comparable JavaDoc>() {
94         public int compare(Comparable JavaDoc o1, Comparable JavaDoc o2) {
95             return o1.compareTo(o2);
96         }
97     };
98     public static final Comparator LEXICAL_COMPARATOR = new Comparator() {
99         public int compare(Object JavaDoc o1, Object JavaDoc o2) {
100             return o1.toString().compareTo(o2.toString());
101         }
102     };
103
104     private final Icon ICON_ASCENDING = new ImageIcon(org.openide.util.Utilities.loadImage("org/netbeans/modules/openide/explorer/columnsSortedAsc.gif", true)); // NOI18N
105
private final Icon ICON_DESCENDING = new ImageIcon(org.openide.util.Utilities.loadImage("org/netbeans/modules/openide/explorer/columnsSortedDesc.gif", true)); // NOI18N
106

107     private Row[] viewToModel;
108     private int[] modelToView;
109
110     private JTableHeader tableHeader;
111     private MouseListener mouseListener;
112     private TableModelListener JavaDoc tableModelListener;
113     // key is either Class or Integer
114
private Map<Object JavaDoc, Comparator> columnComparators = new HashMap<Object JavaDoc, Comparator>();
115     private List JavaDoc<Directive> sortingColumns = new ArrayList<Directive>();
116
117     public TableSorter() {
118         this.mouseListener = new MouseHandler();
119         this.tableModelListener = new TableModelHandler();
120     }
121
122     public TableSorter(TableModel tableModel) {
123         this();
124         setTableModel(tableModel);
125     }
126
127     public TableSorter(TableModel tableModel, JTableHeader tableHeader) {
128         this();
129         setTableHeader(tableHeader);
130         setTableModel(tableModel);
131     }
132
133     private void clearSortingState() {
134         viewToModel = null;
135         modelToView = null;
136     }
137
138     public TableModel getTableModel() {
139         return tableModel;
140     }
141
142     public final void setTableModel(TableModel tableModel) {
143         if (this.tableModel != null) {
144             this.tableModel.removeTableModelListener(tableModelListener);
145         }
146
147         this.tableModel = tableModel;
148         if (this.tableModel != null) {
149             this.tableModel.addTableModelListener(tableModelListener);
150         }
151
152         clearSortingState();
153         fireTableStructureChanged();
154     }
155
156     public JTableHeader getTableHeader() {
157         return tableHeader;
158     }
159
160     public void setTableHeader(JTableHeader tableHeader) {
161         if (this.tableHeader != null) {
162             this.tableHeader.removeMouseListener(mouseListener);
163             TableCellRenderer defaultRenderer = this.tableHeader.getDefaultRenderer();
164             if (defaultRenderer instanceof SortableHeaderRenderer) {
165                 this.tableHeader.setDefaultRenderer(((SortableHeaderRenderer) defaultRenderer).tableCellRenderer);
166             }
167         }
168         this.tableHeader = tableHeader;
169         if (this.tableHeader != null) {
170             this.tableHeader.addMouseListener(mouseListener);
171             this.tableHeader.setDefaultRenderer(
172                     new SortableHeaderRenderer(this.tableHeader.getDefaultRenderer()));
173         }
174     }
175
176     public boolean isSorting() {
177         return sortingColumns.size() != 0;
178     }
179
180     private Directive getDirective(int column) {
181         for (int i = 0; i < sortingColumns.size(); i++) {
182             Directive directive = sortingColumns.get(i);
183             if (directive.column == column) {
184                 return directive;
185             }
186         }
187         return EMPTY_DIRECTIVE;
188     }
189
190     public int getSortingStatus(int column) {
191         return getDirective(column).direction;
192     }
193
194     private void sortingStatusChanged() {
195         clearSortingState();
196         fireTableDataChanged();
197         if (tableHeader != null) {
198             tableHeader.repaint();
199         }
200     }
201
202     public void setSortingStatus(int column, int status) {
203         Directive directive = getDirective(column);
204         if (directive != EMPTY_DIRECTIVE) {
205             sortingColumns.remove(directive);
206         }
207         if (status != NOT_SORTED) {
208             sortingColumns.add(new Directive(column, status));
209         }
210         sortingStatusChanged();
211     }
212
213     protected Icon getHeaderRendererIcon(int column, int size) {
214         Directive directive = getDirective(column);
215         if (directive == EMPTY_DIRECTIVE) {
216             return null;
217         }
218         return directive.direction == ASCENDING ? ICON_ASCENDING : ICON_DESCENDING;
219     }
220
221     private void cancelSorting() {
222         sortingColumns.clear();
223         sortingStatusChanged();
224     }
225
226     public void setColumnComparator(Class JavaDoc type, Comparator comparator) {
227         if (comparator == null) {
228             columnComparators.remove(type);
229         } else {
230             columnComparators.put(type, comparator);
231         }
232     }
233
234     /**
235      * Sets comparator that will be used to compare values in the specified column. Note that the Comparator will obtain
236      * row indices (as Integer objects) to compare and NOT actual cell values.
237      *
238      * @param column model index of the column to be sorted
239      * @param comparator comparator that will sort the given column
240      */

241     public void setColumnComparator(int column, Comparator comparator) {
242         if (comparator == null) {
243             columnComparators.remove(Integer.valueOf(column));
244         } else {
245             columnComparators.put(Integer.valueOf(column), comparator);
246         }
247     }
248     
249     protected Comparator getComparator(int column) {
250         Class JavaDoc columnType = tableModel.getColumnClass(column);
251         Comparator comparator = columnComparators.get(columnType);
252         if (comparator != null) {
253             return comparator;
254         }
255         if (Comparable JavaDoc.class.isAssignableFrom(columnType)) {
256             return COMPARABLE_COMAPRATOR;
257         }
258         return LEXICAL_COMPARATOR;
259     }
260
261     private Row[] getViewToModel() {
262         if (viewToModel == null) {
263             int tableModelRowCount = tableModel.getRowCount();
264             viewToModel = new Row[tableModelRowCount];
265             for (int row = 0; row < tableModelRowCount; row++) {
266                 viewToModel[row] = new Row(row);
267             }
268
269             if (isSorting()) {
270                 Arrays.sort(viewToModel);
271             }
272         }
273         return viewToModel;
274     }
275
276     public int modelIndex(int viewIndex) {
277         return getViewToModel()[viewIndex].modelIndex;
278     }
279
280     private int[] getModelToView() {
281         if (modelToView == null) {
282             int n = getViewToModel().length;
283             modelToView = new int[n];
284             for (int i = 0; i < n; i++) {
285                 modelToView[modelIndex(i)] = i;
286             }
287         }
288         return modelToView;
289     }
290
291     // TableModel interface methods
292

293     public int getRowCount() {
294         return (tableModel == null) ? 0 : tableModel.getRowCount();
295     }
296
297     public int getColumnCount() {
298         return (tableModel == null) ? 0 : tableModel.getColumnCount();
299     }
300
301     public String JavaDoc getColumnName(int column) {
302         return tableModel.getColumnName(column);
303     }
304
305     public Class JavaDoc getColumnClass(int column) {
306         return tableModel.getColumnClass(column);
307     }
308
309     public boolean isCellEditable(int row, int column) {
310         return tableModel.isCellEditable(modelIndex(row), column);
311     }
312
313     public Object JavaDoc getValueAt(int row, int column) {
314         return tableModel.getValueAt(modelIndex(row), column);
315     }
316
317     public void setValueAt(Object JavaDoc aValue, int row, int column) {
318         tableModel.setValueAt(aValue, modelIndex(row), column);
319     }
320
321     // Helper classes
322

323     private class Row implements Comparable JavaDoc {
324         private int modelIndex;
325
326         public Row(int index) {
327             this.modelIndex = index;
328         }
329
330         public int compareTo(Object JavaDoc o) {
331             int row1 = modelIndex;
332             int row2 = ((Row) o).modelIndex;
333
334             for (Iterator<Directive> it = sortingColumns.iterator(); it.hasNext();) {
335                 Directive directive = it.next();
336                 int column = directive.column;
337                 Object JavaDoc o1 = tableModel.getValueAt(row1, column);
338                 Object JavaDoc o2 = tableModel.getValueAt(row2, column);
339
340                 int comparison = 0;
341                 // Define null less than everything, except null.
342
if (o1 == null && o2 == null) {
343                     comparison = 0;
344                 } else if (o1 == null) {
345                     comparison = -1;
346                 } else if (o2 == null) {
347                     comparison = 1;
348                 } else {
349                     Comparator comparator = columnComparators.get(Integer.valueOf(column));
350                     if (comparator != null) {
351                         comparison = comparator.compare(Integer.valueOf(row1), Integer.valueOf(row2));
352                     } else {
353                         comparison = getComparator(column).compare(o1, o2);
354                     }
355                 }
356                 if (comparison != 0) {
357                     return directive.direction == DESCENDING ? -comparison : comparison;
358                 }
359             }
360             return 0;
361         }
362     }
363
364     private class TableModelHandler implements TableModelListener JavaDoc {
365         public void tableChanged(TableModelEvent JavaDoc e) {
366             // If we're not sorting by anything, just pass the event along.
367
if (!isSorting()) {
368                 clearSortingState();
369                 fireTableChanged(e);
370                 return;
371             }
372                 
373             // If the table structure has changed, cancel the sorting; the
374
// sorting columns may have been either moved or deleted from
375
// the model.
376
if (e.getFirstRow() == TableModelEvent.HEADER_ROW) {
377                 cancelSorting();
378                 fireTableChanged(e);
379                 return;
380             }
381
382             // We can map a cell event through to the view without widening
383
// when the following conditions apply:
384
//
385
// a) all the changes are on one row (e.getFirstRow() == e.getLastRow()) and,
386
// b) all the changes are in one column (column != TableModelEvent.ALL_COLUMNS) and,
387
// c) we are not sorting on that column (getSortingStatus(column) == NOT_SORTED) and,
388
// d) a reverse lookup will not trigger a sort (modelToView != null)
389
//
390
// Note: INSERT and DELETE events fail this test as they have column == ALL_COLUMNS.
391
//
392
// The last check, for (modelToView != null) is to see if modelToView
393
// is already allocated. If we don't do this check; sorting can become
394
// a performance bottleneck for applications where cells
395
// change rapidly in different parts of the table. If cells
396
// change alternately in the sorting column and then outside of
397
// it this class can end up re-sorting on alternate cell updates -
398
// which can be a performance problem for large tables. The last
399
// clause avoids this problem.
400
int column = e.getColumn();
401             if (e.getFirstRow() == e.getLastRow()
402                     && column != TableModelEvent.ALL_COLUMNS
403                     && getSortingStatus(column) == NOT_SORTED
404                     && modelToView != null) {
405                 int viewIndex = getModelToView()[e.getFirstRow()];
406                 fireTableChanged(new TableModelEvent JavaDoc(TableSorter.this,
407                                                      viewIndex, viewIndex,
408                                                      column, e.getType()));
409                 return;
410             }
411
412             // Something has happened to the data that may have invalidated the row order.
413
clearSortingState();
414             fireTableDataChanged();
415         }
416     }
417
418     private class MouseHandler extends MouseAdapter {
419         public void mouseClicked(MouseEvent e) {
420             JTableHeader h = (JTableHeader) e.getSource();
421             TableColumnModel columnModel = h.getColumnModel();
422             int viewColumn = columnModel.getColumnIndexAtX(e.getX());
423             int column = columnModel.getColumn(viewColumn).getModelIndex();
424             if (column != -1) {
425                 int status = getSortingStatus(column);
426                 if (!e.isControlDown()) {
427                     cancelSorting();
428                 }
429                 // Cycle the sorting states through {NOT_SORTED, ASCENDING, DESCENDING} or
430
// {NOT_SORTED, DESCENDING, ASCENDING} depending on whether shift is pressed.
431
status += e.isShiftDown() ? -1 : 1;
432                 status = (status + 4) % 3 - 1; // signed mod, returning {-1, 0, 1}
433
setSortingStatus(column, status);
434             }
435         }
436     }
437
438     private class SortableHeaderRenderer implements TableCellRenderer {
439         private TableCellRenderer tableCellRenderer;
440
441         public SortableHeaderRenderer(TableCellRenderer tableCellRenderer) {
442             this.tableCellRenderer = tableCellRenderer;
443         }
444
445         public Component getTableCellRendererComponent(JTable table,
446                                                        Object JavaDoc value,
447                                                        boolean isSelected,
448                                                        boolean hasFocus,
449                                                        int row,
450                                                        int column) {
451             Component c = tableCellRenderer.getTableCellRendererComponent(table,
452                     value, isSelected, hasFocus, row, column);
453             if (c instanceof JLabel) {
454                 JLabel l = (JLabel) c;
455                 int modelColumn = table.convertColumnIndexToModel(column);
456                 Directive directive = getDirective(modelColumn);
457                 if (directive != EMPTY_DIRECTIVE) {
458                     l.setFont(l.getFont().deriveFont(Font.BOLD));
459                 }
460                 l.setHorizontalTextPosition(JLabel.LEFT);
461                 l.setIcon(getHeaderRendererIcon(modelColumn, l.getFont().getSize()));
462             }
463             return c;
464         }
465     }
466
467     private static class Directive {
468         private int column;
469         private int direction;
470
471         public Directive(int column, int direction) {
472             this.column = column;
473             this.direction = direction;
474         }
475     }
476 }
477
Popular Tags