001: /*
002: * FilteredTableModel.java - A Filtered table model decorator
003: * :tabSize=8:indentSize=8:noTabs=false:
004: * :folding=explicit:collapseFolds=1:
005: *
006: * Copyright (C) 2007 Shlomy Reinstein
007: * Copyright (C) 2007 Matthieu Casanova
008: *
009: * This program is free software; you can redistribute it and/or
010: * modify it under the terms of the GNU General Public License
011: * as published by the Free Software Foundation; either version 2
012: * of the License, or any later version.
013: *
014: * This program is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
017: * GNU General Public License for more details.
018: *
019: * You should have received a copy of the GNU General Public License
020: * along with this program; if not, write to the Free Software
021: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
022: */
023: package org.gjt.sp.jedit.gui;
024:
025: import javax.swing.*;
026: import javax.swing.event.TableModelEvent;
027: import javax.swing.event.TableModelListener;
028: import javax.swing.table.AbstractTableModel;
029: import javax.swing.table.TableModel;
030: import java.util.*;
031:
032: /**
033: * This TableModel delegates another model to add some filtering features to any
034: * JTable.
035: * To use it you must implement the abstract method passFilter().
036: * This method is called for each row, and must return true if the row should be
037: * visible, and false otherwise.
038: * It is also possible to override the method prepareFilter() that allow you to
039: * transform the filter String. Usually you can return it as lowercase
040: * <p/>
041: * Here is an example of how to use it extracted from the InstallPanel
042: * <code>
043: * PluginTableModel tableModel = new PluginTableModel();
044: * filteredTableModel = new FilteredTableModel<PluginTableModel>(tableModel)
045: * {
046: * public String prepareFilter(String filter)
047: * {
048: * return filter.toLowerCase();
049: * }
050: * <p/>
051: * public boolean passFilter(int row, String filter)
052: * {
053: * String pluginName = (String) delegated.getValueAt(row, 1);
054: * return pluginName.toLowerCase().contains(filter);
055: * }
056: * };
057: * table = new JTable(filteredTableModel);
058: * filteredTableModel.setTable(table);
059: * </code>
060: * It is not mandatory but highly recommended to give the JTable instance to the
061: * model in order to keep the selection after the filter has been updated
062: *
063: * @author Shlomy Reinstein
064: * @author Matthieu Casanova
065: * @version $Id: Buffer.java 8190 2006-12-07 07:58:34Z kpouer $
066: * @since jEdit 4.3pre11
067: */
068: public abstract class FilteredTableModel<E extends TableModel> extends
069: AbstractTableModel implements TableModelListener {
070: /**
071: * The delegated table model.
072: */
073: protected E delegated;
074:
075: private Vector<Integer> filteredIndices;
076:
077: /**
078: * This map contains the delegated indices as key and true indices as values.
079: */
080: private Map<Integer, Integer> invertedIndices;
081:
082: private String filter;
083:
084: private JTable table;
085:
086: //{{{ FilteredTableModel() constructor
087: protected FilteredTableModel(E delegated) {
088: this .delegated = delegated;
089: delegated.addTableModelListener(this );
090: resetFilter();
091: } //}}}
092:
093: //{{{ setTable() method
094: /**
095: * Set the JTable that uses this model.
096: * It is used to restore the selection after the filter has been applied
097: * If it is null,
098: *
099: * @param table the table that uses the model
100: */
101: public void setTable(JTable table) {
102: if (table.getModel() != this )
103: throw new IllegalArgumentException("The given table "
104: + table + " doesn't use this model " + this );
105: this .table = table;
106: } //}}}
107:
108: //{{{ getDelegated() method
109: public E getDelegated() {
110: return delegated;
111: } //}}}
112:
113: //{{{ setDelegated() method
114: public void setDelegated(E delegated) {
115: this .delegated.removeTableModelListener(this );
116: delegated.addTableModelListener(this );
117: this .delegated = delegated;
118: } //}}}
119:
120: //{{{ resetFilter() method
121: private void resetFilter() {
122: filteredIndices = null;
123: } //}}}
124:
125: //{{{ setFilter() method
126: public void setFilter(String filter) {
127: Set<Integer> selectedIndices = saveSelection();
128: this .filter = filter;
129: if (filter != null && filter.length() > 0) {
130: int size = delegated.getRowCount();
131: filter = prepareFilter(filter);
132: Vector<Integer> indices = new Vector<Integer>(size);
133: Map<Integer, Integer> invertedIndices = new HashMap<Integer, Integer>();
134: for (int i = 0; i < size; i++) {
135: if (passFilter(i, filter)) {
136: Integer delegatedIndice = Integer.valueOf(i);
137: indices.add(delegatedIndice);
138:
139: invertedIndices.put(delegatedIndice,
140: indices.size() - 1);
141: }
142: }
143: this .invertedIndices = invertedIndices;
144: filteredIndices = indices;
145: } else
146: resetFilter();
147:
148: fireTableDataChanged();
149: restoreSelection(selectedIndices);
150: } //}}}
151:
152: //{{{ prepareFilter() method
153: public String prepareFilter(String filter) {
154: return filter;
155: } //}}}
156:
157: //{{{ passFilter() method
158: /**
159: * This callback indicates if a row passes the filter.
160: *
161: * @param row the row number the delegate row count
162: * @param filter the filter string
163: * @return true if the row must be visible
164: */
165: public abstract boolean passFilter(int row, String filter);
166:
167: //}}}
168:
169: //{{{ saveSelection()
170:
171: private Set<Integer> saveSelection() {
172: if (table == null)
173: return null;
174: int[] rows = table.getSelectedRows();
175: if (rows.length == 0)
176: return null;
177:
178: Set<Integer> selectedRows = new HashSet<Integer>(rows.length);
179: for (int row : rows) {
180: selectedRows.add(getTrueRow(row));
181: }
182: return selectedRows;
183: } //}}}
184:
185: //{{{ restoreSelection() method
186: private void restoreSelection(Set<Integer> selectedIndices) {
187: if (selectedIndices == null || getRowCount() == 0)
188: return;
189:
190: for (Integer selectedIndex : selectedIndices) {
191: int i = getInternal2ExternalRow(selectedIndex.intValue());
192: if (i != -1)
193: table.getSelectionModel().setSelectionInterval(i, i);
194: }
195: } //}}}
196:
197: //{{{ getRowCount() method
198: public int getRowCount() {
199: if (filteredIndices == null)
200: return delegated.getRowCount();
201: return filteredIndices.size();
202: } //}}}
203:
204: //{{{ getColumnCount() method
205: public int getColumnCount() {
206: return delegated.getColumnCount();
207: } //}}}
208:
209: //{{{ getColumnName() method
210: public String getColumnName(int columnIndex) {
211: return delegated.getColumnName(columnIndex);
212: } //}}}
213:
214: //{{{ getColumnClass() method
215: public Class<?> getColumnClass(int columnIndex) {
216: return delegated.getColumnClass(columnIndex);
217: } //}}}
218:
219: //{{{ isCellEditable() method
220: public boolean isCellEditable(int rowIndex, int columnIndex) {
221: int trueRowIndex = getTrueRow(rowIndex);
222: return delegated.isCellEditable(trueRowIndex, columnIndex);
223: } //}}}
224:
225: //{{{ getValueAt() method
226: public Object getValueAt(int rowIndex, int columnIndex) {
227: int trueRowIndex = getTrueRow(rowIndex);
228: return delegated.getValueAt(trueRowIndex, columnIndex);
229: } //}}}
230:
231: //{{{ setValueAt() method
232: public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
233: int trueRowIndex = getTrueRow(rowIndex);
234: delegated.setValueAt(aValue, trueRowIndex, columnIndex);
235: } //}}}
236:
237: //{{{ getTrueRow() method
238: /**
239: * Converts a row index from the JTable to an internal row index from the delegated model.
240: *
241: * @param rowIndex the row index
242: * @return the row index in the delegated model
243: */
244: public int getTrueRow(int rowIndex) {
245: if (filteredIndices == null)
246: return rowIndex;
247: return filteredIndices.get(rowIndex).intValue();
248: } //}}}
249:
250: //{{{ getInternal2ExternalRow() method
251: /**
252: * Converts a row index from the delegated table model into a row index of the JTable.
253: *
254: * @param internalRowIndex the internal row index
255: * @return the table row index or -1 if this row is not visible
256: */
257: public int getInternal2ExternalRow(int internalRowIndex) {
258: if (invertedIndices == null)
259: return internalRowIndex;
260:
261: Integer externalRowIndex = invertedIndices
262: .get(internalRowIndex);
263: if (externalRowIndex == null)
264: return -1;
265:
266: return externalRowIndex.intValue();
267: } //}}}
268:
269: /**
270: * This fine grain notification tells listeners the exact range
271: * of cells, rows, or columns that changed.
272: */
273: public void tableChanged(TableModelEvent e) {
274: setFilter(filter);
275: }
276: }
|