Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: javatools/swing/table/IndexedTableSorter.java


1   /*
2    * IndexedTableSorter.java
3    *
4    * Created on 11 maggio 2002, 12.01
5       Javatools (modified version) - Some useful general classes.
6       Copyright (C) 2002-2003  Chris Bitmead (original) Antonio Petrelli (modified)
7   
8       This program is free software; you can redistribute it and/or modify
9       it under the terms of the GNU General Public License as published by
10      the Free Software Foundation; either version 2 of the License, or
11      (at your option) any later version.
12  
13      This program is distributed in the hope that it will be useful,
14      but WITHOUT ANY WARRANTY; without even the implied warranty of
15      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16      GNU General Public License for more details.
17  
18      You should have received a copy of the GNU General Public License
19      along with this program; if not, write to the Free Software
20      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  
22      Contact me at: brenmcguire@users.sourceforge.net
23   */
24  
25  package javatools.swing.table;
26  
27  import java.util.*;
28  
29  import javax.swing.table.TableModel;
30  import javax.swing.event.TableModelEvent;
31  
32  // Imports for picking up mouse events from the JTable. 
33  
34  import java.awt.event.MouseAdapter;
35  import java.awt.event.MouseEvent;
36  import java.awt.event.InputEvent;
37  import javax.swing.JTable;
38  import javax.swing.table.JTableHeader;
39  import javax.swing.table.TableColumnModel;
40  import javatools.util.FormattedDate;
41  
42  /**
43   * It seems a table, it is a table sorter for indexed tables.
44   * Based upon the sorter in the Java tutorial.
45   * @author Antonio Petrelli
46   * @version 0.1.7
47   */
48  public class IndexedTableSorter extends IndexedTableMap {
49  
50      /** Creates new IndexedTableSorter */
51      public IndexedTableSorter() {
52          lastSortedColumn = -1;
53          lastSortingDirection = true;
54          indexes = new int[0]; // for consistency
55      }
56  
57      /** Creates a new IndexedTableSorter.
58       * @param model The base table model to use.
59       */    
60      public IndexedTableSorter(IndexedTableModel model) {
61          lastSortedColumn = -1;
62          lastSortingDirection = true;
63          setModel(model);
64      }
65  
66      /** Sets the table model to sort.
67       * @param model The model.
68       */    
69      public void setModel(IndexedTableModel model) {
70          super.setModel(model); 
71          reallocateIndexes(); 
72      }
73      
74      /** Compares two rows by a column.
75       * @param row1 The first row.
76       * @param row2 The second row.
77       * @param column The column to use as a criterion.
78       * @return The compare result.
79       */    
80      public int compareRowsByColumn(int row1, int row2, int column) {
81          Class type = model.getColumnClass(column);
82          TableModel data = model;
83  
84          // Check for nulls.
85  
86          Object o1 = data.getValueAt(row1, column);
87          Object o2 = data.getValueAt(row2, column); 
88  
89          // If both values are null, return 0.
90          if (o1 == null && o2 == null) {
91              return 0; 
92          } else if (o1 == null) { // Define null less than everything. 
93              return -1; 
94          } else if (o2 == null) { 
95              return 1; 
96          }
97  
98          /*
99           * We copy all returned values from the getValue call in case
100          * an optimised model is reusing one object to return many
101          * values.  The Number subclasses in the JDK are immutable and
102          * so will not be used in this way but other subclasses of
103          * Number might want to do this to save space and avoid
104          * unnecessary heap allocation.
105          */
106 
107         if (type.getSuperclass() == java.lang.Number.class) {
108             Number n1 = (Number)data.getValueAt(row1, column);
109             double d1 = n1.doubleValue();
110             Number n2 = (Number)data.getValueAt(row2, column);
111             double d2 = n2.doubleValue();
112 
113             if (d1 < d2) {
114                 return -1;
115             } else if (d1 > d2) {
116                 return 1;
117             } else {
118                 return 0;
119             }
120         } else if (type == java.util.Date.class) {
121             Date d1 = (Date)data.getValueAt(row1, column);
122             long n1 = d1.getTime();
123             Date d2 = (Date)data.getValueAt(row2, column);
124             long n2 = d2.getTime();
125 
126             if (n1 < n2) {
127                 return -1;
128             } else if (n1 > n2) {
129                 return 1;
130             } else {
131                 return 0;
132             }
133         } else if (type == javatools.util.FormattedDate.class) {
134             FormattedDate d1 = (FormattedDate)data.getValueAt(row1, column);
135             long n1 = d1.getTime();
136             FormattedDate d2 = (FormattedDate)data.getValueAt(row2, column);
137             long n2 = d2.getTime();
138 
139             if (n1 < n2) {
140                 return -1;
141             } else if (n1 > n2) {
142                 return 1;
143             } else {
144                 return 0;
145             }
146         } else if (type == String.class) {
147             String s1 = (String)data.getValueAt(row1, column);
148             String s2    = (String)data.getValueAt(row2, column);
149             int result = s1.compareTo(s2);
150 
151             if (result < 0) {
152                 return -1;
153             } else if (result > 0) {
154                 return 1;
155             } else {
156                 return 0;
157             }
158         } else if (type == Boolean.class) {
159             Boolean bool1 = (Boolean)data.getValueAt(row1, column);
160             boolean b1 = bool1.booleanValue();
161             Boolean bool2 = (Boolean)data.getValueAt(row2, column);
162             boolean b2 = bool2.booleanValue();
163 
164             if (b1 == b2) {
165                 return 0;
166             } else if (b1) { // Define false < true
167                 return 1;
168             } else {
169                 return -1;
170             }
171         } else {
172             Object v1 = data.getValueAt(row1, column);
173             String s1 = v1.toString();
174             Object v2 = data.getValueAt(row2, column);
175             String s2 = v2.toString();
176             int result = s1.compareTo(s2);
177 
178             if (result < 0) {
179                 return -1;
180             } else if (result > 0) {
181                 return 1;
182             } else {
183           return 0;
184             }
185         }
186     }
187 
188     /** Compares two columns.
189      * @param row1 The first row.
190      * @param row2 The second row.
191      * @return The compare result.
192      */    
193     public int compare(int row1, int row2) {
194         compares++;
195         for (int level = 0; level < sortingColumns.size(); level++) {
196             Integer column = (Integer)sortingColumns.elementAt(level);
197             int result = compareRowsByColumn(row1, row2, column.intValue());
198             if (result != 0) {
199                 return ascending ? result : -result;
200             }
201         }
202         return 0;
203     }
204 
205     /** Initializes indexes.
206      */    
207     public void reallocateIndexes() {
208         int rowCount = model.getRowCount();
209 
210         // Set up a new array of indexes with the right number of elements
211         // for the new data model.
212         indexes = new int[rowCount];
213 
214         // Initialise with the identity mapping.
215         for (int row = 0; row < rowCount; row++) {
216             indexes[row] = row;
217         }
218     }
219 
220     /** Method that is called whenever the table changes.
221      * @param e The event.
222      */    
223     public void tableChanged(TableModelEvent e) {
224         //System.out.println("Sorter: tableChanged"); 
225         reallocateIndexes();
226 
227         super.tableChanged(e);
228     }
229 
230     /** Checks if the model is OK.
231      */    
232     public void checkModel() {
233         if (indexes.length != model.getRowCount()) {
234             System.err.println("Sorter not informed of a change in model.");
235         }
236     }
237 
238     /** Sorts the table.
239      * @param sender Ignored (what????).
240      */    
241     public void sort(Object sender) {
242         checkModel();
243 
244         compares = 0;
245         // n2sort();
246         // qsort(0, indexes.length-1);
247         shuttlesort((int[])indexes.clone(), indexes, 0, indexes.length);
248         //System.out.println("Compares: "+compares);
249     }
250 
251     /** Sorts 2 items.
252      */    
253     public void n2sort() {
254         for (int i = 0; i < getRowCount(); i++) {
255             for (int j = i+1; j < getRowCount(); j++) {
256                 if (compare(indexes[i], indexes[j]) == -1) {
257                     swap(i, j);
258                 }
259             }
260         }
261     }
262 
263     // This is a home-grown implementation which we have not had time
264     // to research - it may perform poorly in some circumstances. It
265     // requires twice the space of an in-place algorithm and makes
266     // NlogN assigments shuttling the values between the two
267     // arrays. The number of compares appears to vary between N-1 and
268     // NlogN depending on the initial order but the main reason for
269     // using it here is that, unlike qsort, it is stable.
270     /** Sorts using shuttlesort.
271      * @param from The origin indexes.
272      * @param to The destination indexes.
273      * @param low The lower limit.
274      * @param high The higher limit.
275      */    
276     public void shuttlesort(int from[], int to[], int low, int high) {
277         if (high - low < 2) {
278             return;
279         }
280         int middle = (low + high)/2;
281         shuttlesort(to, from, low, middle);
282         shuttlesort(to, from, middle, high);
283 
284         int p = low;
285         int q = middle;
286 
287         /* This is an optional short-cut; at each recursive call,
288         check to see if the elements in this subset are already
289         ordered.  If so, no further comparisons are needed; the
290         sub-array can just be copied.  The array must be copied rather
291         than assigned otherwise sister calls in the recursion might
292         get out of sinc.  When the number of elements is three they
293         are partitioned so that the first set, [low, mid), has one
294         element and and the second, [mid, high), has two. We skip the
295         optimisation when the number of elements is three or less as
296         the first compare in the normal merge will produce the same
297         sequence of steps. This optimisation seems to be worthwhile
298         for partially ordered lists but some analysis is needed to
299         find out how the performance drops to Nlog(N) as the initial
300         order diminishes - it may drop very quickly.  */
301 
302         if (high - low >= 4 && compare(from[middle-1], from[middle]) <= 0) {
303             for (int i = low; i < high; i++) {
304                 to[i] = from[i];
305             }
306             return;
307         }
308 
309         // A normal merge. 
310 
311         for (int i = low; i < high; i++) {
312             if (q >= high || (p < middle && compare(from[p], from[q]) <= 0)) {
313                 to[i] = from[p++];
314             }
315             else {
316                 to[i] = from[q++];
317             }
318         }
319     }
320 
321     /** Swaps (logically) two rows.
322      * @param i The first index.
323      * @param j The second index.
324      */    
325     public void swap(int i, int j) {
326         int tmp = indexes[i];
327         indexes[i] = indexes[j];
328         indexes[j] = tmp;
329     }
330 
331     // The mapping only affects the contents of the data rows.
332     // Pass all requests to these rows through the mapping array: "indexes".
333 
334     /** Returns the value in a certain position.
335      * @param aRow The row index.
336      * @param aColumn The column index.
337      * @return The value.
338      */    
339     public Object getValueAt(int aRow, int aColumn) {
340         checkModel();
341         return model.getValueAt(indexes[aRow], aColumn);
342     }
343 
344     /** Returns the index.
345      * @param row The row index.
346      * @throws ArrayIndexOutOfBoundsException If <CODE>row</CODE> is not correct.
347      * @return The index.
348      */    
349     public Object getIndex(int row) throws ArrayIndexOutOfBoundsException {
350         checkModel();
351         return model.getIndex(indexes[row]);
352     }
353     
354     /** Sets a value in the table.
355      * @param aValue The value.
356      * @param aRow The row index.
357      * @param aColumn The column index.
358      */    
359     public void setValueAt(Object aValue, int aRow, int aColumn) {
360         checkModel();
361         model.setValueAt(aValue, indexes[aRow], aColumn);
362     }
363 
364     /** Sets an index.
365      * @param row The row index.
366      * @param index The index.
367      * @throws ArrayIndexOutOfBoundsException If <CODE>row</CODE> is not correct.
368      */    
369     public void setIndex(int row, Object index) throws ArrayIndexOutOfBoundsException {
370         checkModel();
371         model.setIndex(indexes[row], index);
372     }
373     
374     /** Removes a row.
375      * @param row The row index.
376      */    
377     public void removeRow (int row) {
378         checkModel();
379         model.removeRow(indexes[row]);
380     }
381     
382     /** Sorts the table by a column.
383      * @param column The column to sort by.
384      */    
385     public void sortByColumn(int column) {
386         sortByColumn(column, true);
387     }
388 
389     /** Sorts the table by column.
390      * @param column The column to use.
391      * @param ascending <CODE>true</CODE>: ascending order;
392      * <CODE>false</CODE>: descending order.
393      */    
394     public void sortByColumn(int column, boolean ascending) {
395         this.ascending = ascending;
396         sortingColumns.removeAllElements();
397         sortingColumns.addElement(new Integer(column));
398         sort(this);
399         super.tableChanged(new TableModelEvent(this)); 
400     }
401 
402     // There is no-where else to put this. 
403     // Add a mouse listener to the Table to trigger a table sort 
404     // when a column heading is clicked in the JTable. 
405     /** Method used to add a mouse listener to catch click on table headers.
406      * @param table The table.
407      */    
408     public void addMouseListenerToHeaderInTable(JTable table) { 
409         final IndexedTableSorter sorter = this; 
410         final JTable tableView = table; 
411         tableView.setColumnSelectionAllowed(false); 
412         MouseAdapter listMouseListener = new MouseAdapter() {
413             public void mouseClicked(MouseEvent e) {
414                 TableColumnModel columnModel = tableView.getColumnModel();
415                 int viewColumn = columnModel.getColumnIndexAtX(e.getX());
416                 int column = tableView.convertColumnIndexToModel(viewColumn); 
417                 if (column == lastSortedColumn)
418                     lastSortingDirection = !lastSortingDirection;
419                 else {
420                     lastSortedColumn = column;
421                     lastSortingDirection = true;
422                 }
423                 if (e.getClickCount() == 1 && column != -1) {
424                     //System.out.println("Sorting ..."); 
425                     // int shiftPressed = e.getModifiers()&InputEvent.SHIFT_MASK; 
426                     // boolean ascending = (shiftPressed == 0); 
427                     sorter.sortByColumn(column, lastSortingDirection); 
428                 }
429             }
430         };
431         JTableHeader th = tableView.getTableHeader(); 
432         th.addMouseListener(listMouseListener); 
433     }
434 
435     /** The indexes to use for correct order.
436      */    
437     protected int             indexes[];
438     /** Contains the columns to use to sort.
439      */    
440     protected Vector          sortingColumns = new Vector();
441     /** <CODE>true</CODE>: ascending order;
442      * <CODE>false</CODE>: descending order.
443      */    
444     protected boolean         ascending = true;
445     /** Contains the number of made comparisons.
446      */    
447     protected int compares;
448     /** Contains the last sorted column to alternate ascending and descending order.
449      */    
450     protected int lastSortedColumn;
451     /** Contains the last sorting direction, to alternate ascending and descending
452      * order.
453      */    
454     protected boolean lastSortingDirection;
455 }