001package ca.bc.webarts.widgets;
002
003/********************************************************************************
004 *
005 * SortableTableModel Class
006 * Author   : Amir Kost
007 * Purpose  : A TableModel that enables column sorting by clicking the column
008 *
009 * This class extends the DefaultTableModel class and enables sorting of columns
010 * It sorts the TableModel's dataVector whenever a column is clicked.
011 * Clicking the same column again causes it to be sorted in reverse order.
012 * The sort is based on Java's Collection class sort method.
013 *
014 * Usage :
015 *
016 * SortableTableModel model = new SortableTableModel();
017 * JTable table = new JTable(model);
018 * model.addMouseListenerToHeaderInTable(table);
019 *
020 *******************************************************************************/
021
022import java.util.Vector;
023import java.util.Comparator;
024import java.util.Date;
025import java.util.Collections;
026
027import javax.swing.table.TableModel;
028import javax.swing.table.DefaultTableModel;
029import javax.swing.table.TableColumnModel;
030import javax.swing.table.JTableHeader;
031import javax.swing.event.TableModelEvent;
032import java.awt.event.MouseAdapter;
033import java.awt.event.MouseEvent;
034import java.awt.event.InputEvent;
035import javax.swing.JTable;
036
037public class SortableTableModel extends DefaultTableModel implements Comparator{
038
039    protected   int         currCol;
040    protected   Vector      ascendCol;  // this vector stores the state (ascending or descending) of each column
041    protected   Integer     one         = new Integer(1);
042    protected   Integer     minusOne    = new Integer(-1);
043
044    public SortableTableModel() {
045        super();
046        ascendCol = new Vector();
047    }
048
049    public SortableTableModel(DefaultTableModel copyModel) {
050        super();
051        ascendCol = new Vector();
052        if (copyModel!=null)
053        {
054          Vector columnData = new Vector();
055          Vector data = new Vector(); // vector of vectors
056          for (int i=0;i< copyModel.getColumnCount() ; i++)
057          {
058            columnData.removeAllElements();
059            for (int j = 0; j< copyModel.getRowCount(); j++)
060              columnData.add(copyModel.getValueAt(j,i));
061            super.addColumn(copyModel.getColumnName(i), columnData);
062            ascendCol.add(one);
063          }
064
065        }
066    }
067
068    /*******************************************************************
069     * addColumn methods are inherited from the DefaultTableModel class.
070     *******************************************************************/
071
072    public void addColumn(Object columnName) {
073        super.addColumn(columnName);
074        ascendCol.add(one);
075    }
076
077    public void addColumn(Object columnName, Object[] columnData) {
078        super.addColumn(columnName, columnData);
079        ascendCol.add(one);
080    }
081
082    public void addColumn(Object columnName, Vector columnData) {
083        super.addColumn(columnName, columnData);
084        ascendCol.add(one);
085    }
086
087    /*****************************************************************
088     * This method is the implementation of the Comparator interface.
089     * It is used for sorting the rows
090     *****************************************************************/
091    public int compare(Object v1, Object v2) {
092
093        // the comparison is between 2 vectors, each representing a row
094        // the comparison is done between 2 objects from the different rows that are in the column that is being sorted
095
096        int ascending = ((Integer) ascendCol.get(currCol)).intValue();
097        if (v1 == null && v2 == null) {
098            return 0;
099        } else if (v2 == null) { // Define null less than everything.
100            return 1 * ascending;
101        } else if (v1 == null) {
102            return -1 * ascending;
103        }
104
105        Object o1 = ((Vector) v1).get(currCol);
106        Object o2 = ((Vector) v2).get(currCol);
107
108        // If both values are null, return 0.
109        if (o1 == null && o2 == null) {
110            return 0;
111        } else if (o2 == null) { // Define null less than everything.
112            return 1 * ascending;
113        } else if (o1 == null) {
114            return -1 * ascending;
115        }
116
117        if (o1 instanceof Number && o2 instanceof Number) {
118            Number n1 = (Number) o1;
119            double d1 = n1.doubleValue();
120            Number n2 = (Number) o2;
121            double d2 = n2.doubleValue();
122
123            if (d1 == d2) {
124                return 0;
125            } else if (d1 > d2) {
126                return 1 * ascending;
127            } else {
128                return -1 * ascending;
129            }
130
131        } else if (o1 instanceof Boolean && o2 instanceof Boolean) {
132            Boolean bool1 = (Boolean) o1;
133            boolean b1 = bool1.booleanValue();
134            Boolean bool2 = (Boolean) o2;
135            boolean b2 = bool2.booleanValue();
136
137            if (b1 == b2) {
138                return 0;
139            } else if (b1) {
140                return 1 * ascending;
141            } else {
142                return -1 * ascending;
143            }
144
145        } else {
146            // default case
147            if (o1 instanceof Comparable && o2 instanceof Comparable) {
148                Comparable c1 = (Comparable) o1;
149                Comparable c2 = (Comparable) o2; // superflous cast, no need for it!
150
151                try {
152                    return c1.compareTo(c2) * ascending;
153                } catch (ClassCastException cce) {
154                    // forget it... we'll deal with them like 2 normal objects below.
155                }
156            }
157
158            String s1 = o1.toString();
159            String s2 = o2.toString();
160            return s1.compareTo(s2) * ascending;
161        }
162    }
163
164    /***************************************************************************
165     * This method sorts the rows using Java's Collections class.
166     * After sorting, it changes the state of the column -
167     * if the column was ascending, its new state is descending, and vice versa.
168     ***************************************************************************/
169    public void sort() {
170        Collections.sort(dataVector, this);
171        Integer val = (Integer) ascendCol.get(currCol);
172        ascendCol.remove(currCol);
173        if(val.equals(one)) // change the state of the column
174            ascendCol.add(currCol, minusOne);
175        else
176            ascendCol.add(currCol, one);
177    }
178
179    public void sortByColumn(int column) {
180        this.currCol = column;
181        sort();
182        fireTableChanged(new TableModelEvent(this));
183    }
184
185    // Add a mouse listener to the Table to trigger a table sort
186    // when a column heading is clicked in the JTable.
187    public void addMouseListenerToHeaderInTable(JTable table) {
188        final SortableTableModel sorter = this;
189        final JTable tableView = table;
190        tableView.setColumnSelectionAllowed(false);
191        MouseAdapter listMouseListener = new MouseAdapter() {
192            public void mouseClicked(MouseEvent e) {
193                TableColumnModel columnModel = tableView.getColumnModel();
194                int viewColumn = columnModel.getColumnIndexAtX(e.getX());
195                int column = tableView.convertColumnIndexToModel(viewColumn);
196                if (e.getClickCount() == 1 && column != -1) {
197                    int shiftPressed = e.getModifiers()&InputEvent.SHIFT_MASK;
198                    boolean ascending = (shiftPressed == 0);
199                    sorter.sortByColumn(column);
200                }
201            }
202        };
203        JTableHeader th = tableView.getTableHeader();
204        th.addMouseListener(listMouseListener);
205    }
206
207}
208