source: 3thparty/jmessenger/src/nu/fw/jeti/util/TableSorter.java @ 3952

Revision 3952, 17.3 KB checked in by alexandrecorreia, 13 years ago (diff)

Ticket #1710 - Adicao do codigo fonte java do componente jmessenger(jabberit_messenger)

  • Property svn:executable set to *
Line 
1package nu.fw.jeti.util;
2
3import java.awt.*;
4import java.awt.event.*;
5import java.util.*;
6import java.util.List;
7
8import javax.swing.*;
9import javax.swing.event.TableModelEvent;
10import javax.swing.event.TableModelListener;
11import javax.swing.table.*;
12
13/**
14 * TableSorter is a decorator for TableModels; adding sorting
15 * functionality to a supplied TableModel. TableSorter does
16 * not store or copy the data in its TableModel; instead it maintains
17 * a map from the row indexes of the view to the row indexes of the
18 * model. As requests are made of the sorter (like getValueAt(row, col))
19 * they are passed to the underlying model after the row numbers
20 * have been translated via the internal mapping array. This way,
21 * the TableSorter appears to hold another copy of the table
22 * with the rows in a different order.
23 * <p>
24 * TableSorter registers itself as a listener to the underlying model, 
25 * just as the JTable itself would. Events revieved from the model 
26 * are examined, sometimes manipulated (typically widened), and then
27 * passed on to the TableSorter's listeners (typically the JTable).
28 * If a change to the model has invalidated the order of TableSorter's
29 * rows, a note of this is made and the sorter will resort the
30 * rows the next time a value is requested.
31 * <p>
32 * When the tableHeader property is set, either by using the
33 * setTableHeader() method or the two argument constructor, the
34 * table header may be used as a complete UI for TableSorter.
35 * The default renderer of the tableHeader is decorated with a renderer
36 * that indicates the sorting status of each column. In addition,
37 * a mouse listener is installed with the following behavior:
38 * <ul>
39 * <li>
40 * Mouse-click: Clears the sorting status of all other columns
41 * and advances the sorting status of that column through three
42 * values: {NOT_SORTED, ASCENDING, DESCENDING} (then back to
43 * NOT_SORTED again).
44 * <li>
45 * SHIFT-mouse-click: Clears the sorting status of all other columns
46 * and cycles the sorting status of the column through the same
47 * three values, in a different order: {NOT_SORTED, DESCENDING, ASCENDING}. 
48 * <li>
49 * CONTROL-mouse-click and CONTROL-SHIFT-mouse-click: as above except
50 * that the changes to the column do not cancel the statuses of columns
51 * that are already sorting - giving a way to initiate a compound
52 * sort. 
53 * </ul>
54 *
55 * @author Philip Milne
56 * @author Brendon McLean
57 * @author Dan van Enckevort
58 * @author Parwinder Sekhon
59 *
60 */
61
62 /*
63 *  This is a, long overdue, rewrite of a class of the same name that
64 *  first appeared in the swing table demos in 1997. This version was
65 *  last updated on: 25:2:2004
66 */
67public class TableSorter extends AbstractTableModel {
68    protected TableModel tableModel;
69
70    public static final int DESCENDING = -1;
71    public static final int NOT_SORTED = 0;
72    public static final int ASCENDING = 1;
73
74    private static Directive EMPTY_DIRECTIVE = new Directive(-1, NOT_SORTED);
75   
76    public static final Comparator COMPARABLE_COMAPRATOR = new Comparator() {
77        public int compare(Object o1, Object o2) {
78            return ((Comparable) o1).compareTo(o2);
79        }
80    };
81    public static final Comparator LEXICAL_COMPARATOR = new Comparator() {
82        public int compare(Object o1, Object o2) {
83            return o1.toString().compareTo(o2.toString());
84        }
85    };
86
87    private Row[] viewToModel;
88    private int[] modelToView;
89
90    private JTableHeader tableHeader;
91    private MouseListener mouseListener;
92    private TableModelListener tableModelListener;
93    private Map columnComparators = new HashMap();
94    private List sortingColumns = new ArrayList();
95
96    public TableSorter() {
97        this.mouseListener = new MouseHandler();
98        this.tableModelListener = new TableModelHandler();
99    }
100
101    public TableSorter(TableModel tableModel) {
102        this();
103        setTableModel(tableModel);
104    }
105
106    public TableSorter(TableModel tableModel, JTableHeader tableHeader) {
107        this();
108        setTableHeader(tableHeader);
109        setTableModel(tableModel);
110    }
111
112    private void clearSortingState() {
113        viewToModel = null;
114        modelToView = null;
115    }
116
117    public TableModel getTableModel() {
118        return tableModel;
119    }
120
121    public void setTableModel(TableModel tableModel) {
122        if (this.tableModel != null) {
123            this.tableModel.removeTableModelListener(tableModelListener);
124        }
125
126        this.tableModel = tableModel;
127        if (this.tableModel != null) {
128            this.tableModel.addTableModelListener(tableModelListener);
129        }
130
131        clearSortingState();
132        fireTableStructureChanged();
133    }
134
135    public JTableHeader getTableHeader() {
136        return tableHeader;
137    }
138
139    public void setTableHeader(JTableHeader tableHeader) {
140        if (this.tableHeader != null) {
141            this.tableHeader.removeMouseListener(mouseListener);
142            TableCellRenderer defaultRenderer = this.tableHeader.getDefaultRenderer();
143            if (defaultRenderer instanceof SortableHeaderRenderer) {
144                this.tableHeader.setDefaultRenderer(((SortableHeaderRenderer) defaultRenderer).tableCellRenderer);
145            }
146        }
147        this.tableHeader = tableHeader;
148        if (this.tableHeader != null) {
149            this.tableHeader.addMouseListener(mouseListener);
150            this.tableHeader.setDefaultRenderer(
151                    new SortableHeaderRenderer(this.tableHeader.getDefaultRenderer()));
152        }
153    }
154
155    public boolean isSorting() {
156        return sortingColumns.size() != 0;
157    }
158
159    private Directive getDirective(int column) {
160        for (int i = 0; i < sortingColumns.size(); i++) {
161            Directive directive = (Directive)sortingColumns.get(i);
162            if (directive.column == column) {
163                return directive;
164            }
165        }
166        return EMPTY_DIRECTIVE;
167    }
168
169    public int getSortingStatus(int column) {
170        return getDirective(column).direction;
171    }
172
173    private void sortingStatusChanged() {
174        clearSortingState();
175        fireTableDataChanged();
176        tableHeader.repaint();
177    }
178
179    public void setSortingStatus(int column, int status) {
180        Directive directive = getDirective(column);
181        if (directive != EMPTY_DIRECTIVE) {
182            sortingColumns.remove(directive);
183        }
184        if (status != NOT_SORTED) {
185            sortingColumns.add(new Directive(column, status));
186        }
187        sortingStatusChanged();
188    }
189
190    protected Icon getHeaderRendererIcon(int column, int size) {
191        Directive directive = getDirective(column);
192        if (directive == EMPTY_DIRECTIVE) {
193            return null;
194        }
195        return new Arrow(directive.direction == DESCENDING, size, sortingColumns.indexOf(directive));
196    }
197   
198    private void cancelSorting() {
199        sortingColumns.clear();
200        sortingStatusChanged();
201    }
202
203    public void setColumnComparator(Class type, Comparator comparator) {
204        if (comparator == null) {
205            columnComparators.remove(type);
206        } else {
207            columnComparators.put(type, comparator);
208        }
209    }
210
211    protected Comparator getComparator(int column) {
212        Class columnType = tableModel.getColumnClass(column);
213        Comparator comparator = (Comparator) columnComparators.get(columnType);
214        if (comparator != null) {
215            return comparator;
216        }
217        if (Comparable.class.isAssignableFrom(columnType)) {
218            return COMPARABLE_COMAPRATOR;
219        }
220        return LEXICAL_COMPARATOR;
221    }
222
223    private Row[] getViewToModel() {
224        if (viewToModel == null) {
225            int tableModelRowCount = tableModel.getRowCount();
226            viewToModel = new Row[tableModelRowCount];
227            for (int row = 0; row < tableModelRowCount; row++) {
228                viewToModel[row] = new Row(row);
229            }
230
231            if (isSorting()) {
232                Arrays.sort(viewToModel);
233            }
234        }
235        return viewToModel;
236    }
237
238    public int modelIndex(int viewIndex) {
239        return getViewToModel()[viewIndex].modelIndex;
240    }
241   
242    private int[] getModelToView() {
243        if (modelToView == null) {
244            int n = getViewToModel().length;
245            modelToView = new int[n];
246            for (int i = 0; i < n; i++) {
247                modelToView[modelIndex(i)] = i;
248            }
249        }
250        return modelToView;
251    }
252
253    // TableModel interface methods
254
255    public int getRowCount() {
256        return (tableModel == null) ? 0 : tableModel.getRowCount();
257    }
258
259    public int getColumnCount() {
260        return (tableModel == null) ? 0 : tableModel.getColumnCount();
261    }
262
263    public String getColumnName(int column) {
264        return tableModel.getColumnName(column);
265    }
266
267    public Class getColumnClass(int column) {
268        return tableModel.getColumnClass(column);
269    }
270
271    public boolean isCellEditable(int row, int column) {
272        return tableModel.isCellEditable(modelIndex(row), column);
273    }
274
275    public Object getValueAt(int row, int column) {
276        return tableModel.getValueAt(modelIndex(row), column);
277    }
278
279    public void setValueAt(Object aValue, int row, int column) {
280        tableModel.setValueAt(aValue, modelIndex(row), column);
281    }
282
283    // Helper classes
284   
285    private class Row implements Comparable {
286        private int modelIndex;
287
288        public Row(int index) {
289            this.modelIndex = index;
290        }
291
292        public int compareTo(Object o) {
293            int row1 = modelIndex;
294            int row2 = ((Row) o).modelIndex;
295
296            for (Iterator it = sortingColumns.iterator(); it.hasNext();) {
297                Directive directive = (Directive) it.next();
298                int column = directive.column;
299                Object o1 = tableModel.getValueAt(row1, column);
300                Object o2 = tableModel.getValueAt(row2, column);
301
302                int comparison = 0;
303                // Define null less than everything, except null.
304                if (o1 == null && o2 == null) {
305                    comparison = 0;                 
306                } else if (o1 == null) {
307                    comparison = -1;
308                } else if (o2 == null) {
309                    comparison = 1;
310                } else {
311                    comparison = getComparator(column).compare(o1, o2);
312                }
313                if (comparison != 0) {
314                    return directive.direction == DESCENDING ? -comparison : comparison;
315                }
316            }
317            return 0;
318        }
319    }
320
321    private class TableModelHandler implements TableModelListener {
322        public void tableChanged(TableModelEvent e) {
323            // If we're not sorting by anything, just pass the event along.
324            if (!isSorting()) {
325                fireTableChanged(e);
326                return;
327            }
328               
329            // If the table structure has changed, cancel the sorting; the
330            // sorting columns may have been either moved or deleted from
331            // the model.
332            if (e.getFirstRow() == TableModelEvent.HEADER_ROW) {
333                cancelSorting();
334                fireTableChanged(e);
335                return;
336            }
337
338            // We can map a cell event through to the view without widening             
339            // when the following conditions apply:
340            //
341            // a) all the changes are on one row (e.getFirstRow() == e.getLastRow()) and,
342            // b) all the changes are in one column (column != TableModelEvent.ALL_COLUMNS) and,
343            // c) we are not sorting on that column (getSortingStatus(column) == NOT_SORTED) and,
344            // d) a reverse lookup will not trigger a sort (modelToView != null)
345            //
346            // Note: INSERT and DELETE events fail this test as they have column == ALL_COLUMNS.
347            //
348            // The last check, for (modelToView != null) is to see if modelToView
349            // is already allocated. If we don't do this check; sorting can become
350            // a performance bottleneck for applications where cells 
351            // change rapidly in different parts of the table. If cells
352            // change alternately in the sorting column and then outside of             
353            // it this class can end up re-sorting on alternate cell updates -
354            // which can be a performance problem for large tables. The last
355            // clause avoids this problem.
356            int column = e.getColumn();
357            if (e.getFirstRow() == e.getLastRow()
358             && column != TableModelEvent.ALL_COLUMNS
359             && getSortingStatus(column) == NOT_SORTED
360             && modelToView != null) {
361                int viewIndex = getModelToView()[e.getFirstRow()];
362                fireTableChanged(new TableModelEvent(TableSorter.this,
363                                                     viewIndex, viewIndex,
364                                                     column, e.getType()));
365                return;
366            }
367
368            // Something has happened to the data that may have invalidated the row order.
369            clearSortingState();
370            fireTableDataChanged();
371            return;
372        }
373    }
374
375    private class MouseHandler extends MouseAdapter {
376        public void mouseClicked(MouseEvent e) {
377            JTableHeader h = (JTableHeader) e.getSource();
378            TableColumnModel columnModel = h.getColumnModel();
379            int viewColumn = columnModel.getColumnIndexAtX(e.getX());
380            int column = columnModel.getColumn(viewColumn).getModelIndex();
381            if (column != -1) {
382                int status = getSortingStatus(column);
383                if (!e.isControlDown()) {
384                    cancelSorting();
385                }
386                // Cycle the sorting states through {NOT_SORTED, ASCENDING, DESCENDING} or
387                // {NOT_SORTED, DESCENDING, ASCENDING} depending on whether shift is pressed.
388                status = status + (e.isShiftDown() ? -1 : 1);
389                status = (status + 1) % 3 - 1; // signed mod returning {-1, 0, 1}
390                setSortingStatus(column, status);
391            }
392        }
393    }
394
395    private static class Arrow implements Icon {
396        private boolean descending;
397        private int size;
398        private int priority;
399       
400        public Arrow(boolean descending, int size, int priority) {
401            this.descending = descending;
402            this.size = size;
403            this.priority = priority;
404        }
405
406        public void paintIcon(Component c, Graphics g, int x, int y) {
407            Color color = c == null ? Color.GRAY : c.getBackground();             
408            // In a compound sort, make each succesive triangle 20%
409            // smaller than the previous one.
410            int dx = (int)(size/2*Math.pow(0.8, priority));
411            int dy = descending ? dx: -dx;
412            // Align icon (roughly) with font baseline.
413            y = y + 5*size/6 + (descending ? -dy : 0);
414            int shift = descending ? 1 : -1;
415            g.translate(x, y);
416
417            // Right diagonal.
418            g.setColor(color.darker());
419            g.drawLine(dx / 2, dy, 0, 0);
420            g.drawLine(dx / 2, dy + shift, 0, shift);
421           
422            // Left diagonal.
423            g.setColor(color.brighter());
424            g.drawLine(dx / 2, dy, dx, 0);
425            g.drawLine(dx / 2, dy + shift, dx, shift);
426           
427            // Horizontal line.
428            if (descending) {
429                g.setColor(color.darker().darker());
430            } else {
431                g.setColor(color.brighter().brighter());
432            }
433            g.drawLine(dx, 0, 0, 0);
434
435            g.setColor(color);
436            g.translate(-x, -y);
437        }
438
439        public int getIconWidth() {
440            return size;
441        }
442
443        public int getIconHeight() {
444            return size;
445        }
446    }
447
448    private class SortableHeaderRenderer implements TableCellRenderer {
449        private TableCellRenderer tableCellRenderer;
450
451        public SortableHeaderRenderer(TableCellRenderer tableCellRenderer) {
452            this.tableCellRenderer = tableCellRenderer;
453        }
454
455        public Component getTableCellRendererComponent(JTable table,
456                                                       Object value,
457                                                       boolean isSelected,
458                                                       boolean hasFocus,
459                                                       int row,
460                                                       int column) {
461            Component c = tableCellRenderer.getTableCellRendererComponent(table, value,
462                                                                          isSelected,
463                                                                          hasFocus,
464                                                                          row, column);
465            if (c instanceof JLabel) {
466                JLabel l = (JLabel) c;
467                l.setHorizontalTextPosition(JLabel.LEFT);
468                int modelColumn = table.convertColumnIndexToModel(column);
469                l.setIcon(getHeaderRendererIcon(modelColumn, l.getFont().getSize()));
470            }
471            return c;
472        }
473    }
474   
475    private static class Directive {
476        private int column;
477        private int direction;
478
479        public Directive(int column, int direction) {
480            this.column = column;
481            this.direction = direction;
482        }
483    }
484}
485
486/*
487 * Overrides for emacs
488 * Local variables:
489 * tab-width: 4
490 * End:
491 */
Note: See TracBrowser for help on using the repository browser.