OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 80 | Rev 174 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
17 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
7
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
8
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
13
 
14
 package org.openconcerto.utils;
15
 
16
/*
17
 * Créé le 21 févr. 2005
18
 */
19
 
20
import java.awt.Color;
21
import java.awt.Component;
22
import java.awt.Graphics;
23
import java.awt.event.MouseAdapter;
24
import java.awt.event.MouseEvent;
25
import java.awt.event.MouseListener;
26
import java.beans.PropertyChangeListener;
27
import java.beans.PropertyChangeSupport;
28
import java.util.ArrayList;
29
import java.util.Arrays;
30
import java.util.Collections;
31
import java.util.Comparator;
32
import java.util.HashMap;
33
import java.util.Iterator;
34
import java.util.List;
35
import java.util.Map;
36
 
37
import javax.swing.Icon;
38
import javax.swing.JLabel;
39
import javax.swing.JTable;
156 ilm 40
import javax.swing.SwingConstants;
41
import javax.swing.SwingUtilities;
17 ilm 42
import javax.swing.event.TableModelEvent;
43
import javax.swing.event.TableModelListener;
44
import javax.swing.table.AbstractTableModel;
45
import javax.swing.table.JTableHeader;
46
import javax.swing.table.TableCellRenderer;
47
import javax.swing.table.TableColumnModel;
48
import javax.swing.table.TableModel;
49
 
156 ilm 50
import net.jcip.annotations.Immutable;
51
 
17 ilm 52
/**
53
 * TableSorter is a decorator for TableModels; adding sorting functionality to a supplied
54
 * TableModel. TableSorter does not store or copy the data in its TableModel; instead it maintains a
55
 * map from the row indexes of the view to the row indexes of the model. As requests are made of the
56
 * sorter (like getValueAt(row, col)) they are passed to the underlying model after the row numbers
57
 * have been translated via the internal mapping array. This way, the TableSorter appears to hold
156 ilm 58
 * another copy of the table with the rows in a different order.
59
 * <p/>
60
 * TableSorter registers itself as a listener to the underlying model, just as the JTable itself
61
 * would. Events recieved from the model are examined, sometimes manipulated (typically widened),
62
 * and then passed on to the TableSorter's listeners (typically the JTable). If a change to the
63
 * model has invalidated the order of TableSorter's rows, a note of this is made and the sorter will
64
 * resort the rows the next time a value is requested.
65
 * <p/>
66
 * When the tableHeader property is set, either by using the setTableHeader() method or the two
67
 * argument constructor, the table header may be used as a complete UI for TableSorter. The default
68
 * renderer of the tableHeader is decorated with a renderer that indicates the sorting status of
69
 * each column. In addition, a mouse listener is installed with the following behavior:
17 ilm 70
 * <ul>
71
 * <li>Mouse-click: Clears the sorting status of all other columns and advances the sorting status
72
 * of that column through three values: {NOT_SORTED, ASCENDING, DESCENDING} (then back to NOT_SORTED
73
 * again).
74
 * <li>SHIFT-mouse-click: Clears the sorting status of all other columns and cycles the sorting
75
 * status of the column through the same three values, in the opposite order: {NOT_SORTED,
76
 * DESCENDING, ASCENDING}.
77
 * <li>CONTROL-mouse-click and CONTROL-SHIFT-mouse-click: as above except that the changes to the
78
 * column do not cancel the statuses of columns that are already sorting - giving a way to initiate
79
 * a compound sort.
80
 * </ul>
156 ilm 81
 * <p/>
82
 * This is a long overdue rewrite of a class of the same name that first appeared in the swing table
83
 * demos in 1997.
17 ilm 84
 *
85
 * @author Philip Milne
86
 * @author Brendon McLean
87
 * @author Dan van Enckevort
88
 * @author Parwinder Sekhon
89
 * @version 2.0 02/27/04
90
 */
