1 package net.sourceforge.phpdt.internal.ui.util;
3 import java.util.Comparator;
4 import java.util.HashSet;
6 import java.util.Vector;
8 import org.eclipse.jface.util.Assert;
9 import org.eclipse.jface.viewers.ILabelProvider;
10 import org.eclipse.swt.SWT;
11 import org.eclipse.swt.events.DisposeEvent;
12 import org.eclipse.swt.events.DisposeListener;
13 import org.eclipse.swt.events.SelectionListener;
14 import org.eclipse.swt.graphics.Image;
15 import org.eclipse.swt.layout.GridData;
16 import org.eclipse.swt.layout.GridLayout;
17 import org.eclipse.swt.widgets.Composite;
18 import org.eclipse.swt.widgets.Event;
19 import org.eclipse.swt.widgets.Table;
20 import org.eclipse.swt.widgets.TableItem;
23 * A composite widget which holds a list of elements for user selection. The
24 * elements are sorted alphabetically. Optionally, the elements can be filtered
25 * and duplicate entries can be hidden (folding).
27 public class FilteredList extends Composite {
29 public interface FilterMatcher {
36 * a flag indicating whether pattern matching is case
38 * @param ignoreWildCards
39 * a flag indicating whether wildcard characters are
42 void setFilter(String pattern, boolean ignoreCase,
43 boolean ignoreWildCards);
46 * Returns <code>true</code> if the object matches the pattern,
47 * <code>false</code> otherwise. <code>setFilter()</code> must have
48 * been called at least once prior to a call to this method.
50 boolean match(Object element);
53 private class DefaultFilterMatcher implements FilterMatcher {
54 private StringMatcher fMatcher;
56 public void setFilter(String pattern, boolean ignoreCase,
57 boolean ignoreWildCards) {
58 fMatcher = new StringMatcher(pattern + '*', ignoreCase,
62 public boolean match(Object element) {
63 return fMatcher.match(fRenderer.getText(element));
69 private ILabelProvider fRenderer;
71 private boolean fMatchEmtpyString = true;
73 private boolean fIgnoreCase;
75 private boolean fAllowDuplicates;
77 private String fFilter = ""; //$NON-NLS-1$
79 private TwoArrayQuickSorter fSorter;
81 private Object[] fElements = new Object[0];
83 private Label[] fLabels;
85 private Vector fImages = new Vector();
87 private int[] fFoldedIndices;
89 private int fFoldedCount;
91 private int[] fFilteredIndices;
93 private int fFilteredCount;
95 private FilterMatcher fFilterMatcher = new DefaultFilterMatcher();
97 private Comparator fComparator;
99 private static class Label {
100 public final String string;
102 public final Image image;
104 public Label(String string, Image image) {
105 this.string = string;
109 public boolean equals(Label label) {
113 return string.equals(label.string) && image.equals(label.image);
117 private final class LabelComparator implements Comparator {
118 private boolean fIgnoreCase;
120 LabelComparator(boolean ignoreCase) {
121 fIgnoreCase = ignoreCase;
124 public int compare(Object left, Object right) {
125 Label leftLabel = (Label) left;
126 Label rightLabel = (Label) right;
130 if (fComparator == null) {
131 value = fIgnoreCase ? leftLabel.string
132 .compareToIgnoreCase(rightLabel.string)
133 : leftLabel.string.compareTo(rightLabel.string);
136 .compare(leftLabel.string, rightLabel.string);
142 // images are allowed to be null
143 if (leftLabel.image == null) {
144 return (rightLabel.image == null) ? 0 : -1;
145 } else if (rightLabel.image == null) {
148 return fImages.indexOf(leftLabel.image)
149 - fImages.indexOf(rightLabel.image);
156 * Constructs a new instance of a filtered list.
159 * the parent composite.
163 * the label renderer.
165 * specifies whether sorting and folding is case sensitive.
166 * @param allowDuplicates
167 * specifies whether folding of duplicates is desired.
168 * @param matchEmptyString
169 * specifies whether empty filter strings should filter
170 * everything or nothing.
172 public FilteredList(Composite parent, int style, ILabelProvider renderer,
173 boolean ignoreCase, boolean allowDuplicates,
174 boolean matchEmptyString) {
175 super(parent, SWT.NONE);
177 GridLayout layout = new GridLayout();
178 layout.marginHeight = 0;
179 layout.marginWidth = 0;
182 fList = new Table(this, style);
183 fList.setLayoutData(new GridData(GridData.FILL_BOTH));
184 fList.addDisposeListener(new DisposeListener() {
185 public void widgetDisposed(DisposeEvent e) {
190 fRenderer = renderer;
191 fIgnoreCase = ignoreCase;
192 fSorter = new TwoArrayQuickSorter(new LabelComparator(ignoreCase));
193 fAllowDuplicates = allowDuplicates;
194 fMatchEmtpyString = matchEmptyString;
198 * Sets the list of elements.
201 * the elements to be shown in the list.
203 public void setElements(Object[] elements) {
204 if (elements == null) {
205 fElements = new Object[0];
207 // copy list for sorting
208 fElements = new Object[elements.length];
209 System.arraycopy(elements, 0, fElements, 0, elements.length);
212 int length = fElements.length;
215 fLabels = new Label[length];
216 Set imageSet = new HashSet();
217 for (int i = 0; i != length; i++) {
218 String text = fRenderer.getText(fElements[i]);
219 Image image = fRenderer.getImage(fElements[i]);
221 fLabels[i] = new Label(text, image);
225 fImages.addAll(imageSet);
227 fSorter.sort(fLabels, fElements);
229 fFilteredIndices = new int[length];
230 fFilteredCount = filter();
232 fFoldedIndices = new int[length];
233 fFoldedCount = fold();
239 * Tests if the list (before folding and filtering) is empty.
241 * @return returns <code>true</code> if the list is empty,
242 * <code>false</code> otherwise.
244 public boolean isEmpty() {
245 return (fElements == null) || (fElements.length == 0);
249 * Sets the filter matcher.
251 public void setFilterMatcher(FilterMatcher filterMatcher) {
252 Assert.isNotNull(filterMatcher);
253 fFilterMatcher = filterMatcher;
257 * Sets a custom comparator for sorting the list.
259 public void setComparator(Comparator comparator) {
260 Assert.isNotNull(comparator);
261 fComparator = comparator;
265 * Adds a selection listener to the list.
268 * the selection listener to be added.
270 public void addSelectionListener(SelectionListener listener) {
271 fList.addSelectionListener(listener);
275 * Removes a selection listener from the list.
278 * the selection listener to be removed.
280 public void removeSelectionListener(SelectionListener listener) {
281 fList.removeSelectionListener(listener);
285 * Sets the selection of the list.
288 * an array of indices specifying the selection.
290 public void setSelection(int[] selection) {
291 fList.setSelection(selection);
295 * Returns the selection of the list.
297 * @return returns an array of indices specifying the current selection.
299 public int[] getSelectionIndices() {
300 return fList.getSelectionIndices();
304 * Returns the selection of the list. This is a convenience function for
305 * <code>getSelectionIndices()</code>.
307 * @return returns the index of the selection, -1 for no selection.
309 public int getSelectionIndex() {
310 return fList.getSelectionIndex();
314 * Sets the selection of the list.
317 * the array of elements to be selected.
319 public void setSelection(Object[] elements) {
320 if ((elements == null) || (fElements == null))
324 int[] indices = new int[elements.length];
325 for (int i = 0; i != elements.length; i++) {
327 for (j = 0; j != fFoldedCount; j++) {
328 int max = (j == fFoldedCount - 1) ? fFilteredCount
329 : fFoldedIndices[j + 1];
332 for (l = fFoldedIndices[j]; l != max; l++) {
333 // found matching element?
334 if (fElements[fFilteredIndices[l]].equals(elements[i])) {
345 if (j == fFoldedCount)
349 fList.setSelection(indices);
353 * Returns an array of the selected elements. The type of the elements
354 * returned in the list are the same as the ones passed with
355 * <code>setElements</code>. The array does not contain the rendered
358 * @return returns the array of selected elements.
360 public Object[] getSelection() {
361 if (fList.isDisposed() || (fList.getSelectionCount() == 0))
362 return new Object[0];
364 int[] indices = fList.getSelectionIndices();
365 Object[] elements = new Object[indices.length];
367 for (int i = 0; i != indices.length; i++)
368 elements[i] = fElements[fFilteredIndices[fFoldedIndices[indices[i]]]];
374 * Sets the filter pattern. Current only prefix filter patterns are
378 * the filter pattern.
380 public void setFilter(String filter) {
381 fFilter = (filter == null) ? "" : filter; //$NON-NLS-1$
383 fFilteredCount = filter();
384 fFoldedCount = fold();
389 * Returns the filter pattern.
391 * @return returns the filter pattern.
393 public String getFilter() {
398 * Returns all elements which are folded together to one entry in the list.
401 * the index selecting the entry in the list.
402 * @return returns an array of elements folded together, <code>null</code>
403 * if index is out of range.
405 public Object[] getFoldedElements(int index) {
406 if ((index < 0) || (index >= fFoldedCount))
409 int start = fFoldedIndices[index];
410 int count = (index == fFoldedCount - 1) ? fFilteredCount - start
411 : fFoldedIndices[index + 1] - start;
413 Object[] elements = new Object[count];
414 for (int i = 0; i != count; i++)
415 elements[i] = fElements[fFilteredIndices[start + i]];
421 * Folds duplicate entries. Two elements are considered as a pair of
422 * duplicates if they coiincide in the rendered string and image. @return
423 * returns the number of elements after folding.
426 if (fAllowDuplicates) {
427 for (int i = 0; i != fFilteredCount; i++)
428 fFoldedIndices[i] = i; // identity mapping
430 return fFilteredCount;
435 for (int i = 0; i != fFilteredCount; i++) {
436 int j = fFilteredIndices[i];
438 Label current = fLabels[j];
439 if (!current.equals(last)) {
440 fFoldedIndices[k] = i;
450 * Filters the list with the filter pattern. @return returns the number of
451 * elements after filtering.
453 private int filter() {
454 if (((fFilter == null) || (fFilter.length() == 0))
455 && !fMatchEmtpyString)
458 fFilterMatcher.setFilter(fFilter.trim(), fIgnoreCase, false);
461 for (int i = 0; i != fElements.length; i++) {
462 if (fFilterMatcher.match(fElements[i]))
463 fFilteredIndices[k++] = i;
470 * Updates the list widget.
472 private void updateList() {
473 if (fList.isDisposed())
476 fList.setRedraw(false);
479 int itemCount = fList.getItemCount();
480 if (fFoldedCount < itemCount)
481 fList.remove(0, itemCount - fFoldedCount - 1);
482 else if (fFoldedCount > itemCount)
483 for (int i = 0; i != fFoldedCount - itemCount; i++)
484 new TableItem(fList, SWT.NONE);
487 TableItem[] items = fList.getItems();
488 for (int i = 0; i != fFoldedCount; i++) {
489 TableItem item = items[i];
490 Label label = fLabels[fFilteredIndices[fFoldedIndices[i]]];
492 item.setText(label.string);
493 item.setImage(label.image);
496 // select first item if any
497 if (fList.getItemCount() > 0)
498 fList.setSelection(0);
500 fList.setRedraw(true);
501 fList.notifyListeners(SWT.Selection, new Event());