View Javadoc

    1   package groovy.inspect.swingui;
    2
    3   /*
    4    * Copyright (c) 2003 Sun Microsystems, Inc. All  Rights Reserved.
    5    *
    6    * Redistribution and use in source and binary forms, with or without
    7    * modification, are permitted provided that the following conditions
    8    * are met:
    9    *
    10   * -Redistributions of source code must retain the above copyright
    11   *  notice, this list of conditions and the following disclaimer.
    12   *
    13   * -Redistribution in binary form must reproduct the above copyright
    14   *  notice, this list of conditions and the following disclaimer in
    15   *  the documentation and/or other materials provided with the distribution.
    16   *
    17   * Neither the name of Sun Microsystems, Inc. or the names of contributors
    18   * may be used to endorse or promote products derived from this software
    19   * without specific prior written permission.
    20   *
    21   * This software is provided "AS IS," without a warranty of any kind. ALL
    22   * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
    23   * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
    24   * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT
    25   * BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE AS A RESULT
    26   * OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE SOFTWARE OR ITS
    27   * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
    28   * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
    29   * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
    30   * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE SOFTWARE, EVEN
    31   * IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
    32   *
    33   * You acknowledge that Software is not designed, licensed or intended for
    34   * use in the design, construction, operation or maintenance of any nuclear
    35   * facility.
    36   */
    37
    38  /*
    39   * @(#)TableSorter.java	1.12 03/01/23
    40   */
    41
    42  /***
    43   * A sorter for TableModels. The sorter has a model (conforming to TableModel)
    44   * and itself implements TableModel. TableSorter does not store or copy
    45   * the data in the TableModel, instead it maintains an array of
    46   * integers which it keeps the same size as the number of rows in its
    47   * model. When the model changes it notifies the sorter that something
    48   * has changed eg. "rowsAdded" so that its internal array of integers
    49   * can be reallocated. As requests are made of the sorter (like
    50   * getValueAt(row, col) it redirects them to its model via the mapping
    51   * array. That way the TableSorter appears to hold another copy of the table
    52   * with the rows in a different order. The sorting algorthm used is stable
    53   * which means that it does not move around rows when its comparison
    54   * function returns 0 to denote that they are equivalent.
    55   *
    56   * @version 1.12 01/23/03
    57   * @author Philip Milne
    58   * @author Minimal adjustments by Dierk Koenig, June 2005
    59   */
    60
    61  import java.awt.event.MouseAdapter;
    62  import java.awt.event.MouseEvent;
    63  import java.util.Date;
    64  import java.util.Vector;
    65
    66  import javax.swing.JTable;
    67  import javax.swing.event.TableModelEvent;
    68  import javax.swing.table.JTableHeader;
    69  import javax.swing.table.TableColumnModel;
    70  import javax.swing.table.TableModel;
    71
    72  public class TableSorter extends TableMap
    73  {
    74      int             indexes[];
    75      Vector          sortingColumns = new Vector();
    76      boolean         ascending = true;
    77      int             lastSortedColumn = -1;
    78
    79      public TableSorter()
    80      {
    81          indexes = new int[0]; // For consistency.
    82      }
    83
    84      public TableSorter(TableModel model)
    85      {
    86          setModel(model);
    87      }
    88
    89      public void setModel(TableModel model) {
    90          super.setModel(model);
    91          reallocateIndexes();
    92      }
    93
    94      public int compareRowsByColumn(int row1, int row2, int column)
    95      {
    96          Class type = model.getColumnClass(column);
    97          TableModel data = model;
    98
    99          // Check for nulls
    100
    101         Object o1 = data.getValueAt(row1, column);
    102         Object o2 = data.getValueAt(row2, column);
    103
    104         // If both values are null return 0
    105         if (o1 == null && o2 == null) {
    106             return 0;
    107         }
    108         else if (o1 == null) { // Define null less than everything.
    109             return -1;
    110         }
    111         else if (o2 == null) {
    112             return 1;
    113         }
    114
    115 /* We copy all returned values from the getValue call in case
    116 an optimised model is reusing one object to return many values.
    117 The Number subclasses in the JDK are immutable and so will not be used in
    118 this way but other subclasses of Number might want to do this to save
    119 space and avoid unnecessary heap allocation.
    120 */
    121         if (type.getSuperclass() == java.lang.Number.class)
    122             {
    123                 Number n1 = (Number)data.getValueAt(row1, column);
    124                 double d1 = n1.doubleValue();
    125                 Number n2 = (Number)data.getValueAt(row2, column);
    126                 double d2 = n2.doubleValue();
    127
    128                 if (d1 < d2)
    129                     return -1;
    130                 else if (d1 > d2)
    131                     return 1;
    132                 else
    133                     return 0;
    134             }
    135         else if (type == java.util.Date.class)
    136             {
    137                 Date d1 = (Date)data.getValueAt(row1, column);
    138                 long n1 = d1.getTime();
    139                 Date d2 = (Date)data.getValueAt(row2, column);
    140                 long n2 = d2.getTime();
    141
    142                 if (n1 < n2)
    143                     return -1;
    144                 else if (n1 > n2)
    145                     return 1;
    146                 else return 0;
    147             }
    148         else if (type == String.class)
    149             {
    150                 String s1 = (String)data.getValueAt(row1, column);
    151                 String s2    = (String)data.getValueAt(row2, column);
    152                 int result = s1.compareTo(s2);
    153
    154                 if (result < 0)
    155                     return -1;
    156                 else if (result > 0)
    157                     return 1;
    158                 else return 0;
    159             }
    160         else if (type == Boolean.class)
    161             {
    162                 Boolean bool1 = (Boolean)data.getValueAt(row1, column);
    163                 boolean b1 = bool1.booleanValue();
    164                 Boolean bool2 = (Boolean)data.getValueAt(row2, column);
    165                 boolean b2 = bool2.booleanValue();
    166
    167                 if (b1 == b2)
    168                     return 0;
    169                 else if (b1) // Define false < true
    170                     return 1;
    171                 else
    172                     return -1;
    173             }
    174         else
    175             {
    176                 Object v1 = data.getValueAt(row1, column);
    177                 String s1 = v1.toString();
    178                 Object v2 = data.getValueAt(row2, column);
    179                 String s2 = v2.toString();
    180                 int result = s1.compareTo(s2);
    181
    182                 if (result < 0)
    183                     return -1;
    184                 else if (result > 0)
    185                     return 1;
    186                 else return 0;
    187             }
    188     }
    189
    190     public int compare(int row1, int row2)
    191     {
    192         for(int level = 0; level < sortingColumns.size(); level++)
    193             {
    194                 Integer column = (Integer)sortingColumns.elementAt(level);
    195                 int result = compareRowsByColumn(row1, row2, column.intValue());
    196                 if (result != 0)
    197                     return ascending ? result : -result;
    198             }
    199         return 0;
    200     }
    201
    202     public void  reallocateIndexes()
    203     {
    204         int rowCount = model.getRowCount();
    205
    206         // Set up a new array of indexes with the right number of elements
    207         // for the new data model.
    208         indexes = new int[rowCount];
    209
    210         // Initialise with the identity mapping.
    211         for(int row = 0; row < rowCount; row++)
    212             indexes[row] = row;
    213     }
    214
    215     public void tableChanged(TableModelEvent e)
    216     {
    217 	System.out.println("Sorter: tableChanged");
    218         reallocateIndexes();
    219
    220         super.tableChanged(e);
    221     }
    222
    223     public void checkModel()
    224     {
    225         if (indexes.length != model.getRowCount()) {
    226             System.err.println("Sorter not informed of a change in model.");
    227         }
    228     }
    229
    230     public void  sort(Object sender)
    231     {
    232         checkModel();
    233         shuttlesort((int[])indexes.clone(), indexes, 0, indexes.length);
    234     }
    235
    236     public void n2sort() {
    237         for(int i = 0; i < getRowCount(); i++) {
    238             for(int j = i+1; j < getRowCount(); j++) {
    239                 if (compare(indexes[i], indexes[j]) == -1) {
    240                     swap(i, j);
    241                 }
    242             }
    243         }
    244     }
    245
    246     // This is a home-grown implementation which we have not had time
    247     // to research - it may perform poorly in some circumstances. It
    248     // requires twice the space of an in-place algorithm and makes
    249     // NlogN assigments shuttling the values between the two
    250     // arrays. The number of compares appears to vary between N-1 and
    251     // NlogN depending on the initial order but the main reason for
    252     // using it here is that, unlike qsort, it is stable.
    253     public void shuttlesort(int from[], int to[], int low, int high) {
    254         if (high - low < 2) {
    255             return;
    256         }
    257         int middle = (low + high)/2;
    258         shuttlesort(to, from, low, middle);
    259         shuttlesort(to, from, middle, high);
    260
    261         int p = low;
    262         int q = middle;
    263
    264         /* This is an optional short-cut; at each recursive call,
    265         check to see if the elements in this subset are already
    266         ordered.  If so, no further comparisons are needed; the
    267         sub-array can just be copied.  The array must be copied rather
    268         than assigned otherwise sister calls in the recursion might
    269         get out of sinc.  When the number of elements is three they
    270         are partitioned so that the first set, [low, mid), has one
    271         element and and the second, [mid, high), has two. We skip the
    272         optimisation when the number of elements is three or less as
    273         the first compare in the normal merge will produce the same
    274         sequence of steps. This optimisation seems to be worthwhile
    275         for partially ordered lists but some analysis is needed to
    276         find out how the performance drops to Nlog(N) as the initial
    277         order diminishes - it may drop very quickly.  */
    278
    279         if (high - low >= 4 && compare(from[middle-1], from[middle]) <= 0) {
    280             for (int i = low; i < high; i++) {
    281                 to[i] = from[i];
    282             }
    283             return;
    284         }
    285
    286         // A normal merge.
    287
    288         for(int i = low; i < high; i++) {
    289             if (q >= high || (p < middle && compare(from[p], from[q]) <= 0)) {
    290                 to[i] = from[p++];
    291             }
    292             else {
    293                 to[i] = from[q++];
    294             }
    295         }
    296     }
    297
    298     public void swap(int i, int j) {
    299         int tmp = indexes[i];
    300         indexes[i] = indexes[j];
    301         indexes[j] = tmp;
    302     }
    303
    304     // The mapping only affects the contents of the data rows.
    305     // Pass all requests to these rows through the mapping array: "indexes".
    306
    307     public Object getValueAt(int aRow, int aColumn)
    308     {
    309         checkModel();
    310         return model.getValueAt(indexes[aRow], aColumn);
    311     }
    312
    313     public void setValueAt(Object aValue, int aRow, int aColumn)
    314     {
    315         checkModel();
    316         model.setValueAt(aValue, indexes[aRow], aColumn);
    317     }
    318
    319     public void sortByColumn(int column) {
    320         sortByColumn(column, true);
    321     }
    322
    323     public void sortByColumn(int column, boolean ascending) {
    324         this.ascending = ascending;
    325         sortingColumns.removeAllElements();
    326         sortingColumns.addElement(new Integer(column));
    327         sort(this);
    328         super.tableChanged(new TableModelEvent(this));
    329     }
    330
    331     // There is no-where else to put this.
    332     // Add a mouse listener to the Table to trigger a table sort
    333     // when a column heading is clicked in the JTable.
    334     public void addMouseListenerToHeaderInTable(JTable table) {
    335         final TableSorter sorter = this;
    336         final JTable tableView = table;
    337         tableView.setColumnSelectionAllowed(false);
    338         MouseAdapter listMouseListener = new MouseAdapter() {
    339             public void mouseClicked(MouseEvent e) {
    340                 TableColumnModel columnModel = tableView.getColumnModel();
    341                 int viewColumn = columnModel.getColumnIndexAtX(e.getX());
    342                 int column = tableView.convertColumnIndexToModel(viewColumn);
    343                 if(e.getClickCount() == 1 && column != -1) {
    344                     if (lastSortedColumn == column) ascending = !ascending;
    345                     sorter.sortByColumn(column, ascending);
    346                     lastSortedColumn = column;
    347                 }
    348              }
    349          };
    350         JTableHeader th = tableView.getTableHeader();
    351         th.addMouseListener(listMouseListener);
    352     }
    353
    354
    355
    356 }
    357