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