001: package net.sf.memoranda.ui.table;
002:
003: /**
004: * A sorter for TableModels. The sorter has a model (conforming to TableModel)
005: * and itself implements TableModel. TableSorter does not store or copy
006: * the data in the TableModel, instead it maintains an array of
007: * integers which it keeps the same size as the number of rows in its
008: * model. When the model changes it notifies the sorter that something
009: * has changed eg. "rowsAdded" so that its internal array of integers
010: * can be reallocated. As requests are made of the sorter (like
011: * getValueAt(row, col) it redirects them to its model via the mapping
012: * array. That way the TableSorter appears to hold another copy of the table
013: * with the rows in a different order. The sorting algorthm used is stable
014: * which means that it does not move around rows when its comparison
015: * function returns 0 to denote that they are equivalent.
016: *
017: * @version 1.5 12/17/97
018: * @author Philip Milne
019: */
020:
021: import java.awt.event.MouseAdapter;
022: import java.awt.event.MouseEvent;
023: import java.util.Date;
024: import java.util.Vector;
025: import java.util.Hashtable;
026:
027: import javax.swing.JTable;
028: import javax.swing.event.TableModelEvent;
029: import javax.swing.table.JTableHeader;
030: import javax.swing.table.TableColumnModel;
031: import javax.swing.table.TableModel;
032: import net.sf.memoranda.util.Local;
033:
034: /*$Id: TableSorter.java,v 1.7 2004/10/07 08:52:32 ivanrise Exp $*/
035: public class TableSorter extends TableMap {
036: int indexes[];
037: Vector sortingColumns = new Vector();
038: boolean ascending = true;
039: int compares;
040: int sortBy = 0;
041:
042: public TableSorter() {
043: indexes = new int[0]; // for consistency
044: }
045:
046: public TableSorter(TableModel model) {
047: setModel(model);
048: }
049:
050: public void setModel(TableModel model) {
051: super .setModel(model);
052: reallocateIndexes();
053: }
054:
055: public int compareRowsByColumn(int row1, int row2, int column) {
056: Class type = model.getColumnClass(column);
057: TableModel data = model;
058:
059: // Check for nulls.
060:
061: Object o1 = data.getValueAt(row1, column);
062: Object o2 = data.getValueAt(row2, column);
063:
064: // If both values are null, return 0.
065: if (o1 == null && o2 == null) {
066: return 0;
067: } else if (o1 == null) { // Define null less than everything.
068: return -1;
069: } else if (o2 == null) {
070: return 1;
071: }
072:
073: /*
074: * We copy all returned values from the getValue call in case
075: * an optimised model is reusing one object to return many
076: * values. The Number subclasses in the JDK are immutable and
077: * so will not be used in this way but other subclasses of
078: * Number might want to do this to save space and avoid
079: * unnecessary heap allocation.
080: */
081:
082: if (type.getSuperclass() == java.lang.Integer.class) {
083: Integer n1 = (Integer) data.getValueAt(row1, column);
084: int i1 = n1.intValue();
085: Integer n2 = (Integer) data.getValueAt(row2, column);
086: int i2 = n2.intValue();
087:
088: if (i1 < i2) {
089: return -1;
090: } else if (i1 > i2) {
091: return 1;
092: } else {
093: return 0;
094: }
095: }
096:
097: else if (type.getSuperclass() == java.lang.Number.class) {
098: Number n1 = (Number) data.getValueAt(row1, column);
099: double d1 = n1.doubleValue();
100: Number n2 = (Number) data.getValueAt(row2, column);
101: double d2 = n2.doubleValue();
102:
103: if (d1 < d2) {
104: return -1;
105: } else if (d1 > d2) {
106: return 1;
107: } else {
108: return 0;
109: }
110: } else if (type == java.util.Date.class) {
111: Date d1 = (Date) data.getValueAt(row1, column);
112: long n1 = d1.getTime();
113: Date d2 = (Date) data.getValueAt(row2, column);
114: long n2 = d2.getTime();
115:
116: if (n1 < n2) {
117: return -1;
118: } else if (n1 > n2) {
119: return 1;
120: } else {
121: return 0;
122: }
123: } else if (type == String.class) {
124: int result;
125: if (data.getColumnName(column).equals(
126: Local.getString("Priority"))) {
127: Hashtable priority = new Hashtable();
128: priority.put(Local.getString("Lowest"), new Integer(1));
129: priority.put(Local.getString("Low"), new Integer(2));
130: priority.put(Local.getString("Normal"), new Integer(3));
131: priority.put(Local.getString("High"), new Integer(4));
132: priority
133: .put(Local.getString("Highest"), new Integer(5));
134:
135: Integer s1 = (Integer) priority.get((String) data
136: .getValueAt(row1, column));
137: Integer s2 = (Integer) priority.get((String) data
138: .getValueAt(row2, column));
139: if (s1 == null || s2 == null)
140: return 0;
141: result = s1.compareTo(s2);
142: } else if (data.getColumnName(column).equals(
143: Local.getString("Status"))) {
144: Hashtable priority = new Hashtable();
145: priority.put(Local.getString("Completed"), new Integer(
146: 1));
147: priority.put(Local.getString("Failed"), new Integer(2));
148: priority.put(Local.getString("Scheduled"), new Integer(
149: 3));
150: priority.put(Local.getString("Active"), new Integer(4));
151: priority.put(Local.getString("Deadline"),
152: new Integer(5));
153:
154: Integer s1 = (Integer) priority.get((String) data
155: .getValueAt(row1, column));
156: Integer s2 = (Integer) priority.get((String) data
157: .getValueAt(row2, column));
158: if (s1 == null || s2 == null)
159: return 0;
160: result = s1.compareTo(s2);
161: } else {
162: String s1 = (String) data.getValueAt(row1, column);
163: String s2 = (String) data.getValueAt(row2, column);
164: result = s1.compareTo(s2);
165: }
166:
167: if (result < 0) {
168: return -1;
169: } else if (result > 0) {
170: return 1;
171: } else {
172: return 0;
173: }
174: } else if (type == Boolean.class) {
175: Boolean bool1 = (Boolean) data.getValueAt(row1, column);
176: boolean b1 = bool1.booleanValue();
177: Boolean bool2 = (Boolean) data.getValueAt(row2, column);
178: boolean b2 = bool2.booleanValue();
179:
180: if (b1 == b2) {
181: return 0;
182: } else if (b1) { // Define false < true
183: return 1;
184: } else {
185: return -1;
186: }
187: } else {
188: Object v1 = data.getValueAt(row1, column);
189: String s1 = v1.toString();
190: Object v2 = data.getValueAt(row2, column);
191: String s2 = v2.toString();
192: int result = s1.compareTo(s2);
193:
194: if (result < 0) {
195: return -1;
196: } else if (result > 0) {
197: return 1;
198: } else {
199: return 0;
200: }
201: }
202: }
203:
204: public int compare(int row1, int row2) {
205: compares++;
206: for (int level = 0; level < sortingColumns.size(); level++) {
207: Integer column = (Integer) sortingColumns.elementAt(level);
208: int result = compareRowsByColumn(row1, row2, column
209: .intValue());
210: if (result != 0) {
211: return ascending ? result : -result;
212: }
213: }
214: return 0;
215: }
216:
217: public void reallocateIndexes() {
218: int rowCount = model.getRowCount();
219:
220: // Set up a new array of indexes with the right number of elements
221: // for the new data model.
222: indexes = new int[rowCount];
223:
224: // Initialise with the identity mapping.
225: for (int row = 0; row < rowCount; row++) {
226: indexes[row] = row;
227: }
228: }
229:
230: public void tableChanged(TableModelEvent e) {
231: //System.out.println("Sorter: tableChanged");
232: reallocateIndexes();
233:
234: super .tableChanged(e);
235: }
236:
237: public void checkModel() {
238: if (indexes.length != model.getRowCount()) {
239: System.err
240: .println("Sorter not informed of a change in model.");
241: setModel(model);
242: }
243: }
244:
245: public void sort(Object sender) {
246: checkModel();
247:
248: compares = 0;
249: // n2sort();
250: // qsort(0, indexes.length-1);
251: shuttlesort((int[]) indexes.clone(), indexes, 0, indexes.length);
252: //System.out.println("Compares: "+compares);
253: }
254:
255: public void n2sort() {
256: for (int i = 0; i < getRowCount(); i++) {
257: for (int j = i + 1; j < getRowCount(); j++) {
258: if (compare(indexes[i], indexes[j]) == -1) {
259: swap(i, j);
260: }
261: }
262: }
263: }
264:
265: // This is a home-grown implementation which we have not had time
266: // to research - it may perform poorly in some circumstances. It
267: // requires twice the space of an in-place algorithm and makes
268: // NlogN assigments shuttling the values between the two
269: // arrays. The number of compares appears to vary between N-1 and
270: // NlogN depending on the initial order but the main reason for
271: // using it here is that, unlike qsort, it is stable.
272: public void shuttlesort(int from[], int to[], int low, int high) {
273: if (high - low < 2) {
274: return;
275: }
276: int middle = (low + high) / 2;
277: shuttlesort(to, from, low, middle);
278: shuttlesort(to, from, middle, high);
279:
280: int p = low;
281: int q = middle;
282:
283: /* This is an optional short-cut; at each recursive call,
284: check to see if the elements in this subset are already
285: ordered. If so, no further comparisons are needed; the
286: sub-array can just be copied. The array must be copied rather
287: than assigned otherwise sister calls in the recursion might
288: get out of sinc. When the number of elements is three they
289: are partitioned so that the first set, [low, mid), has one
290: element and and the second, [mid, high), has two. We skip the
291: optimisation when the number of elements is three or less as
292: the first compare in the normal merge will produce the same
293: sequence of steps. This optimisation seems to be worthwhile
294: for partially ordered lists but some analysis is needed to
295: find out how the performance drops to Nlog(N) as the initial
296: order diminishes - it may drop very quickly. */
297:
298: if (high - low >= 4
299: && compare(from[middle - 1], from[middle]) <= 0) {
300: for (int i = low; i < high; i++) {
301: to[i] = from[i];
302: }
303: return;
304: }
305:
306: // A normal merge.
307:
308: for (int i = low; i < high; i++) {
309: if (q >= high
310: || (p < middle && compare(from[p], from[q]) <= 0)) {
311: to[i] = from[p++];
312: } else {
313: to[i] = from[q++];
314: }
315: }
316: }
317:
318: public void swap(int i, int j) {
319: int tmp = indexes[i];
320: indexes[i] = indexes[j];
321: indexes[j] = tmp;
322: }
323:
324: // The mapping only affects the contents of the data rows.
325: // Pass all requests to these rows through the mapping array: "indexes".
326:
327: public Object getValueAt(int aRow, int aColumn) {
328: checkModel();
329: return model.getValueAt(indexes[aRow], aColumn);
330: }
331:
332: public void setValueAt(Object aValue, int aRow, int aColumn) {
333: checkModel();
334: model.setValueAt(aValue, indexes[aRow], aColumn);
335: }
336:
337: public void sortByColumn(int column) {
338: //sortByColumn(column, true);
339: sortByColumn(column, ascending);
340: }
341:
342: public void sortByColumn(int column, boolean ascending) {
343: sortBy = column;
344: this .ascending = ascending;
345: sortingColumns.removeAllElements();
346: sortingColumns.addElement(new Integer(column));
347: sort(this );
348: super .tableChanged(new TableModelEvent(this ));
349: }
350:
351: public int getSortedBy() {
352: return sortBy;
353: }
354:
355: public boolean isAscending() {
356: return ascending;
357: }
358:
359: // There is no-where else to put this.
360: // Add a mouse listener to the Table to trigger a table sort
361: // when a column heading is clicked in the JTable.
362: public void addMouseListenerToHeaderInTable(JTable table) {
363: final TableSorter sorter = this ;
364: final JTable tableView = table;
365: tableView.setColumnSelectionAllowed(false);
366: MouseAdapter listMouseListener = new MouseAdapter() {
367: boolean ascending = false;
368:
369: public void mouseClicked(MouseEvent e) {
370: TableColumnModel columnModel = tableView
371: .getColumnModel();
372: int viewColumn = columnModel
373: .getColumnIndexAtX(e.getX());
374: int column = tableView
375: .convertColumnIndexToModel(viewColumn);
376: if (e.getClickCount() == 1 && column != -1) {
377: //System.out.println("Sorting ...");
378: //int shiftPressed = e.getModifiers()&InputEvent.SHIFT_MASK;
379: //boolean ascending = (shiftPressed == 0);
380: if (column == sortBy)
381: ascending = !ascending;
382: else
383: ascending = true;
384: sorter.sortByColumn(column, ascending);
385: tableView.getTableHeader().updateUI();
386: }
387: }
388: };
389: JTableHeader th = tableView.getTableHeader();
390: th.addMouseListener(listMouseListener);
391: }
392: }
|