1 package baseCode.gui.table;
2
3 import java.awt.Color;
4 import java.awt.Component;
5 import java.awt.Graphics;
6 import java.awt.Point;
7 import java.awt.event.MouseAdapter;
8 import java.awt.event.MouseEvent;
9 import java.awt.event.MouseListener;
10 import java.util.ArrayList;
11 import java.util.Arrays;
12 import java.util.Comparator;
13 import java.util.HashMap;
14 import java.util.Iterator;
15 import java.util.List;
16 import java.util.Map;
17 import java.util.Vector;
18
19 import javax.swing.Icon;
20 import javax.swing.JLabel;
21 import javax.swing.JTable;
22 import javax.swing.SwingConstants;
23 import javax.swing.event.TableModelEvent;
24 import javax.swing.event.TableModelListener;
25 import javax.swing.table.AbstractTableModel;
26 import javax.swing.table.JTableHeader;
27 import javax.swing.table.TableCellRenderer;
28 import javax.swing.table.TableColumnModel;
29 import javax.swing.table.TableModel;
30
31 import baseCode.gui.JMatrixDisplay;
32
33 /***
34 * TableSorter is a decorator for TableModels; adding sorting functionality to a supplied TableModel. TableSorter does
35 * not store or copy the data in its TableModel; instead it maintains a map from the row indexes of the view to the row
36 * indexes of the model. As requests are made of the sorter (like getValueAt(row, col)) they are passed to the
37 * underlying model after the row numbers have been translated via the internal mapping array. This way, the TableSorter
38 * appears to hold another copy of the table with the rows in a different order. <p/>TableSorter registers itself as a
39 * listener to the underlying model, just as the JTable itself would. Events recieved from the model are examined,
40 * sometimes manipulated (typically widened), and then passed on to the TableSorter's listeners (typically the JTable).
41 * If a change to the model has invalidated the order of TableSorter's rows, a note of this is made and the sorter will
42 * resort the rows the next time a value is requested. <p/>When the tableHeader property is set, either by using the
43 * setTableHeader() method or the two argument constructor, the table header may be used as a complete UI for
44 * TableSorter. The default renderer of the tableHeader is decorated with a renderer that indicates the sorting status
45 * of each column. In addition, a mouse listener is installed with the following behavior:
46 * <ul>
47 * <li>Mouse-click: Clears the sorting status of all other columns and advances the sorting status of that column
48 * through three values: {NOT_SORTED, ASCENDING, DESCENDING} (then back to NOT_SORTED again).
49 * <li>SHIFT-mouse-click: Clears the sorting status of all other columns and cycles the sorting status of the column
50 * through the same three values, in the opposite order: {NOT_SORTED, DESCENDING, ASCENDING}.
51 * <li>CONTROL-mouse-click and CONTROL-SHIFT-mouse-click: as above except that the changes to the column do not cancel
52 * the statuses of columns that are already sorting - giving a way to initiate a compound sort.
53 * </ul>
54 * <p/>This is a long overdue rewrite of a class of the same name that first appeared in the swing table demos in 1997.
55 *
56 * @author Philip Milne
57 * @author Brendon McLean
58 * @author Dan van Enckevort
59 * @author Parwinder Sekhon
60 * @author Paul Pavlidis (minor)
61 * @version 2.0 02/27/04
62 * @version $Id: TableSorter.java,v 1.10 2004/09/20 22:19:03 pavlidis Exp $
63 */
64
65 public class TableSorter extends AbstractTableModel {
66 protected TableModel tableModel;
67 JMatrixDisplay m_matrixDisplay;
68
69 public static final int DESCENDING = -1;
70 public static final int NOT_SORTED = 0;
71 public static final int ASCENDING = 1;
72
73 private static Directive EMPTY_DIRECTIVE = new Directive( -1, NOT_SORTED );
74
75 public static final Comparator COMPARABLE_COMAPRATOR = new Comparator() {
76 public int compare( Object o1, Object o2 ) {
77 return ( ( Comparable ) o1 ).compareTo( o2 );
78 }
79 };
80 public static final Comparator LEXICAL_COMPARATOR = new Comparator() {
81 public int compare( Object o1, Object o2 ) {
82 return o1.toString().compareTo( o2.toString() );
83 }
84 };
85
86 private Row[] viewToModel;
87 int[] modelToView;
88
89 private JTableHeader tableHeader;
90 private MouseListener mouseListener;
91 private TableModelListener tableModelListener;
92 private Map columnComparators = new HashMap();
93 List sortingColumns = new ArrayList();
94
95 public TableSorter() {
96 this.mouseListener = new MouseHandler();
97 this.tableModelListener = new TableModelHandler();
98 }
99
100 public TableSorter( TableModel tableModel ) {
101 this();
102 setTableModel( tableModel );
103 }
104
105 public TableSorter( TableModel tableModel, JTableHeader tableHeader ) {
106 this();
107 setTableHeader( tableHeader );
108 setTableModel( tableModel );
109 }
110
111 public TableSorter( TableModel tableModel, JMatrixDisplay matrixDisplay ) {
112 this( tableModel );
113 m_matrixDisplay = matrixDisplay;
114 }
115
116 void clearSortingState() {
117 viewToModel = null;
118 modelToView = null;
119 }
120
121 public TableModel getTableModel() {
122 return tableModel;
123 }
124
125 public void setTableModel( TableModel tableModel ) {
126 if ( this.tableModel != null ) {
127 this.tableModel.removeTableModelListener( tableModelListener );
128 }
129
130 this.tableModel = tableModel;
131 if ( this.tableModel != null ) {
132 this.tableModel.addTableModelListener( tableModelListener );
133 }
134
135 clearSortingState();
136 fireTableStructureChanged();
137 }
138
139 public JTableHeader getTableHeader() {
140 return tableHeader;
141 }
142
143 public void setTableHeader( JTableHeader tableHeader ) {
144 if ( this.tableHeader != null ) {
145 this.tableHeader.removeMouseListener( mouseListener );
146 TableCellRenderer defaultRenderer = this.tableHeader
147 .getDefaultRenderer();
148 if ( defaultRenderer instanceof SortableHeaderRenderer ) {
149 this.tableHeader
150 .setDefaultRenderer( ( ( SortableHeaderRenderer ) defaultRenderer ).tableCellRenderer );
151 }
152 }
153 this.tableHeader = tableHeader;
154 if ( this.tableHeader != null ) {
155 this.tableHeader.addMouseListener( mouseListener );
156 this.tableHeader.setDefaultRenderer( new SortableHeaderRenderer(
157 this.tableHeader.getDefaultRenderer() ) );
158 }
159 }
160
161 public boolean isSorting() {
162 return sortingColumns.size() != 0;
163 }
164
165 private Directive getDirective( int column ) {
166 for ( int i = 0; i < sortingColumns.size(); i++ ) {
167 Directive directive = ( Directive ) sortingColumns.get( i );
168 if ( directive.column == column ) {
169 return directive;
170 }
171 }
172 return EMPTY_DIRECTIVE;
173 }
174
175 public int getSortingStatus( int column ) {
176 return getDirective( column ).direction;
177 }
178
179 private void sortingStatusChanged() {
180 clearSortingState();
181 fireTableDataChanged();
182 if ( tableHeader != null ) {
183 tableHeader.repaint();
184 }
185 }
186
187 public void setSortingStatus( int column, int status ) {
188 Directive directive = getDirective( column );
189 if ( directive != EMPTY_DIRECTIVE ) {
190 sortingColumns.remove( directive );
191 }
192 if ( status != NOT_SORTED ) {
193 sortingColumns.add( new Directive( column, status ) );
194 }
195 sortingStatusChanged();
196 }
197
198 protected Icon getHeaderRendererIcon( int column, int size ) {
199 Directive directive = getDirective( column );
200 if ( directive == EMPTY_DIRECTIVE ) {
201 return null;
202 }
203 return new Arrow( directive.direction == DESCENDING, size, sortingColumns
204 .indexOf( directive ) );
205 }
206
207 public void cancelSorting() {
208 sortingColumns.clear();
209 sortingStatusChanged();
210 }
211
212 public void setColumnComparator( Class type, Comparator comparator ) {
213 if ( comparator == null ) {
214 columnComparators.remove( type );
215 } else {
216 columnComparators.put( type, comparator );
217 }
218 }
219
220 protected Comparator getComparator( int column ) {
221 Class columnType = tableModel.getColumnClass( column );
222 Comparator comparator = ( Comparator ) columnComparators.get( columnType );
223 if ( comparator != null ) {
224 return comparator;
225 }
226 if ( Comparable.class.isAssignableFrom( columnType ) ) {
227 return COMPARABLE_COMAPRATOR;
228 }
229 return LEXICAL_COMPARATOR;
230 }
231
232 protected Comparator getComparator( Class columnClass ) {
233 Class columnType = columnClass;
234 Comparator comparator = ( Comparator ) columnComparators.get( columnType );
235 if ( comparator != null ) {
236 return comparator;
237 }
238 if ( Comparable.class.isAssignableFrom( columnType ) ) {
239 return COMPARABLE_COMAPRATOR;
240 }
241 return LEXICAL_COMPARATOR;
242 }
243
244 private Row[] getViewToModel() {
245 if ( viewToModel == null ) {
246 int tableModelRowCount = tableModel.getRowCount();
247 viewToModel = new Row[tableModelRowCount];
248 for ( int row = 0; row < tableModelRowCount; row++ ) {
249 viewToModel[row] = new Row( row );
250 }
251
252 if ( isSorting() ) {
253 Arrays.sort( viewToModel );
254 }
255 }
256 return viewToModel;
257 }
258
259 public int modelIndex( int viewIndex ) {
260 return getViewToModel()[viewIndex].modelIndex;
261 }
262
263 int[] getModelToView() {
264 if ( modelToView == null ) {
265 int n = getViewToModel().length;
266 modelToView = new int[n];
267 for ( int i = 0; i < n; i++ ) {
268 modelToView[modelIndex( i )] = i;
269 }
270 }
271 return modelToView;
272 }
273
274
275
276 public int getRowCount() {
277 return ( tableModel == null ) ? 0 : tableModel.getRowCount();
278 }
279
280 public int getColumnCount() {
281 return ( tableModel == null ) ? 0 : tableModel.getColumnCount();
282 }
283
284 public String getColumnName( int column ) {
285 return tableModel.getColumnName( column );
286 }
287
288 public Class getColumnClass( int column ) {
289 return tableModel.getColumnClass( column );
290 }
291
292 public boolean isCellEditable( int row, int column ) {
293 return tableModel.isCellEditable( modelIndex( row ), column );
294 }
295
296 public Object getValueAt( int row, int column ) {
297 return tableModel.getValueAt( modelIndex( row ), column );
298 }
299
300 public void setValueAt( Object aValue, int row, int column ) {
301 tableModel.setValueAt( aValue, modelIndex( row ), column );
302 }
303
304
305
306 private class Row implements Comparable {
307 int modelIndex;
308
309 public Row( int index ) {
310 this.modelIndex = index;
311 }
312
313 public int compareTo( Object o ) {
314 int row1 = modelIndex;
315 int row2 = ( ( Row ) o ).modelIndex;
316
317 for ( Iterator it = sortingColumns.iterator(); it.hasNext(); ) {
318 Directive directive = ( Directive ) it.next();
319 int column = directive.column;
320 Object o1 = tableModel.getValueAt( row1, column );
321 Object o2 = tableModel.getValueAt( row2, column );
322 Comparator comparator = null;
323 int comparison = 0;
324 boolean favor = false;
325
326 if ( o1 == null && o2 == null ) {
327 comparison = 0;
328 } else if ( o1 == null ) {
329 if ( directive.direction == DESCENDING )
330 comparison = -1;
331 else
332
333 comparison = 1;
334 } else if ( o2 == null ) {
335 if ( directive.direction == DESCENDING )
336 comparison = 1;
337 else
338 comparison = -1;
339 } else if ( o1 != null && o2 != null ) {
340 if ( o1.getClass().equals( Double.class ) ) {
341 comparator = getComparator( Double.class );
342 } else if ( o1.getClass().equals( Integer.class ) ) {
343 comparator = getComparator( Integer.class );
344 } else if ( o1.getClass().equals( Point.class ) ) {
345 comparator = getComparator( Double.class );
346
347
348
349
350
351
352
353 /***
354 * @todo we shouldn't include a dependency on JMatrixDisplay here. Find a workaround.
355 */
356 if ( m_matrixDisplay != null ) {
357
358 Point p1 = ( Point ) o1;
359 Point p2 = ( Point ) o2;
360
361 o1 = new Double( m_matrixDisplay.getValue( p1.x, p1.y ) );
362 o2 = new Double( m_matrixDisplay.getValue( p2.x, p2.y ) );
363 }
364 } else if ( o1.getClass().equals( ArrayList.class ) ) {
365 if ( ( ( ArrayList ) o1 ).get( 0 ).getClass().equals(
366 Double.class ) ) {
367 comparator = getComparator( Double.class );
368
369 Double a = ( Double ) ( ( ArrayList ) o1 ).get( 0 );
370 Double b = ( Double ) ( ( ArrayList ) o2 ).get( 0 );
371
372
373
374
375 o1 = a;
376 o2 = b;
377 }
378 } else if ( o1.getClass().equals( Vector.class ) ) {
379 if ( ( ( Vector ) o1 ).get( 0 ).getClass().equals(
380 String.class ) ) {
381 if ( ( ( Vector ) o1 ).size() == 2 ) {
382 comparison = -1;
383 favor = true;
384 } else if ( ( ( Vector ) o2 ).size() == 2 ) {
385 comparison = 1;
386 favor = true;
387 } else
388 {
389 comparator = getComparator( String.class );
390 String a = ( String ) ( ( Vector ) o1 ).get( 0 );
391 String b = ( String ) ( ( Vector ) o2 ).get( 0 );
392 o1 = a;
393 o2 = b;
394 }
395 } else if ( ( ( Vector ) o1 ).get( 0 ).getClass().equals(
396 Double.class ) ) {
397
398
399 Double a = ( Double ) ( ( ArrayList ) o1 ).get( 0 );
400 Double b = ( Double ) ( ( ArrayList ) o2 ).get( 0 );
401 comparator = getComparator( Double.class );
402
403
404
405
406 o1 = a;
407 o2 = b;
408 }
409 } else {
410 comparator = getComparator( column );
411 }
412 if ( favor != true ) {
413 comparison = comparator.compare( o1, o2 );
414 }
415 }
416
417 if ( comparison != 0 ) {
418 return directive.direction == DESCENDING ? -comparison
419 : comparison;
420 }
421 }
422 return 0;
423 }
424 }
425
426 private class TableModelHandler implements TableModelListener {
427 public void tableChanged( TableModelEvent e ) {
428
429 if ( !isSorting() ) {
430 clearSortingState();
431 fireTableChanged( e );
432 return;
433 }
434
435
436
437
438 if ( e.getFirstRow() == TableModelEvent.HEADER_ROW ) {
439 cancelSorting();
440 fireTableChanged( e );
441 return;
442 }
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462 int column = e.getColumn();
463 if ( e.getFirstRow() == e.getLastRow()
464 && column != TableModelEvent.ALL_COLUMNS
465 && getSortingStatus( column ) == NOT_SORTED
466 && modelToView != null ) {
467 int viewIndex = getModelToView()[e.getFirstRow()];
468 fireTableChanged( new TableModelEvent( TableSorter.this, viewIndex,
469 viewIndex, column, e.getType() ) );
470 return;
471 }
472
473
474 clearSortingState();
475 fireTableDataChanged();
476 return;
477 }
478 }
479
480 private class MouseHandler extends MouseAdapter {
481 public void mouseClicked( MouseEvent e ) {
482 if ( e.getButton() == MouseEvent.BUTTON1 ) {
483 JTableHeader h = ( JTableHeader ) e.getSource();
484 TableColumnModel columnModel = h.getColumnModel();
485 int viewColumn = columnModel.getColumnIndexAtX( e.getX() );
486 int column = columnModel.getColumn( viewColumn ).getModelIndex();
487 if ( column != -1 ) {
488 int status = getSortingStatus( column );
489 if ( !e.isControlDown() ) {
490 cancelSorting();
491 }
492
493 status = ( status == ASCENDING ) ? DESCENDING : ASCENDING;
494 setSortingStatus( column, status );
495 }
496 }
497 }
498 }
499
500 private static class Arrow implements Icon {
501 private boolean descending;
502 private int size;
503 private int priority;
504
505 public Arrow( boolean descending, int size, int priority ) {
506 this.descending = descending;
507 this.size = size;
508 this.priority = priority;
509 }
510
511 public void paintIcon( Component c, Graphics g, int x, int y ) {
512 Color color = c == null ? Color.GRAY : c.getBackground();
513
514
515 int dx = ( int ) ( size / 2 * Math.pow( 0.8, priority ) );
516 int dy = descending ? dx : -dx;
517
518 y = y + 5 * size / 6 + ( descending ? -dy : 0 );
519 int shift = descending ? 1 : -1;
520 g.translate( x, y );
521
522
523 g.setColor( color.darker() );
524 g.drawLine( dx / 2, dy, 0, 0 );
525 g.drawLine( dx / 2, dy + shift, 0, shift );
526
527
528 g.setColor( color.brighter() );
529 g.drawLine( dx / 2, dy, dx, 0 );
530 g.drawLine( dx / 2, dy + shift, dx, shift );
531
532
533 if ( descending ) {
534 g.setColor( color.darker().darker() );
535 } else {
536 g.setColor( color.brighter().brighter() );
537 }
538 g.drawLine( dx, 0, 0, 0 );
539
540 g.setColor( color );
541 g.translate( -x, -y );
542 }
543
544 public int getIconWidth() {
545 return size;
546 }
547
548 public int getIconHeight() {
549 return size;
550 }
551 }
552
553 private class SortableHeaderRenderer implements TableCellRenderer {
554 TableCellRenderer tableCellRenderer;
555
556 public SortableHeaderRenderer( TableCellRenderer tableCellRenderer ) {
557 this.tableCellRenderer = tableCellRenderer;
558 }
559
560 public Component getTableCellRendererComponent( JTable table,
561 Object value, boolean isSelected, boolean hasFocus, int row,
562 int column ) {
563 Component c = tableCellRenderer.getTableCellRendererComponent( table,
564 value, isSelected, hasFocus, row, column );
565 if ( c instanceof JLabel ) {
566 JLabel l = ( JLabel ) c;
567 l.setHorizontalTextPosition( SwingConstants.LEFT );
568 l.setVerticalAlignment( SwingConstants.BOTTOM );
569 int modelColumn = table.convertColumnIndexToModel( column );
570 l.setIcon( getHeaderRendererIcon( modelColumn, l.getFont()
571 .getSize() ) );
572 }
573 return c;
574 }
575 }
576
577 private static class Directive {
578 int column;
579 int direction;
580
581 public Directive( int column, int direction ) {
582 this.column = column;
583 this.direction = direction;
584 }
585 }
586 }