]> git.proxmox.com Git - mirror_edk2.git/blob - Tools/Source/FrameworkWizard/src/org/tianocore/frameworkwizard/platform/ui/TableSorter.java
2453f12a1cf91f7dc45acfb440a0fc6f64013169
[mirror_edk2.git] / Tools / Source / FrameworkWizard / src / org / tianocore / frameworkwizard / platform / ui / TableSorter.java
1 package org.tianocore.frameworkwizard.platform.ui;
2
3 import java.awt.*;
4 import java.awt.event.*;
5 import java.util.*;
6 import java.util.List;
7
8 import javax.swing.*;
9 import javax.swing.event.TableModelEvent;
10 import javax.swing.event.TableModelListener;
11 import javax.swing.table.*;
12
13
14 public class TableSorter extends AbstractTableModel {
15 /**
16 *
17 */
18 private static final long serialVersionUID = 1L;
19
20 protected TableModel tableModel;
21
22 public static final int DESCENDING = -1;
23 public static final int NOT_SORTED = 0;
24 public static final int ASCENDING = 1;
25
26 private static Directive EMPTY_DIRECTIVE = new Directive(-1, NOT_SORTED);
27
28
29 private Row[] viewToModel;
30 private int[] modelToView;
31
32 private JTableHeader tableHeader;
33 private MouseListener mouseListener;
34 private TableModelListener tableModelListener;
35
36 private List<Directive> sortingColumns = new ArrayList<Directive>();
37
38 public TableSorter() {
39 this.mouseListener = new MouseHandler();
40 this.tableModelListener = new TableModelHandler();
41 }
42
43 public TableSorter(TableModel tableModel) {
44 this();
45 setTableModel(tableModel);
46 }
47
48
49 private void clearSortingState() {
50 viewToModel = null;
51 modelToView = null;
52 }
53
54 public TableModel getTableModel() {
55 return tableModel;
56 }
57
58 public void setTableModel(TableModel tableModel) {
59 if (this.tableModel != null) {
60 this.tableModel.removeTableModelListener(tableModelListener);
61 }
62
63 this.tableModel = tableModel;
64 if (this.tableModel != null) {
65 this.tableModel.addTableModelListener(tableModelListener);
66 }
67
68 clearSortingState();
69 fireTableStructureChanged();
70 }
71
72 public JTableHeader getTableHeader() {
73 return tableHeader;
74 }
75
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);
82 }
83 }
84 this.tableHeader = tableHeader;
85 if (this.tableHeader != null) {
86 this.tableHeader.addMouseListener(mouseListener);
87 this.tableHeader.setDefaultRenderer(
88 new SortableHeaderRenderer(this.tableHeader.getDefaultRenderer()));
89 }
90 }
91
92 public boolean isSorting() {
93 return sortingColumns.size() != 0;
94 }
95
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) {
100 return directive;
101 }
102 }
103 return EMPTY_DIRECTIVE;
104 }
105
106 public int getSortingStatus(int column) {
107 return getDirective(column).direction;
108 }
109
110 private void sortingStatusChanged() {
111 clearSortingState();
112 fireTableDataChanged();
113 if (tableHeader != null) {
114 tableHeader.repaint();
115 }
116 }
117
118 public void setSortingStatus(int column, int status) {
119 Directive directive = getDirective(column);
120 if (directive != EMPTY_DIRECTIVE) {
121 sortingColumns.remove(directive);
122 }
123 if (status != NOT_SORTED) {
124 sortingColumns.add(new Directive(column, status));
125 }
126 sortingStatusChanged();
127 }
128
129 protected Icon getHeaderRendererIcon(int column, int size) {
130 Directive directive = getDirective(column);
131 if (directive == EMPTY_DIRECTIVE) {
132 return null;
133 }
134 return new Arrow(directive.direction == DESCENDING, size, sortingColumns.indexOf(directive));
135 }
136
137 private void cancelSorting() {
138 sortingColumns.clear();
139 sortingStatusChanged();
140 }
141
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);
148 }
149
150 if (isSorting()) {
151 Arrays.sort(viewToModel);
152 }
153 }
154 return viewToModel;
155 }
156
157 public int modelIndex(int viewIndex) {
158 return getViewToModel()[viewIndex].modelIndex;
159 }
160
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;
167 }
168 }
169 return modelToView;
170 }
171
172 // TableModel interface methods
173
174 public int getRowCount() {
175 return (tableModel == null) ? 0 : tableModel.getRowCount();
176 }
177
178 public int getColumnCount() {
179 return (tableModel == null) ? 0 : tableModel.getColumnCount();
180 }
181
182 public String getColumnName(int column) {
183 return tableModel.getColumnName(column);
184 }
185
186 public Class<?> getColumnClass(int column) {
187 return tableModel.getColumnClass(column);
188 }
189
190 public boolean isCellEditable(int row, int column) {
191 return tableModel.isCellEditable(modelIndex(row), column);
192 }
193
194 public Object getValueAt(int row, int column) {
195 return tableModel.getValueAt(modelIndex(row), column);
196 }
197
198 public void setValueAt(Object aValue, int row, int column) {
199 tableModel.setValueAt(aValue, modelIndex(row), column);
200 }
201
202 // Helper classes
203
204 private class Row implements Comparable {
205 private int modelIndex;
206
207 public Row(int index) {
208 this.modelIndex = index;
209 }
210
211 public int compareTo(Object o) {
212 int row1 = modelIndex;
213 int row2 = ((Row) o).modelIndex;
214
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);
220
221 int comparison = 0;
222 // Define null less than everything, except null.
223 if (o1 == null && o2 == null) {
224 comparison = 0;
225 } else if (o1 == null) {
226 comparison = -1;
227 } else if (o2 == null) {
228 comparison = 1;
229 } else {
230 comparison = o1.toString().compareTo(o2.toString());;
231 }
232 if (comparison != 0) {
233 return directive.direction == DESCENDING ? -comparison : comparison;
234 }
235 }
236 return 0;
237 }
238 }
239
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.
243 if (!isSorting()) {
244 clearSortingState();
245 fireTableChanged(e);
246 return;
247 }
248
249 // If the table structure has changed, cancel the sorting; the
250 // sorting columns may have been either moved or deleted from
251 // the model.
252 if (e.getFirstRow() == TableModelEvent.HEADER_ROW) {
253 cancelSorting();
254 fireTableChanged(e);
255 return;
256 }
257
258 // We can map a cell event through to the view without widening
259 // when the following conditions apply:
260 //
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)
265 //
266 // Note: INSERT and DELETE events fail this test as they have column == ALL_COLUMNS.
267 //
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()));
285 return;
286 }
287
288 // Something has happened to the data that may have invalidated the row order.
289 clearSortingState();
290 fireTableDataChanged();
291 return;
292 }
293 }
294
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();
301 if (column != -1) {
302 int status = getSortingStatus(column);
303 if (!e.isControlDown()) {
304 cancelSorting();
305 }
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);
311 }
312 }
313 }
314
315 private static class Arrow implements Icon {
316 private boolean descending;
317 private int size;
318 private int priority;
319
320 public Arrow(boolean descending, int size, int priority) {
321 this.descending = descending;
322 this.size = size;
323 this.priority = priority;
324 }
325
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;
335 g.translate(x, y);
336
337 // Right diagonal.
338 g.setColor(color.darker());
339 g.drawLine(dx / 2, dy, 0, 0);
340 g.drawLine(dx / 2, dy + shift, 0, shift);
341
342 // Left diagonal.
343 g.setColor(color.brighter());
344 g.drawLine(dx / 2, dy, dx, 0);
345 g.drawLine(dx / 2, dy + shift, dx, shift);
346
347 // Horizontal line.
348 if (descending) {
349 g.setColor(color.darker().darker());
350 } else {
351 g.setColor(color.brighter().brighter());
352 }
353 g.drawLine(dx, 0, 0, 0);
354
355 g.setColor(color);
356 g.translate(-x, -y);
357 }
358
359 public int getIconWidth() {
360 return size;
361 }
362
363 public int getIconHeight() {
364 return size;
365 }
366 }
367
368 private class SortableHeaderRenderer implements TableCellRenderer {
369 private TableCellRenderer tableCellRenderer;
370
371 public SortableHeaderRenderer(TableCellRenderer tableCellRenderer) {
372 this.tableCellRenderer = tableCellRenderer;
373 }
374
375 public Component getTableCellRendererComponent(JTable table,
376 Object value,
377 boolean isSelected,
378 boolean hasFocus,
379 int row,
380 int column) {
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()));
388 }
389 return c;
390 }
391 }
392
393 private static class Directive {
394 private int column;
395 private int direction;
396
397 public Directive(int column, int direction) {
398 this.column = column;
399 this.direction = direction;
400 }
401 }
402 }