001/**
002 * jshred - Shred's Toolbox
003 *
004 * Copyright (C) 2009 Richard "Shred" Körber
005 *   http://jshred.shredzone.org
006 *
007 * This program is free software: you can redistribute it and/or modify
008 * it under the terms of the GNU General Public License / GNU Lesser
009 * General Public License as published by the Free Software Foundation,
010 * either version 3 of the License, or (at your option) any later version.
011 *
012 * Licensed under the Apache License, Version 2.0 (the "License");
013 * you may not use this file except in compliance with the License.
014 *
015 * This program is distributed in the hope that it will be useful,
016 * but WITHOUT ANY WARRANTY; without even the implied warranty of
017 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
018 *
019 */
020package net.shredzone.jshred.swing;
021
022import java.awt.Rectangle;
023
024import javax.swing.JTable;
025import javax.swing.table.JTableHeader;
026import javax.swing.table.TableModel;
027
028/**
029 * This is a {@link JTable} which allows the user to sort each column in ascending or
030 * descending order. Everything you have to do to is just to use the {@link JSortedTable}
031 * instead of a {@link JTable}, and pass a {@link SortableTableModel} to it.
032 * <p>
033 * If you want to use a classic {@link TableModel}, you can wrap it using the
034 * {@link SortableTableModelProxy} object, and then pass the
035 * {@link SortableTableModelProxy} to this class.
036 * <p>
037 * Starting with Java 1.6, Swing brings an own implementation for sortable table, which
038 * may be preferable to this solution.
039 *
040 * @author Richard "Shred" Körber
041 */
042public class JSortedTable extends JTable {
043    private static final long serialVersionUID = 3256728372624110384L;
044
045    /**
046     * Creates a new, empty {@link JSortedTable}.
047     */
048    public JSortedTable() {
049        super();
050    }
051
052    /**
053     * Creates a new {@link JSortedTable} with the given {@link SortableTableModel}.
054     *
055     * @param model
056     *            {@link SortableTableModel} to be used.
057     */
058    public JSortedTable(SortableTableModel model) {
059        super(model);
060    }
061
062    /**
063     * Sets the {@link TableModel} to be used. You must pass a {@link SortableTableModel}
064     * here, otherwise you'll get an {@link IllegalArgumentException}.
065     *
066     * @param model
067     *            A {@link SortableTableModel}
068     */
069    @Override
070    public void setModel(TableModel model) {
071        if (!(model instanceof SortableTableModel)) {
072            throw new IllegalArgumentException("You must provide a SortableTableModel");
073        }
074
075        // --- Remember current column ---
076        int column = 0;
077        boolean desc = false;
078        SortableTableModel current = (SortableTableModel) getModel();
079        if (current != null) {
080            column = current.getSortedColumn();
081            desc = current.isDescending();
082        }
083
084        // --- Set new model ---
085        super.setModel(model);
086
087        // --- Sort by this column ---
088        SortableTableModel newmodel = (SortableTableModel) model;
089        if (column >= newmodel.getColumnCount()) {
090            // Column does not exist any more. Use first column.
091            column = 0;
092            desc = false;
093        }
094        newmodel.sortByColumn(column, desc);
095    }
096
097    /**
098     * Sorts by a certain column. If this is the currently sorted column, the sort order
099     * will be reversed. Otherwise the given column will be sorted ascendingly. This
100     * method simulates a mouse click on the appropriate column header.
101     * <p>
102     * Since R11, the selection will be kept if the {@link SortableTableModelProxy} is
103     * used.
104     *
105     * @param columnIndex
106     *            Column to be sorted.
107     * @since R4
108     */
109    public void sortByColumn(int columnIndex) {
110        // --- Sorting allowed? ---
111        if (getModel() instanceof ExtendedSortableTableModel) {
112            ExtendedSortableTableModel ext = (ExtendedSortableTableModel) getModel();
113            if (!ext.isColumnSortable(columnIndex)) return;
114        }
115
116        // --- Proxy? ---
117        final SortableTableModel model = (SortableTableModel) getModel();
118        SortableTableModelProxy proxy = null;
119        if (model instanceof SortableTableModelProxy) {
120            proxy = (SortableTableModelProxy) model;
121        }
122
123        // --- Remember all selected indices ---
124        int[] rows = null;
125        if (proxy != null) {
126            rows = getSelectedRows();
127            for (int ix = 0; ix < rows.length; ix++) {
128                rows[ix] = proxy.mapRow(rows[ix]);
129            }
130        }
131
132        // --- Change sort order ---
133        if (model.getSortedColumn() != columnIndex) {
134            model.sortByColumn(columnIndex, false);
135        } else {
136            model.sortByColumn(columnIndex, !model.isDescending());
137        }
138
139        // --- Restore the selection ---
140        clearSelection();
141        if (proxy != null && rows != null) {
142            for (int ix = 0; ix < rows.length; ix++) {
143                int row = proxy.unmapRow(rows[ix]);
144                addRowSelectionInterval(row, row);
145                if (ix == 0) {
146                    Rectangle cellRect = getCellRect(row, 0, false);
147                    if (cellRect != null) {
148                        scrollRectToVisible(cellRect);
149                    }
150                }
151            }
152        }
153    }
154
155    @Override
156    protected JTableHeader createDefaultTableHeader() {
157        return new SortTableHeader(columnModel);
158    }
159
160    @Override
161    protected TableModel createDefaultDataModel() {
162        return new SortableTableModelProxy(super.createDefaultDataModel());
163    }
164
165}