1 package org
.tianocore
.frameworkwizard
.platform
.ui
;
4 import java
.awt
.event
.*;
9 import javax
.swing
.event
.TableModelEvent
;
10 import javax
.swing
.event
.TableModelListener
;
11 import javax
.swing
.table
.*;
14 public class TableSorter
extends AbstractTableModel
{
18 private static final long serialVersionUID
= 1L;
20 protected TableModel tableModel
;
22 public static final int DESCENDING
= -1;
23 public static final int NOT_SORTED
= 0;
24 public static final int ASCENDING
= 1;
26 private static Directive EMPTY_DIRECTIVE
= new Directive(-1, NOT_SORTED
);
29 private Row
[] viewToModel
;
30 private int[] modelToView
;
32 private JTableHeader tableHeader
;
33 private MouseListener mouseListener
;
34 private TableModelListener tableModelListener
;
36 private List
<Directive
> sortingColumns
= new ArrayList
<Directive
>();
38 public TableSorter() {
39 this.mouseListener
= new MouseHandler();
40 this.tableModelListener
= new TableModelHandler();
43 public TableSorter(TableModel tableModel
) {
45 setTableModel(tableModel
);
49 private void clearSortingState() {
54 public TableModel
getTableModel() {
58 public void setTableModel(TableModel tableModel
) {
59 if (this.tableModel
!= null) {
60 this.tableModel
.removeTableModelListener(tableModelListener
);
63 this.tableModel
= tableModel
;
64 if (this.tableModel
!= null) {
65 this.tableModel
.addTableModelListener(tableModelListener
);
69 fireTableStructureChanged();
72 public JTableHeader
getTableHeader() {
76 public void setTableHeader(JTableHeader tableHeader
) {
77 if (this.tableHeader
!= null) {
78 this.tableHeader
.removeMouseListener(mouseListener
);
79 TableCellRenderer defaultRenderer
= this.tableHeader
.getDefaultRenderer();
80 if (defaultRenderer
instanceof SortableHeaderRenderer
) {
81 this.tableHeader
.setDefaultRenderer(((SortableHeaderRenderer
) defaultRenderer
).tableCellRenderer
);
84 this.tableHeader
= tableHeader
;
85 if (this.tableHeader
!= null) {
86 this.tableHeader
.addMouseListener(mouseListener
);
87 this.tableHeader
.setDefaultRenderer(
88 new SortableHeaderRenderer(this.tableHeader
.getDefaultRenderer()));
92 public boolean isSorting() {
93 return sortingColumns
.size() != 0;
96 private Directive
getDirective(int column
) {
97 for (int i
= 0; i
< sortingColumns
.size(); i
++) {
98 Directive directive
= (Directive
)sortingColumns
.get(i
);
99 if (directive
.column
== column
) {
103 return EMPTY_DIRECTIVE
;
106 public int getSortingStatus(int column
) {
107 return getDirective(column
).direction
;
110 private void sortingStatusChanged() {
112 fireTableDataChanged();
113 if (tableHeader
!= null) {
114 tableHeader
.repaint();
118 public void setSortingStatus(int column
, int status
) {
119 Directive directive
= getDirective(column
);
120 if (directive
!= EMPTY_DIRECTIVE
) {
121 sortingColumns
.remove(directive
);
123 if (status
!= NOT_SORTED
) {
124 sortingColumns
.add(new Directive(column
, status
));
126 sortingStatusChanged();
129 protected Icon
getHeaderRendererIcon(int column
, int size
) {
130 Directive directive
= getDirective(column
);
131 if (directive
== EMPTY_DIRECTIVE
) {
134 return new Arrow(directive
.direction
== DESCENDING
, size
, sortingColumns
.indexOf(directive
));
137 private void cancelSorting() {
138 sortingColumns
.clear();
139 sortingStatusChanged();
142 private Row
[] getViewToModel() {
143 if (viewToModel
== null) {
144 int tableModelRowCount
= tableModel
.getRowCount();
145 viewToModel
= new Row
[tableModelRowCount
];
146 for (int row
= 0; row
< tableModelRowCount
; row
++) {
147 viewToModel
[row
] = new Row(row
);
151 Arrays
.sort(viewToModel
);
157 public int modelIndex(int viewIndex
) {
158 return getViewToModel()[viewIndex
].modelIndex
;
161 public int[] getModelToView() {
162 if (modelToView
== null) {
163 int n
= getViewToModel().length
;
164 modelToView
= new int[n
];
165 for (int i
= 0; i
< n
; i
++) {
166 modelToView
[modelIndex(i
)] = i
;
172 // TableModel interface methods
174 public int getRowCount() {
175 return (tableModel
== null) ?
0 : tableModel
.getRowCount();
178 public int getColumnCount() {
179 return (tableModel
== null) ?
0 : tableModel
.getColumnCount();
182 public String
getColumnName(int column
) {
183 return tableModel
.getColumnName(column
);
186 public Class
<?
> getColumnClass(int column
) {
187 return tableModel
.getColumnClass(column
);
190 public boolean isCellEditable(int row
, int column
) {
191 return tableModel
.isCellEditable(modelIndex(row
), column
);
194 public Object
getValueAt(int row
, int column
) {
195 return tableModel
.getValueAt(modelIndex(row
), column
);
198 public void setValueAt(Object aValue
, int row
, int column
) {
199 tableModel
.setValueAt(aValue
, modelIndex(row
), column
);
204 private class Row
implements Comparable
{
205 private int modelIndex
;
207 public Row(int index
) {
208 this.modelIndex
= index
;
211 public int compareTo(Object o
) {
212 int row1
= modelIndex
;
213 int row2
= ((Row
) o
).modelIndex
;
215 for (Iterator it
= sortingColumns
.iterator(); it
.hasNext();) {
216 Directive directive
= (Directive
) it
.next();
217 int column
= directive
.column
;
218 Object o1
= tableModel
.getValueAt(row1
, column
);
219 Object o2
= tableModel
.getValueAt(row2
, column
);
222 // Define null less than everything, except null.
223 if (o1
== null && o2
== null) {
225 } else if (o1
== null) {
227 } else if (o2
== null) {
230 comparison
= o1
.toString().compareTo(o2
.toString());;
232 if (comparison
!= 0) {
233 return directive
.direction
== DESCENDING ?
-comparison
: comparison
;
240 private class TableModelHandler
implements TableModelListener
{
241 public void tableChanged(TableModelEvent e
) {
242 // If we're not sorting by anything, just pass the event along.
249 // If the table structure has changed, cancel the sorting; the
250 // sorting columns may have been either moved or deleted from
252 if (e
.getFirstRow() == TableModelEvent
.HEADER_ROW
) {
258 // We can map a cell event through to the view without widening
259 // when the following conditions apply:
261 // a) all the changes are on one row (e.getFirstRow() == e.getLastRow()) and,
262 // b) all the changes are in one column (column != TableModelEvent.ALL_COLUMNS) and,
263 // c) we are not sorting on that column (getSortingStatus(column) == NOT_SORTED) and,
264 // d) a reverse lookup will not trigger a sort (modelToView != null)
266 // Note: INSERT and DELETE events fail this test as they have column == ALL_COLUMNS.
268 // The last check, for (modelToView != null) is to see if modelToView
269 // is already allocated. If we don't do this check; sorting can become
270 // a performance bottleneck for applications where cells
271 // change rapidly in different parts of the table. If cells
272 // change alternately in the sorting column and then outside of
273 // it this class can end up re-sorting on alternate cell updates -
274 // which can be a performance problem for large tables. The last
275 // clause avoids this problem.
276 int column
= e
.getColumn();
277 if (e
.getFirstRow() == e
.getLastRow()
278 && column
!= TableModelEvent
.ALL_COLUMNS
279 && getSortingStatus(column
) == NOT_SORTED
280 && modelToView
!= null) {
281 int viewIndex
= getModelToView()[e
.getFirstRow()];
282 fireTableChanged(new TableModelEvent(TableSorter
.this,
283 viewIndex
, viewIndex
,
284 column
, e
.getType()));
288 // Something has happened to the data that may have invalidated the row order.
290 fireTableDataChanged();
295 private class MouseHandler
extends MouseAdapter
{
296 public void mouseClicked(MouseEvent e
) {
297 JTableHeader h
= (JTableHeader
) e
.getSource();
298 TableColumnModel columnModel
= h
.getColumnModel();
299 int viewColumn
= columnModel
.getColumnIndexAtX(e
.getX());
300 int column
= columnModel
.getColumn(viewColumn
).getModelIndex();
302 int status
= getSortingStatus(column
);
303 if (!e
.isControlDown()) {
306 // Cycle the sorting states through {NOT_SORTED, ASCENDING, DESCENDING} or
307 // {NOT_SORTED, DESCENDING, ASCENDING} depending on whether shift is pressed.
308 status
= status
+ (e
.isShiftDown() ?
-1 : 1);
309 status
= (status
+ 4) % 3 - 1; // signed mod, returning {-1, 0, 1}
310 setSortingStatus(column
, status
);
315 private static class Arrow
implements Icon
{
316 private boolean descending
;
318 private int priority
;
320 public Arrow(boolean descending
, int size
, int priority
) {
321 this.descending
= descending
;
323 this.priority
= priority
;
326 public void paintIcon(Component c
, Graphics g
, int x
, int y
) {
327 Color color
= c
== null ? Color
.GRAY
: c
.getBackground();
328 // In a compound sort, make each succesive triangle 20%
329 // smaller than the previous one.
330 int dx
= (int)(size
/2*Math
.pow(0.8, priority
));
331 int dy
= descending ? dx
: -dx
;
332 // Align icon (roughly) with font baseline.
333 y
= y
+ 5*size
/6 + (descending ?
-dy
: 0);
334 int shift
= descending ?
1 : -1;
338 g
.setColor(color
.darker());
339 g
.drawLine(dx
/ 2, dy
, 0, 0);
340 g
.drawLine(dx
/ 2, dy
+ shift
, 0, shift
);
343 g
.setColor(color
.brighter());
344 g
.drawLine(dx
/ 2, dy
, dx
, 0);
345 g
.drawLine(dx
/ 2, dy
+ shift
, dx
, shift
);
349 g
.setColor(color
.darker().darker());
351 g
.setColor(color
.brighter().brighter());
353 g
.drawLine(dx
, 0, 0, 0);
359 public int getIconWidth() {
363 public int getIconHeight() {
368 private class SortableHeaderRenderer
implements TableCellRenderer
{
369 private TableCellRenderer tableCellRenderer
;
371 public SortableHeaderRenderer(TableCellRenderer tableCellRenderer
) {
372 this.tableCellRenderer
= tableCellRenderer
;
375 public Component
getTableCellRendererComponent(JTable table
,
381 Component c
= tableCellRenderer
.getTableCellRendererComponent(table
,
382 value
, isSelected
, hasFocus
, row
, column
);
383 if (c
instanceof JLabel
) {
384 JLabel l
= (JLabel
) c
;
385 l
.setHorizontalTextPosition(JLabel
.LEFT
);
386 int modelColumn
= table
.convertColumnIndexToModel(column
);
387 l
.setIcon(getHeaderRendererIcon(modelColumn
, l
.getFont().getSize()));
393 private static class Directive
{
395 private int direction
;
397 public Directive(int column
, int direction
) {
398 this.column
= column
;
399 this.direction
= direction
;