View Javadoc

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