1 package net.sourceforge.phpdt.internal.ui.util;
3 import java.util.Comparator;
4 import java.util.HashSet;
6 import java.util.Vector;
9 //import org.eclipse.jface.text.Assert;
10 import org.eclipse.core.runtime.Assert;
11 import org.eclipse.jface.viewers.ILabelProvider;
12 import org.eclipse.swt.SWT;
13 import org.eclipse.swt.events.DisposeEvent;
14 import org.eclipse.swt.events.DisposeListener;
15 import org.eclipse.swt.events.SelectionListener;
16 import org.eclipse.swt.graphics.Image;
17 import org.eclipse.swt.layout.GridData;
18 import org.eclipse.swt.layout.GridLayout;
19 import org.eclipse.swt.widgets.Composite;
20 import org.eclipse.swt.widgets.Event;
21 import org.eclipse.swt.widgets.Table;
22 import org.eclipse.swt.widgets.TableItem;
25 * A composite widget which holds a list of elements for user selection. The
26 * elements are sorted alphabetically. Optionally, the elements can be filtered
27 * and duplicate entries can be hidden (folding).
29 public class FilteredList extends Composite {
31 public interface FilterMatcher {
38 * a flag indicating whether pattern matching is case
40 * @param ignoreWildCards
41 * a flag indicating whether wildcard characters are
44 void setFilter(String pattern, boolean ignoreCase,
45 boolean ignoreWildCards);
48 * Returns <code>true</code> if the object matches the pattern,
49 * <code>false</code> otherwise. <code>setFilter()</code> must have
50 * been called at least once prior to a call to this method.
52 boolean match(Object element);
55 private class DefaultFilterMatcher implements FilterMatcher {
56 private StringMatcher fMatcher;
58 public void setFilter(String pattern, boolean ignoreCase,
59 boolean ignoreWildCards) {
60 fMatcher = new StringMatcher(pattern + '*', ignoreCase,
64 public boolean match(Object element) {
65 return fMatcher.match(fRenderer.getText(element));
71 private ILabelProvider fRenderer;
73 private boolean fMatchEmtpyString = true;
75 private boolean fIgnoreCase;
77 private boolean fAllowDuplicates;
79 private String fFilter = ""; //$NON-NLS-1$
81 private TwoArrayQuickSorter fSorter;
83 private Object[] fElements = new Object[0];
85 private Label[] fLabels;
87 private Vector fImages = new Vector();
89 private int[] fFoldedIndices;
91 private int fFoldedCount;
93 private int[] fFilteredIndices;
95 private int fFilteredCount;
97 private FilterMatcher fFilterMatcher = new DefaultFilterMatcher();
99 private Comparator fComparator;
101 private static class Label {
102 public final String string;
104 public final Image image;
106 public Label(String string, Image image) {
107 this.string = string;
111 public boolean equals(Label label) {
115 return string.equals(label.string) && image.equals(label.image);
119 private final class LabelComparator implements Comparator {
120 private boolean fIgnoreCase;
122 LabelComparator(boolean ignoreCase) {
123 fIgnoreCase = ignoreCase;
126 public int compare(Object left, Object right) {
127 Label leftLabel = (Label) left;
128 Label rightLabel = (Label) right;
132 if (fComparator == null) {
133 value = fIgnoreCase ? leftLabel.string
134 .compareToIgnoreCase(rightLabel.string)
135 : leftLabel.string.compareTo(rightLabel.string);
138 .compare(leftLabel.string, rightLabel.string);
144 // images are allowed to be null
145 if (leftLabel.image == null) {
146 return (rightLabel.image == null) ? 0 : -1;
147 } else if (rightLabel.image == null) {
150 return fImages.indexOf(leftLabel.image)
151 - fImages.indexOf(rightLabel.image);
158 * Constructs a new instance of a filtered list.
161 * the parent composite.
165 * the label renderer.
167 * specifies whether sorting and folding is case sensitive.
168 * @param allowDuplicates
169 * specifies whether folding of duplicates is desired.
170 * @param matchEmptyString
171 * specifies whether empty filter strings should filter
172 * everything or nothing.
174 public FilteredList(Composite parent, int style, ILabelProvider renderer,
175 boolean ignoreCase, boolean allowDuplicates,
176 boolean matchEmptyString) {
177 super(parent, SWT.NONE);
179 GridLayout layout = new GridLayout();
180 layout.marginHeight = 0;
181 layout.marginWidth = 0;
184 fList = new Table(this, style);
185 fList.setLayoutData(new GridData(GridData.FILL_BOTH));
186 fList.addDisposeListener(new DisposeListener() {
187 public void widgetDisposed(DisposeEvent e) {
192 fRenderer = renderer;
193 fIgnoreCase = ignoreCase;
194 fSorter = new TwoArrayQuickSorter(new LabelComparator(ignoreCase));
195 fAllowDuplicates = allowDuplicates;
196 fMatchEmtpyString = matchEmptyString;
200 * Sets the list of elements.
203 * the elements to be shown in the list.
205 public void setElements(Object[] elements) {
206 if (elements == null) {
207 fElements = new Object[0];
209 // copy list for sorting
210 fElements = new Object[elements.length];
211 System.arraycopy(elements, 0, fElements, 0, elements.length);
214 int length = fElements.length;
217 fLabels = new Label[length];
218 Set imageSet = new HashSet();
219 for (int i = 0; i != length; i++) {
220 String text = fRenderer.getText(fElements[i]);
221 Image image = fRenderer.getImage(fElements[i]);
223 fLabels[i] = new Label(text, image);
227 fImages.addAll(imageSet);
229 fSorter.sort(fLabels, fElements);
231 fFilteredIndices = new int[length];
232 fFilteredCount = filter();
234 fFoldedIndices = new int[length];
235 fFoldedCount = fold();
241 * Tests if the list (before folding and filtering) is empty.
243 * @return returns <code>true</code> if the list is empty,
244 * <code>false</code> otherwise.
246 public boolean isEmpty() {
247 return (fElements == null) || (fElements.length == 0);
251 * Sets the filter matcher.
253 // public void setFilterMatcher(FilterMatcher filterMatcher) {
254 // Assert.isNotNull(filterMatcher);
255 // fFilterMatcher = filterMatcher;
259 * Sets a custom comparator for sorting the list.
261 public void setComparator(Comparator comparator) {
262 Assert.isNotNull(comparator);
263 fComparator = comparator;
267 * Adds a selection listener to the list.
270 * the selection listener to be added.
272 public void addSelectionListener(SelectionListener listener) {
273 fList.addSelectionListener(listener);
277 * Removes a selection listener from the list.
280 * the selection listener to be removed.
282 public void removeSelectionListener(SelectionListener listener) {
283 fList.removeSelectionListener(listener);
287 * Sets the selection of the list.
290 * an array of indices specifying the selection.
292 public void setSelection(int[] selection) {
293 fList.setSelection(selection);
297 * Returns the selection of the list.
299 * @return returns an array of indices specifying the current selection.
301 public int[] getSelectionIndices() {
302 return fList.getSelectionIndices();
306 * Returns the selection of the list. This is a convenience function for
307 * <code>getSelectionIndices()</code>.
309 * @return returns the index of the selection, -1 for no selection.
311 public int getSelectionIndex() {
312 return fList.getSelectionIndex();
316 * Sets the selection of the list.
319 * the array of elements to be selected.
321 public void setSelection(Object[] elements) {
322 if ((elements == null) || (fElements == null))
326 int[] indices = new int[elements.length];
327 for (int i = 0; i != elements.length; i++) {
329 for (j = 0; j != fFoldedCount; j++) {
330 int max = (j == fFoldedCount - 1) ? fFilteredCount
331 : fFoldedIndices[j + 1];
334 for (l = fFoldedIndices[j]; l != max; l++) {
335 // found matching element?
336 if (fElements[fFilteredIndices[l]].equals(elements[i])) {
347 if (j == fFoldedCount)
351 fList.setSelection(indices);
355 * Returns an array of the selected elements. The type of the elements
356 * returned in the list are the same as the ones passed with
357 * <code>setElements</code>. The array does not contain the rendered
360 * @return returns the array of selected elements.
362 public Object[] getSelection() {
363 if (fList.isDisposed() || (fList.getSelectionCount() == 0))
364 return new Object[0];
366 int[] indices = fList.getSelectionIndices();
367 Object[] elements = new Object[indices.length];
369 for (int i = 0; i != indices.length; i++)
370 elements[i] = fElements[fFilteredIndices[fFoldedIndices[indices[i]]]];
376 * Sets the filter pattern. Current only prefix filter patterns are
380 * the filter pattern.
382 public void setFilter(String filter) {
383 fFilter = (filter == null) ? "" : filter; //$NON-NLS-1$
385 fFilteredCount = filter();
386 fFoldedCount = fold();
391 * Returns the filter pattern.
393 * @return returns the filter pattern.
395 public String getFilter() {
400 * Returns all elements which are folded together to one entry in the list.
403 * the index selecting the entry in the list.
404 * @return returns an array of elements folded together, <code>null</code>
405 * if index is out of range.
407 public Object[] getFoldedElements(int index) {
408 if ((index < 0) || (index >= fFoldedCount))
411 int start = fFoldedIndices[index];
412 int count = (index == fFoldedCount - 1) ? fFilteredCount - start
413 : fFoldedIndices[index + 1] - start;
415 Object[] elements = new Object[count];
416 for (int i = 0; i != count; i++)
417 elements[i] = fElements[fFilteredIndices[start + i]];
423 * Folds duplicate entries. Two elements are considered as a pair of
424 * duplicates if they coiincide in the rendered string and image. @return
425 * returns the number of elements after folding.
428 if (fAllowDuplicates) {
429 for (int i = 0; i != fFilteredCount; i++)
430 fFoldedIndices[i] = i; // identity mapping
432 return fFilteredCount;
437 for (int i = 0; i != fFilteredCount; i++) {
438 int j = fFilteredIndices[i];
440 Label current = fLabels[j];
441 if (!current.equals(last)) {
442 fFoldedIndices[k] = i;
452 * Filters the list with the filter pattern. @return returns the number of
453 * elements after filtering.
455 private int filter() {
456 if (((fFilter == null) || (fFilter.length() == 0))
457 && !fMatchEmtpyString)
460 fFilterMatcher.setFilter(fFilter.trim(), fIgnoreCase, false);
463 for (int i = 0; i != fElements.length; i++) {
464 if (fFilterMatcher.match(fElements[i]))
465 fFilteredIndices[k++] = i;
472 * Updates the list widget.
474 private void updateList() {
475 if (fList.isDisposed())
478 fList.setRedraw(false);
481 int itemCount = fList.getItemCount();
482 if (fFoldedCount < itemCount)
483 fList.remove(0, itemCount - fFoldedCount - 1);
484 else if (fFoldedCount > itemCount)
485 for (int i = 0; i != fFoldedCount - itemCount; i++)
486 new TableItem(fList, SWT.NONE);
489 TableItem[] items = fList.getItems();
490 for (int i = 0; i != fFoldedCount; i++) {
491 TableItem item = items[i];
492 Label label = fLabels[fFilteredIndices[fFoldedIndices[i]]];
494 item.setText(label.string);
495 item.setImage(label.image);
498 // select first item if any
499 if (fList.getItemCount() > 0)
500 fList.setSelection(0);
502 fList.setRedraw(true);
503 fList.notifyListeners(SWT.Selection, new Event());