1 | /**
|
---|
2 | * A sorter for TableModels. The sorter has a model (conforming to TableModel)
|
---|
3 | * and itself implements TableModel. TableSorter does not store or copy
|
---|
4 | * the data in the TableModel, instead it maintains an array of
|
---|
5 | * integers which it keeps the same size as the number of rows in its
|
---|
6 | * model. When the model changes it notifies the sorter that something
|
---|
7 | * has changed eg. "rowsAdded" so that its internal array of integers
|
---|
8 | * can be reallocated. As requests are made of the sorter (like
|
---|
9 | * getValueAt(row, col) it redirects them to its model via the mapping
|
---|
10 | * array. That way the TableSorter appears to hold another copy of the table
|
---|
11 | * with the rows in a different order. The sorting algorthm used is stable
|
---|
12 | * which means that it does not move around rows when its comparison
|
---|
13 | * function returns 0 to denote that they are equivalent.
|
---|
14 | *
|
---|
15 | * @version 1.5 12/17/97
|
---|
16 | * @author Philip Milne
|
---|
17 | */
|
---|
18 | package org.nzdl.gsdl.SimpleGraphicalClient;
|
---|
19 |
|
---|
20 |
|
---|
21 | import java.util.*;
|
---|
22 |
|
---|
23 | import javax.swing.table.TableModel;
|
---|
24 | import javax.swing.event.TableModelEvent;
|
---|
25 |
|
---|
26 | // Imports for picking up mouse events from the JTable.
|
---|
27 |
|
---|
28 | import java.awt.event.MouseAdapter;
|
---|
29 | import java.awt.event.MouseEvent;
|
---|
30 | import java.awt.event.InputEvent;
|
---|
31 | import javax.swing.JTable;
|
---|
32 | import javax.swing.table.JTableHeader;
|
---|
33 | import javax.swing.table.TableColumnModel;
|
---|
34 |
|
---|
35 | public class TableSorter extends TableMap {
|
---|
36 | int indexes[];
|
---|
37 | Vector sortingColumns = new Vector();
|
---|
38 | boolean ascending = true;
|
---|
39 | int compares;
|
---|
40 |
|
---|
41 | public TableSorter() {
|
---|
42 | indexes = new int[0]; // for consistency
|
---|
43 | }
|
---|
44 |
|
---|
45 | public TableSorter(TableModel model) {
|
---|
46 | setModel(model);
|
---|
47 | }
|
---|
48 |
|
---|
49 | public void setModel(TableModel model) {
|
---|
50 | super.setModel(model);
|
---|
51 | reallocateIndexes();
|
---|
52 | }
|
---|
53 |
|
---|
54 | public int compareRowsByColumn(int row1, int row2, int column) {
|
---|
55 | Class type = model.getColumnClass(column);
|
---|
56 | TableModel data = model;
|
---|
57 |
|
---|
58 | // Check for nulls.
|
---|
59 |
|
---|
60 | Object o1 = data.getValueAt(row1, column);
|
---|
61 | Object o2 = data.getValueAt(row2, column);
|
---|
62 |
|
---|
63 | // If both values are null, return 0.
|
---|
64 | if (o1 == null && o2 == null) {
|
---|
65 | return 0;
|
---|
66 | } else if (o1 == null) { // Define null less than everything.
|
---|
67 | return -1;
|
---|
68 | } else if (o2 == null) {
|
---|
69 | return 1;
|
---|
70 | }
|
---|
71 |
|
---|
72 | /*
|
---|
73 | * We copy all returned values from the getValue call in case
|
---|
74 | * an optimised model is reusing one object to return many
|
---|
75 | * values. The Number subclasses in the JDK are immutable and
|
---|
76 | * so will not be used in this way but other subclasses of
|
---|
77 | * Number might want to do this to save space and avoid
|
---|
78 | * unnecessary heap allocation.
|
---|
79 | */
|
---|
80 |
|
---|
81 | if (type.getSuperclass() == java.lang.Number.class) {
|
---|
82 | Number n1 = (Number)data.getValueAt(row1, column);
|
---|
83 | double d1 = n1.doubleValue();
|
---|
84 | Number n2 = (Number)data.getValueAt(row2, column);
|
---|
85 | double d2 = n2.doubleValue();
|
---|
86 |
|
---|
87 | if (d1 < d2) {
|
---|
88 | return -1;
|
---|
89 | } else if (d1 > d2) {
|
---|
90 | return 1;
|
---|
91 | } else {
|
---|
92 | return 0;
|
---|
93 | }
|
---|
94 | } else if (type == java.util.Date.class) {
|
---|
95 | Date d1 = (Date)data.getValueAt(row1, column);
|
---|
96 | long n1 = d1.getTime();
|
---|
97 | Date d2 = (Date)data.getValueAt(row2, column);
|
---|
98 | long n2 = d2.getTime();
|
---|
99 |
|
---|
100 | if (n1 < n2) {
|
---|
101 | return -1;
|
---|
102 | } else if (n1 > n2) {
|
---|
103 | return 1;
|
---|
104 | } else {
|
---|
105 | return 0;
|
---|
106 | }
|
---|
107 | } else if (type == String.class) {
|
---|
108 | String s1 = (String)data.getValueAt(row1, column);
|
---|
109 | String s2 = (String)data.getValueAt(row2, column);
|
---|
110 | int result = s1.compareTo(s2);
|
---|
111 |
|
---|
112 | if (result < 0) {
|
---|
113 | return -1;
|
---|
114 | } else if (result > 0) {
|
---|
115 | return 1;
|
---|
116 | } else {
|
---|
117 | return 0;
|
---|
118 | }
|
---|
119 | } else if (type == Boolean.class) {
|
---|
120 | Boolean bool1 = (Boolean)data.getValueAt(row1, column);
|
---|
121 | boolean b1 = bool1.booleanValue();
|
---|
122 | Boolean bool2 = (Boolean)data.getValueAt(row2, column);
|
---|
123 | boolean b2 = bool2.booleanValue();
|
---|
124 |
|
---|
125 | if (b1 == b2) {
|
---|
126 | return 0;
|
---|
127 | } else if (b1) { // Define false < true
|
---|
128 | return 1;
|
---|
129 | } else {
|
---|
130 | return -1;
|
---|
131 | }
|
---|
132 | } else {
|
---|
133 | Object v1 = data.getValueAt(row1, column);
|
---|
134 | String s1 = v1.toString();
|
---|
135 | Object v2 = data.getValueAt(row2, column);
|
---|
136 | String s2 = v2.toString();
|
---|
137 | int result = s1.compareTo(s2);
|
---|
138 |
|
---|
139 | if (result < 0) {
|
---|
140 | return -1;
|
---|
141 | } else if (result > 0) {
|
---|
142 | return 1;
|
---|
143 | } else {
|
---|
144 | return 0;
|
---|
145 | }
|
---|
146 | }
|
---|
147 | }
|
---|
148 |
|
---|
149 | public int compare(int row1, int row2) {
|
---|
150 | compares++;
|
---|
151 | for (int level = 0; level < sortingColumns.size(); level++) {
|
---|
152 | Integer column = (Integer)sortingColumns.elementAt(level);
|
---|
153 | int result = compareRowsByColumn(row1, row2, column.intValue());
|
---|
154 | if (result != 0) {
|
---|
155 | return ascending ? result : -result;
|
---|
156 | }
|
---|
157 | }
|
---|
158 | return 0;
|
---|
159 | }
|
---|
160 |
|
---|
161 | public void reallocateIndexes() {
|
---|
162 | int rowCount = model.getRowCount();
|
---|
163 |
|
---|
164 | // Set up a new array of indexes with the right number of elements
|
---|
165 | // for the new data model.
|
---|
166 | indexes = new int[rowCount];
|
---|
167 |
|
---|
168 | // Initialise with the identity mapping.
|
---|
169 | for (int row = 0; row < rowCount; row++) {
|
---|
170 | indexes[row] = row;
|
---|
171 | }
|
---|
172 | }
|
---|
173 |
|
---|
174 | public void tableChanged(TableModelEvent e) {
|
---|
175 | //System.out.println("Sorter: tableChanged");
|
---|
176 | reallocateIndexes();
|
---|
177 |
|
---|
178 | super.tableChanged(e);
|
---|
179 | }
|
---|
180 |
|
---|
181 | public void checkModel() {
|
---|
182 | if (indexes.length != model.getRowCount()) {
|
---|
183 | System.err.println("Sorter not informed of a change in model.");
|
---|
184 | }
|
---|
185 | }
|
---|
186 |
|
---|
187 | public void sort(Object sender) {
|
---|
188 | checkModel();
|
---|
189 |
|
---|
190 | compares = 0;
|
---|
191 | // n2sort();
|
---|
192 | // qsort(0, indexes.length-1);
|
---|
193 | shuttlesort((int[])indexes.clone(), indexes, 0, indexes.length);
|
---|
194 | //System.out.println("Compares: "+compares);
|
---|
195 | }
|
---|
196 |
|
---|
197 | public void n2sort() {
|
---|
198 | for (int i = 0; i < getRowCount(); i++) {
|
---|
199 | for (int j = i+1; j < getRowCount(); j++) {
|
---|
200 | if (compare(indexes[i], indexes[j]) == -1) {
|
---|
201 | swap(i, j);
|
---|
202 | }
|
---|
203 | }
|
---|
204 | }
|
---|
205 | }
|
---|
206 |
|
---|
207 | // This is a home-grown implementation which we have not had time
|
---|
208 | // to research - it may perform poorly in some circumstances. It
|
---|
209 | // requires twice the space of an in-place algorithm and makes
|
---|
210 | // NlogN assigments shuttling the values between the two
|
---|
211 | // arrays. The number of compares appears to vary between N-1 and
|
---|
212 | // NlogN depending on the initial order but the main reason for
|
---|
213 | // using it here is that, unlike qsort, it is stable.
|
---|
214 | public void shuttlesort(int from[], int to[], int low, int high) {
|
---|
215 | if (high - low < 2) {
|
---|
216 | return;
|
---|
217 | }
|
---|
218 | int middle = (low + high)/2;
|
---|
219 | shuttlesort(to, from, low, middle);
|
---|
220 | shuttlesort(to, from, middle, high);
|
---|
221 |
|
---|
222 | int p = low;
|
---|
223 | int q = middle;
|
---|
224 |
|
---|
225 | /* This is an optional short-cut; at each recursive call,
|
---|
226 | check to see if the elements in this subset are already
|
---|
227 | ordered. If so, no further comparisons are needed; the
|
---|
228 | sub-array can just be copied. The array must be copied rather
|
---|
229 | than assigned otherwise sister calls in the recursion might
|
---|
230 | get out of sinc. When the number of elements is three they
|
---|
231 | are partitioned so that the first set, [low, mid), has one
|
---|
232 | element and and the second, [mid, high), has two. We skip the
|
---|
233 | optimisation when the number of elements is three or less as
|
---|
234 | the first compare in the normal merge will produce the same
|
---|
235 | sequence of steps. This optimisation seems to be worthwhile
|
---|
236 | for partially ordered lists but some analysis is needed to
|
---|
237 | find out how the performance drops to Nlog(N) as the initial
|
---|
238 | order diminishes - it may drop very quickly. */
|
---|
239 |
|
---|
240 | if (high - low >= 4 && compare(from[middle-1], from[middle]) <= 0) {
|
---|
241 | for (int i = low; i < high; i++) {
|
---|
242 | to[i] = from[i];
|
---|
243 | }
|
---|
244 | return;
|
---|
245 | }
|
---|
246 |
|
---|
247 | // A normal merge.
|
---|
248 |
|
---|
249 | for (int i = low; i < high; i++) {
|
---|
250 | if (q >= high || (p < middle && compare(from[p], from[q]) <= 0)) {
|
---|
251 | to[i] = from[p++];
|
---|
252 | }
|
---|
253 | else {
|
---|
254 | to[i] = from[q++];
|
---|
255 | }
|
---|
256 | }
|
---|
257 | }
|
---|
258 |
|
---|
259 | public void swap(int i, int j) {
|
---|
260 | int tmp = indexes[i];
|
---|
261 | indexes[i] = indexes[j];
|
---|
262 | indexes[j] = tmp;
|
---|
263 | }
|
---|
264 |
|
---|
265 | // The mapping only affects the contents of the data rows.
|
---|
266 | // Pass all requests to these rows through the mapping array: "indexes".
|
---|
267 |
|
---|
268 | public Object getValueAt(int aRow, int aColumn) {
|
---|
269 | checkModel();
|
---|
270 | return model.getValueAt(indexes[aRow], aColumn);
|
---|
271 | }
|
---|
272 |
|
---|
273 | public void setValueAt(Object aValue, int aRow, int aColumn) {
|
---|
274 | checkModel();
|
---|
275 | model.setValueAt(aValue, indexes[aRow], aColumn);
|
---|
276 | }
|
---|
277 |
|
---|
278 | public void sortByColumn(int column) {
|
---|
279 | sortByColumn(column, true);
|
---|
280 | }
|
---|
281 |
|
---|
282 | public void sortByColumn(int column, boolean ascending) {
|
---|
283 | this.ascending = ascending;
|
---|
284 | sortingColumns.removeAllElements();
|
---|
285 | sortingColumns.addElement(new Integer(column));
|
---|
286 | sort(this);
|
---|
287 | super.tableChanged(new TableModelEvent(this));
|
---|
288 | }
|
---|
289 |
|
---|
290 | // There is no-where else to put this.
|
---|
291 | // Add a mouse listener to the Table to trigger a table sort
|
---|
292 | // when a column heading is clicked in the JTable.
|
---|
293 | public void addMouseListenerToHeaderInTable(JTable table) {
|
---|
294 | final TableSorter sorter = this;
|
---|
295 | final JTable tableView = table;
|
---|
296 | tableView.setColumnSelectionAllowed(false);
|
---|
297 | MouseAdapter listMouseListener = new MouseAdapter() {
|
---|
298 | public void mouseClicked(MouseEvent e) {
|
---|
299 | TableColumnModel columnModel = tableView.getColumnModel();
|
---|
300 | int viewColumn = columnModel.getColumnIndexAtX(e.getX());
|
---|
301 | int column = tableView.convertColumnIndexToModel(viewColumn);
|
---|
302 | if (e.getClickCount() == 1 && column != -1) {
|
---|
303 | //System.out.println("Sorting ...");
|
---|
304 | int shiftPressed = e.getModifiers()&InputEvent.SHIFT_MASK;
|
---|
305 | boolean ascending = (shiftPressed == 0);
|
---|
306 | sorter.sortByColumn(column, ascending);
|
---|
307 | }
|
---|
308 | }
|
---|
309 | };
|
---|
310 | JTableHeader th = tableView.getTableHeader();
|
---|
311 | th.addMouseListener(listMouseListener);
|
---|
312 | }
|
---|
313 | }
|
---|