1 package com.jbidwatcher.ui.table; 2 /* 3 * @(#)TableSorter.java 1.5 97/12/17 4 * 5 * Copyright (c) 1997 Sun Microsystems, Inc. All Rights Reserved. 6 * 7 * This software is the confidential and proprietary information of Sun 8 * Microsystems, Inc. ("Confidential Information"). You shall not 9 * disclose such Confidential Information and shall use it only in 10 * accordance with the terms of the license agreement you entered into 11 * with Sun. 12 * 13 * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE 14 * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 16 * PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES 17 * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING 18 * THIS SOFTWARE OR ITS DERIVATIVES. 19 * 20 */ 21 22 /** 23 * A sorter for AbstractSortableTableModels. The sorter has a model (conforming to AbstractSortableTableModel) 24 * and itself implements AbstractSortableTableModel. TableSorter does not store or copy 25 * the data in the AbstractSortableTableModel, instead it maintains an array of 26 * integers which it keeps the same size as the number of rows in its 27 * model. When the model changes it notifies the sorter that something 28 * has changed eg. "rowsAdded" so that its internal array of integers 29 * can be reallocated. As requests are made of the sorter (like 30 * getSortByValueAt(row, col) it redirects them to its model via the mapping 31 * array. That way the TableSorter appears to hold another copy of the table 32 * with the rows in a different order. The sorting algorthm used is stable 33 * which means that it does not move around rows when its comparison 34 * function returns 0 to denote that they are equivalent. 35 * 36 * @version 1.5 12/17/97 37 * @author Philip Milne 38 */ 39 40 import com.jbidwatcher.util.config.JConfig; 41 42 import java.util.*; 43 import javax.swing.event.TableModelEvent; 44 import javax.swing.event.TableModelListener; 45 46 // Imports for picking up mouse events from the JTable. 47 48 import java.awt.event.MouseAdapter; 49 import java.awt.event.MouseEvent; 50 import java.awt.event.InputEvent; 51 import java.awt.*; 52 import javax.swing.table.*; 53 import javax.swing.*; 54 55 public class TableSorter extends Transformation implements TableModelListener { 56 private JTable _table = null; 57 private ColumnStateList columnStateList; 58 private BaseTransformation _model = null; 59 private SortTransformation _sorted = null; 60 61 private static final Icon ascend = new ImageIcon(JConfig.getResource("/icons/ascend_10x5.gif")); 62 private static final Icon descend = new ImageIcon(JConfig.getResource("/icons/descend_10x5.gif")); 63 TableSorter(String name, String defaultColumn, BaseTransformation tm)64 public TableSorter(String name, String defaultColumn, BaseTransformation tm) { 65 _model = tm; 66 columnStateList = new ColumnStateList(); 67 _sorted = new SortTransformation(_model); 68 m_tm = _sorted; 69 setDefaults(name, defaultColumn); 70 } 71 tableChanged(TableModelEvent e)72 public void tableChanged(TableModelEvent e) { 73 fireTableChanged(e); 74 } 75 sort()76 public void sort() { 77 final TableSorter sorter = this; 78 79 SwingUtilities.invokeLater(new Runnable() { 80 public void run() { 81 Selection save = new Selection(_table, _sorted); 82 _sorted.sort(); 83 _table.tableChanged(new TableModelEvent(sorter)); 84 restoreSelection(save); 85 } 86 }); 87 } 88 sortByList()89 private void sortByList() { 90 _sorted.setSortList(columnStateList); 91 92 if(_table != null) sort(); 93 } 94 setDefaults(String inName, String defaultColumn)95 private void setDefaults(String inName, String defaultColumn) { 96 columnStateList.clear(); 97 98 for(int i=0; ; i++) { 99 String sortByColumn; 100 String sortDirection; 101 if(i==0) { 102 // Initially sort by ending time, ascending. 103 sortByColumn = JConfig.queryDisplayProperty(inName + ".sort_by", defaultColumn); 104 sortDirection = JConfig.queryDisplayProperty(inName + ".sort_direction", "ascending"); 105 } else { 106 sortByColumn = JConfig.queryDisplayProperty(inName + ".sort_by" + "_" + i); 107 sortDirection = JConfig.queryDisplayProperty(inName + ".sort_direction" + "_" + i); 108 } 109 110 if(sortByColumn == null || sortDirection == null) { 111 break; 112 } 113 114 ColumnState cs = new ColumnState(getColumnNumber(sortByColumn), sortDirection.equals("ascending") ? 1 : -1); 115 116 if(columnStateList.indexOf(cs) == -1) columnStateList.add(cs); 117 } 118 119 sortByList(); 120 } 121 getRowCount()122 public int getRowCount() { return m_tm.getRowCount(); } getColumnCount()123 public int getColumnCount() { return m_tm.getColumnCount(); } getValueAt(int row, int col)124 public synchronized Object getValueAt(int row, int col) { return m_tm.getValueAt(row, col); } 125 delete(Object o)126 public boolean delete(Object o) { 127 final int myRow; 128 synchronized(this) { 129 myRow = m_tm.findRow(o); 130 } 131 132 final TableSorter sorter = this; 133 if(myRow == -1) return false; 134 final Selection save = new Selection(_table, _sorted); 135 save.delete(myRow); 136 synchronized (this) { 137 m_tm.delete(myRow); 138 } 139 try { 140 SwingUtilities.invokeAndWait(new Runnable() { 141 public void run() { 142 _table.tableChanged(new TableModelEvent(sorter, myRow, myRow, TableModelEvent.ALL_COLUMNS, TableModelEvent.DELETE)); 143 restoreSelection(save); 144 } 145 }); 146 } catch (Exception e) { 147 e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. 148 } 149 return true; 150 } 151 152 private static class Selection { 153 protected final int[] selected; // used ONLY within save/restoreSelection(); 154 protected int lead = -1; 155 protected SortTransformation sorter; 156 delete(int viewRow)157 protected void delete(int viewRow) { 158 int modelRow = sorter.convertRowIndexToModel(viewRow); 159 for(int i=0; i<selected.length; i++) { 160 if(selected[i] > modelRow) { 161 selected[i]--; 162 } else if(selected[i] == modelRow) { 163 selected[i] = -1; 164 } 165 } 166 if(lead > modelRow) lead--; else if(lead == modelRow) lead = -1; 167 } 168 Selection(JTable table, SortTransformation sortBy)169 protected Selection(JTable table, SortTransformation sortBy) { 170 sorter = sortBy; 171 selected = table.getSelectedRows(); // in view coordinates 172 for (int i = 0; i < selected.length; i++) { 173 int view = selected[i]; 174 selected[i] = sorter.convertRowIndexToModel(view); // model coordinates 175 } 176 177 if (selected.length > 0) { 178 // convert lead selection index to model coordinates 179 lead = sorter.convertRowIndexToModel(table.getSelectionModel().getLeadSelectionIndex()); 180 } 181 } 182 } 183 restoreSelection(Selection selection)184 private void restoreSelection(Selection selection) { 185 _table.clearSelection(); // call overridden version 186 187 boolean lead_selected = false; 188 for (int i = 0; i < selection.selected.length; i++) { 189 int selected = selection.selected[i]; 190 if(selected != selection.lead && selected != -1) { 191 int index = _sorted.convertRowIndexToView(selected); 192 if(index != -1) _table.getSelectionModel().addSelectionInterval(index, index); 193 } 194 if(selected == selection.lead) lead_selected = true; 195 } 196 197 if(selection.lead >= 0) { 198 int new_lead = _sorted.convertRowIndexToView(selection.lead); 199 if(new_lead != -1) { 200 if(lead_selected) { 201 _table.getSelectionModel().addSelectionInterval(new_lead, new_lead); 202 } else { 203 _table.getSelectionModel().removeSelectionInterval(new_lead, new_lead); 204 } 205 } 206 } 207 } 208 insert(Object o)209 public int insert(Object o) { 210 final Selection save = getSelectionSafely(); 211 212 int myRow; 213 synchronized(this) { 214 myRow = m_tm.insert(o); 215 } 216 if(myRow == -1) return -1; 217 218 final TableSorter sorter = this; 219 final int count = m_tm.getRowCount(); 220 221 SwingUtilities.invokeLater(new Runnable() { 222 public void run() { 223 _table.tableChanged(new TableModelEvent(sorter, 0, count, TableModelEvent.ALL_COLUMNS, TableModelEvent.INSERT)); 224 if(save != null) restoreSelection(save); 225 } 226 }); 227 return myRow; 228 } 229 getSelectionSafely()230 private Selection getSelectionSafely() { 231 Selection save; 232 try { 233 save = new Selection(_table, _sorted); 234 } catch(Exception e) { 235 save = null; 236 } 237 return save; 238 } 239 update(final Object updated)240 public boolean update(final Object updated) { 241 final int myRow; 242 synchronized(this) { 243 myRow = m_tm.findRow(updated); 244 } 245 final TableSorter sorter = this; 246 if (myRow != -1) { 247 SwingUtilities.invokeLater(new Runnable() { 248 public void run() { 249 Selection save = new Selection(_table, _sorted); 250 _table.tableChanged(new TableModelEvent(sorter, myRow)); 251 restoreSelection(save); 252 } 253 }); 254 } 255 return myRow != -1; 256 } 257 updateTime()258 public void updateTime() { 259 final TableSorter sorter = this; 260 SwingUtilities.invokeLater(new Runnable() { 261 public void run() { 262 _table.tableChanged(new TableModelEvent(sorter, 0, _sorted.getRowCount(), getColumnNumber("Time left"))); 263 } 264 }); 265 } 266 267 private static class SortHeaderRenderer extends JLabel implements TableCellRenderer { SortHeaderRenderer()268 public SortHeaderRenderer() { 269 setHorizontalTextPosition(LEFT); 270 setHorizontalAlignment(CENTER); 271 } 272 getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col)273 public Component getTableCellRendererComponent(JTable table, Object value, 274 boolean isSelected, boolean hasFocus, 275 int row, int col) { 276 if (table != null) { 277 JTableHeader header = table.getTableHeader(); 278 if (header != null) { 279 setForeground(header.getForeground()); 280 setBackground(header.getBackground()); 281 setFont(header.getFont()); 282 } 283 } 284 285 setText((value == null) ? "" : value.toString()); 286 setBorder(UIManager.getBorder("TableHeader.cellBorder")); 287 288 return this; 289 } 290 } 291 getSortProperties(String prefix, Properties outProps)292 public Properties getSortProperties(String prefix, Properties outProps) { 293 TableColumnModel tableColumnModel = _table.getColumnModel(); 294 for(int i=0; i < columnStateList.size(); i++) { 295 ColumnState columnState = columnStateList.get(i); 296 297 // Restore original header 298 int viewCol = _table.convertColumnIndexToView(columnState.getColumn()); 299 if(viewCol != -1) { 300 TableColumn tableColumn = tableColumnModel.getColumn(viewCol); 301 tableColumn.setHeaderValue(columnState.getHeaderValue()); 302 303 outProps.setProperty(prefix + ".sort_by" + (i > 0 ? "_" + i : ""), _model.getColumnName(columnState.getColumn())); 304 outProps.setProperty(prefix + ".sort_direction" + (i > 0 ? "_" + i : ""), columnState.getSort() == 1 ? "ascending" : "descending"); 305 } 306 } 307 308 return outProps; 309 } 310 setArrow(TableColumnModel tcm, int col, int direction)311 private void setArrow(TableColumnModel tcm, int col, int direction) { 312 if(col == -1) return; 313 //col = _table.convertColumnIndexToModel(col); 314 TableColumn tc = tcm.getColumn(col); 315 TableCellRenderer tcr = tc.getHeaderRenderer(); 316 317 if (tcr == null || !(tcr instanceof SortHeaderRenderer)) { 318 tcr = new SortHeaderRenderer(); 319 tc.setHeaderRenderer(tcr); 320 } 321 322 SortHeaderRenderer shr = (SortHeaderRenderer) tcr; 323 324 switch (direction) { 325 case -1: 326 shr.setIcon(ascend); 327 break; 328 case 0: 329 shr.setIcon(null); 330 break; 331 case 1: 332 shr.setIcon(descend); 333 break; 334 // Can't happen, because the result set is only -1, 335 // 0, 1, but static analysis can't determine that. 336 default: 337 break; 338 } 339 } 340 341 // There is no-where else to put this. 342 // Add a mouse listener to the Table to trigger a table sort 343 // when a column heading is clicked in the JTable. addMouseListenerToHeaderInTable(JTable table)344 public void addMouseListenerToHeaderInTable(JTable table) { 345 TableColumnModel tableColumnModel = table.getColumnModel(); 346 347 _table = table; 348 349 // Restore the header as it was saved 350 for(int i=0; i < columnStateList.size(); i++) { 351 ColumnState columnState = columnStateList.get(i); 352 int viewCol = table.convertColumnIndexToView(columnState.getColumn()); 353 if(viewCol != -1) { 354 TableColumn tableColumn = tableColumnModel.getColumn(viewCol); 355 356 // Save original header 357 String headerValue = (String) tableColumn.getHeaderValue(); 358 columnState.setHeaderValue(headerValue); 359 // Set new header 360 tableColumn.setHeaderValue(headerValue + (i > 0 ? " (" + (i + 1) + ")" : "")); 361 tableColumn.setIdentifier(headerValue); 362 // Set arrow 363 setArrow(tableColumnModel, table.convertColumnIndexToView(columnState.getColumn()), columnState.getSort()); 364 } 365 } 366 367 table.setColumnSelectionAllowed(false); 368 MouseAdapter listMouseListener = new SortMouseAdapter(table, this); 369 table.getTableHeader().addMouseListener(listMouseListener); 370 } 371 removeColumn(String colId, JTable table)372 public void removeColumn(String colId, JTable table) { 373 for(int i=0; i<columnStateList.size(); i++) { 374 ColumnState cs = columnStateList.get(i); 375 if(cs.getHeaderValue().equals(colId)) { 376 columnStateList.remove(cs); 377 i--; 378 } 379 refreshColumns(table, false); 380 } 381 } 382 refreshColumns(JTable table, boolean resetHeaders)383 private void refreshColumns(JTable table, boolean resetHeaders) { 384 int skipped = 0; 385 TableColumnModel columnModel = table.getColumnModel(); 386 for(int i=0; i<columnStateList.size(); i++) { 387 ColumnState cs = columnStateList.get(i); 388 int view = table.convertColumnIndexToView(cs.getColumn()); 389 if(view != -1) { 390 TableColumn tc = columnModel.getColumn(view); 391 392 if(resetHeaders) { 393 setArrow(columnModel, view, 0); 394 tc.setHeaderValue(cs.getHeaderValue()); 395 } else { 396 tc.setHeaderValue(cs.getHeaderValue() + (i > 0 ? " (" + (i + 1 - skipped) + ")" : "")); 397 } 398 } else { 399 skipped++; 400 } 401 } 402 } 403 getObjectAt(int x, int y)404 public Object getObjectAt(int x, int y) { 405 if (_table != null) { 406 int rowPoint = _table.rowAtPoint(new Point(x, y)); 407 408 // A menu item has been selected, instead of a context menu. 409 // This is NOT a valid test, because the popup locations aren't 410 // reset! 411 if (x == 0 && y == 0) { 412 rowPoint = _table.getSelectedRow(); 413 } 414 415 if (rowPoint != -1) { 416 return getValueAt(rowPoint, -1); 417 } 418 } 419 return null; 420 } 421 select(Selector s)422 public boolean select(Selector s) { 423 return s.select(_table); 424 } 425 getSelectedRows()426 public int[] getSelectedRows() { 427 return _table.getSelectedRows(); 428 } 429 getTable()430 public JTable getTable() { return _table; } 431 432 private class SortMouseAdapter extends MouseAdapter 433 { 434 private final JTable mTable; 435 private final TableSorter mSorter; 436 SortMouseAdapter(JTable table, TableSorter sorter)437 public SortMouseAdapter(JTable table, TableSorter sorter) { 438 mTable = table; 439 mSorter = sorter; 440 } 441 mouseClicked(MouseEvent e)442 public void mouseClicked(MouseEvent e) { 443 TableColumnModel columnModel = mTable.getColumnModel(); 444 int viewColumn = columnModel.getColumnIndexAtX(e.getX()); 445 TableColumn tc = columnModel.getColumn(viewColumn); 446 int modelColumn = mTable.convertColumnIndexToModel(viewColumn); 447 448 if(e.getClickCount() == 1) { 449 ColumnState columnState = new ColumnState(modelColumn); 450 int csidx = columnStateList.indexOf(columnState); 451 if ((e.getModifiers() & InputEvent.CTRL_MASK) != InputEvent.CTRL_MASK) { 452 if(columnStateList.size() > 1 || csidx == -1) { 453 refreshColumns(mTable, true); 454 columnStateList.clear(); 455 columnState.setSortState(1); 456 } else { 457 if(columnStateList.size() == 1) { 458 columnState = columnStateList.get(0); 459 columnState.setSortState(columnState.getSort()==1?-1:1); 460 } 461 } 462 columnState.setHeaderValue((String) tc.getHeaderValue()); 463 setArrow(columnModel, viewColumn, columnState.getSort()); 464 columnStateList.add(columnState); 465 } else { 466 if (csidx == -1) { 467 // Not yet sorted by this column, add to list 468 columnState.setHeaderValue((String) tc.getHeaderValue()); 469 columnStateList.add(columnState); 470 } else { 471 columnState = columnStateList.get(csidx); 472 } 473 474 // Transition to next sort (undefined -> asc, asc -> desc, desc -> undefined 475 int state = columnState.setNextSortState(); 476 477 setArrow(columnModel, viewColumn, state); 478 479 if (state == 0) { 480 // Restore original header 481 tc.setHeaderValue(columnState.getHeaderValue()); 482 // Reached undef state again so remove from sort list 483 columnStateList.remove(columnState); 484 } 485 486 // Renumber new / rest of headers accordingly 487 refreshColumns(mTable, false); 488 } 489 490 mSorter.sortByList(); 491 mTable.getTableHeader().repaint(); 492 } 493 } 494 } 495 enableInsertionSorting()496 public void enableInsertionSorting() { 497 _sorted.sortOnInsert(); 498 } 499 } 500