blob: 7c7083d18c715fdd88a0fc2db93a708558d22e8b [file] [log] [blame]
/**
*
*/
package net.sf.taverna.t2.lang.ui;
/**
* Copied from code found at http://www.informit.com/guides/content.aspx?g=java&seqNum=57
*/
import java.util.Date;
import java.util.Vector;
import javax.swing.table.TableModel;
import javax.swing.event.TableModelEvent;
//Imports for picking up mouse events from the JTable.
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.InputEvent;
import javax.swing.JTable;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableColumnModel;
public class TableSorter extends TableMap {
int indexes[];
Vector sortingColumns = new Vector();
boolean ascending = true;
int compares;
public TableSorter() {
indexes = new int[0]; // for consistency
}
public TableSorter(TableModel model) {
setModel(model);
}
public void setModel(TableModel model) {
super.setModel(model);
reallocateIndexes();
}
public int compareRowsByColumn(int row1, int row2, int column) {
Class type = model.getColumnClass(column);
TableModel data = model;
// Check for nulls.
Object o1 = data.getValueAt(row1, column);
Object o2 = data.getValueAt(row2, column);
// If both values are null, return 0.
if (o1 == null && o2 == null) {
return 0;
} else if (o1 == null) { // Define null less than everything.
return -1;
} else if (o2 == null) {
return 1;
}
if (o1 instanceof Comparable) {
return ((Comparable) o1).compareTo(o2);
}
/*
* We copy all returned values from the getValue call in case an
* optimised model is reusing one object to return many values. The
* Number subclasses in the JDK are immutable and so will not be used in
* this way but other subclasses of Number might want to do this to save
* space and avoid unnecessary heap allocation.
*/
if (type.getSuperclass() == java.lang.Number.class) {
Number n1 = (Number) data.getValueAt(row1, column);
double d1 = n1.doubleValue();
Number n2 = (Number) data.getValueAt(row2, column);
double d2 = n2.doubleValue();
if (d1 < d2) {
return -1;
} else if (d1 > d2) {
return 1;
} else {
return 0;
}
} else if (type == java.util.Date.class) {
Date d1 = (Date) data.getValueAt(row1, column);
long n1 = d1.getTime();
Date d2 = (Date) data.getValueAt(row2, column);
long n2 = d2.getTime();
if (n1 < n2) {
return -1;
} else if (n1 > n2) {
return 1;
} else {
return 0;
}
} else if (type == String.class) {
String s1 = (String) data.getValueAt(row1, column);
String s2 = (String) data.getValueAt(row2, column);
int result = s1.compareTo(s2);
if (result < 0) {
return -1;
} else if (result > 0) {
return 1;
} else {
return 0;
}
} else if (type == Boolean.class) {
Boolean bool1 = (Boolean) data.getValueAt(row1, column);
boolean b1 = bool1.booleanValue();
Boolean bool2 = (Boolean) data.getValueAt(row2, column);
boolean b2 = bool2.booleanValue();
if (b1 == b2) {
return 0;
} else if (b1) { // Define false < true
return 1;
} else {
return -1;
}
} else {
Object v1 = data.getValueAt(row1, column);
String s1 = v1.toString();
Object v2 = data.getValueAt(row2, column);
String s2 = v2.toString();
int result = s1.compareTo(s2);
if (result < 0) {
return -1;
} else if (result > 0) {
return 1;
} else {
return 0;
}
}
}
public int compare(int row1, int row2) {
compares++;
for (int level = 0; level < sortingColumns.size(); level++) {
Integer column = (Integer) sortingColumns.elementAt(level);
int result = compareRowsByColumn(row1, row2, column.intValue());
if (result != 0) {
return ascending ? result : -result;
}
}
return 0;
}
public void reallocateIndexes() {
int rowCount = model.getRowCount();
// Set up a new array of indexes with the right number of elements
// for the new data model.
indexes = new int[rowCount];
// Initialise with the identity mapping.
for (int row = 0; row < rowCount; row++) {
indexes[row] = row;
}
}
public void tableChanged(TableModelEvent e) {
// System.out.println("Sorter: tableChanged");
reallocateIndexes();
super.tableChanged(e);
}
public void checkModel() {
if (indexes.length != model.getRowCount()) {
System.err.println("Sorter not informed of a change in model.");
}
}
public void sort(Object sender) {
checkModel();
compares = 0;
// n2sort();
// qsort(0, indexes.length-1);
shuttlesort((int[]) indexes.clone(), indexes, 0, indexes.length);
// System.out.println("Compares: "+compares);
}
public void n2sort() {
for (int i = 0; i < getRowCount(); i++) {
for (int j = i + 1; j < getRowCount(); j++) {
if (compare(indexes[i], indexes[j]) == -1) {
swap(i, j);
}
}
}
}
// This is a home-grown implementation which we have not had time
// to research - it may perform poorly in some circumstances. It
// requires twice the space of an in-place algorithm and makes
// NlogN assigments shuttling the values between the two
// arrays. The number of compares appears to vary between N-1 and
// NlogN depending on the initial order but the main reason for
// using it here is that, unlike qsort, it is stable.
public void shuttlesort(int from[], int to[], int low, int high) {
if (high - low < 2) {
return;
}
int middle = (low + high) / 2;
shuttlesort(to, from, low, middle);
shuttlesort(to, from, middle, high);
int p = low;
int q = middle;
/*
* This is an optional short-cut; at each recursive call, check to see
* if the elements in this subset are already ordered. If so, no further
* comparisons are needed; the sub-array can just be copied. The array
* must be copied rather than assigned otherwise sister calls in the
* recursion might get out of sinc. When the number of elements is three
* they are partitioned so that the first set, [low, mid), has one
* element and and the second, [mid, high), has two. We skip the
* optimisation when the number of elements is three or less as the
* first compare in the normal merge will produce the same sequence of
* steps. This optimisation seems to be worthwhile for partially ordered
* lists but some analysis is needed to find out how the performance
* drops to Nlog(N) as the initial order diminishes - it may drop very
* quickly.
*/
if (high - low >= 4 && compare(from[middle - 1], from[middle]) <= 0) {
for (int i = low; i < high; i++) {
to[i] = from[i];
}
return;
}
// A normal merge.
for (int i = low; i < high; i++) {
if (q >= high || (p < middle && compare(from[p], from[q]) <= 0)) {
to[i] = from[p++];
} else {
to[i] = from[q++];
}
}
}
public void swap(int i, int j) {
int tmp = indexes[i];
indexes[i] = indexes[j];
indexes[j] = tmp;
}
// The mapping only affects the contents of the data rows.
// Pass all requests to these rows through the mapping array: "indexes".
public Object getValueAt(int aRow, int aColumn) {
checkModel();
return model.getValueAt(indexes[aRow], aColumn);
}
public void setValueAt(Object aValue, int aRow, int aColumn) {
checkModel();
model.setValueAt(aValue, indexes[aRow], aColumn);
}
public void sortByColumn(int column) {
sortByColumn(column, true);
}
public void sortByColumn(int column, boolean ascending) {
this.ascending = ascending;
sortingColumns.removeAllElements();
sortingColumns.addElement(new Integer(column));
sort(this);
super.tableChanged(new TableModelEvent(this));
}
private int lastSortedColumn = -1;
private boolean lastAscending = false;
public void sortColumn(JTable tableView, int column) {
int currentlySelectedRow = tableView.getSelectedRow();
int underlyingSelectedRow = -1;
if (currentlySelectedRow != -1) {
underlyingSelectedRow = transposeRow(currentlySelectedRow);
}
// System.out.println("Sorting ...");
boolean ascendingColumn = true;
if (lastSortedColumn == column) {
ascendingColumn = !lastAscending;
}
lastSortedColumn = column;
lastAscending = ascendingColumn;
this.sortByColumn(column, ascendingColumn);
if (underlyingSelectedRow != -1) {
for (int row = 0; row < indexes.length; row++) {
if (transposeRow(row) == underlyingSelectedRow) {
tableView.setRowSelectionInterval(row, row);
}
}
}
}
public void resort(JTable tableView) {
if (lastSortedColumn != -1) {
lastAscending = !lastAscending;
sortColumn(tableView, lastSortedColumn);
}
}
// There is no-where else to put this.
// Add a mouse listener to the Table to trigger a table sort
// when a column heading is clicked in the JTable.
public void addMouseListenerToHeaderInTable(JTable table) {
final TableSorter sorter = this;
final JTable tableView = table;
tableView.setColumnSelectionAllowed(false);
MouseAdapter listMouseListener = new MouseAdapter() {
private int lastClickedColumn = -1;
private boolean lastAscending = false;
public void mouseClicked(MouseEvent e) {
TableColumnModel columnModel = tableView.getColumnModel();
int viewColumn = columnModel.getColumnIndexAtX(e.getX());
int column = tableView.convertColumnIndexToModel(viewColumn);
if (e.getClickCount() == 1 && column != -1) {
sortColumn(tableView, column);
}
}
};
JTableHeader th = tableView.getTableHeader();
th.addMouseListener(listMouseListener);
}
public int transposeRow(int row) {
return indexes[row];
}
}