91
@SuppressWarnings("unqualified-field-access")
92
public class TableSorter extends AbstractTableModel {
93
    protected TableModel tableModel;
94
 
95
    public static final int DESCENDING = -1;
96
    public static final int NOT_SORTED = 0;
97
    public static final int ASCENDING = 1;
98
 
99
    private static Directive EMPTY_DIRECTIVE = new Directive(-1, NOT_SORTED);
100
 
101
    public static final Comparator COMPARABLE_COMAPRATOR = new Comparator() {
156 ilm 102
        @Override
17 ilm 103
        public int compare(Object o1, Object o2) {
104
            return ((Comparable) o1).compareTo(o2);
105
        }
106
    };
156 ilm 107
    public static final Comparator<Object> LEXICAL_COMPARATOR = new Comparator<Object>() {
108
        @Override
17 ilm 109
        public int compare(Object o1, Object o2) {
110
            return o1.toString().compareTo(o2.toString());
111
        }
112
    };
113
 
114
    private Row[] viewToModel;
115
    private int[] modelToView;
116
 
117
    private JTableHeader tableHeader;
118
    private MouseListener mouseListener;
119
    private TableModelListener tableModelListener;
156 ilm 120
    private Map<Class<?>, Comparator<?>> columnComparators = new HashMap<>();
121
    private final List<Directive> sortingColumns = new ArrayList<>();
17 ilm 122
 
80 ilm 123
    private boolean enabled;
17 ilm 124
    private boolean sorting;
125
    private final PropertyChangeSupport supp;
126
 
127
    public TableSorter() {
128
        this.mouseListener = new MouseHandler();
129
        this.tableModelListener = new TableModelHandler();
80 ilm 130
        this.enabled = true;
17 ilm 131
        this.sorting = false;
132
        this.supp = new PropertyChangeSupport(this);
133
    }
134
 
135
    public TableSorter(TableModel tableModel) {
136
        this();
137
        setTableModel(tableModel);
138
    }
139
 
140
    public TableSorter(TableModel tableModel, JTableHeader tableHeader) {
141
        this();
142
        setTableHeader(tableHeader);
143
        setTableModel(tableModel);
144
    }
145
 
146
    private void clearSortingState() {
147
        viewToModel = null;
148
        modelToView = null;
149
    }
150
 
151
    public TableModel getTableModel() {
152
        return tableModel;
153
    }
154
 
155
    public void setTableModel(TableModel tableModel) {
156 ilm 156
        if (!SwingUtilities.isEventDispatchThread()) {
157
            throw new IllegalStateException("must be called from EDT");
158
        }
17 ilm 159
        if (this.tableModel != null) {
160
            this.tableModel.removeTableModelListener(tableModelListener);
161
        }
162
 
163
        final TableModel old = this.tableModel;
164
        this.tableModel = tableModel;
165
        this.supp.firePropertyChange("tableModel", old, tableModel);
166
        if (this.tableModel != null) {
167
            this.tableModel.addTableModelListener(tableModelListener);
168
        }
169
 
170
        clearSortingState();
171
        if (this.tableModel != null)
172
            fireTableStructureChanged();
173
    }
174
 
80 ilm 175
    public final void setSortingEnabled(final boolean b) {
176
        this.setSortingEnabled(b, true);
177
    }
178
 
179
    // this prevent the user from changing the sort, but setSortingStatus() still works (like
180
    // JTextComponent.setEnabled()/setText())
181
    public final void setSortingEnabled(final boolean b, final boolean cancelSort) {
182
        if (this.enabled != b) {
183
            this.enabled = b;
184
            if (this.enabled) {
185
                this.tableHeader.addMouseListener(mouseListener);
186
            } else {
187
                this.tableHeader.removeMouseListener(mouseListener);
188
            }
189
            if (cancelSort && this.isSorting())
190
                this.cancelSorting(true);
191
        }
192
    }
193
 
194
    public final boolean isSortingEnabled() {
195
        return this.enabled;
196
    }
197
 
17 ilm 198
    public JTableHeader getTableHeader() {
199
        return tableHeader;
200
    }
201
 
202
    public void setTableHeader(JTableHeader tableHeader) {
156 ilm 203
        if (!SwingUtilities.isEventDispatchThread()) {
204
            throw new IllegalStateException("must be called from EDT");
205
        }
17 ilm 206
        if (this.tableHeader != null) {
207
            this.tableHeader.removeMouseListener(mouseListener);
208
            TableCellRenderer defaultRenderer = this.tableHeader.getDefaultRenderer();
209
            if (defaultRenderer instanceof SortableHeaderRenderer) {
210
                this.tableHeader.setDefaultRenderer(((SortableHeaderRenderer) defaultRenderer).tableCellRenderer);
211
            }
212
        }
213
        this.tableHeader = tableHeader;
214
        if (this.tableHeader != null) {
80 ilm 215
            if (this.isSortingEnabled())
216
                this.tableHeader.addMouseListener(mouseListener);
17 ilm 217
            this.tableHeader.setDefaultRenderer(new SortableHeaderRenderer(this.tableHeader.getDefaultRenderer()));
218
        }
219
    }
220
 
221
    public boolean isSorting() {
222
        return sortingColumns.size() != 0;
223
    }
224
 
156 ilm 225
    public final List<Directive> getSortingColumns() {
226
        return Collections.unmodifiableList(this.sortingColumns);
227
    }
228
 
229
    public void setSortingColumns(List<Directive> sortingColumns) {
230
        if (!SwingUtilities.isEventDispatchThread()) {
231
            throw new IllegalStateException("must be called from EDT");
232
        }
233
        this.sortingColumns.clear();
234
        this.sortingColumns.addAll(sortingColumns);
235
        sortingStatusChanged();
236
    }
237
 
17 ilm 238
    private Directive getDirective(int column) {
239
        for (int i = 0; i < sortingColumns.size(); i++) {
156 ilm 240
            Directive directive = sortingColumns.get(i);
17 ilm 241
            if (directive.column == column) {
242
                return directive;
243
            }
244
        }
245
        return EMPTY_DIRECTIVE;
246
    }
247
 
248
    public int getSortingStatus(int column) {
249
        return getDirective(column).direction;
250
    }
251
 
252
    public synchronized boolean isBeingSorted() {
253
        return this.sorting;
254
    }
255
 
256
    private void setSorting(boolean sorting) {
257
        final boolean old = this.sorting;
258
        if (old != sorting) {
259
            this.sorting = sorting;
260
            this.supp.firePropertyChange("sorting", old, this.sorting);
261
        }
262
    }
263
 
264
    private synchronized void sortingStatusChanged() {
156 ilm 265
        if (!SwingUtilities.isEventDispatchThread()) {
266
            throw new IllegalStateException("must be called from EDT");
267
        }
17 ilm 268
        this.sortingStatusChanged(true);
269
    }
270
 
271
    private synchronized void sortingStatusChanged(final boolean fire) {
272
        clearSortingState();
273
        if (fire) {
274
            fireTableDataChanged();
275
        }
276
        if (tableHeader != null) {
277
            tableHeader.repaint();
278
        }
279
    }
280
 
281
    public void setSortingStatus(int column, int status) {
282
        Directive directive = getDirective(column);
283
        if (directive != EMPTY_DIRECTIVE) {
284
            sortingColumns.remove(directive);
285
        }
286
        if (status != NOT_SORTED) {
287
            sortingColumns.add(new Directive(column, status));
288
        }
289
        sortingStatusChanged();
290
    }
291
 
292
    protected Icon getHeaderRendererIcon(int column, int size) {
293
        Directive directive = getDirective(column);
294
        if (directive == EMPTY_DIRECTIVE) {
295
            return null;
296
        }
297
        return new Arrow(directive.direction == DESCENDING, size, sortingColumns.indexOf(directive));
298
    }
299
 
300
    private void cancelSorting(final boolean fire) {
301
        sortingColumns.clear();
302
        sortingStatusChanged(fire);
303
    }
304
 
156 ilm 305
    public void setColumnComparator(Class<?> type, Comparator<?> comparator) {
17 ilm 306
        if (comparator == null) {
307
            columnComparators.remove(type);
308
        } else {
309
            columnComparators.put(type, comparator);
310
        }
311
    }
312
 
313
    protected Comparator getComparator(int column) {
156 ilm 314
        Class<?> columnType = tableModel.getColumnClass(column);
315
        Comparator<?> comparator = columnComparators.get(columnType);
17 ilm 316
        if (comparator != null) {
317
            return comparator;
318
        }
319
        if (Comparable.class.isAssignableFrom(columnType)) {
320
            return COMPARABLE_COMAPRATOR;
321
        }
322
        return LEXICAL_COMPARATOR;
323
    }
324
 
325
    private Row[] getViewToModel() {
156 ilm 326
        if (!SwingUtilities.isEventDispatchThread()) {
327
            throw new IllegalStateException("must be called from EDT");
328
        }
17 ilm 329
        if (viewToModel == null) {
330
            int tableModelRowCount = tableModel.getRowCount();
331
            viewToModel = new Row[tableModelRowCount];
332
            for (int row = 0; row < tableModelRowCount; row++) {
333
                viewToModel[row] = new Row(row);
334
            }
335
 
336
            if (isSorting()) {
337
                Arrays.sort(viewToModel);
338
            }
339
        }
340
        return viewToModel;
341
    }
342
 
343
    /**
344
     * Returns the model index for the passed view index. Eg if the 5th row in the model is sorted
345
     * at the first position, modelIndex(1) will return 5.
346
     *
347
     * @param viewIndex the index in the view (eg a JTable).
348
     * @return the corresponding index in the model.
349
     */
350
    public int modelIndex(int viewIndex) {
351
        return getViewToModel()[viewIndex].modelIndex;
352
    }
353
 
354
    private int[] getModelToView() {
156 ilm 355
        if (!SwingUtilities.isEventDispatchThread()) {
356
            throw new IllegalStateException("must be called from EDT");
357
        }
17 ilm 358
        if (modelToView == null) {
359
            int n = getViewToModel().length;
360
            modelToView = new int[n];
361
            for (int i = 0; i < n; i++) {
362
                modelToView[modelIndex(i)] = i;
363
            }
364
        }
365
        return modelToView;
366
    }
367
 
368
    public int viewIndex(int modelIndex) {
369
        return getModelToView()[modelIndex];
370
    }
371
 
372
    // TableModel interface methods
373
 
156 ilm 374
    @Override
17 ilm 375
    public int getRowCount() {
156 ilm 376
        if (!SwingUtilities.isEventDispatchThread()) {
377
            throw new IllegalStateException("must be called from EDT");
378
        }
17 ilm 379
        return (tableModel == null) ? 0 : tableModel.getRowCount();
380
    }
381
 
156 ilm 382
    @Override
17 ilm 383
    public int getColumnCount() {
156 ilm 384
        if (!SwingUtilities.isEventDispatchThread()) {
385
            throw new IllegalStateException("must be called from EDT");
386
        }
17 ilm 387
        return (tableModel == null) ? 0 : tableModel.getColumnCount();
388
    }
389
 
156 ilm 390
    @Override
17 ilm 391
    public String getColumnName(int column) {
392
        return tableModel.getColumnName(column);
393
    }
394
 
156 ilm 395
    @Override
396
    public Class<?> getColumnClass(int column) {
17 ilm 397
        return tableModel.getColumnClass(column);
398
    }
399
 
156 ilm 400
    @Override
17 ilm 401
    public boolean isCellEditable(int row, int column) {
402
        return tableModel.isCellEditable(modelIndex(row), column);
403
    }
404
 
156 ilm 405
    @Override
17 ilm 406
    public Object getValueAt(int row, int column) {
156 ilm 407
        if (!SwingUtilities.isEventDispatchThread()) {
408
            throw new IllegalStateException("must be called from EDT");
409
        }
17 ilm 410
        return tableModel.getValueAt(modelIndex(row), column);
411
    }
412
 
156 ilm 413
    @Override
17 ilm 414
    public void setValueAt(Object aValue, int row, int column) {
156 ilm 415
        if (!SwingUtilities.isEventDispatchThread()) {
416
            throw new IllegalStateException("must be called from EDT");
417
        }
17 ilm 418
        tableModel.setValueAt(aValue, modelIndex(row), column);
419
    }
420
 
421
    public void addPropertyChangeListener(PropertyChangeListener l) {
422
        this.supp.addPropertyChangeListener(l);
423
    }
424
 
425
    public void addPropertyChangeListener(String name, PropertyChangeListener l) {
426
        this.supp.addPropertyChangeListener(name, l);
427
    }
428
 
429
    public void rmPropertyChangeListener(PropertyChangeListener l) {
430
        this.supp.removePropertyChangeListener(l);
431
    }
432
 
433
    // Helper classes
434
 
156 ilm 435
    private class Row implements Comparable<Row> {
17 ilm 436
        private int modelIndex;
437
 
438
        public Row(int index) {
439
            this.modelIndex = index;
440
        }
441
 
156 ilm 442
        @Override
443
        public int compareTo(Row o) {
17 ilm 444
            int row1 = modelIndex;
156 ilm 445
            int row2 = o.modelIndex;
17 ilm 446
 
156 ilm 447
            for (Iterator<Directive> it = sortingColumns.iterator(); it.hasNext();) {
448
                Directive directive = it.next();
17 ilm 449
                int column = directive.column;
450
                Object o1 = tableModel.getValueAt(row1, column);
451
                Object o2 = tableModel.getValueAt(row2, column);
452
 
453
                int comparison = 0;
454
                // Define null less than everything, except null.
455
                if (o1 == null && o2 == null) {
456
                    comparison = 0;
457
                } else if (o1 == null) {
458
                    comparison = -1;
459
                } else if (o2 == null) {
460
                    comparison = 1;
461
                } else {
462
                    comparison = getComparator(column).compare(o1, o2);
463
                }
464
                if (comparison != 0) {
465
                    return directive.direction == DESCENDING ? -comparison : comparison;
466
                }
467
            }
468
            return 0;
469
        }
470
    }
471
 
472
    private class TableModelHandler implements TableModelListener {
156 ilm 473
        @Override
17 ilm 474
        public void tableChanged(TableModelEvent e) {
475
            // If we're not sorting by anything, just pass the event along.
476
            if (!isSorting()) {
477
                clearSortingState();
478
                fireTableChanged(e);
479
                return;
480
            }
481
 
482
            // If the table structure has changed, cancel the sorting; the
483
            // sorting columns may have been either moved or deleted from
484
            // the model.
485
            if (e.getFirstRow() == TableModelEvent.HEADER_ROW) {
486
                // don't fire a tableChanged otherwise listeners could access removed columns
487
                // (since we're firing a tableChanged before the structureChanged)
488
                cancelSorting(false);
489
                fireTableChanged(e);
490
                return;
491
            }
492
 
493
            // We can map a cell event through to the view without widening
494
            // when the following conditions apply:
80 ilm 495
            //
17 ilm 496
            // a) all the changes are on one row (e.getFirstRow() == e.getLastRow()) and,
497
            // b) all the changes are in one column (column != TableModelEvent.ALL_COLUMNS) and,
498
            // c) we are not sorting on that column (getSortingStatus(column) == NOT_SORTED) and,
499
            // d) a reverse lookup will not trigger a sort (modelToView != null)
500
            //
501
            // Note: INSERT and DELETE events fail this test as they have column == ALL_COLUMNS.
80 ilm 502
            //
17 ilm 503
            // The last check, for (modelToView != null) is to see if modelToView
504
            // is already allocated. If we don't do this check; sorting can become
505
            // a performance bottleneck for applications where cells
506
            // change rapidly in different parts of the table. If cells
507
            // change alternately in the sorting column and then outside of
508
            // it this class can end up re-sorting on alternate cell updates -
509
            // which can be a performance problem for large tables. The last
510
            // clause avoids this problem.
511
            int column = e.getColumn();
512
            if (e.getFirstRow() == e.getLastRow() && column != TableModelEvent.ALL_COLUMNS && modelToView != null && doesntChangeSort(e)) {
513
                int viewIndex = getModelToView()[e.getFirstRow()];
514
                fireTableChanged(new TableModelEvent(TableSorter.this, viewIndex, viewIndex, column, e.getType()));
515
                return;
516
            }
517
 
518
            // Something has happened to the data that may have invalidated the row order.
519
            clearSortingState();
520
            fireTableDataChanged();
521
            return;
522
        }
523
    }
524
 
525
    // whether e change the order of the rows
526
    private boolean doesntChangeSort(TableModelEvent e) {
527
        int column = e.getColumn();
528
        if (getSortingStatus(column) == NOT_SORTED)
529
            return true;
530
 
531
        final int viewIndex = getModelToView()[e.getFirstRow()];
532
        // the changed row plus the previous and next ones.
533
        final List<Row> currentL = new ArrayList<Row>();
534
        if (viewIndex > 0)
535
            currentL.add(getViewToModel()[viewIndex - 1]);
536
        currentL.add(getViewToModel()[viewIndex]);
537
        if (viewIndex < getViewToModel().length - 1)
538
            currentL.add(getViewToModel()[viewIndex + 1]);
539
 
540
        // the new order
541
        final List<Row> newL = new ArrayList<Row>(currentL);
542
        Collections.sort(newL);
543
 
544
        return currentL.equals(newL);
545
    }
546
 
547
    private class MouseHandler extends MouseAdapter {
156 ilm 548
        @Override
17 ilm 549
        public void mouseClicked(MouseEvent e) {
550
            JTableHeader h = (JTableHeader) e.getSource();
551
            TableColumnModel columnModel = h.getColumnModel();
552
            int viewColumn = columnModel.getColumnIndexAtX(e.getX());
553
            // don't continue if we can't find the column
554
            if (viewColumn == -1)
555
                return;
556
            int column = columnModel.getColumn(viewColumn).getModelIndex();
557
            if (column != -1) {
558
                setSorting(true);
559
                int status = getSortingStatus(column);
560
                if (!e.isControlDown()) {
561
                    // don't need to fire since setSortingStatus() will
562
                    cancelSorting(false);
563
                }
564
                // Cycle the sorting states through {NOT_SORTED, ASCENDING, DESCENDING} or
565
                // {NOT_SORTED, DESCENDING, ASCENDING} depending on whether shift is pressed.
566
                status = status + (e.isShiftDown() ? -1 : 1);
567
                status = (status + 4) % 3 - 1; // signed mod, returning {-1, 0, 1}
568
                setSortingStatus(column, status);
569
                setSorting(false);
570
            }
571
        }
572
    }
573
 
574
    private static class Arrow implements Icon {
575
        private boolean descending;
576
        private int size;
577
        private int priority;
578
 
579
        public Arrow(boolean descending, int size, int priority) {
580
            this.descending = descending;
581
            this.size = size;
582
            this.priority = priority;
583
        }
584
 
156 ilm 585
        @Override
17 ilm 586
        public void paintIcon(Component c, Graphics g, int x, int y) {
587
            Color color = c == null ? Color.GRAY : c.getBackground();
588
            // In a compound sort, make each succesive triangle 20%
589
            // smaller than the previous one.
590
            int dx = (int) (size / 2 * Math.pow(0.8, priority));
591
            int dy = descending ? dx : -dx;
592
            // Align icon (roughly) with font baseline.
593
            y = y + 5 * size / 6 + (descending ? -dy : 0);
594
            int shift = descending ? 1 : -1;
595
            g.translate(x, y);
596
 
597
            // Right diagonal.
598
            g.setColor(color.darker());
599
            g.drawLine(dx / 2, dy, 0, 0);
600
            g.drawLine(dx / 2, dy + shift, 0, shift);
601
 
602
            // Left diagonal.
603
            g.setColor(color.brighter());
604
            g.drawLine(dx / 2, dy, dx, 0);
605
            g.drawLine(dx / 2, dy + shift, dx, shift);
606
 
607
            // Horizontal line.
608
            if (descending) {
609
                g.setColor(color.darker().darker());
610
            } else {
611
                g.setColor(color.brighter().brighter());
612
            }
613
            g.drawLine(dx, 0, 0, 0);
614
 
615
            g.setColor(color);
616
            g.translate(-x, -y);
617
        }
618
 
156 ilm 619
        @Override
17 ilm 620
        public int getIconWidth() {
621
            return size;
622
        }
623
 
156 ilm 624
        @Override
17 ilm 625
        public int getIconHeight() {
626
            return size;
627
        }
628
    }
629
 
630
    private class SortableHeaderRenderer implements TableCellRenderer {
631
        private TableCellRenderer tableCellRenderer;
632
 
633
        public SortableHeaderRenderer(TableCellRenderer tableCellRenderer) {
634
            this.tableCellRenderer = tableCellRenderer;
635
        }
636
 
156 ilm 637
        @Override
17 ilm 638
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
639
            Component c = tableCellRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
640
            if (c instanceof JLabel) {
641
                JLabel l = (JLabel) c;
156 ilm 642
                l.setHorizontalTextPosition(SwingConstants.LEFT);
17 ilm 643
                int modelColumn = table.convertColumnIndexToModel(column);
644
                l.setIcon(getHeaderRendererIcon(modelColumn, l.getFont().getSize()));
645
            }
646
            return c;
647
        }
648
    }
649
 
156 ilm 650
    @Immutable
651
    public static class Directive {
652
        private final int column;
653
        private final int direction;
17 ilm 654
 
655
        public Directive(int column, int direction) {
656
            this.column = column;
657
            this.direction = direction;
658
        }
156 ilm 659
 
660
        public final int getColumn() {
661
            return this.column;
662
        }
663
 
664
        public final int getDirection() {
665
            return this.direction;
666
        }
667
 
668
        @Override
669
        public String toString() {
670
            return this.getClass().getSimpleName() + " " + this.getDirection() + " on column " + this.getColumn();
671
        }
17 ilm 672
    }
673
}