001: package com.jeta.swingbuilder.gui.components;
002:
003: import java.util.Date;
004:
005: import javax.swing.JTable;
006: import javax.swing.event.TableModelEvent;
007: import javax.swing.event.TableModelListener;
008: import javax.swing.table.JTableHeader;
009: import javax.swing.table.TableModel;
010:
011: import com.jeta.swingbuilder.gui.utils.FormDesignerUtils;
012:
013: /**
014: * A sorter for TableModels. The sorter has a model (conforming to TableModel)
015: * and itself implements TableModel. TableSorter does not store or copy the data
016: * in the TableModel, instead it maintains an array of integers which it keeps
017: * the same size as the number of rows in its model. When the model changes it
018: * notifies the sorter that something has changed eg. "rowsAdded" so that its
019: * internal array of integers can be reallocated. As requests are made of the
020: * sorter (like getValueAt(row, col) it redirects them to its model via the
021: * mapping array. That way the TableSorter appears to hold another copy of the
022: * table with the rows in a different order. The sorting algorthm used is stable
023: * which means that it does not move around rows when its comparison function
024: * returns 0 to denote that they are equivalent.
025: *
026: * @version 1.5 12/17/97
027: * @author Philip Milne
028: */
029: public class TableSorter extends TableMap {
030: int m_indexes[];
031: private int m_sortingColumn;
032:
033: // the current sort mode
034: private SortMode m_sortmode;
035:
036: public TableSorter() {
037: m_indexes = new int[0]; // for consistency
038: m_sortmode = SortMode.NONE;
039: }
040:
041: public TableSorter(TableModel model) {
042: this ();
043: setModel(model);
044: }
045:
046: public void checkModel() {
047: if (m_indexes.length != model.getRowCount()) {
048: if (FormDesignerUtils.isDebug()) {
049: System.err
050: .println("Sorter not informed of a change in model: indexes.length = "
051: + m_indexes.length
052: + " model.rowCount = "
053: + model.getRowCount());
054: }
055: }
056: }
057:
058: public int compareRowsByColumn(int row1, int row2, int column) {
059: Class type = model.getColumnClass(column);
060: TableModel data = model;
061:
062: // Check for nulls.
063:
064: Object o1 = data.getValueAt(row1, column);
065: Object o2 = data.getValueAt(row2, column);
066:
067: // If both values are null, return 0.
068: if (o1 == null && o2 == null) {
069: return 0;
070: } else if (o1 == null) {
071: // Define null less than everything.
072: return -1;
073: } else if (o2 == null) {
074: return 1;
075: }
076:
077: /*
078: * We copy all returned values from the getValue call in case an
079: * optimised model is reusing one object to return many values. The
080: * Number subclasses in the JDK are immutable and so will not be used in
081: * this way but other subclasses of Number might want to do this to save
082: * space and avoid unnecessary heap allocation.
083: */
084:
085: if (type.getSuperclass() == java.lang.Number.class) {
086: Number n1 = (Number) data.getValueAt(row1, column);
087: double d1 = n1.doubleValue();
088: Number n2 = (Number) data.getValueAt(row2, column);
089: double d2 = n2.doubleValue();
090:
091: if (d1 < d2) {
092: return -1;
093: } else if (d1 > d2) {
094: return 1;
095: } else {
096: return 0;
097: }
098: } else if (type == java.util.Date.class) {
099: Date d1 = (Date) data.getValueAt(row1, column);
100: long n1 = d1.getTime();
101: Date d2 = (Date) data.getValueAt(row2, column);
102: long n2 = d2.getTime();
103:
104: if (n1 < n2) {
105: return -1;
106: } else if (n1 > n2) {
107: return 1;
108: } else {
109: return 0;
110: }
111: } else if (type == String.class) {
112: String s1 = (String) data.getValueAt(row1, column);
113: String s2 = (String) data.getValueAt(row2, column);
114: int result = s1.compareToIgnoreCase(s2);
115:
116: if (result < 0) {
117: return -1;
118: } else if (result > 0) {
119: return 1;
120: } else {
121: return 0;
122: }
123: } else if (type == Boolean.class) {
124: Boolean bool1 = (Boolean) data.getValueAt(row1, column);
125: boolean b1 = bool1.booleanValue();
126: Boolean bool2 = (Boolean) data.getValueAt(row2, column);
127: boolean b2 = bool2.booleanValue();
128:
129: if (b1 == b2) {
130: return 0;
131: } else if (b1) { // Define false < true
132: return 1;
133: } else {
134: return -1;
135: }
136: } else {
137: Object v1 = data.getValueAt(row1, column);
138: String s1 = v1.toString();
139: Object v2 = data.getValueAt(row2, column);
140: String s2 = v2.toString();
141: int result = s1.compareTo(s2);
142:
143: if (result < 0) {
144: return -1;
145: } else if (result > 0) {
146: return 1;
147: } else {
148: return 0;
149: }
150: }
151: }
152:
153: public int compare(int row1, int row2) {
154: int result = compareRowsByColumn(row1, row2, m_sortingColumn);
155: if (result != 0) {
156: return (m_sortmode == SortMode.ASCENDING) ? result
157: : -result;
158: }
159: return 0;
160: }
161:
162: /**
163: * @return the actual model row for the given index
164: */
165: public int getModelRow(int index) {
166: return m_indexes[index];
167: }
168:
169: /**
170: * @return the indexes for this sorter
171: */
172: int[] getIndexes() {
173: return m_indexes;
174: }
175:
176: // The mapping only affects the contents of the data rows.
177: // Pass all requests to these rows through the mapping array: "m_indexes".
178: public Object getValueAt(int aRow, int aColumn) {
179: checkModel();
180: return model.getValueAt(m_indexes[aRow], aColumn);
181: }
182:
183: /**
184: * Handles data inserted into the model. We update all indexes affected by
185: * the insert (but we don't resort).
186: *
187: * @param firstRow
188: * the first row of the range of rows that were inserted
189: * @param lastRow
190: * the last row of the range of rows that were inserted
191: */
192: void handleDataDeleted(int firstRow, int lastRow) {
193: int oldsize = m_indexes.length;
194: int delta = lastRow - firstRow + 1;
195: int newsize = oldsize - delta;
196:
197: // create the new set of indexes, and copy the data over
198: int[] newindexes = new int[newsize];
199: System.arraycopy(m_indexes, 0, newindexes, 0, firstRow);
200:
201: // we need to shift all affected indexes up as well as correct the index
202: // values
203: int newindex = firstRow;
204: for (int index = lastRow + 1; index < oldsize; index++) {
205: newindexes[newindex] = m_indexes[index];
206: if (newindexes[newindex] >= firstRow)
207: newindexes[newindex] -= delta;
208:
209: newindex++;
210: }
211: m_indexes = newindexes;
212: }
213:
214: /**
215: * Handles data inserted into the model. We update all indexes affected by
216: * the insert (but we don't resort).
217: *
218: * @param firstRow
219: * the first row of the range of rows that were inserted
220: * @param lastRow
221: * the last row of the range of rows that were inserted
222: */
223: void handleDataInserted(int firstRow, int lastRow) {
224: int oldsize = m_indexes.length;
225: int delta = lastRow - firstRow + 1;
226: int newsize = oldsize + delta;
227:
228: // create the new set of indexes, and copy the data over
229: int[] newindexes = new int[newsize];
230: if (oldsize > 0) {
231: System.arraycopy(m_indexes, 0, newindexes, 0, oldsize);
232:
233: // we need to shift all affected indexes down as well as correct the
234: // index values
235: for (int index = (newsize - 1); index >= (newsize - delta); index--) {
236: newindexes[index] = newindexes[index - delta];
237: if (newindexes[index] >= firstRow)
238: newindexes[index] += delta;
239: }
240: }
241:
242: for (int index = firstRow; index <= lastRow; index++) {
243: newindexes[index] = index; // set to unity for newly added rows
244: }
245:
246: m_indexes = newindexes;
247: }
248:
249: public void reallocateIndexes() {
250: int rowCount = model.getRowCount();
251:
252: // Set up a new array of indexes with the right number of elements
253: // for the new data model.
254: m_indexes = new int[rowCount];
255:
256: // Initialise with the identity mapping.
257: for (int row = 0; row < rowCount; row++) {
258: m_indexes[row] = row;
259: }
260: }
261:
262: void setIndexes(int[] indexes) {
263: m_indexes = indexes;
264: super .tableChanged(new TableModelEvent(this ));
265: }
266:
267: public void setModel(TableModel model) {
268: super .setModel(model);
269: if (model == null)
270: return;
271:
272: reallocateIndexes();
273:
274: // let's get table changed events for the model, so that we can update
275: // our indexes according. For example, if the user adds a row to the end
276: // of the model, we need to make sure our indexes are updated to include
277: // the newly added rows (we don't resort though)
278: model.addTableModelListener(new TableModelListener() {
279: public void tableChanged(TableModelEvent e) {
280: // okay, let's handle the various cases
281: if (e.getType() == TableModelEvent.INSERT) {
282: // data was added to model, so let's update only those
283: // indexes
284: // that were affected
285: int firstrow = e.getFirstRow();
286: int lastrow = e.getLastRow();
287: handleDataInserted(firstrow, lastrow);
288: } else if (e.getType() == TableModelEvent.DELETE) {
289: // data was removed from the model, so let's update only
290: // those indexes
291: // that were affected
292: int firstrow = e.getFirstRow();
293: int lastrow = e.getLastRow();
294: handleDataDeleted(firstrow, lastrow);
295: } else {
296: // System.out.println( "TableSorter got unknown event:
297: // sortmode = " + m_sortmode + " col = " + m_sortingColumn
298: // );
299: // assume that the entire table has changed
300: reallocateIndexes();
301: if (m_sortmode != SortMode.NONE)
302: sort(this );
303: }
304: // we don't worry about updates, the user must manually resort
305: // if
306: // he wants the data in sorted order
307: fireTableChanged(e);
308: }
309: });
310:
311: }
312:
313: public void sort(Object sender) {
314: checkModel();
315: // n2sort();
316: // qsort(0, indexes.length-1);
317: shuttlesort((int[]) m_indexes.clone(), m_indexes, 0,
318: m_indexes.length);
319: // System.out.println("Compares: "+compares);
320: }
321:
322: public void n2sort() {
323: for (int i = 0; i < getRowCount(); i++) {
324: for (int j = i + 1; j < getRowCount(); j++) {
325: if (compare(m_indexes[i], m_indexes[j]) == -1) {
326: swap(i, j);
327: }
328: }
329: }
330: }
331:
332: // This is a home-grown implementation which we have not had time
333: // to research - it may perform poorly in some circumstances. It
334: // requires twice the space of an in-place algorithm and makes
335: // NlogN assigments shuttling the values between the two
336: // arrays. The number of compares appears to vary between N-1 and
337: // NlogN depending on the initial order but the main reason for
338: // using it here is that, unlike qsort, it is stable.
339: public void shuttlesort(int from[], int to[], int low, int high) {
340:
341: if (high - low < 2) {
342: return;
343: }
344: int middle = (low + high) / 2;
345: shuttlesort(to, from, low, middle);
346: shuttlesort(to, from, middle, high);
347:
348: int p = low;
349: int q = middle;
350:
351: /*
352: * This is an optional short-cut; at each recursive call, check to see
353: * if the elements in this subset are already ordered. If so, no further
354: * comparisons are needed; the sub-array can just be copied. The array
355: * must be copied rather than assigned otherwise sister calls in the
356: * recursion might get out of sinc. When the number of elements is three
357: * they are partitioned so that the first set, [low, mid), has one
358: * element and and the second, [mid, high), has two. We skip the
359: * optimisation when the number of elements is three or less as the
360: * first compare in the normal merge will produce the same sequence of
361: * steps. This optimisation seems to be worthwhile for partially ordered
362: * lists but some analysis is needed to find out how the performance
363: * drops to Nlog(N) as the initial order diminishes - it may drop very
364: * quickly.
365: */
366:
367: if (high - low >= 4
368: && compare(from[middle - 1], from[middle]) <= 0) {
369: for (int i = low; i < high; i++) {
370: to[i] = from[i];
371: }
372: return;
373: }
374:
375: // A normal merge.
376:
377: for (int i = low; i < high; i++) {
378: if (q >= high
379: || (p < middle && compare(from[p], from[q]) <= 0)) {
380: to[i] = from[p++];
381: } else {
382: to[i] = from[q++];
383: }
384: }
385: }
386:
387: public void swap(int i, int j) {
388: int tmp = m_indexes[i];
389: m_indexes[i] = m_indexes[j];
390: m_indexes[j] = tmp;
391: }
392:
393: public void setValueAt(Object aValue, int aRow, int aColumn) {
394: checkModel();
395: model.setValueAt(aValue, m_indexes[aRow], aColumn);
396: }
397:
398: public void sortByColumn(int column) {
399: sortByColumn(column, SortMode.ASCENDING);
400: }
401:
402: public void sortByColumn(int column, SortMode mode) {
403: m_sortingColumn = column;
404: m_sortmode = mode;
405: if (mode == SortMode.ASCENDING) {
406: sort(this );
407: super .tableChanged(new TableModelEvent(this ));
408: } else if (mode == SortMode.DESCENDING) {
409: sort(this );
410: super .tableChanged(new TableModelEvent(this ));
411: } else {
412: // no sort
413: reallocateIndexes();
414: super .tableChanged(new TableModelEvent(this ));
415: }
416: }
417:
418: // There is no-where else to put this.
419: // Add a mouse listener to the Table to trigger a table sort
420: // when a column heading is clicked in the JTable.
421: public void addMouseListenerToHeaderInTable(JTable table) {
422: table.setColumnSelectionAllowed(false);
423: JTableHeader th = table.getTableHeader();
424: th.addMouseListener(new TableSorterHeaderMouseAdapter(this,
425: table));
426: }
427: }
|