OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 174 | 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
 *
182 ilm 4
 * Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
17 ilm 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) {
174 ilm 351
        try {
352
            return getViewToModel()[viewIndex].modelIndex;
353
        } catch (Exception e) {
354
            throw new IllegalStateException(this + " couldn't get model index for view index " + viewIndex + " with " + this.getTableModel().getRowCount() + " row(s) in our model", e);
355
        }
17 ilm 356
    }
357
 
358
    private int[] getModelToView() {
156 ilm 359
        if (!SwingUtilities.isEventDispatchThread()) {
360
            throw new IllegalStateException("must be called from EDT");
361
        }
17 ilm 362
        if (modelToView == null) {
363
            int n = getViewToModel().length;
364
            modelToView = new int[n];
365
            for (int i = 0; i < n; i++) {
366
                modelToView[modelIndex(i)] = i;
367
            }
368
        }
369
        return modelToView;
370
    }
371
 
372
    public int viewIndex(int modelIndex) {
373
        return getModelToView()[modelIndex];
374
    }
375
 
376
    // TableModel interface methods
377
 
156 ilm 378
    @Override
17 ilm 379
    public int getRowCount() {
156 ilm 380
        if (!SwingUtilities.isEventDispatchThread()) {
381
            throw new IllegalStateException("must be called from EDT");
382
        }
17 ilm 383
        return (tableModel == null) ? 0 : tableModel.getRowCount();
384
    }
385
 
156 ilm 386
    @Override
17 ilm 387
    public int getColumnCount() {
156 ilm 388
        if (!SwingUtilities.isEventDispatchThread()) {
389
            throw new IllegalStateException("must be called from EDT");
390
        }
17 ilm 391
        return (tableModel == null) ? 0 : tableModel.getColumnCount();
392
    }
393
 
156 ilm 394
    @Override
17 ilm 395
    public String getColumnName(int column) {
396
        return tableModel.getColumnName(column);
397
    }
398
 
156 ilm 399
    @Override
400
    public Class<?> getColumnClass(int column) {
17 ilm 401
        return tableModel.getColumnClass(column);
402
    }
403
 
156 ilm 404
    @Override
17 ilm 405
    public boolean isCellEditable(int row, int column) {
406
        return tableModel.isCellEditable(modelIndex(row), column);
407
    }
408
 
156 ilm 409
    @Override
17 ilm 410
    public Object getValueAt(int row, int column) {
156 ilm 411
        if (!SwingUtilities.isEventDispatchThread()) {
412
            throw new IllegalStateException("must be called from EDT");
413
        }
17 ilm 414
        return tableModel.getValueAt(modelIndex(row), column);
415
    }
416
 
156 ilm 417
    @Override
17 ilm 418
    public void setValueAt(Object aValue, int row, int column) {
156 ilm 419
        if (!SwingUtilities.isEventDispatchThread()) {
420
            throw new IllegalStateException("must be called from EDT");
421
        }
17 ilm 422
        tableModel.setValueAt(aValue, modelIndex(row), column);
423
    }
424
 
425
    public void addPropertyChangeListener(PropertyChangeListener l) {
426
        this.supp.addPropertyChangeListener(l);
427
    }
428
 
429
    public void addPropertyChangeListener(String name, PropertyChangeListener l) {
430
        this.supp.addPropertyChangeListener(name, l);
431
    }
432
 
433
    public void rmPropertyChangeListener(PropertyChangeListener l) {
434
        this.supp.removePropertyChangeListener(l);
435
    }
436
 
174 ilm 437
    @Override
438
    public String toString() {
439
        return this.getClass().getSimpleName() + " on " + this.getTableModel();
440
    }
441
 
17 ilm 442
    // Helper classes
443
 
156 ilm 444
    private class Row implements Comparable<Row> {
17 ilm 445
        private int modelIndex;
446
 
447
        public Row(int index) {
448
            this.modelIndex = index;
449
        }
450
 
156 ilm 451
        @Override
452
        public int compareTo(Row o) {
17 ilm 453
            int row1 = modelIndex;
156 ilm 454
            int row2 = o.modelIndex;
17 ilm 455
 
156 ilm 456
            for (Iterator<Directive> it = sortingColumns.iterator(); it.hasNext();) {
457
                Directive directive = it.next();
17 ilm 458
                int column = directive.column;
459
                Object o1 = tableModel.getValueAt(row1, column);
460
                Object o2 = tableModel.getValueAt(row2, column);
461
 
462
                int comparison = 0;
463
                // Define null less than everything, except null.
464
                if (o1 == null && o2 == null) {
465
                    comparison = 0;
466
                } else if (o1 == null) {
467
                    comparison = -1;
468
                } else if (o2 == null) {
469
                    comparison = 1;
470
                } else {
182 ilm 471
                    final Comparator comparator = getComparator(column);
472
                    try {
473
                        comparison = comparator.compare(o1, o2);
474
                    } catch (Exception e) {
475
                        throw new IllegalStateException("Couldn't compare column " + column + " using " + comparator + " for objects " + o1 + " and " + o2, e);
476
                    }
17 ilm 477
                }
478
                if (comparison != 0) {
479
                    return directive.direction == DESCENDING ? -comparison : comparison;
480
                }
481
            }
482
            return 0;
483
        }
484
    }
