/*******************************************************************************
* Copyright (C) 2009-2011 Amir Hassan <amir@viel-zu.org>
*
* This program 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 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
******************************************************************************/
package org.wooden.component.table;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.swing.Icon;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
public class SortedModel extends AbstractTableModel {
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;
}
@Override
public int getIconHeight() {
return this.size;
}
@Override
public int getIconWidth() {
return this.size;
}
@Override
public void paintIcon(java.awt.Component c, java.awt.Graphics g, int x,
int y) {
java.awt.Color color = c != null ? c.getBackground()
: java.awt.Color.GRAY;
int dx = (int) ((this.size / 2) * Math.pow(0.80000000000000004D,
this.priority));
int dy = this.descending ? dx : -dx;
y = y + (5 * this.size) / 6 + (this.descending ? -dy : 0);
int shift = this.descending ? 1 : -1;
g.translate(x, y);
g.setColor(color.darker());
g.drawLine(dx / 2, dy, 0, 0);
g.drawLine(dx / 2, dy + shift, 0, shift);
g.setColor(color.brighter());
g.drawLine(dx / 2, dy, dx, 0);
g.drawLine(dx / 2, dy + shift, dx, shift);
if (this.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);
}
}
private static class Directive {
private int column;
private int direction;
public Directive(int column, int direction) {
this.column = column;
this.direction = direction;
}
}
private class MouseHandler extends java.awt.event.MouseAdapter {
private MouseHandler() {
super();
}
MouseHandler(MouseHandler mousehandler) {
this();
}
@Override
public void mouseClicked(java.awt.event.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 = SortedModel.this.getSortingStatus(column);
if (!e.isControlDown())
SortedModel.this.cancelSorting();
status += e.isShiftDown() ? -1 : 1;
status = (status + 4) % 3 - 1;
SortedModel.this.setSortingStatus(column, status);
}
}
}
private class Row implements Comparable {
private int modelIndex;
public Row(int index) {
super();
this.modelIndex = index;
}
@Override
public int compareTo(Object o) {
int row1 = this.modelIndex;
int row2 = ((Row) o).modelIndex;
for (Iterator it = SortedModel.this.sortingColumns.iterator(); it
.hasNext();) {
Directive directive = (Directive) it.next();
int column = directive.column;
Object o1 = SortedModel.this.tableModel.getValueAt(row1, column);
Object o2 = SortedModel.this.tableModel.getValueAt(row2, column);
int comparison = 0;
if (o1 == null && o2 == null)
comparison = 0;
else if (o1 == null)
comparison = -1;
else if (o2 == null)
comparison = 1;
else if (o1.equals("..")) {
if (directive.direction == -1)
comparison = 1;
else
comparison = -1;
} else if (o2.equals("..")) {
if (directive.direction == -1)
comparison = -1;
else
comparison = 1;
} else {
comparison = SortedModel.this.getComparator(column).compare(o1, o2);
}
if (comparison != 0)
return directive.direction != -1 ? comparison : -comparison;
}
return 0;
}
}
private class SortableHeaderRenderer implements TableCellRenderer {
private TableCellRenderer tableCellRenderer;
public SortableHeaderRenderer(TableCellRenderer tableCellRenderer) {
super();
this.tableCellRenderer = tableCellRenderer;
}
@Override
public java.awt.Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus, int row, int column) {
java.awt.Component c = this.tableCellRenderer
.getTableCellRendererComponent(table, value, isSelected, hasFocus,
row, column);
if (c instanceof JLabel) {
JLabel l = (JLabel) c;
l.setHorizontalTextPosition(2);
int modelColumn = table.convertColumnIndexToModel(column);
l.setIcon(SortedModel.this.getHeaderRendererIcon(modelColumn, l
.getFont().getSize()));
}
return c;
}
}
private class TableModelHandler implements TableModelListener {
private TableModelHandler() {
super();
}
TableModelHandler(TableModelHandler tablemodelhandler) {
this();
}
@Override
public void tableChanged(TableModelEvent e) {
if (!SortedModel.this.isSorting()) {
SortedModel.this.clearSortingState();
SortedModel.this.fireTableChanged(e);
return;
}
if (e.getFirstRow() == -1) {
SortedModel.this.cancelSorting();
SortedModel.this.fireTableChanged(e);
return;
}
int column = e.getColumn();
if (e.getFirstRow() == e.getLastRow() && column != -1
&& SortedModel.this.getSortingStatus(column) == 0
&& SortedModel.this.modelToView != null) {
int viewIndex = SortedModel.this.getModelToView()[e.getFirstRow()];
SortedModel.this.fireTableChanged(new TableModelEvent(SortedModel.this,
viewIndex, viewIndex, column, e.getType()));
return;
} else {
SortedModel.this.clearSortingState();
SortedModel.this.fireTableDataChanged();
return;
}
}
}
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, 0);
public static final Comparator COMPARABLE_COMAPRATOR = new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((Comparable) o1).compareTo(o2);
}
};
public static final Comparator LEXICAL_COMPARATOR = new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return o1.toString().compareTo(o2.toString());
}
};
private Row viewToModel[];
private int modelToView[];
private JTableHeader tableHeader;
private java.awt.event.MouseListener mouseListener;
private TableModelListener tableModelListener;
private Map columnComparators;
private List sortingColumns;
public SortedModel() {
this.columnComparators = new HashMap();
this.sortingColumns = new ArrayList();
this.mouseListener = new MouseHandler(null);
this.tableModelListener = new TableModelHandler(null);
}
public SortedModel(int sortingColumn, int status) {
this();
this.setSortingStatus(sortingColumn, status);
}
public SortedModel(TableModel tableModel) {
this();
this.setTableModel(tableModel);
}
public SortedModel(TableModel tableModel, JTableHeader tableHeader) {
this();
this.setTableHeader(tableHeader);
this.setTableModel(tableModel);
}
private void cancelSorting() {
this.sortingColumns.clear();
this.sortingStatusChanged();
}
private void clearSortingState() {
this.viewToModel = null;
this.modelToView = null;
}
@Override
public Class getColumnClass(int column) {
return this.tableModel.getColumnClass(column);
}
@Override
public int getColumnCount() {
return this.tableModel != null ? this.tableModel.getColumnCount() : 0;
}
@Override
public String getColumnName(int column) {
return this.tableModel.getColumnName(column);
}
protected Comparator getComparator(int column) {
Class columnType = this.tableModel.getColumnClass(column);
Comparator comparator = (Comparator) this.columnComparators.get(columnType);
if (comparator != null)
return comparator;
if (java.lang.Comparable.class.isAssignableFrom(columnType))
return COMPARABLE_COMAPRATOR;
else
return LEXICAL_COMPARATOR;
}
private Directive getDirective(int column) {
for (int i = 0; i < this.sortingColumns.size(); i++) {
Directive directive = (Directive) this.sortingColumns.get(i);
if (directive.column == column)
return directive;
}
return EMPTY_DIRECTIVE;
}
protected Icon getHeaderRendererIcon(int column, int size) {
Directive directive = this.getDirective(column);
if (directive == EMPTY_DIRECTIVE)
return null;
else
return new Arrow(directive.direction == -1, size,
this.sortingColumns.indexOf(directive));
}
private int[] getModelToView() {
if (this.modelToView == null) {
int n = this.getViewToModel().length;
this.modelToView = new int[n];
for (int i = 0; i < n; i++)
this.modelToView[this.modelIndex(i)] = i;
}
return this.modelToView;
}
@Override
public int getRowCount() {
return this.tableModel != null ? this.tableModel.getRowCount() : 0;
}
public int getSortingStatus(int column) {
return this.getDirective(column).direction;
}
public JTableHeader getTableHeader() {
return this.tableHeader;
}
public TableModel getTableModel() {
return this.tableModel;
}
@Override
public Object getValueAt(int row, int column) {
return this.tableModel.getValueAt(this.modelIndex(row), column);
}
private Row[] getViewToModel() {
if (this.viewToModel == null) {
int tableModelRowCount = this.tableModel.getRowCount();
this.viewToModel = new Row[tableModelRowCount];
for (int row = 0; row < tableModelRowCount; row++)
this.viewToModel[row] = new Row(row);
if (this.isSorting())
Arrays.sort(this.viewToModel);
}
return this.viewToModel;
}
public void install(JTable table) {
this.setTableHeader(table.getTableHeader());
this.setTableModel(table.getModel());
table.setModel(this);
}
@Override
public boolean isCellEditable(int row, int column) {
return this.tableModel.isCellEditable(this.modelIndex(row), column);
}
public boolean isSorting() {
return this.sortingColumns.size() != 0;
}
public int modelIndex(int viewIndex) {
try {
return this.getViewToModel()[viewIndex].modelIndex;
} catch (Exception ex) {
return 0;
}
}
public void setColumnComparator(Class type, Comparator comparator) {
if (comparator == null)
this.columnComparators.remove(type);
else
this.columnComparators.put(type, comparator);
}
public void setSortingStatus(int column, int status) {
Directive directive = this.getDirective(column);
if (directive != EMPTY_DIRECTIVE)
this.sortingColumns.remove(directive);
if (status != 0)
this.sortingColumns.add(new Directive(column, status));
this.sortingStatusChanged();
}
public void setTableHeader(JTableHeader tableHeader) {
if (this.tableHeader != null) {
this.tableHeader.removeMouseListener(this.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(this.mouseListener);
this.tableHeader.setDefaultRenderer(new SortableHeaderRenderer(
this.tableHeader.getDefaultRenderer()));
}
}
public void setTableModel(TableModel tableModel) {
if (this.tableModel != null)
this.tableModel.removeTableModelListener(this.tableModelListener);
this.tableModel = tableModel;
if (this.tableModel != null)
this.tableModel.addTableModelListener(this.tableModelListener);
this.clearSortingState();
this.fireTableStructureChanged();
}
@Override
public void setValueAt(Object aValue, int row, int column) {
this.tableModel.setValueAt(aValue, this.modelIndex(row), column);
}
private void sortingStatusChanged() {
this.clearSortingState();
this.fireTableDataChanged();
if (this.tableHeader != null)
this.tableHeader.repaint();
}
}