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 }