001 package util.swing;
002
003 import java.util.*;
004
005 import javax.swing.table.TableModel;
006 import javax.swing.event.TableModelEvent;
007
008 import java.awt.event.MouseAdapter;
009 import java.awt.event.MouseEvent;
010 import java.awt.event.InputEvent;
011 import javax.swing.JTable;
012 import javax.swing.table.JTableHeader;
013 import javax.swing.table.TableColumnModel;
014
015 /**
016 * A sorter for TableModels. The sorter has a model (conforming to TableModel)
017 * and itself implements TableModel. TableSorter does not store or copy
018 * the data in the TableModel, instead it maintains an array of
019 * integers which it keeps the same size as the number of rows in its
020 * model. When the model changes it notifies the sorter that something
021 * has changed eg. "rowsAdded" so that its internal array of integers
022 * can be reallocated. As requests are made of the sorter (like
023 * getValueAt(row, col) it redirects them to its model via the mapping
024 * array. That way the TableSorter appears to hold another copy of the table
025 * with the rows in a different order. The sorting algorthm used is stable
026 * which means that it does not move around rows when its comparison
027 * function returns 0 to denote that they are equivalent.
028 *
029 * @version 1.6 2003-03-25
030 * @author Philip Milne
031 * @author Thomas Medack
032 * @author Andreas Bartho
033 */
034 public class TableSorter extends TableMap {
035
036 /**
037 * Field of indexes.
038 */
039 private int indexes[], reverseIndexes[];
040
041 /**
042 * Sorting vector.
043 */
044 private Vector sortingColumns = new Vector();
045
046 /**
047 * Sort ascending or descending.
048 */
049 private boolean ascending = true;
050
051 /**
052 * The column to be sorted.
053 */
054 private int actualColumn = 0;
055
056 /**
057 * Internal helper variable.
058 */
059 private int compares;
060
061 /**
062 * Constructor.
063 */
064 public TableSorter() {
065 indexes = new int[0]; // for consistency
066 }
067
068 /**
069 * Constructor.
070 * @param model the wrapped {@link TableModel}
071 */
072 public TableSorter(AbstractTableModel model) {
073 setModel(model);
074 }
075
076 /**
077 * Sets the {@link TableModel}.
078 *
079 * @param model the model.
080 */
081 public void setModel(AbstractTableModel model) {
082 super.setModel(model);
083 reallocateIndexes();
084 }
085
086 /**
087 * Compares columns of two specific rows.
088 * @param row1 first row.
089 * @param row2 second row.
090 * @param column the column index.
091 * @return a comparative value.
092 */
093 public int compareRowsByColumn(int row1, int row2, int column) {
094 Class type = model.getColumnClass(column);
095 TableModel data = model;
096 // Check for nulls.
097 Object o1 = data.getValueAt(row1, column);
098 Object o2 = data.getValueAt(row2, column);
099 // If both values are null, return 0.
100 if (o1 == null && o2 == null) {
101 return 0;
102 } else {
103 if (o1 == null) { // Define null less than everything.
104 return -1;
105 } else {
106 if (o2 == null) {
107 return 1;
108 }
109 }
110 }
111 String s1 = o1.toString();
112 String s2 = o2.toString();
113
114 if ((o1 instanceof Comparable) && (o2 instanceof Comparable)) {
115 java.util.Comparator cmp = model.getEntryDescriptor().getColumnOrder(column);
116 return (cmp == null) ? ((Comparable)o1).compareTo(o2) : cmp.compare(model.getRecord(row1),
117 model.getRecord(row2));
118 } else {
119 int result = s1.compareTo(s2);
120 if (result < 0) {
121 return -1;
122 } else {
123 if (result > 0) {
124 return 1;
125 } else {
126 return 0;
127 }
128 }
129 }
130 }
131
132 /**
133 * Comparison of two rows.
134 *
135 * @param row1 first row.
136 * @param row2 second row.
137 * @return a comparative value.
138 */
139 public int compare(int row1, int row2) {
140 compares++;
141 for (int level = 0; level < sortingColumns.size(); level++) {
142 Integer column = (Integer)sortingColumns.elementAt(level);
143 int result = compareRowsByColumn(row1, row2, column.intValue());
144 if (result != 0) {
145 return ascending ? result : -result;
146 }
147 }
148 return 0;
149 }
150
151 /**
152 * Helper method.
153 */
154 private void reallocateIndexes() {
155 int rowCount = model.getRowCount();
156 // Set up a new array of indexes with the right number of elements
157 // for the new data model.
158 indexes = new int[rowCount];
159 // Initialize with the identity mapping.
160 for (int row = 0; row < rowCount; row++) {
161 indexes[row] = row;
162 }
163 }
164
165 /**
166 * Reacts on TableChangeEvents, either converts them as needed or passes them to the model.
167 * @param e the event.
168 */
169 public void tableChanged(TableModelEvent e) {
170 if (indexes.length == getModel().getRowCount() && indexes.length != 0) {
171 createReverseIndexes();
172 super.tableChanged(new TableModelEvent(this, reverseIndexes[e.getFirstRow()]));
173 } else {
174 //if big changes happened (row add or remove) pass event to parent
175 reallocateIndexes();
176 //sortByColumn(actualColumn, ascending);
177 super.tableChanged(e);
178 }
179 }
180
181 /**
182 * Creates a reverse index table.
183 *
184 * This method maps the line number for each table to have the view updated
185 *
186 * Example: indexes = [2 3 0 1 4]<br>
187 * reverseIndex stores in i, where i appeares in indexes:<br>
188 * 0 is on position 2, 1 is on position 3, 2 is on position 0 ... => reverseIndex = [2 3 0 1 4]<br>
189 * Mapping this array with the row number sent by a TableChangeEvent event, all tables have the
190 * correct lines updated (has only effect on CountingStocks)
191 */
192 private void createReverseIndexes() {
193 reverseIndexes = new int[indexes.length];
194 for (int i = 0; i < indexes.length; i++) {
195 for (int j = 0; j < indexes.length; j++) {
196 if (indexes[j] == i) {
197 reverseIndexes[i] = j;
198 }
199 }
200 }
201 }
202
203 /**
204 * Helper method.
205 */
206 private void checkModel() {
207 if (indexes.length != model.getRowCount()) {
208 System.err.println("Sorter not informed of a change in model.");
209 }
210 }
211
212 /**
213 * Sorts the model. It uses {@link shuttlesort(int, int, int, int) shuttlesort}.
214 */
215 private void sort() {
216 checkModel();
217 compares = 0;
218 shuttlesort((int[])indexes.clone(), indexes, 0, indexes.length);
219 }
220
221 // This is a home-grown implementation which we have not had time
222 // to research - it may perform poorly in some circumstances. It
223 // requires twice the space of an in-place algorithm and makes
224 // NlogN assigments shuttling the values between the two
225 // arrays. The number of compares appears to vary between N-1 and
226 // NlogN depending on the initial order but the main reason for
227 // using it here is that, unlike qsort, it is stable.
228 /**
229 * Sorting method.
230 *
231 * @param from the unsorted index array.
232 * @param to the sorted index array (i.e. the result).
233 * @param low lowest field to be taken into consideration on sorting.
234 * @param high highest field to be taken into consideration on sorting.
235 */
236 private void shuttlesort(int from[], int to[], int low, int high) {
237 if (high - low < 2) {
238 return;
239 }
240 int middle = (low + high) / 2;
241 shuttlesort(to, from, low, middle);
242 shuttlesort(to, from, middle, high);
243
244 int p = low;
245 int q = middle;
246
247 if (high - low >= 4 && compare(from[middle - 1], from[middle]) <= 0) {
248 for (int i = low; i < high; i++) {
249 to[i] = from[i];
250 }
251 return;
252 }
253
254 // A normal merge.
255
256 for (int i = low; i < high; i++) {
257 if (q >= high || (p < middle && compare(from[p], from[q]) <= 0)) {
258 to[i] = from[p++];
259 } else {
260 to[i] = from[q++];
261 }
262 }
263 }
264
265 /**
266 * Helper method. Swaps two ints.
267 *
268 * @param i first int.
269 * @param j secont int
270 */
271 private void swap(int i, int j) {
272 int tmp = indexes[i];
273 indexes[i] = indexes[j];
274 indexes[j] = tmp;
275 }
276
277 // The mapping only affects the contents of the data rows.
278 // Pass all requests to these rows through the mapping array: "indexes".
279
280 /**
281 * Changes the value of a table cell.
282 *
283 * @param aValue the value to set.
284 * @param aRow the row of the TableCell to be changed.
285 * @param aColumn the column of the table cell to be changed.
286 */
287 public Object getValueAt(int aRow, int aColumn) {
288 checkModel();
289 if (aRow >= 0 && model.getRowCount() > 0) {
290 return model.getValueAt(indexes[aRow], aColumn);
291 } else {
292 return null;
293 }
294 }
295
296 /**
297 * Gets the record.
298 *
299 * @param row the affected table row.
300 * @return the appropriate record.
301 */
302 public Object getRecord(int row) {
303 checkModel();
304 if (row >= 0 && model.getRowCount() > 0 && row < model.getRowCount()) { //prevent ArrayIndexOutOfBoundException when last table entry has
305 // disappered but is yet queried (e.g. all elements of the last
306 // row of a TTFS table have been moved)
307 return model.getRecord(indexes[row]);
308 } else {
309 return null;
310 }
311 }
312
313 /**
314 * Changes the value of a table cell.
315 *
316 * @param aValue the value to set.
317 * @param aRow the row of the TableCell to be changed.
318 * @param aColumn the column of the table cell to be changed.
319 */
320 public void setValueAt(Object aValue, int aRow, int aColumn) {
321 checkModel();
322 if (aRow >= 0) {
323 model.setValueAt(aValue, indexes[aRow], aColumn);
324 }
325 }
326
327 /**
328 * Sorts the table ascending by a column.
329 *
330 * @param column the column by which the table should be sorted.
331 */
332 public void sortByColumn(int column) {
333 sortByColumn(column, true);
334 }
335
336 /**
337 * Sorts the table by a column.
338 *
339 * @param column the column by which the table should be sorted.
340 * @param ascending if <code>true</code> sort sequence is ascending, otherwise descending.
341 */
342 public void sortByColumn(int column, boolean ascending) {
343 this.ascending = ascending;
344 sortingColumns.removeAllElements();
345 sortingColumns.addElement(new Integer(column));
346 sort();
347 super.tableChanged(new TableModelEvent(this));
348 }
349
350 // There is no-where else to put this.
351 // Add a mouse listener to the Table to trigger a table sort
352 // when a column heading is clicked in the JTable.
353
354 /**
355 * Adds a mouse listener to the table header.
356 *
357 * @param table the table to which header the listener is to be added
358 */
359 public void addMouseListenerToHeaderInTable(JTable table, final Object[] ao) {
360 final TableSorter sorter = this;
361 final JTable tableView = table;
362 final TableEntryDescriptor ted = getModel().getEntryDescriptor();
363 ascending = true;
364 tableView.setColumnSelectionAllowed(false);
365 MouseAdapter listMouseListener = new MouseAdapter() {
366 int activeColumn = -1; //no active column yet (prevent table from being reverse ordered)
367 public void mouseClicked(MouseEvent e) {
368 TableColumnModel columnModel = tableView.getColumnModel();
369 int viewColumn = columnModel.getColumnIndexAtX(e.getX());
370 int column = tableView.convertColumnIndexToModel(viewColumn);
371 //initialize ascending everytime a different column is chosen
372 if (column != activeColumn) {
373 ascending = false;
374 activeColumn = column;
375 }
376 if (e.getClickCount() == 1 && column != -1) {
377 // check TED if sorting by this column is allowed
378 if (ted.canSortByColumn(column)) {
379 actualColumn = column;
380 ascending = !ascending;
381 ao[0] = new Integer(column);
382 ao[1] = new Boolean(ascending);
383 sorter.sortByColumn(actualColumn, ascending);
384 // Fire Tableheader changed
385 tableView.getTableHeader().resizeAndRepaint();
386 }
387 }
388 }
389 };
390 JTableHeader th = tableView.getTableHeader();
391 th.addMouseListener(listMouseListener);
392 }
393 }