001: package net.sourceforge.squirrel_sql.fw.gui;
002:
003: /*
004: * Copyright (C) 2002-2004 Johan Compagner
005: * jcompagner@j-com.nl
006: *
007: * Modifications copyright (C) 2002-2004 Colin Bell
008: * colbell@users.sourceforge.net
009: *
010: * This library is free software; you can redistribute it and/or
011: * modify it under the terms of the GNU Lesser General Public
012: * License as published by the Free Software Foundation; either
013: * version 2.1 of the License, or (at your option) any later version.
014: *
015: * This library 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. See the GNU
018: * Lesser General Public License for more details.
019: *
020: * You should have received a copy of the GNU Lesser General Public
021: * License along with this library; if not, write to the Free Software
022: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
023: */
024: import java.text.Collator;
025: import java.util.Arrays;
026: import java.util.Comparator;
027:
028: import javax.swing.event.TableModelEvent;
029: import javax.swing.event.TableModelListener;
030: import javax.swing.table.AbstractTableModel;
031: import javax.swing.table.TableModel;
032:
033: import net.sourceforge.squirrel_sql.fw.datasetviewer.MyTableModel;
034: import net.sourceforge.squirrel_sql.fw.datasetviewer.RowNumberTableColumn;
035:
036: public class SortableTableModel extends AbstractTableModel {
037: private static final long serialVersionUID = -3534263285990454876L;
038:
039: transient private MyTableModelListener _actualModelLis = new MyTableModelListener();
040:
041: /** Column currently being sorted by. -1 means unsorted. */
042: protected int _iColumn = -1;
043:
044: private boolean _bAscending;
045:
046: /** The actual model that this model is wrapped around. */
047: private TableModel _actualModel;
048:
049: public TableModel getActualModel() {
050: return _actualModel;
051: }
052:
053: /**
054: * Contains the indexes within <TT>_actualModel</TT> after sorting. I.E.
055: * if after sorting <TT>_actualModel[1]</TT> should be the first line and
056: * <TT>_actualModel[0]</TT> should be the second line then <TT>_indexes</TT>
057: * will contain <TT>{1,0}</TT>.
058: */
059: private Integer[] _indexes = new Integer[0];
060:
061: public SortableTableModel(TableModel model) {
062: super ();
063: setActualModel(model);
064: }
065:
066: public void setActualModel(TableModel newModel) {
067: if (_actualModel != null) {
068: _actualModel.removeTableModelListener(_actualModelLis);
069: }
070: _actualModel = newModel;
071: if (_actualModel != null) {
072: _actualModel.addTableModelListener(_actualModelLis);
073: }
074: tableChangedIntern();
075: }
076:
077: /**
078: * Return the number of rows in this table.
079: *
080: * @return Number of rows in this table.
081: */
082: public int getRowCount() {
083: return _actualModel != null ? _actualModel.getRowCount() : 0;
084: }
085:
086: /**
087: * Return the number of columns in this table.
088: *
089: * @return Number of columns in this table.
090: */
091: public int getColumnCount() {
092: return _actualModel != null ? _actualModel.getColumnCount() : 0;
093: }
094:
095: /**
096: * Return the value at the specified row/column.
097: *
098: * @param row Row to return data for.
099: * @param col Column to return data for.
100: *
101: * @return value at the specified row/column.
102: */
103: public Object getValueAt(int row, int col) {
104: if (RowNumberTableColumn.ROW_NUMBER_MODEL_INDEX == col) {
105: return Integer.valueOf(row + 1);
106: } else {
107: if (row < _indexes.length) {
108: return _actualModel.getValueAt(
109: _indexes[row].intValue(), col);
110: } else {
111: return null;
112: }
113: }
114: }
115:
116: /**
117: * Set the value at the specified row/column.
118: *
119: * @param value Value to place in cell.
120: * @param row Row to return data for.
121: * @param col Column to return data for.
122: *
123: * @return value at the specified row/column.
124: */
125: public void setValueAt(Object value, int row, int col) {
126: _actualModel.setValueAt(value, _indexes[row].intValue(), col);
127: }
128:
129: /*
130: * @see TableModel#getColumnName(int)
131: */
132: public String getColumnName(int col) {
133: return _actualModel.getColumnName(col);
134: }
135:
136: /*
137: * @see TableModel#getColumnClass(int)
138: */
139: public Class<?> getColumnClass(int col) {
140: return _actualModel.getColumnClass(col);
141: }
142:
143: /**
144: * Delete the selected rows in the actual table.
145: *
146: * @param rows[] List of row indexes in sorted model
147: */
148: public void deleteRows(int[] rows) {
149: int[] actualRows = new int[rows.length];
150: for (int i = 0; i < rows.length; ++i) {
151: if (rows[i] < _indexes.length) {
152: actualRows[i] = _indexes[rows[i]].intValue();
153: }
154: }
155: ((MyTableModel) _actualModel).deleteRows(actualRows);
156: }
157:
158: /**
159: * Insert a new row into the table.
160: */
161: public void insertRow(Object[] values) {
162: // first attempt to add data to underlying table model
163: ((MyTableModel) _actualModel).addRow(values);
164:
165: // tell the rest of the world that the table has changed.
166: // The 'fire' method used here is very course - it says that the whole table
167: // has been changed when really only one row has been added.
168: // However, finer-grained methods did not seem to cause the right
169: // effect, so I'm using this one untill someone reports a problem with it.
170: // Also, if either of these notifications (the actual model and the sortable
171: // model) are eliminated, it either throws an exception or does not update
172: // the GUI. Go figure.
173: // Finally, the 'fire' on the _acutalModel is triggered from this method
174: // rather than from inside the MyJTable code because the add() method used
175: // to add a row is also used when loading the table with lots of rows, and
176: // in that case we do not want to generate events until all of the rows
177: // have been added, so the 'fire' cannot happen there.
178: ((MyTableModel) _actualModel)
179: .fireTableChanged(new TableModelEvent(_actualModel));
180: fireTableChanged(new TableModelEvent(this ));
181: }
182:
183: /**
184: * The actual model may or may not be editable, so return
185: * the value returned by the model when asked if this
186: * cell is editable.
187: *
188: * @param row Row to return data for.
189: * @param col Column to return data for.
190: *
191: * @return value returned by actual model
192: */
193: public boolean isCellEditable(int row, int col) {
194: return _actualModel.isCellEditable(row, col);
195: }
196:
197: /**
198: * Sorts the column specified in a mode depending if the that column
199: * was last sorted and then inverted that mode. If the column was not
200: * the previous sorted column then it will be sorted in ascending mode.
201: */
202: public boolean sortByColumn(int column) {
203: boolean b = true;
204: if (column == _iColumn) {
205: b = !_bAscending;
206: }
207: sortByColumn(column, b);
208: return b;
209: }
210:
211: /**
212: * Sorts the table by the specified column.
213: *
214: * @param column column to sort by
215: * @param ascending sort ascending if <TT>true</TT> else descending.
216: */
217: public void sortByColumn(int column, boolean ascending) {
218: _iColumn = column;
219: _bAscending = ascending;
220: TableModelComparator comparator = new TableModelComparator(
221: column, ascending);
222: // Should the data be first cloned so that the sorting doesn't take place
223: // on the array that is used in getValue()
224: // TODO: This is a must if sorting is done in a thread! ??
225: Arrays.sort(_indexes, comparator);
226: fireTableDataChanged();
227: }
228:
229: public boolean isSortedAscending() {
230: return _bAscending;
231: }
232:
233: public void tableChanged() {
234: tableChangedIntern();
235:
236: if (-1 != _iColumn) {
237: sortByColumn(_iColumn, _bAscending);
238: } else {
239: fireTableDataChanged();
240: }
241: }
242:
243: private void tableChangedIntern() {
244: _indexes = new Integer[getRowCount()];
245: for (int i = 0; i < _indexes.length; ++i) {
246: _indexes[i] = Integer.valueOf(i);
247: }
248: }
249:
250: /**
251: * When the table is sorted table methods like getSelectedRow() return row indices that
252: * correspond to the view not to the model. This method transforms the view index to
253: * the model index.
254: * @param row The view row index.
255: * @return The model row index. -1 if no model index corresponding to row was found.
256: */
257: public int transfromToModelRow(int row) {
258: if (0 > row || row >= _indexes.length) {
259: return -1;
260: }
261:
262: return _indexes[row].intValue();
263: }
264:
265: class TableModelComparator implements Comparator<Integer> {
266: private int _iColumn;
267: private int _iAscending;
268: private final Collator _collator = Collator.getInstance();
269: private boolean _allDataIsString = true;
270:
271: public TableModelComparator(int iColumn) {
272: this (iColumn, true);
273: }
274:
275: public TableModelComparator(int iColumn, boolean ascending) {
276: _iColumn = iColumn;
277: if (ascending) {
278: _iAscending = 1;
279: } else {
280: _iAscending = -1;
281: }
282: _collator.setStrength(Collator.PRIMARY);
283: _collator.setStrength(Collator.TERTIARY);
284:
285: for (int i = 0, limit = _actualModel.getRowCount(); i < limit; ++i) {
286: final Object data = _actualModel
287: .getValueAt(i, _iColumn);
288: if (!(data instanceof String)) {
289: _allDataIsString = false;
290: break;
291: }
292: }
293: }
294:
295: /*
296: * @see Comparator#compare(Object, Object)
297: */
298: public int compare(final Integer i1, final Integer i2) {
299: final Object data1 = _actualModel.getValueAt(i1.intValue(),
300: _iColumn);
301: final Object data2 = _actualModel.getValueAt(i2.intValue(),
302: _iColumn);
303: try {
304: if (data1 == null && data2 == null) {
305: return 0;
306: }
307: if (data1 == null) {
308: return 1 * _iAscending;
309: }
310: if (data2 == null) {
311: return -1 * _iAscending;
312: }
313: // Comparable c1 = (Comparable)data1;
314: // return c1.compareTo(data2) * _iAscending;
315:
316: if (!_allDataIsString) {
317: final Comparable c1 = (Comparable) data1;
318: return c1.compareTo(data2) * _iAscending;
319: }
320: // return _collator.compare(data1.toString(), data2.toString()) * _iAscending;
321: return _collator
322: .compare((String) data1, (String) data2)
323: * _iAscending;
324: } catch (ClassCastException ex) {
325: return data1.toString().compareTo(data2.toString())
326: * _iAscending;
327: }
328: }
329:
330: }
331:
332: protected class MyTableModelListener implements TableModelListener {
333: public void tableChanged(TableModelEvent evt) {
334: SortableTableModel.this.tableChangedIntern();
335: }
336: }
337: }
|