485
 
486
    private class TableModelHandler implements TableModelListener {
156 ilm 487
        @Override
17 ilm 488
        public void tableChanged(TableModelEvent e) {
489
            // If we're not sorting by anything, just pass the event along.
490
            if (!isSorting()) {
491
                clearSortingState();
492
                fireTableChanged(e);
493
                return;
494
            }
495
 
496
            // If the table structure has changed, cancel the sorting; the
497
            // sorting columns may have been either moved or deleted from
498
            // the model.
499
            if (e.getFirstRow() == TableModelEvent.HEADER_ROW) {
500
                // don't fire a tableChanged otherwise listeners could access removed columns
501
                // (since we're firing a tableChanged before the structureChanged)
502
                cancelSorting(false);
503
                fireTableChanged(e);
504
                return;
505
            }
506
 
507
            // We can map a cell event through to the view without widening
508
            // when the following conditions apply:
80 ilm 509
            //
17 ilm 510
            // a) all the changes are on one row (e.getFirstRow() == e.getLastRow()) and,
511
            // b) all the changes are in one column (column != TableModelEvent.ALL_COLUMNS) and,
512
            // c) we are not sorting on that column (getSortingStatus(column) == NOT_SORTED) and,
513
            // d) a reverse lookup will not trigger a sort (modelToView != null)
514
            //
515
            // Note: INSERT and DELETE events fail this test as they have column == ALL_COLUMNS.
80 ilm 516
            //
17 ilm 517
            // The last check, for (modelToView != null) is to see if modelToView
518
            // is already allocated. If we don't do this check; sorting can become
519
            // a performance bottleneck for applications where cells
520
            // change rapidly in different parts of the table. If cells
521
            // change alternately in the sorting column and then outside of
522
            // it this class can end up re-sorting on alternate cell updates -
523
            // which can be a performance problem for large tables. The last
524
            // clause avoids this problem.
525
            int column = e.getColumn();
526
            if (e.getFirstRow() == e.getLastRow() && column != TableModelEvent.ALL_COLUMNS && modelToView != null && doesntChangeSort(e)) {
527
                int viewIndex = getModelToView()[e.getFirstRow()];
528
                fireTableChanged(new TableModelEvent(TableSorter.this, viewIndex, viewIndex, column, e.getType()));
529
                return;
530
            }
531
 
532
            // Something has happened to the data that may have invalidated the row order.
533
            clearSortingState();
534
            fireTableDataChanged();
535
            return;
536
        }
537
    }
538
 
539
    // whether e change the order of the rows
540
    private boolean doesntChangeSort(TableModelEvent e) {
541
        int column = e.getColumn();
542
        if (getSortingStatus(column) == NOT_SORTED)
543
            return true;
544
 
545
        final int viewIndex = getModelToView()[e.getFirstRow()];
546
        // the changed row plus the previous and next ones.
547
        final List<Row> currentL = new ArrayList<Row>();
548
        if (viewIndex > 0)
549
            currentL.add(getViewToModel()[viewIndex - 1]);
550
        currentL.add(getViewToModel()[viewIndex]);
551
        if (viewIndex < getViewToModel().length - 1)
552
            currentL.add(getViewToModel()[viewIndex + 1]);
553
 
554
        // the new order
555
        final List<Row> newL = new ArrayList<Row>(currentL);
556
        Collections.sort(newL);
557
 
558
        return currentL.equals(newL);
559
    }
560
 
