JAL-1620 version bump and release notes
[jalview.git] / src / jalview / util / TableSorter.java
index c03e804..ceaa29d 100755 (executable)
-/*\r
- * Jalview - A Sequence Alignment Editor and Viewer\r
- * Copyright (C) 2006 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle\r
- *\r
- * This program is free software; you can redistribute it and/or\r
- * modify it under the terms of the GNU General Public License\r
- * as published by the Free Software Foundation; either version 2\r
- * of the License, or (at your option) any later version.\r
- *\r
- * This program is distributed in the hope that it will be useful,\r
- * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
- * GNU General Public License for more details.\r
- *\r
- * You should have received a copy of the GNU General Public License\r
- * along with this program; if not, write to the Free Software\r
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA\r
- */\r
-package jalview.util;\r
-\r
-import java.awt.*;\r
-import java.awt.event.*;\r
-import java.util.*;\r
-import java.util.List;\r
-\r
-import javax.swing.*;\r
-import javax.swing.event.TableModelEvent;\r
-import javax.swing.event.TableModelListener;\r
-import javax.swing.table.*;\r
-\r
-/**\r
- * TableSorter is a decorator for TableModels; adding sorting\r
- * functionality to a supplied TableModel. TableSorter does\r
- * not store or copy the data in its TableModel; instead it maintains\r
- * a map from the row indexes of the view to the row indexes of the\r
- * model. As requests are made of the sorter (like getValueAt(row, col))\r
- * they are passed to the underlying model after the row numbers\r
- * have been translated via the internal mapping array. This way,\r
- * the TableSorter appears to hold another copy of the table\r
- * with the rows in a different order.\r
- * <p/>\r
- * TableSorter registers itself as a listener to the underlying model,\r
- * just as the JTable itself would. Events recieved from the model\r
- * are examined, sometimes manipulated (typically widened), and then\r
- * passed on to the TableSorter's listeners (typically the JTable).\r
- * If a change to the model has invalidated the order of TableSorter's\r
- * rows, a note of this is made and the sorter will resort the\r
- * rows the next time a value is requested.\r
- * <p/>\r
- * When the tableHeader property is set, either by using the\r
- * setTableHeader() method or the two argument constructor, the\r
- * table header may be used as a complete UI for TableSorter.\r
- * The default renderer of the tableHeader is decorated with a renderer\r
- * that indicates the sorting status of each column. In addition,\r
- * a mouse listener is installed with the following behavior:\r
- * <ul>\r
- * <li>\r
- * Mouse-click: Clears the sorting status of all other columns\r
- * and advances the sorting status of that column through three\r
- * values: {NOT_SORTED, ASCENDING, DESCENDING} (then back to\r
- * NOT_SORTED again).\r
- * <li>\r
- * SHIFT-mouse-click: Clears the sorting status of all other columns\r
- * and cycles the sorting status of the column through the same\r
- * three values, in the opposite order: {NOT_SORTED, DESCENDING, ASCENDING}.\r
- * <li>\r
- * CONTROL-mouse-click and CONTROL-SHIFT-mouse-click: as above except\r
- * that the changes to the column do not cancel the statuses of columns\r
- * that are already sorting - giving a way to initiate a compound\r
- * sort.\r
- * </ul>\r
- * <p/>\r
- * This is a long overdue rewrite of a class of the same name that\r
- * first appeared in the swing table demos in 1997.\r
- *\r
- * @author Philip Milne\r
- * @author Brendon McLean\r
- * @author Dan van Enckevort\r
- * @author Parwinder Sekhon\r
- * @version 2.0 02/27/04\r
- */\r
-\r
-public class TableSorter extends AbstractTableModel {\r
-    protected TableModel tableModel;\r
-\r
-    public static final int DESCENDING = -1;\r
-    public static final int NOT_SORTED = 0;\r
-    public static final int ASCENDING = 1;\r
-\r
-    private static Directive EMPTY_DIRECTIVE = new Directive(-1, NOT_SORTED);\r
-\r
-    public static final Comparator COMPARABLE_COMAPRATOR = new Comparator() {\r
-        public int compare(Object o1, Object o2) {\r
-            return ((Comparable) o1).compareTo(o2);\r
-        }\r
-    };\r
-    public static final Comparator LEXICAL_COMPARATOR = new Comparator() {\r
-        public int compare(Object o1, Object o2) {\r
-            return o1.toString().compareTo(o2.toString());\r
-        }\r
-    };\r
-\r
-    private Row[] viewToModel;\r
-    private int[] modelToView;\r
-\r
-    private JTableHeader tableHeader;\r
-    private MouseListener mouseListener;\r
-    private TableModelListener tableModelListener;\r
-    private Map columnComparators = new HashMap();\r
-    private List sortingColumns = new ArrayList();\r
-\r
-    public TableSorter() {\r
-        this.mouseListener = new MouseHandler();\r
-        this.tableModelListener = new TableModelHandler();\r
-    }\r
-\r
-    public TableSorter(TableModel tableModel) {\r
-        this();\r
-        setTableModel(tableModel);\r
-    }\r
-\r
-    public TableSorter(TableModel tableModel, JTableHeader tableHeader) {\r
-        this();\r
-        setTableHeader(tableHeader);\r
-        setTableModel(tableModel);\r
-    }\r
-\r
-    private void clearSortingState() {\r
-        viewToModel = null;\r
-        modelToView = null;\r
-    }\r
-\r
-    public TableModel getTableModel() {\r
-        return tableModel;\r
-    }\r
-\r
-    public void setTableModel(TableModel tableModel) {\r
-        if (this.tableModel != null) {\r
-            this.tableModel.removeTableModelListener(tableModelListener);\r
-        }\r
-\r
-        this.tableModel = tableModel;\r
-        if (this.tableModel != null) {\r
-            this.tableModel.addTableModelListener(tableModelListener);\r
-        }\r
-\r
-        clearSortingState();\r
-        fireTableStructureChanged();\r
-    }\r
-\r
-    public JTableHeader getTableHeader() {\r
-        return tableHeader;\r
-    }\r
-\r
-    public void setTableHeader(JTableHeader tableHeader) {\r
-        if (this.tableHeader != null) {\r
-            this.tableHeader.removeMouseListener(mouseListener);\r
-            TableCellRenderer defaultRenderer = this.tableHeader.getDefaultRenderer();\r
-            if (defaultRenderer instanceof SortableHeaderRenderer) {\r
-                this.tableHeader.setDefaultRenderer(((SortableHeaderRenderer) defaultRenderer).tableCellRenderer);\r
-            }\r
-        }\r
-        this.tableHeader = tableHeader;\r
-        if (this.tableHeader != null) {\r
-            this.tableHeader.addMouseListener(mouseListener);\r
-            this.tableHeader.setDefaultRenderer(\r
-                    new SortableHeaderRenderer(this.tableHeader.getDefaultRenderer()));\r
-        }\r
-    }\r
-\r
-    public boolean isSorting() {\r
-        return sortingColumns.size() != 0;\r
-    }\r
-\r
-    private Directive getDirective(int column) {\r
-        for (int i = 0; i < sortingColumns.size(); i++) {\r
-            Directive directive = (Directive)sortingColumns.get(i);\r
-            if (directive.column == column) {\r
-                return directive;\r
-            }\r
-        }\r
-        return EMPTY_DIRECTIVE;\r
-    }\r
-\r
-    public int getSortingStatus(int column) {\r
-        return getDirective(column).direction;\r
-    }\r
-\r
-    private void sortingStatusChanged() {\r
-        clearSortingState();\r
-        fireTableDataChanged();\r
-        if (tableHeader != null) {\r
-            tableHeader.repaint();\r
-        }\r
-    }\r
-\r
-    public void setSortingStatus(int column, int status) {\r
-        Directive directive = getDirective(column);\r
-        if (directive != EMPTY_DIRECTIVE) {\r
-            sortingColumns.remove(directive);\r
-        }\r
-        if (status != NOT_SORTED) {\r
-            sortingColumns.add(new Directive(column, status));\r
-        }\r
-        sortingStatusChanged();\r
-    }\r
-\r
-    protected Icon getHeaderRendererIcon(int column, int size) {\r
-        Directive directive = getDirective(column);\r
-        if (directive == EMPTY_DIRECTIVE) {\r
-            return null;\r
-        }\r
-        return new Arrow(directive.direction == DESCENDING, size, sortingColumns.indexOf(directive));\r
-    }\r
-\r
-    private void cancelSorting() {\r
-        sortingColumns.clear();\r
-        sortingStatusChanged();\r
-    }\r
-\r
-    public void setColumnComparator(Class type, Comparator comparator) {\r
-        if (comparator == null) {\r
-            columnComparators.remove(type);\r
-        } else {\r
-            columnComparators.put(type, comparator);\r
-        }\r
-    }\r
-\r
-    protected Comparator getComparator(int column) {\r
-        Class columnType = tableModel.getColumnClass(column);\r
-        Comparator comparator = (Comparator) columnComparators.get(columnType);\r
-        if (comparator != null) {\r
-            return comparator;\r
-        }\r
-        if (Comparable.class.isAssignableFrom(columnType)) {\r
-            return COMPARABLE_COMAPRATOR;\r
-        }\r
-        return LEXICAL_COMPARATOR;\r
-    }\r
-\r
-    private Row[] getViewToModel() {\r
-        if (viewToModel == null) {\r
-            int tableModelRowCount = tableModel.getRowCount();\r
-            viewToModel = new Row[tableModelRowCount];\r
-            for (int row = 0; row < tableModelRowCount; row++) {\r
-                viewToModel[row] = new Row(row);\r
-            }\r
-\r
-            if (isSorting()) {\r
-                Arrays.sort(viewToModel);\r
-            }\r
-        }\r
-        return viewToModel;\r
-    }\r
-\r
-    public int modelIndex(int viewIndex) {\r
-        return getViewToModel()[viewIndex].modelIndex;\r
-    }\r
-\r
-    private int[] getModelToView() {\r
-        if (modelToView == null) {\r
-            int n = getViewToModel().length;\r
-            modelToView = new int[n];\r
-            for (int i = 0; i < n; i++) {\r
-                modelToView[modelIndex(i)] = i;\r
-            }\r
-        }\r
-        return modelToView;\r
-    }\r
-\r
-    // TableModel interface methods\r
-\r
-    public int getRowCount() {\r
-        return (tableModel == null) ? 0 : tableModel.getRowCount();\r
-    }\r
-\r
-    public int getColumnCount() {\r
-        return (tableModel == null) ? 0 : tableModel.getColumnCount();\r
-    }\r
-\r
-    public String getColumnName(int column) {\r
-        return tableModel.getColumnName(column);\r
-    }\r
-\r
-    public Class getColumnClass(int column) {\r
-        return tableModel.getColumnClass(column);\r
-    }\r
-\r
-    public boolean isCellEditable(int row, int column) {\r
-        return tableModel.isCellEditable(modelIndex(row), column);\r
-    }\r
-\r
-    public Object getValueAt(int row, int column) {\r
-        return tableModel.getValueAt(modelIndex(row), column);\r
-    }\r
-\r
-    public void setValueAt(Object aValue, int row, int column) {\r
-        tableModel.setValueAt(aValue, modelIndex(row), column);\r
-    }\r
-\r
-    // Helper classes\r
-\r
-    private class Row implements Comparable {\r
-        private int modelIndex;\r
-\r
-        public Row(int index) {\r
-            this.modelIndex = index;\r
-        }\r
-\r
-        public int compareTo(Object o) {\r
-            int row1 = modelIndex;\r
-            int row2 = ((Row) o).modelIndex;\r
-\r
-            for (Iterator it = sortingColumns.iterator(); it.hasNext();) {\r
-                Directive directive = (Directive) it.next();\r
-                int column = directive.column;\r
-                Object o1 = tableModel.getValueAt(row1, column);\r
-                Object o2 = tableModel.getValueAt(row2, column);\r
-\r
-                int comparison = 0;\r
-                // Define null less than everything, except null.\r
-                if (o1 == null && o2 == null) {\r
-                    comparison = 0;\r
-                } else if (o1 == null) {\r
-                    comparison = -1;\r
-                } else if (o2 == null) {\r
-                    comparison = 1;\r
-                } else {\r
-                    comparison = getComparator(column).compare(o1, o2);\r
-                }\r
-                if (comparison != 0) {\r
-                    return directive.direction == DESCENDING ? -comparison : comparison;\r
-                }\r
-            }\r
-            return 0;\r
-        }\r
-    }\r
-\r
-    private class TableModelHandler implements TableModelListener {\r
-        public void tableChanged(TableModelEvent e) {\r
-            // If we're not sorting by anything, just pass the event along.\r
-            if (!isSorting()) {\r
-                clearSortingState();\r
-                fireTableChanged(e);\r
-                return;\r
-            }\r
-\r
-            // If the table structure has changed, cancel the sorting; the\r
-            // sorting columns may have been either moved or deleted from\r
-            // the model.\r
-            if (e.getFirstRow() == TableModelEvent.HEADER_ROW) {\r
-                cancelSorting();\r
-                fireTableChanged(e);\r
-                return;\r
-            }\r
-\r
-            // We can map a cell event through to the view without widening\r
-            // when the following conditions apply:\r
-            //\r
-            // a) all the changes are on one row (e.getFirstRow() == e.getLastRow()) and,\r
-            // b) all the changes are in one column (column != TableModelEvent.ALL_COLUMNS) and,\r
-            // c) we are not sorting on that column (getSortingStatus(column) == NOT_SORTED) and,\r
-            // d) a reverse lookup will not trigger a sort (modelToView != null)\r
-            //\r
-            // Note: INSERT and DELETE events fail this test as they have column == ALL_COLUMNS.\r
-            //\r
-            // The last check, for (modelToView != null) is to see if modelToView\r
-            // is already allocated. If we don't do this check; sorting can become\r
-            // a performance bottleneck for applications where cells\r
-            // change rapidly in different parts of the table. If cells\r
-            // change alternately in the sorting column and then outside of\r
-            // it this class can end up re-sorting on alternate cell updates -\r
-            // which can be a performance problem for large tables. The last\r
-            // clause avoids this problem.\r
-            int column = e.getColumn();\r
-            if (e.getFirstRow() == e.getLastRow()\r
-                    && column != TableModelEvent.ALL_COLUMNS\r
-                    && getSortingStatus(column) == NOT_SORTED\r
-                    && modelToView != null) {\r
-                int viewIndex = getModelToView()[e.getFirstRow()];\r
-                fireTableChanged(new TableModelEvent(TableSorter.this,\r
-                                                     viewIndex, viewIndex,\r
-                                                     column, e.getType()));\r
-                return;\r
-            }\r
-\r
-            // Something has happened to the data that may have invalidated the row order.\r
-            clearSortingState();\r
-            fireTableDataChanged();\r
-            return;\r
-        }\r
-    }\r
-\r
-    private class MouseHandler extends MouseAdapter {\r
-        public void mouseClicked(MouseEvent e) {\r
-            JTableHeader h = (JTableHeader) e.getSource();\r
-            TableColumnModel columnModel = h.getColumnModel();\r
-            int viewColumn = columnModel.getColumnIndexAtX(e.getX());\r
-            int column = columnModel.getColumn(viewColumn).getModelIndex();\r
-            if (column != -1) {\r
-                int status = getSortingStatus(column);\r
-                if (!e.isControlDown()) {\r
-                    cancelSorting();\r
-                }\r
-                // Cycle the sorting states through {NOT_SORTED, ASCENDING, DESCENDING} or\r
-                // {NOT_SORTED, DESCENDING, ASCENDING} depending on whether shift is pressed.\r
-                status = status + (e.isShiftDown() ? -1 : 1);\r
-                status = (status + 4) % 3 - 1; // signed mod, returning {-1, 0, 1}\r
-                setSortingStatus(column, status);\r
-            }\r
-        }\r
-    }\r
-\r
-    private static class Arrow implements Icon {\r
-        private boolean descending;\r
-        private int size;\r
-        private int priority;\r
-\r
-        public Arrow(boolean descending, int size, int priority) {\r
-            this.descending = descending;\r
-            this.size = size;\r
-            this.priority = priority;\r
-        }\r
-\r
-        public void paintIcon(Component c, Graphics g, int x, int y) {\r
-            Color color = c == null ? Color.GRAY : c.getBackground();\r
-            // In a compound sort, make each succesive triangle 20%\r
-            // smaller than the previous one.\r
-            int dx = (int)(size/2*Math.pow(0.8, priority));\r
-            int dy = descending ? dx : -dx;\r
-            // Align icon (roughly) with font baseline.\r
-            y = y + 5*size/6 + (descending ? -dy : 0);\r
-            int shift = descending ? 1 : -1;\r
-            g.translate(x, y);\r
-\r
-            // Right diagonal.\r
-            g.setColor(color.darker());\r
-            g.drawLine(dx / 2, dy, 0, 0);\r
-            g.drawLine(dx / 2, dy + shift, 0, shift);\r
-\r
-            // Left diagonal.\r
-            g.setColor(color.brighter());\r
-            g.drawLine(dx / 2, dy, dx, 0);\r
-            g.drawLine(dx / 2, dy + shift, dx, shift);\r
-\r
-            // Horizontal line.\r
-            if (descending) {\r
-                g.setColor(color.darker().darker());\r
-            } else {\r
-                g.setColor(color.brighter().brighter());\r
-            }\r
-            g.drawLine(dx, 0, 0, 0);\r
-\r
-            g.setColor(color);\r
-            g.translate(-x, -y);\r
-        }\r
-\r
-        public int getIconWidth() {\r
-            return size;\r
-        }\r
-\r
-        public int getIconHeight() {\r
-            return size;\r
-        }\r
-    }\r
-\r
-    private class SortableHeaderRenderer implements TableCellRenderer {\r
-        private TableCellRenderer tableCellRenderer;\r
-\r
-        public SortableHeaderRenderer(TableCellRenderer tableCellRenderer) {\r
-            this.tableCellRenderer = tableCellRenderer;\r
-        }\r
-\r
-        public Component getTableCellRendererComponent(JTable table,\r
-                                                       Object value,\r
-                                                       boolean isSelected,\r
-                                                       boolean hasFocus,\r
-                                                       int row,\r
-                                                       int column) {\r
-            Component c = tableCellRenderer.getTableCellRendererComponent(table,\r
-                    value, isSelected, hasFocus, row, column);\r
-            if (c instanceof JLabel) {\r
-                JLabel l = (JLabel) c;\r
-                l.setHorizontalTextPosition(JLabel.LEFT);\r
-                int modelColumn = table.convertColumnIndexToModel(column);\r
-                l.setIcon(getHeaderRendererIcon(modelColumn, l.getFont().getSize()));\r
-            }\r
-            return c;\r
-        }\r
-    }\r
-\r
-    private static class Directive {\r
-        private int column;\r
-        private int direction;\r
-\r
-        public Directive(int column, int direction) {\r
-            this.column = column;\r
-            this.direction = direction;\r
-        }\r
-    }\r
-}\r
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2b1)
+ * Copyright (C) 2014 The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.util;
+
+import java.util.*;
+import java.util.List;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.event.*;
+import javax.swing.table.*;
+
+/**
+ * TableSorter is a decorator for TableModels; adding sorting functionality to a
+ * supplied TableModel. TableSorter does not store or copy the data in its
+ * TableModel; instead it maintains a map from the row indexes of the view to
+ * the row indexes of the model. As requests are made of the sorter (like
+ * getValueAt(row, col)) they are passed to the underlying model after the row
+ * numbers have been translated via the internal mapping array. This way, the
+ * TableSorter appears to hold another copy of the table with the rows in a
+ * different order.
+ * <p/>
+ * TableSorter registers itself as a listener to the underlying model, just as
+ * the JTable itself would. Events recieved from the model are examined,
+ * sometimes manipulated (typically widened), and then passed on to the
+ * TableSorter's listeners (typically the JTable). If a change to the model has
+ * invalidated the order of TableSorter's rows, a note of this is made and the
+ * sorter will resort the rows the next time a value is requested.
+ * <p/>
+ * When the tableHeader property is set, either by using the setTableHeader()
+ * method or the two argument constructor, the table header may be used as a
+ * complete UI for TableSorter. The default renderer of the tableHeader is
+ * decorated with a renderer that indicates the sorting status of each column.
+ * In addition, a mouse listener is installed with the following behavior:
+ * <ul>
+ * <li>Mouse-click: Clears the sorting status of all other columns and advances
+ * the sorting status of that column through three values: {NOT_SORTED,
+ * ASCENDING, DESCENDING} (then back to NOT_SORTED again).
+ * <li>SHIFT-mouse-click: Clears the sorting status of all other columns and
+ * cycles the sorting status of the column through the same three values, in the
+ * opposite order: {NOT_SORTED, DESCENDING, ASCENDING}.
+ * <li>CONTROL-mouse-click and CONTROL-SHIFT-mouse-click: as above except that
+ * the changes to the column do not cancel the statuses of columns that are
+ * already sorting - giving a way to initiate a compound sort.
+ * </ul>
+ * <p/>
+ * This is a long overdue rewrite of a class of the same name that first
+ * appeared in the swing table demos in 1997.
+ * 
+ * @author Philip Milne
+ * @author Brendon McLean
+ * @author Dan van Enckevort
+ * @author Parwinder Sekhon
+ * @version 2.0 02/27/04
+ */
+
+public class TableSorter extends AbstractTableModel
+{
+  protected TableModel tableModel;
+
+  public static final int DESCENDING = -1;
+
+  public static final int NOT_SORTED = 0;
+
+  public static final int ASCENDING = 1;
+
+  private static Directive EMPTY_DIRECTIVE = new Directive(-1, NOT_SORTED);
+
+  public static final Comparator COMPARABLE_COMAPRATOR = new Comparator()
+  {
+    public int compare(Object o1, Object o2)
+    {
+      return ((Comparable) o1).compareTo(o2);
+    }
+  };
+
+  public static final Comparator LEXICAL_COMPARATOR = new Comparator()
+  {
+    public int compare(Object o1, Object o2)
+    {
+      return o1.toString().compareTo(o2.toString());
+    }
+  };
+
+  private Row[] viewToModel;
+
+  private int[] modelToView;
+
+  private JTableHeader tableHeader;
+
+  private MouseListener mouseListener;
+
+  private TableModelListener tableModelListener;
+
+  private Map columnComparators = new HashMap();
+
+  private List sortingColumns = new ArrayList();
+
+  public TableSorter()
+  {
+    this.mouseListener = new MouseHandler();
+    this.tableModelListener = new TableModelHandler();
+  }
+
+  public TableSorter(TableModel tableModel)
+  {
+    this();
+    setTableModel(tableModel);
+  }
+
+  public TableSorter(TableModel tableModel, JTableHeader tableHeader)
+  {
+    this();
+    setTableHeader(tableHeader);
+    setTableModel(tableModel);
+  }
+
+  private void clearSortingState()
+  {
+    viewToModel = null;
+    modelToView = null;
+  }
+
+  public TableModel getTableModel()
+  {
+    return tableModel;
+  }
+
+  public void setTableModel(TableModel tableModel)
+  {
+    if (this.tableModel != null)
+    {
+      this.tableModel.removeTableModelListener(tableModelListener);
+    }
+
+    this.tableModel = tableModel;
+    if (this.tableModel != null)
+    {
+      this.tableModel.addTableModelListener(tableModelListener);
+    }
+
+    clearSortingState();
+    fireTableStructureChanged();
+  }
+
+  public JTableHeader getTableHeader()
+  {
+    return tableHeader;
+  }
+
+  public void setTableHeader(JTableHeader tableHeader)
+  {
+    if (this.tableHeader != null)
+    {
+      this.tableHeader.removeMouseListener(mouseListener);
+      TableCellRenderer defaultRenderer = this.tableHeader
+              .getDefaultRenderer();
+      if (defaultRenderer instanceof SortableHeaderRenderer)
+      {
+        this.tableHeader
+                .setDefaultRenderer(((SortableHeaderRenderer) defaultRenderer).tableCellRenderer);
+      }
+    }
+    this.tableHeader = tableHeader;
+    if (this.tableHeader != null)
+    {
+      this.tableHeader.addMouseListener(mouseListener);
+      this.tableHeader.setDefaultRenderer(new SortableHeaderRenderer(
+              this.tableHeader.getDefaultRenderer()));
+    }
+  }
+
+  public boolean isSorting()
+  {
+    return sortingColumns.size() != 0;
+  }
+
+  private Directive getDirective(int column)
+  {
+    for (int i = 0; i < sortingColumns.size(); i++)
+    {
+      Directive directive = (Directive) sortingColumns.get(i);
+      if (directive.column == column)
+      {
+        return directive;
+      }
+    }
+    return EMPTY_DIRECTIVE;
+  }
+
+  public int getSortingStatus(int column)
+  {
+    return getDirective(column).direction;
+  }
+
+  private void sortingStatusChanged()
+  {
+    clearSortingState();
+    fireTableDataChanged();
+    if (tableHeader != null)
+    {
+      tableHeader.repaint();
+    }
+  }
+
+  public void setSortingStatus(int column, int status)
+  {
+    Directive directive = getDirective(column);
+    if (directive != EMPTY_DIRECTIVE)
+    {
+      sortingColumns.remove(directive);
+    }
+    if (status != NOT_SORTED)
+    {
+      sortingColumns.add(new Directive(column, status));
+    }
+    sortingStatusChanged();
+  }
+
+  protected Icon getHeaderRendererIcon(int column, int size)
+  {
+    Directive directive = getDirective(column);
+    if (directive == EMPTY_DIRECTIVE)
+    {
+      return null;
+    }
+    return new Arrow(directive.direction == DESCENDING, size,
+            sortingColumns.indexOf(directive));
+  }
+
+  private void cancelSorting()
+  {
+    sortingColumns.clear();
+    sortingStatusChanged();
+  }
+
+  public void setColumnComparator(Class type, Comparator comparator)
+  {
+    if (comparator == null)
+    {
+      columnComparators.remove(type);
+    }
+    else
+    {
+      columnComparators.put(type, comparator);
+    }
+  }
+
+  protected Comparator getComparator(int column)
+  {
+    Class columnType = tableModel.getColumnClass(column);
+    Comparator comparator = (Comparator) columnComparators.get(columnType);
+    if (comparator != null)
+    {
+      return comparator;
+    }
+    if (Comparable.class.isAssignableFrom(columnType))
+    {
+      return COMPARABLE_COMAPRATOR;
+    }
+    return LEXICAL_COMPARATOR;
+  }
+
+  private Row[] getViewToModel()
+  {
+    if (viewToModel == null)
+    {
+      int tableModelRowCount = tableModel.getRowCount();
+      viewToModel = new Row[tableModelRowCount];
+      for (int row = 0; row < tableModelRowCount; row++)
+      {
+        viewToModel[row] = new Row(row);
+      }
+
+      if (isSorting())
+      {
+        Arrays.sort(viewToModel);
+      }
+    }
+    return viewToModel;
+  }
+
+  public int modelIndex(int viewIndex)
+  {
+    return getViewToModel()[viewIndex].modelIndex;
+  }
+
+  private int[] getModelToView()
+  {
+    if (modelToView == null)
+    {
+      int n = getViewToModel().length;
+      modelToView = new int[n];
+      for (int i = 0; i < n; i++)
+      {
+        modelToView[modelIndex(i)] = i;
+      }
+    }
+    return modelToView;
+  }
+
+  // TableModel interface methods
+
+  public int getRowCount()
+  {
+    return (tableModel == null) ? 0 : tableModel.getRowCount();
+  }
+
+  public int getColumnCount()
+  {
+    return (tableModel == null) ? 0 : tableModel.getColumnCount();
+  }
+
+  public String getColumnName(int column)
+  {
+    return tableModel.getColumnName(column);
+  }
+
+  public Class getColumnClass(int column)
+  {
+    return tableModel.getColumnClass(column);
+  }
+
+  public boolean isCellEditable(int row, int column)
+  {
+    return tableModel.isCellEditable(modelIndex(row), column);
+  }
+
+  public Object getValueAt(int row, int column)
+  {
+    return tableModel.getValueAt(modelIndex(row), column);
+  }
+
+  public void setValueAt(Object aValue, int row, int column)
+  {
+    tableModel.setValueAt(aValue, modelIndex(row), column);
+  }
+
+  // Helper classes
+
+  private class Row implements Comparable
+  {
+    private int modelIndex;
+
+    public Row(int index)
+    {
+      this.modelIndex = index;
+    }
+
+    public int compareTo(Object o)
+    {
+      int row1 = modelIndex;
+      int row2 = ((Row) o).modelIndex;
+
+      for (Iterator it = sortingColumns.iterator(); it.hasNext();)
+      {
+        Directive directive = (Directive) it.next();
+        int column = directive.column;
+        Object o1 = tableModel.getValueAt(row1, column);
+        Object o2 = tableModel.getValueAt(row2, column);
+
+        int comparison = 0;
+        // Define null less than everything, except null.
+        if (o1 == null && o2 == null)
+        {
+          comparison = 0;
+        }
+        else if (o1 == null)
+        {
+          comparison = -1;
+        }
+        else if (o2 == null)
+        {
+          comparison = 1;
+        }
+        else
+        {
+          comparison = getComparator(column).compare(o1, o2);
+        }
+        if (comparison != 0)
+        {
+          return directive.direction == DESCENDING ? -comparison
+                  : comparison;
+        }
+      }
+      return 0;
+    }
+  }
+
+  private class TableModelHandler implements TableModelListener
+  {
+    public void tableChanged(TableModelEvent e)
+    {
+      // If we're not sorting by anything, just pass the event along.
+      if (!isSorting())
+      {
+        clearSortingState();
+        fireTableChanged(e);
+        return;
+      }
+
+      // If the table structure has changed, cancel the sorting; the
+      // sorting columns may have been either moved or deleted from
+      // the model.
+      if (e.getFirstRow() == TableModelEvent.HEADER_ROW)
+      {
+        cancelSorting();
+        fireTableChanged(e);
+        return;
+      }
+
+      // We can map a cell event through to the view without widening
+      // when the following conditions apply:
+      //
+      // a) all the changes are on one row (e.getFirstRow() == e.getLastRow())
+      // and,
+      // b) all the changes are in one column (column !=
+      // TableModelEvent.ALL_COLUMNS) and,
+      // c) we are not sorting on that column (getSortingStatus(column) ==
+      // NOT_SORTED) and,
+      // d) a reverse lookup will not trigger a sort (modelToView != null)
+      //
+      // Note: INSERT and DELETE events fail this test as they have column ==
+      // ALL_COLUMNS.
+      //
+      // The last check, for (modelToView != null) is to see if modelToView
+      // is already allocated. If we don't do this check; sorting can become
+      // a performance bottleneck for applications where cells
+      // change rapidly in different parts of the table. If cells
+      // change alternately in the sorting column and then outside of
+      // it this class can end up re-sorting on alternate cell updates -
+      // which can be a performance problem for large tables. The last
+      // clause avoids this problem.
+      int column = e.getColumn();
+      if (e.getFirstRow() == e.getLastRow()
+              && column != TableModelEvent.ALL_COLUMNS
+              && getSortingStatus(column) == NOT_SORTED
+              && modelToView != null)
+      {
+        int viewIndex = getModelToView()[e.getFirstRow()];
+        fireTableChanged(new TableModelEvent(TableSorter.this, viewIndex,
+                viewIndex, column, e.getType()));
+        return;
+      }
+
+      // Something has happened to the data that may have invalidated the row
+      // order.
+      clearSortingState();
+      fireTableDataChanged();
+      return;
+    }
+  }
+
+  private class MouseHandler extends MouseAdapter
+  {
+    public void mouseClicked(MouseEvent e)
+    {
+      JTableHeader h = (JTableHeader) e.getSource();
+      TableColumnModel columnModel = h.getColumnModel();
+      int viewColumn = columnModel.getColumnIndexAtX(e.getX());
+      int column = columnModel.getColumn(viewColumn).getModelIndex();
+      if (column != -1)
+      {
+        int status = getSortingStatus(column);
+        if (!e.isControlDown())
+        {
+          cancelSorting();
+        }
+        // Cycle the sorting states through {NOT_SORTED, ASCENDING, DESCENDING}
+        // or
+        // {NOT_SORTED, DESCENDING, ASCENDING} depending on whether shift is
+        // pressed.
+        status = status + (e.isShiftDown() ? -1 : 1);
+        status = (status + 4) % 3 - 1; // signed mod, returning {-1, 0, 1}
+        setSortingStatus(column, status);
+      }
+    }
+  }
+
+  private static class Arrow implements Icon
+  {
+    private boolean descending;
+
+    private int size;
+
+    private int priority;
+
+    public Arrow(boolean descending, int size, int priority)
+    {
+      this.descending = descending;
+      this.size = size;
+      this.priority = priority;
+    }
+
+    public void paintIcon(Component c, Graphics g, int x, int y)
+    {
+      Color color = c == null ? Color.GRAY : c.getBackground();
+      // In a compound sort, make each succesive triangle 20%
+      // smaller than the previous one.
+      int dx = (int) (size / 2 * Math.pow(0.8, priority));
+      int dy = descending ? dx : -dx;
+      // Align icon (roughly) with font baseline.
+      y = y + 5 * size / 6 + (descending ? -dy : 0);
+      int shift = descending ? 1 : -1;
+      g.translate(x, y);
+
+      // Right diagonal.
+      g.setColor(color.darker());
+      g.drawLine(dx / 2, dy, 0, 0);
+      g.drawLine(dx / 2, dy + shift, 0, shift);
+
+      // Left diagonal.
+      g.setColor(color.brighter());
+      g.drawLine(dx / 2, dy, dx, 0);
+      g.drawLine(dx / 2, dy + shift, dx, shift);
+
+      // Horizontal line.
+      if (descending)
+      {
+        g.setColor(color.darker().darker());
+      }
+      else
+      {
+        g.setColor(color.brighter().brighter());
+      }
+      g.drawLine(dx, 0, 0, 0);
+
+      g.setColor(color);
+      g.translate(-x, -y);
+    }
+
+    public int getIconWidth()
+    {
+      return size;
+    }
+
+    public int getIconHeight()
+    {
+      return size;
+    }
+  }
+
+  private class SortableHeaderRenderer implements TableCellRenderer
+  {
+    private TableCellRenderer tableCellRenderer;
+
+    public SortableHeaderRenderer(TableCellRenderer tableCellRenderer)
+    {
+      this.tableCellRenderer = tableCellRenderer;
+    }
+
+    public Component getTableCellRendererComponent(JTable table,
+            Object value, boolean isSelected, boolean hasFocus, int row,
+            int column)
+    {
+      Component c = tableCellRenderer.getTableCellRendererComponent(table,
+              value, isSelected, hasFocus, row, column);
+      if (c instanceof JLabel)
+      {
+        JLabel l = (JLabel) c;
+        l.setHorizontalTextPosition(JLabel.LEFT);
+        int modelColumn = table.convertColumnIndexToModel(column);
+        l.setIcon(getHeaderRendererIcon(modelColumn, l.getFont().getSize()));
+      }
+      return c;
+    }
+  }
+
+  private static class Directive
+  {
+    private int column;
+
+    private int direction;
+
+    public Directive(int column, int direction)
+    {
+      this.column = column;
+      this.direction = direction;
+    }
+  }
+}