1 52 package net.mlw.vlh.swing.support; 53 54 import java.awt.Color ; 55 import java.awt.Component ; 56 import java.awt.Graphics ; 57 import java.awt.event.ActionEvent ; 58 import java.awt.event.ActionListener ; 59 import java.awt.event.MouseAdapter ; 60 import java.awt.event.MouseEvent ; 61 import java.awt.event.MouseListener ; 62 import java.util.ArrayList ; 63 import java.util.Arrays ; 64 import java.util.Comparator ; 65 import java.util.HashMap ; 66 import java.util.Iterator ; 67 import java.util.List ; 68 import java.util.Map ; 69 70 import javax.swing.Icon ; 71 import javax.swing.JLabel ; 72 import javax.swing.JTable ; 73 import javax.swing.event.TableModelEvent ; 74 import javax.swing.event.TableModelListener ; 75 import javax.swing.table.AbstractTableModel ; 76 import javax.swing.table.JTableHeader ; 77 import javax.swing.table.TableCellRenderer ; 78 import javax.swing.table.TableColumnModel ; 79 import javax.swing.table.TableModel ; 80 81 import net.mlw.vlh.ValueList; 82 import net.mlw.vlh.swing.ValueListHelper; 83 import net.mlw.vlh.swing.ValueListTableModel; 84 85 public class TableSorter extends AbstractTableModel implements ValueListTableModel, ActionListener 86 { 87 protected ValueListTableModel valueListTableModel; 88 89 protected TableModel tableModel; 90 91 public static final int DESCENDING = -1; 92 93 public static final int NOT_SORTED = 0; 94 95 public static final int ASCENDING = 1; 96 97 private static Directive EMPTY_DIRECTIVE = new Directive(-1, NOT_SORTED); 98 99 public static final Comparator COMPARABLE_COMAPRATOR = new Comparator () 100 { 101 public int compare(Object o1, Object o2) 102 { 103 return ((Comparable ) o1).compareTo(o2); 104 } 105 }; 106 107 public static final Comparator LEXICAL_COMPARATOR = new Comparator () 108 { 109 public int compare(Object o1, Object o2) 110 { 111 return o1.toString().compareTo(o2.toString()); 112 } 113 }; 114 115 private Row[] viewToModel; 116 117 private int[] modelToView; 118 119 private JTableHeader tableHeader; 120 121 private MouseListener mouseListener; 122 123 private TableModelListener tableModelListener; 124 125 private Map columnComparators = new HashMap (); 126 127 private List sortingColumns = new ArrayList (); 128 129 private ActionListener actionListener; 130 131 public TableSorter() 132 { 133 this.mouseListener = new MouseHandler(); 134 this.tableModelListener = new TableModelHandler(); 135 } 136 137 public TableSorter(TableModel tableModel) 138 { 139 this(); 140 setTableModel(tableModel); 141 } 142 143 public TableSorter(TableModel tableModel, JTableHeader tableHeader) 144 { 145 this(); 146 setTableHeader(tableHeader); 147 setTableModel(tableModel); 148 } 149 150 153 public void actionPerformed(ActionEvent e) 154 { 155 System.out.println("ooops = " + e); 156 157 } 158 159 public void addActionListener(ActionListener actionListener) 160 { 161 this.actionListener = actionListener; 162 } 163 164 private void clearSortingState() 165 { 166 viewToModel = null; 167 modelToView = null; 168 } 169 170 public TableModel getTableModel() 171 { 172 return tableModel; 173 } 174 175 public void setTableModel(TableModel tableModel) 176 { 177 if (this.tableModel != null) 178 { 179 this.tableModel.removeTableModelListener(tableModelListener); 180 } 181 182 this.tableModel = tableModel; 183 184 if (tableModel != null && ValueListTableModel.class.isAssignableFrom(tableModel.getClass())) 185 { 186 valueListTableModel = (ValueListTableModel) tableModel; 187 } 188 189 if (this.tableModel != null) 190 { 191 this.tableModel.addTableModelListener(tableModelListener); 192 } 193 194 clearSortingState(); 195 fireTableStructureChanged(); 196 } 197 198 public JTableHeader getTableHeader() 199 { 200 return tableHeader; 201 } 202 203 public void setTableHeader(JTableHeader tableHeader) 204 { 205 if (this.tableHeader != null) 206 { 207 this.tableHeader.removeMouseListener(mouseListener); 208 TableCellRenderer defaultRenderer = this.tableHeader.getDefaultRenderer(); 209 if (defaultRenderer instanceof SortableHeaderRenderer) 210 { 211 this.tableHeader.setDefaultRenderer(((SortableHeaderRenderer) defaultRenderer).tableCellRenderer); 212 } 213 } 214 this.tableHeader = tableHeader; 215 if (this.tableHeader != null) 216 { 217 this.tableHeader.addMouseListener(mouseListener); 218 this.tableHeader.setDefaultRenderer(new SortableHeaderRenderer(this.tableHeader.getDefaultRenderer())); 219 } 220 } 221 222 public boolean isSorting() 223 { 224 return sortingColumns.size() != 0; 225 } 226 227 private Directive getDirective(int column) 228 { 229 for (int i = 0; i < sortingColumns.size(); i++) 230 { 231 Directive directive = (Directive) sortingColumns.get(i); 232 if (directive.column == column) 233 { 234 return directive; 235 } 236 } 237 return EMPTY_DIRECTIVE; 238 } 239 240 public int getSortingStatus(int column) 241 { 242 return getDirective(column).direction; 243 } 244 245 public String getSortPropertyName(int column) 246 { 247 return valueListTableModel.getSortPropertyName(column); 248 } 249 250 private void sortingStatusChanged() 251 { 252 clearSortingState(); 253 fireTableDataChanged(); 254 if (tableHeader != null) 255 { 256 tableHeader.repaint(); 257 } 258 } 259 260 public void setSortingStatus(int column, int status, boolean fireChange) 261 { 262 Directive directive = getDirective(column); 263 if (directive != EMPTY_DIRECTIVE) 264 { 265 sortingColumns.remove(directive); 266 } 267 if (status != NOT_SORTED) 268 { 269 sortingColumns.add(new Directive(column, status)); 270 } 271 if (fireChange) 272 { 273 sortingStatusChanged(); 274 } 275 } 276 277 protected Icon getHeaderRendererIcon(int column, int size) 278 { 279 Directive directive = getDirective(column); 280 if (directive == EMPTY_DIRECTIVE) 281 { 282 return null; 283 } 284 return new Arrow(directive.direction == DESCENDING, size, sortingColumns.indexOf(directive)); 285 } 286 287 private void cancelSorting() 288 { 289 sortingColumns.clear(); 290 sortingStatusChanged(); 291 } 292 293 public void setColumnComparator(Class type, Comparator comparator) 294 { 295 if (comparator == null) 296 { 297 columnComparators.remove(type); 298 } 299 else 300 { 301 columnComparators.put(type, comparator); 302 } 303 } 304 305 protected Comparator getComparator(int column) 306 { 307 Class columnType = tableModel.getColumnClass(column); 308 Comparator comparator = (Comparator ) columnComparators.get(columnType); 309 if (comparator != null) 310 { 311 return comparator; 312 } 313 if (Comparable .class.isAssignableFrom(columnType)) 314 { 315 return COMPARABLE_COMAPRATOR; 316 } 317 return LEXICAL_COMPARATOR; 318 } 319 320 private Row[] getViewToModel() 321 { 322 if (viewToModel == null) 323 { 324 int tableModelRowCount = tableModel.getRowCount(); 325 viewToModel = new Row[tableModelRowCount]; 326 for (int row = 0; row < tableModelRowCount; row++) 327 { 328 viewToModel[row] = new Row(row); 329 } 330 331 if (isSorting()) 332 { 333 Arrays.sort(viewToModel); 334 } 335 } 336 return viewToModel; 337 } 338 339 public int modelIndex(int viewIndex) 340 { 341 Row[] rows = getViewToModel(); 342 if (viewIndex < 0 || viewIndex >= rows.length) 343 { 344 return -1; 345 } 346 else 347 { 348 return rows[viewIndex].modelIndex; 349 } 350 } 351 352 public int viewIndex(int modelIndex) 353 { 354 if (modelIndex < 0) 355 { 356 return -1; 357 } 358 int[] modelToView = getModelToView(); 359 if (modelIndex < 0 || modelIndex >= modelToView.length) 360 { 361 return modelIndex; 362 } 363 else 364 { 365 return modelToView[modelIndex]; 366 } 367 } 368 369 private int[] getModelToView() 370 { 371 if (modelToView == null) 372 { 373 int n = getViewToModel().length; 374 modelToView = new int[n]; 375 for (int i = 0; i < n; i++) 376 { 377 modelToView[modelIndex(i)] = i; 378 } 379 } 380 return modelToView; 381 } 382 383 385 public int getRowCount() 386 { 387 return (tableModel == null) ? 0 : tableModel.getRowCount(); 388 } 389 390 public int getColumnCount() 391 { 392 return (tableModel == null) ? 0 : tableModel.getColumnCount(); 393 } 394 395 public String getColumnName(int column) 396 { 397 return tableModel.getColumnName(column); 398 } 399 400 public Class getColumnClass(int column) 401 { 402 return tableModel.getColumnClass(column); 403 } 404 405 public boolean isCellEditable(int row, int column) 406 { 407 return tableModel.isCellEditable(modelIndex(row), column); 408 } 409 410 public Object getValueAt(int row, int column) 411 { 412 return tableModel.getValueAt(modelIndex(row), column); 413 } 414 415 public void setValueAt(Object aValue, int row, int column) 416 { 417 tableModel.setValueAt(aValue, modelIndex(row), column); 418 } 419 420 423 public List getSortingColumns() 424 { 425 return sortingColumns; 426 } 427 428 431 public synchronized int trimFromList(int maxSize) 432 { 433 if (valueListTableModel != null) 434 { 435 int size = valueListTableModel.getRowCount(); 436 if (size >= maxSize) 437 { 438 int index = modelIndex(0); 439 modelToView = null; 440 viewToModel = null; 441 valueListTableModel.trimFromList(0); 442 fireTableRowsDeleted(0, 1); 443 return index; 444 } 445 else 446 { 447 return -1; 448 } 449 } 450 else 451 { 452 throw new IllegalStateException ("A ValueListTableModel must be assigned to call this method."); 453 } 454 455 } 456 457 public int addBean(Object bean) 458 { 459 if (valueListTableModel != null) 460 { 461 modelToView = null; 462 viewToModel = null; 463 int size = valueListTableModel.getRowCount(); 464 valueListTableModel.addBean(bean); 465 fireTableRowsInserted(size, size + 1); 466 return viewIndex(size); 467 } 468 else 469 { 470 throw new IllegalStateException ("A ValueListTableModel must be assigned to call this method."); 471 } 472 } 473 474 public int removeBean(Object bean) 475 { 476 if (valueListTableModel != null) 477 { 478 int index = valueListTableModel.removeBean(bean); 479 modelToView = null; 480 viewToModel = null; 481 482 fireTableRowsDeleted(index, index + 1); 483 return viewIndex(index); 484 } 485 else 486 { 487 throw new IllegalStateException ("A ValueListTableModel must be assigned to call this method."); 488 } 489 } 490 491 493 private class Row implements Comparable 494 { 495 private int modelIndex; 496 497 public Row(int index) 498 { 499 this.modelIndex = index; 500 } 501 502 public int compareTo(Object o) 503 { 504 int row1 = modelIndex; 505 int row2 = ((Row) o).modelIndex; 506 507 for (Iterator it = sortingColumns.iterator(); it.hasNext();) 508 { 509 Directive directive = (Directive) it.next(); 510 int column = directive.column; 511 Object o1 = tableModel.getValueAt(row1, column); 512 Object o2 = tableModel.getValueAt(row2, column); 513 514 int comparison = 0; 515 if (o1 == null && o2 == null) 517 { 518 comparison = 0; 519 } 520 else if (o1 == null) 521 { 522 comparison = -1; 523 } 524 else if (o2 == null) 525 { 526 comparison = 1; 527 } 528 else 529 { 530 comparison = getComparator(column).compare(o1, o2); 531 } 532 if (comparison != 0) 533 { 534 return directive.direction == DESCENDING ? -comparison : comparison; 535 } 536 } 537 return 0; 538 } 539 } 540 541 private class TableModelHandler implements TableModelListener 542 { 543 public void tableChanged(TableModelEvent e) 544 { 545 if (!isSorting()) 547 { 548 clearSortingState(); 549 fireTableChanged(e); 550 return; 551 } 552 553 if (e.getFirstRow() == TableModelEvent.HEADER_ROW) 557 { 558 cancelSorting(); 559 fireTableChanged(e); 560 return; 561 } 562 563 int column = e.getColumn(); 582 if (e.getFirstRow() == e.getLastRow() && column != TableModelEvent.ALL_COLUMNS && getSortingStatus(column) == NOT_SORTED 583 && modelToView != null) 584 { 585 int viewIndex = getModelToView()[e.getFirstRow()]; 586 fireTableChanged(new TableModelEvent (TableSorter.this, viewIndex, viewIndex, column, e.getType())); 587 return; 588 } 589 590 clearSortingState(); 592 fireTableDataChanged(); 593 return; 594 } 595 } 596 597 private class MouseHandler extends MouseAdapter 598 { 599 public void mouseClicked(MouseEvent e) 600 { 601 JTableHeader h = (JTableHeader ) e.getSource(); 602 TableColumnModel columnModel = h.getColumnModel(); 603 int viewColumn = columnModel.getColumnIndexAtX(e.getX()); 604 int column = columnModel.getColumn(viewColumn).getModelIndex(); 605 if (column != -1) 606 { 607 int status = getSortingStatus(column); 608 if (!e.isControlDown()) 609 { 610 cancelSorting(); 611 } 612 status = status + (e.isShiftDown() ? -1 : 1); 615 status = (status + 4) % 3 - 1; 617 if (actionListener != null) 618 { 619 setSortingStatus(column, status, false); 620 actionListener.actionPerformed(new ActionEvent (e.getSource(), e.getID(), ValueListHelper.ACTION_COMMAND_SORT, e 621 .getModifiers())); 622 } 623 else 624 { 625 setSortingStatus(column, status, true); 626 } 627 628 } 629 } 630 } 631 632 private static class Arrow implements Icon 633 { 634 private boolean descending; 635 636 private int size; 637 638 private int priority; 639 640 public Arrow(boolean descending, int size, int priority) 641 { 642 this.descending = descending; 643 this.size = size; 644 this.priority = priority; 645 } 646 647 public void paintIcon(Component c, Graphics g, int x, int y) 648 { 649 Color color = c == null ? Color.GRAY : c.getBackground(); 650 int dx = (int) (size / 2 * Math.pow(0.8, priority)); 653 int dy = descending ? dx : -dx; 654 y = y + 5 * size / 6 + (descending ? -dy : 0); 656 int shift = descending ? 1 : -1; 657 g.translate(x, y); 658 659 g.setColor(color.darker()); 661 g.drawLine(dx / 2, dy, 0, 0); 662 g.drawLine(dx / 2, dy + shift, 0, shift); 663 664 g.setColor(color.brighter()); 666 g.drawLine(dx / 2, dy, dx, 0); 667 g.drawLine(dx / 2, dy + shift, dx, shift); 668 669 if (descending) 671 { 672 g.setColor(color.darker().darker()); 673 } 674 else 675 { 676 g.setColor(color.brighter().brighter()); 677 } 678 g.drawLine(dx, 0, 0, 0); 679 680 g.setColor(color); 681 g.translate(-x, -y); 682 } 683 684 public int getIconWidth() 685 { 686 return size; 687 } 688 689 public int getIconHeight() 690 { 691 return size; 692 } 693 } 694 695 private class SortableHeaderRenderer implements TableCellRenderer 696 { 697 private TableCellRenderer tableCellRenderer; 698 699 public SortableHeaderRenderer(TableCellRenderer tableCellRenderer) 700 { 701 this.tableCellRenderer = tableCellRenderer; 702 } 703 704 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) 705 { 706 Component c = tableCellRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); 707 if (c instanceof JLabel ) 708 { 709 JLabel l = (JLabel ) c; 710 l.setHorizontalTextPosition(JLabel.LEFT); 711 int modelColumn = table.convertColumnIndexToModel(column); 712 l.setIcon(getHeaderRendererIcon(modelColumn, l.getFont().getSize())); 713 } 714 return c; 715 } 716 } 717 718 public static class Directive 719 { 720 private int column; 721 722 private int direction; 723 724 public Directive(int column, int direction) 725 { 726 this.column = column; 727 this.direction = direction; 728 } 729 730 733 public int getColumn() 734 { 735 return column; 736 } 737 738 741 public int getDirection() 742 { 743 return direction; 744 } 745 } 746 747 public Object getBean(int row) 748 { 749 return valueListTableModel.getBean(row); 750 } 751 752 public void setValueList(ValueList valueList) 753 { 754 modelToView = null; 755 viewToModel = null; 756 valueListTableModel.setValueList(valueList); 757 } 758 759 public boolean contains(Object bean) 760 { 761 if (valueListTableModel != null) 762 { 763 return valueListTableModel.contains(bean); 764 } 765 else 766 { 767 throw new IllegalStateException ("A ValueListTableModel must be assigned to call this method."); 768 } 769 } 770 } | Popular Tags |