PageRenderTime 28ms CodeModel.GetById 10ms app.highlight 14ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-5-pre1/org/gjt/sp/jedit/gui/FilteredTableModel.java

#
Java | 298 lines | 150 code | 34 blank | 114 comment | 27 complexity | 6563d7245567d0cefd3670a3340c2c74 MD5 | raw file
  1/*
  2 * FilteredTableModel.java - A Filtered table model decorator
  3 * :tabSize=8:indentSize=8:noTabs=false:
  4 * :folding=explicit:collapseFolds=1:
  5 *
  6 * Copyright (C) 2007 Shlomy Reinstein
  7 * Copyright (C) 2007 Matthieu Casanova
  8 *
  9 * This program is free software; you can redistribute it and/or
 10 * modify it under the terms of the GNU General Public License
 11 * as published by the Free Software Foundation; either version 2
 12 * of the License, or any later version.
 13 *
 14 * This program is distributed in the hope that it will be useful,
 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 17 * GNU General Public License for more details.
 18 *
 19 * You should have received a copy of the GNU General Public License
 20 * along with this program; if not, write to the Free Software
 21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 22 */
 23package org.gjt.sp.jedit.gui;
 24
 25import javax.swing.*;
 26import javax.swing.event.TableModelEvent;
 27import javax.swing.event.TableModelListener;
 28import javax.swing.table.AbstractTableModel;
 29import javax.swing.table.TableModel;
 30import java.util.*;
 31
 32/**
 33 * This TableModel delegates another model to add some filtering features to any
 34 * JTable.
 35 * To use it you must implement the abstract method passFilter().
 36 * This method is called for each row, and must return true if the row should be
 37 * visible, and false otherwise.
 38 * It is also possible to override the method prepareFilter() that allow you to
 39 * transform the filter String. Usually you can return it as lowercase
 40 * <p/>
 41 * Here is an example of how to use it extracted from the InstallPanel
 42 * <code>
 43 * PluginTableModel tableModel = new PluginTableModel();
 44 * filteredTableModel = new FilteredTableModel<PluginTableModel>(tableModel)
 45 * {
 46 * public String prepareFilter(String filter)
 47 * {
 48 * return filter.toLowerCase();
 49 * }
 50 * <p/>
 51 * public boolean passFilter(int row, String filter)
 52 * {
 53 * String pluginName = (String) delegated.getValueAt(row, 1);
 54 * return pluginName.toLowerCase().contains(filter);
 55 * }
 56 * };
 57 * table = new JTable(filteredTableModel);
 58 * filteredTableModel.setTable(table);
 59 * </code>
 60 * It is not mandatory but highly recommended to give the JTable instance to the
 61 * model in order to keep the selection after the filter has been updated
 62 *
 63 * @author Shlomy Reinstein
 64 * @author Matthieu Casanova
 65 * @version $Id: Buffer.java 8190 2006-12-07 07:58:34Z kpouer $
 66 * @since jEdit 4.3pre11
 67 */
 68public abstract class FilteredTableModel<E extends TableModel> extends AbstractTableModel implements TableModelListener
 69{
 70	/**
 71	 * The delegated table model.
 72	 */
 73	protected E delegated;
 74
 75	private Vector<Integer> filteredIndices;
 76
 77	/**
 78	 * This map contains the delegated indices as key and true indices as values.
 79	 */
 80	private Map<Integer, Integer> invertedIndices;
 81
 82	private String filter;
 83
 84	private JTable table;
 85
 86	//{{{ FilteredTableModel() constructor
 87	protected FilteredTableModel(E delegated)
 88	{
 89		this.delegated = delegated;
 90		delegated.addTableModelListener(this);
 91		resetFilter();
 92	} //}}}
 93
 94	//{{{ setTable() method
 95	/**
 96	 * Set the JTable that uses this model.
 97	 * It is used to restore the selection after the filter has been applied
 98	 * If it is null,
 99	 *
100	 * @param table the table that uses the model
101	 */
102	public void setTable(JTable table)
103	{
104		if (table.getModel() != this)
105			throw new IllegalArgumentException("The given table " + table + " doesn't use this model " + this);
106		this.table = table;
107	} //}}}
108
109
110	//{{{ getDelegated() method
111	public E getDelegated()
112	{
113		return delegated;
114	} //}}}
115
116	//{{{ setDelegated() method
117	public void setDelegated(E delegated)
118	{
119		this.delegated.removeTableModelListener(this);
120		delegated.addTableModelListener(this);
121		this.delegated = delegated;
122	} //}}}
123
124	//{{{ resetFilter() method
125	private void resetFilter()
126	{
127		filteredIndices = null;
128	} //}}}
129
130	//{{{ setFilter() method
131	public void setFilter(String filter)
132	{
133		Set<Integer> selectedIndices = saveSelection();
134		this.filter = filter;
135		if (filter != null && filter.length() > 0)
136		{
137			int size = delegated.getRowCount();
138			filter = prepareFilter(filter);
139			Vector<Integer> indices = new Vector<Integer>(size);
140			Map<Integer, Integer> invertedIndices = new HashMap<Integer, Integer>();
141			for (int i = 0; i < size; i++)
142			{
143				if (passFilter(i, filter))
144				{
145					Integer delegatedIndice = Integer.valueOf(i);
146					indices.add(delegatedIndice);
147
148					invertedIndices.put(delegatedIndice, indices.size() - 1);
149				}
150			}
151			this.invertedIndices = invertedIndices;
152			filteredIndices = indices;
153		}
154		else
155			resetFilter();
156
157		fireTableDataChanged();
158		restoreSelection(selectedIndices);
159	} //}}}
160
161	//{{{ prepareFilter() method
162	public String prepareFilter(String filter)
163	{
164		return filter;
165	} //}}}
166
167	//{{{ passFilter() method
168	/**
169	 * This callback indicates if a row passes the filter.
170	 *
171	 * @param row    the row number the delegate row count
172	 * @param filter the filter string
173	 * @return true if the row must be visible
174	 */
175	public abstract boolean passFilter(int row, String filter);
176	//}}}
177
178	//{{{ saveSelection()
179
180	private Set<Integer> saveSelection()
181	{
182		if (table == null)
183			return null;
184		int[] rows = table.getSelectedRows();
185		if (rows.length == 0)
186			return null;
187
188		Set<Integer> selectedRows = new HashSet<Integer>(rows.length);
189		for (int row : rows)
190		{
191			selectedRows.add(getTrueRow(row));
192		}
193		return selectedRows;
194	} //}}}
195
196	//{{{ restoreSelection() method
197	private void restoreSelection(Set<Integer> selectedIndices)
198	{
199		if (selectedIndices == null || getRowCount() == 0)
200			return; 
201		
202		for (Integer selectedIndex : selectedIndices)
203		{
204			int i = getInternal2ExternalRow(selectedIndex.intValue());
205			if (i != -1)
206				table.getSelectionModel().setSelectionInterval(i, i);
207		}
208	}  //}}}
209
210	//{{{ getRowCount() method
211	public int getRowCount()
212	{
213		if (filteredIndices == null)
214			return delegated.getRowCount();
215		return filteredIndices.size();
216	} //}}}
217
218	//{{{ getColumnCount() method
219	public int getColumnCount()
220	{
221		return delegated.getColumnCount();
222	} //}}}
223
224	//{{{ getColumnName() method
225	public String getColumnName(int columnIndex)
226	{
227		return delegated.getColumnName(columnIndex);
228	} //}}}
229
230	//{{{ getColumnClass() method
231	public Class<?> getColumnClass(int columnIndex)
232	{
233		return delegated.getColumnClass(columnIndex);
234	} //}}}
235
236	//{{{ isCellEditable() method
237	public boolean isCellEditable(int rowIndex, int columnIndex)
238	{
239		int trueRowIndex = getTrueRow(rowIndex);
240		return delegated.isCellEditable(trueRowIndex, columnIndex);
241	} //}}}
242
243	//{{{ getValueAt() method
244	public Object getValueAt(int rowIndex, int columnIndex)
245	{
246		int trueRowIndex = getTrueRow(rowIndex);
247		return delegated.getValueAt(trueRowIndex, columnIndex);
248	} //}}}
249
250	//{{{ setValueAt() method
251	public void setValueAt(Object aValue, int rowIndex, int columnIndex)
252	{
253		int trueRowIndex = getTrueRow(rowIndex);
254		delegated.setValueAt(aValue, trueRowIndex, columnIndex);
255	} //}}}
256
257	//{{{ getTrueRow() method
258	/**
259	 * Converts a row index from the JTable to an internal row index from the delegated model.
260	 *
261	 * @param rowIndex the row index
262	 * @return the row index in the delegated model
263	 */
264	public int getTrueRow(int rowIndex)
265	{
266		if (filteredIndices == null)
267			return rowIndex;
268		return filteredIndices.get(rowIndex).intValue();
269	} //}}}
270
271	//{{{ getInternal2ExternalRow() method
272	/**
273	 * Converts a row index from the delegated table model into a row index of the JTable.
274	 *
275	 * @param internalRowIndex the internal row index
276	 * @return the table row index or -1 if this row is not visible
277	 */
278	public int getInternal2ExternalRow(int internalRowIndex)
279	{
280		if (invertedIndices == null)
281			return internalRowIndex;
282
283		Integer externalRowIndex = invertedIndices.get(internalRowIndex);
284		if (externalRowIndex == null)
285			return -1;
286
287		return externalRowIndex.intValue();
288	} //}}}
289
290	/**
291	 * This fine grain notification tells listeners the exact range
292	 * of cells, rows, or columns that changed.
293	 */
294	public void tableChanged(TableModelEvent e)
295	{
296		setFilter(filter);
297	}
298}