001: package groovy.inspect.swingui;
002:
003: /*
004: * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * -Redistributions of source code must retain the above copyright
011: * notice, this list of conditions and the following disclaimer.
012: *
013: * -Redistribution in binary form must reproduct the above copyright
014: * notice, this list of conditions and the following disclaimer in
015: * the documentation and/or other materials provided with the distribution.
016: *
017: * Neither the name of Sun Microsystems, Inc. or the names of contributors
018: * may be used to endorse or promote products derived from this software
019: * without specific prior written permission.
020: *
021: * This software is provided "AS IS," without a warranty of any kind. ALL
022: * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
023: * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
024: * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT
025: * BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE AS A RESULT
026: * OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE SOFTWARE OR ITS
027: * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
028: * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
029: * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
030: * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE SOFTWARE, EVEN
031: * IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
032: *
033: * You acknowledge that Software is not designed, licensed or intended for
034: * use in the design, construction, operation or maintenance of any nuclear
035: * facility.
036: */
037:
038: /*
039: * @(#)TableSorter.java 1.12 03/01/23
040: */
041:
042: /**
043: * A sorter for TableModels. The sorter has a model (conforming to TableModel)
044: * and itself implements TableModel. TableSorter does not store or copy
045: * the data in the TableModel, instead it maintains an array of
046: * integers which it keeps the same size as the number of rows in its
047: * model. When the model changes it notifies the sorter that something
048: * has changed eg. "rowsAdded" so that its internal array of integers
049: * can be reallocated. As requests are made of the sorter (like
050: * getValueAt(row, col) it redirects them to its model via the mapping
051: * array. That way the TableSorter appears to hold another copy of the table
052: * with the rows in a different order. The sorting algorthm used is stable
053: * which means that it does not move around rows when its comparison
054: * function returns 0 to denote that they are equivalent.
055: *
056: * @version 1.12 01/23/03
057: * @author Philip Milne
058: * @author Minimal adjustments by Dierk Koenig, June 2005
059: */
060:
061: import java.awt.event.MouseAdapter;
062: import java.awt.event.MouseEvent;
063: import java.util.Date;
064: import java.util.Vector;
065:
066: import javax.swing.JTable;
067: import javax.swing.event.TableModelEvent;
068: import javax.swing.table.JTableHeader;
069: import javax.swing.table.TableColumnModel;
070: import javax.swing.table.TableModel;
071:
072: public class TableSorter extends TableMap {
073: int indexes[];
074: Vector sortingColumns = new Vector();
075: boolean ascending = true;
076: int lastSortedColumn = -1;
077:
078: public TableSorter() {
079: indexes = new int[0]; // For consistency.
080: }
081:
082: public TableSorter(TableModel model) {
083: setModel(model);
084: }
085:
086: public void setModel(TableModel model) {
087: super .setModel(model);
088: reallocateIndexes();
089: }
090:
091: public int compareRowsByColumn(int row1, int row2, int column) {
092: Class type = model.getColumnClass(column);
093: TableModel data = model;
094:
095: // Check for nulls
096:
097: Object o1 = data.getValueAt(row1, column);
098: Object o2 = data.getValueAt(row2, column);
099:
100: // If both values are null return 0
101: if (o1 == null && o2 == null) {
102: return 0;
103: } else if (o1 == null) { // Define null less than everything.
104: return -1;
105: } else if (o2 == null) {
106: return 1;
107: }
108:
109: /* We copy all returned values from the getValue call in case
110: an optimised model is reusing one object to return many values.
111: The Number subclasses in the JDK are immutable and so will not be used in
112: this way but other subclasses of Number might want to do this to save
113: space and avoid unnecessary heap allocation.
114: */
115: if (type.getSuperclass() == java.lang.Number.class) {
116: Number n1 = (Number) data.getValueAt(row1, column);
117: double d1 = n1.doubleValue();
118: Number n2 = (Number) data.getValueAt(row2, column);
119: double d2 = n2.doubleValue();
120:
121: if (d1 < d2)
122: return -1;
123: else if (d1 > d2)
124: return 1;
125: else
126: return 0;
127: } else if (type == java.util.Date.class) {
128: Date d1 = (Date) data.getValueAt(row1, column);
129: long n1 = d1.getTime();
130: Date d2 = (Date) data.getValueAt(row2, column);
131: long n2 = d2.getTime();
132:
133: if (n1 < n2)
134: return -1;
135: else if (n1 > n2)
136: return 1;
137: else
138: return 0;
139: } else if (type == String.class) {
140: String s1 = (String) data.getValueAt(row1, column);
141: String s2 = (String) data.getValueAt(row2, column);
142: int result = s1.compareTo(s2);
143:
144: if (result < 0)
145: return -1;
146: else if (result > 0)
147: return 1;
148: else
149: return 0;
150: } else if (type == Boolean.class) {
151: Boolean bool1 = (Boolean) data.getValueAt(row1, column);
152: boolean b1 = bool1.booleanValue();
153: Boolean bool2 = (Boolean) data.getValueAt(row2, column);
154: boolean b2 = bool2.booleanValue();
155:
156: if (b1 == b2)
157: return 0;
158: else if (b1) // Define false < true
159: return 1;
160: else
161: return -1;
162: } else {
163: Object v1 = data.getValueAt(row1, column);
164: String s1 = v1.toString();
165: Object v2 = data.getValueAt(row2, column);
166: String s2 = v2.toString();
167: int result = s1.compareTo(s2);
168:
169: if (result < 0)
170: return -1;
171: else if (result > 0)
172: return 1;
173: else
174: return 0;
175: }
176: }
177:
178: public int compare(int row1, int row2) {
179: for (int level = 0; level < sortingColumns.size(); level++) {
180: Integer column = (Integer) sortingColumns.elementAt(level);
181: int result = compareRowsByColumn(row1, row2, column
182: .intValue());
183: if (result != 0)
184: return ascending ? result : -result;
185: }
186: return 0;
187: }
188:
189: public void reallocateIndexes() {
190: int rowCount = model.getRowCount();
191:
192: // Set up a new array of indexes with the right number of elements
193: // for the new data model.
194: indexes = new int[rowCount];
195:
196: // Initialise with the identity mapping.
197: for (int row = 0; row < rowCount; row++)
198: indexes[row] = row;
199: }
200:
201: public void tableChanged(TableModelEvent e) {
202: System.out.println("Sorter: tableChanged");
203: reallocateIndexes();
204:
205: super .tableChanged(e);
206: }
207:
208: public void checkModel() {
209: if (indexes.length != model.getRowCount()) {
210: System.err
211: .println("Sorter not informed of a change in model.");
212: }
213: }
214:
215: public void sort(Object sender) {
216: checkModel();
217: shuttlesort((int[]) indexes.clone(), indexes, 0, indexes.length);
218: }
219:
220: public void n2sort() {
221: for (int i = 0; i < getRowCount(); i++) {
222: for (int j = i + 1; j < getRowCount(); j++) {
223: if (compare(indexes[i], indexes[j]) == -1) {
224: swap(i, j);
225: }
226: }
227: }
228: }
229:
230: // This is a home-grown implementation which we have not had time
231: // to research - it may perform poorly in some circumstances. It
232: // requires twice the space of an in-place algorithm and makes
233: // NlogN assigments shuttling the values between the two
234: // arrays. The number of compares appears to vary between N-1 and
235: // NlogN depending on the initial order but the main reason for
236: // using it here is that, unlike qsort, it is stable.
237: public void shuttlesort(int from[], int to[], int low, int high) {
238: if (high - low < 2) {
239: return;
240: }
241: int middle = (low + high) / 2;
242: shuttlesort(to, from, low, middle);
243: shuttlesort(to, from, middle, high);
244:
245: int p = low;
246: int q = middle;
247:
248: /* This is an optional short-cut; at each recursive call,
249: check to see if the elements in this subset are already
250: ordered. If so, no further comparisons are needed; the
251: sub-array can just be copied. The array must be copied rather
252: than assigned otherwise sister calls in the recursion might
253: get out of sinc. When the number of elements is three they
254: are partitioned so that the first set, [low, mid), has one
255: element and and the second, [mid, high), has two. We skip the
256: optimisation when the number of elements is three or less as
257: the first compare in the normal merge will produce the same
258: sequence of steps. This optimisation seems to be worthwhile
259: for partially ordered lists but some analysis is needed to
260: find out how the performance drops to Nlog(N) as the initial
261: order diminishes - it may drop very quickly. */
262:
263: if (high - low >= 4
264: && compare(from[middle - 1], from[middle]) <= 0) {
265: for (int i = low; i < high; i++) {
266: to[i] = from[i];
267: }
268: return;
269: }
270:
271: // A normal merge.
272:
273: for (int i = low; i < high; i++) {
274: if (q >= high
275: || (p < middle && compare(from[p], from[q]) <= 0)) {
276: to[i] = from[p++];
277: } else {
278: to[i] = from[q++];
279: }
280: }
281: }
282:
283: public void swap(int i, int j) {
284: int tmp = indexes[i];
285: indexes[i] = indexes[j];
286: indexes[j] = tmp;
287: }
288:
289: // The mapping only affects the contents of the data rows.
290: // Pass all requests to these rows through the mapping array: "indexes".
291:
292: public Object getValueAt(int aRow, int aColumn) {
293: checkModel();
294: return model.getValueAt(indexes[aRow], aColumn);
295: }
296:
297: public void setValueAt(Object aValue, int aRow, int aColumn) {
298: checkModel();
299: model.setValueAt(aValue, indexes[aRow], aColumn);
300: }
301:
302: public void sortByColumn(int column) {
303: sortByColumn(column, true);
304: }
305:
306: public void sortByColumn(int column, boolean ascending) {
307: this .ascending = ascending;
308: sortingColumns.removeAllElements();
309: sortingColumns.addElement(new Integer(column));
310: sort(this );
311: super .tableChanged(new TableModelEvent(this ));
312: }
313:
314: // There is no-where else to put this.
315: // Add a mouse listener to the Table to trigger a table sort
316: // when a column heading is clicked in the JTable.
317: public void addMouseListenerToHeaderInTable(JTable table) {
318: final TableSorter sorter = this ;
319: final JTable tableView = table;
320: tableView.setColumnSelectionAllowed(false);
321: MouseAdapter listMouseListener = new MouseAdapter() {
322: public void mouseClicked(MouseEvent e) {
323: TableColumnModel columnModel = tableView
324: .getColumnModel();
325: int viewColumn = columnModel
326: .getColumnIndexAtX(e.getX());
327: int column = tableView
328: .convertColumnIndexToModel(viewColumn);
329: if (e.getClickCount() == 1 && column != -1) {
330: if (lastSortedColumn == column)
331: ascending = !ascending;
332: sorter.sortByColumn(column, ascending);
333: lastSortedColumn = column;
334: }
335: }
336: };
337: JTableHeader th = tableView.getTableHeader();
338: th.addMouseListener(listMouseListener);
339: }
340:
341: }
|