561
    private class MouseHandler extends MouseAdapter {
156 ilm 562
        @Override
17 ilm 563
        public void mouseClicked(MouseEvent e) {
564
            JTableHeader h = (JTableHeader) e.getSource();
565
            TableColumnModel columnModel = h.getColumnModel();
566
            int viewColumn = columnModel.getColumnIndexAtX(e.getX());
567
            // don't continue if we can't find the column
568
            if (viewColumn == -1)
569
                return;
570
            int column = columnModel.getColumn(viewColumn).getModelIndex();
571
            if (column != -1) {
572
                setSorting(true);
573
                int status = getSortingStatus(column);
574
                if (!e.isControlDown()) {
575
                    // don't need to fire since setSortingStatus() will
576
                    cancelSorting(false);
577
                }
578
                // Cycle the sorting states through {NOT_SORTED, ASCENDING, DESCENDING} or
579
                // {NOT_SORTED, DESCENDING, ASCENDING} depending on whether shift is pressed.
580
                status = status + (e.isShiftDown() ? -1 : 1);
581
                status = (status + 4) % 3 - 1; // signed mod, returning {-1, 0, 1}
582
                setSortingStatus(column, status);
583
                setSorting(false);
584
            }
585
        }
586
    }
587
 
588
    private static class Arrow implements Icon {
589
        private boolean descending;
590
        private int size;
591
        private int priority;
592
 
593
        public Arrow(boolean descending, int size, int priority) {
594
            this.descending = descending;
595
            this.size = size;
596
            this.priority = priority;
597
        }
598
 
156 ilm 599
        @Override
17 ilm 600
        public void paintIcon(Component c, Graphics g, int x, int y) {
601
            Color color = c == null ? Color.GRAY : c.getBackground();
602
            // In a compound sort, make each succesive triangle 20%
603
            // smaller than the previous one.
604
            int dx = (int) (size / 2 * Math.pow(0.8, priority));
605
            int dy = descending ? dx : -dx;
606
            // Align icon (roughly) with font baseline.
607
            y = y + 5 * size / 6 + (descending ? -dy : 0);
608
            int shift = descending ? 1 : -1;
609
            g.translate(x, y);
610
 
611
            // Right diagonal.
612
            g.setColor(color.darker());
613
            g.drawLine(dx / 2, dy, 0, 0);
614
            g.drawLine(dx / 2, dy + shift, 0, shift);
615
 
616
            // Left diagonal.
617
            g.setColor(color.brighter());
618
            g.drawLine(dx / 2, dy, dx, 0);
619
            g.drawLine(dx / 2, dy + shift, dx, shift);
620
 
621
            // Horizontal line.
622
            if (descending) {
623
                g.setColor(color.darker().darker());
624
            } else {
625
                g.setColor(color.brighter().brighter());
626
            }
627
            g.drawLine(dx, 0, 0, 0);
628
 
629
            g.setColor(color);
630
            g.translate(-x, -y);
631
        }
632
 
156 ilm 633
        @Override
17 ilm 634
        public int getIconWidth() {
635
            return size;
636
        }
637
 
156 ilm 638
        @Override
17 ilm 639
        public int getIconHeight() {
640
            return size;
641
        }
642
    }
643
 
644
    private class SortableHeaderRenderer implements TableCellRenderer {
645
        private TableCellRenderer tableCellRenderer;
646
 
647
        public SortableHeaderRenderer(TableCellRenderer tableCellRenderer) {
648
            this.tableCellRenderer = tableCellRenderer;
649
        }
650
 
156 ilm 651
        @Override
17 ilm 652
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
182 ilm 653
            if (column < 0) {
654
                Log.get().severe("out of bound renderer, column :" + column);
655
                return new JLabel("");
656
            }
657
 
17 ilm 658
            Component c = tableCellRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
659
            if (c instanceof JLabel) {
660
                JLabel l = (JLabel) c;
156 ilm 661
                l.setHorizontalTextPosition(SwingConstants.LEFT);
17 ilm 662
                int modelColumn = table.convertColumnIndexToModel(column);
663
                l.setIcon(getHeaderRendererIcon(modelColumn, l.getFont().getSize()));
664
            }
665
            return c;
666
        }
667
    }
668
 
156 ilm 669
    @Immutable
670
    public static class Directive {
671
        private final int column;
672
        private final int direction;
17 ilm 673
 
674
        public Directive(int column, int direction) {
675
            this.column = column;
676
            this.direction = direction;
677
        }
156 ilm 678
 
679
        public final int getColumn() {
680
            return this.column;
681
        }
682
 
683
        public final int getDirection() {
684
            return this.direction;
685
        }
686
 
687
        @Override
688
        public String toString() {
689
            return this.getClass().getSimpleName() + " " + this.getDirection() + " on column " + this.getColumn();
690
        }
17 ilm 691
    }
692
}