X-Git-Url: http://secure.phpeclipse.com diff --git a/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/ui/util/FilteredList.java b/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/ui/util/FilteredList.java new file mode 100644 index 0000000..a9e209f --- /dev/null +++ b/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/ui/util/FilteredList.java @@ -0,0 +1,506 @@ +package net.sourceforge.phpdt.internal.ui.util; + +import java.util.Comparator; +import java.util.HashSet; +import java.util.Set; +import java.util.Vector; + +//incastrix +//import org.eclipse.jface.text.Assert; +import org.eclipse.core.runtime.Assert; +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableItem; + +/** + * A composite widget which holds a list of elements for user selection. The + * elements are sorted alphabetically. Optionally, the elements can be filtered + * and duplicate entries can be hidden (folding). + */ +public class FilteredList extends Composite { + + public interface FilterMatcher { + /** + * Sets the filter. + * + * @param pattern + * the filter pattern. + * @param ignoreCase + * a flag indicating whether pattern matching is case + * insensitive or not. + * @param ignoreWildCards + * a flag indicating whether wildcard characters are + * interpreted or not. + */ + void setFilter(String pattern, boolean ignoreCase, + boolean ignoreWildCards); + + /** + * Returns true if the object matches the pattern, + * false otherwise. setFilter() must have + * been called at least once prior to a call to this method. + */ + boolean match(Object element); + } + + private class DefaultFilterMatcher implements FilterMatcher { + private StringMatcher fMatcher; + + public void setFilter(String pattern, boolean ignoreCase, + boolean ignoreWildCards) { + fMatcher = new StringMatcher(pattern + '*', ignoreCase, + ignoreWildCards); + } + + public boolean match(Object element) { + return fMatcher.match(fRenderer.getText(element)); + } + } + + private Table fList; + + private ILabelProvider fRenderer; + + private boolean fMatchEmtpyString = true; + + private boolean fIgnoreCase; + + private boolean fAllowDuplicates; + + private String fFilter = ""; //$NON-NLS-1$ + + private TwoArrayQuickSorter fSorter; + + private Object[] fElements = new Object[0]; + + private Label[] fLabels; + + private Vector fImages = new Vector(); + + private int[] fFoldedIndices; + + private int fFoldedCount; + + private int[] fFilteredIndices; + + private int fFilteredCount; + + private FilterMatcher fFilterMatcher = new DefaultFilterMatcher(); + + private Comparator fComparator; + + private static class Label { + public final String string; + + public final Image image; + + public Label(String string, Image image) { + this.string = string; + this.image = image; + } + + public boolean equals(Label label) { + if (label == null) + return false; + + return string.equals(label.string) && image.equals(label.image); + } + } + + private final class LabelComparator implements Comparator { + private boolean fIgnoreCase; + + LabelComparator(boolean ignoreCase) { + fIgnoreCase = ignoreCase; + } + + public int compare(Object left, Object right) { + Label leftLabel = (Label) left; + Label rightLabel = (Label) right; + + int value; + + if (fComparator == null) { + value = fIgnoreCase ? leftLabel.string + .compareToIgnoreCase(rightLabel.string) + : leftLabel.string.compareTo(rightLabel.string); + } else { + value = fComparator + .compare(leftLabel.string, rightLabel.string); + } + + if (value != 0) + return value; + + // images are allowed to be null + if (leftLabel.image == null) { + return (rightLabel.image == null) ? 0 : -1; + } else if (rightLabel.image == null) { + return +1; + } else { + return fImages.indexOf(leftLabel.image) + - fImages.indexOf(rightLabel.image); + } + } + + } + + /** + * Constructs a new instance of a filtered list. + * + * @param parent + * the parent composite. + * @param style + * the widget style. + * @param renderer + * the label renderer. + * @param ignoreCase + * specifies whether sorting and folding is case sensitive. + * @param allowDuplicates + * specifies whether folding of duplicates is desired. + * @param matchEmptyString + * specifies whether empty filter strings should filter + * everything or nothing. + */ + public FilteredList(Composite parent, int style, ILabelProvider renderer, + boolean ignoreCase, boolean allowDuplicates, + boolean matchEmptyString) { + super(parent, SWT.NONE); + + GridLayout layout = new GridLayout(); + layout.marginHeight = 0; + layout.marginWidth = 0; + setLayout(layout); + + fList = new Table(this, style); + fList.setLayoutData(new GridData(GridData.FILL_BOTH)); + fList.addDisposeListener(new DisposeListener() { + public void widgetDisposed(DisposeEvent e) { + fRenderer.dispose(); + } + }); + + fRenderer = renderer; + fIgnoreCase = ignoreCase; + fSorter = new TwoArrayQuickSorter(new LabelComparator(ignoreCase)); + fAllowDuplicates = allowDuplicates; + fMatchEmtpyString = matchEmptyString; + } + + /** + * Sets the list of elements. + * + * @param elements + * the elements to be shown in the list. + */ + public void setElements(Object[] elements) { + if (elements == null) { + fElements = new Object[0]; + } else { + // copy list for sorting + fElements = new Object[elements.length]; + System.arraycopy(elements, 0, fElements, 0, elements.length); + } + + int length = fElements.length; + + // fill labels + fLabels = new Label[length]; + Set imageSet = new HashSet(); + for (int i = 0; i != length; i++) { + String text = fRenderer.getText(fElements[i]); + Image image = fRenderer.getImage(fElements[i]); + + fLabels[i] = new Label(text, image); + imageSet.add(image); + } + fImages.clear(); + fImages.addAll(imageSet); + + fSorter.sort(fLabels, fElements); + + fFilteredIndices = new int[length]; + fFilteredCount = filter(); + + fFoldedIndices = new int[length]; + fFoldedCount = fold(); + + updateList(); + } + + /** + * Tests if the list (before folding and filtering) is empty. + * + * @return returns true if the list is empty, + * false otherwise. + */ + public boolean isEmpty() { + return (fElements == null) || (fElements.length == 0); + } + + /** + * Sets the filter matcher. + */ +// public void setFilterMatcher(FilterMatcher filterMatcher) { +// Assert.isNotNull(filterMatcher); +// fFilterMatcher = filterMatcher; +// } + + /** + * Sets a custom comparator for sorting the list. + */ + public void setComparator(Comparator comparator) { + Assert.isNotNull(comparator); + fComparator = comparator; + } + + /** + * Adds a selection listener to the list. + * + * @param listener + * the selection listener to be added. + */ + public void addSelectionListener(SelectionListener listener) { + fList.addSelectionListener(listener); + } + + /** + * Removes a selection listener from the list. + * + * @param listener + * the selection listener to be removed. + */ + public void removeSelectionListener(SelectionListener listener) { + fList.removeSelectionListener(listener); + } + + /** + * Sets the selection of the list. + * + * @param selection + * an array of indices specifying the selection. + */ + public void setSelection(int[] selection) { + fList.setSelection(selection); + } + + /** + * Returns the selection of the list. + * + * @return returns an array of indices specifying the current selection. + */ + public int[] getSelectionIndices() { + return fList.getSelectionIndices(); + } + + /** + * Returns the selection of the list. This is a convenience function for + * getSelectionIndices(). + * + * @return returns the index of the selection, -1 for no selection. + */ + public int getSelectionIndex() { + return fList.getSelectionIndex(); + } + + /** + * Sets the selection of the list. + * + * @param elements + * the array of elements to be selected. + */ + public void setSelection(Object[] elements) { + if ((elements == null) || (fElements == null)) + return; + + // fill indices + int[] indices = new int[elements.length]; + for (int i = 0; i != elements.length; i++) { + int j; + for (j = 0; j != fFoldedCount; j++) { + int max = (j == fFoldedCount - 1) ? fFilteredCount + : fFoldedIndices[j + 1]; + + int l; + for (l = fFoldedIndices[j]; l != max; l++) { + // found matching element? + if (fElements[fFilteredIndices[l]].equals(elements[i])) { + indices[i] = j; + break; + } + } + + if (l != max) + break; + } + + // not found + if (j == fFoldedCount) + indices[i] = 0; + } + + fList.setSelection(indices); + } + + /** + * Returns an array of the selected elements. The type of the elements + * returned in the list are the same as the ones passed with + * setElements. The array does not contain the rendered + * strings. + * + * @return returns the array of selected elements. + */ + public Object[] getSelection() { + if (fList.isDisposed() || (fList.getSelectionCount() == 0)) + return new Object[0]; + + int[] indices = fList.getSelectionIndices(); + Object[] elements = new Object[indices.length]; + + for (int i = 0; i != indices.length; i++) + elements[i] = fElements[fFilteredIndices[fFoldedIndices[indices[i]]]]; + + return elements; + } + + /** + * Sets the filter pattern. Current only prefix filter patterns are + * supported. + * + * @param filter + * the filter pattern. + */ + public void setFilter(String filter) { + fFilter = (filter == null) ? "" : filter; //$NON-NLS-1$ + + fFilteredCount = filter(); + fFoldedCount = fold(); + updateList(); + } + + /** + * Returns the filter pattern. + * + * @return returns the filter pattern. + */ + public String getFilter() { + return fFilter; + } + + /** + * Returns all elements which are folded together to one entry in the list. + * + * @param index + * the index selecting the entry in the list. + * @return returns an array of elements folded together, null + * if index is out of range. + */ + public Object[] getFoldedElements(int index) { + if ((index < 0) || (index >= fFoldedCount)) + return null; + + int start = fFoldedIndices[index]; + int count = (index == fFoldedCount - 1) ? fFilteredCount - start + : fFoldedIndices[index + 1] - start; + + Object[] elements = new Object[count]; + for (int i = 0; i != count; i++) + elements[i] = fElements[fFilteredIndices[start + i]]; + + return elements; + } + + /* + * Folds duplicate entries. Two elements are considered as a pair of + * duplicates if they coiincide in the rendered string and image. @return + * returns the number of elements after folding. + */ + private int fold() { + if (fAllowDuplicates) { + for (int i = 0; i != fFilteredCount; i++) + fFoldedIndices[i] = i; // identity mapping + + return fFilteredCount; + + } else { + int k = 0; + Label last = null; + for (int i = 0; i != fFilteredCount; i++) { + int j = fFilteredIndices[i]; + + Label current = fLabels[j]; + if (!current.equals(last)) { + fFoldedIndices[k] = i; + k++; + last = current; + } + } + return k; + } + } + + /* + * Filters the list with the filter pattern. @return returns the number of + * elements after filtering. + */ + private int filter() { + if (((fFilter == null) || (fFilter.length() == 0)) + && !fMatchEmtpyString) + return 0; + + fFilterMatcher.setFilter(fFilter.trim(), fIgnoreCase, false); + + int k = 0; + for (int i = 0; i != fElements.length; i++) { + if (fFilterMatcher.match(fElements[i])) + fFilteredIndices[k++] = i; + } + + return k; + } + + /* + * Updates the list widget. + */ + private void updateList() { + if (fList.isDisposed()) + return; + + fList.setRedraw(false); + + // resize table + int itemCount = fList.getItemCount(); + if (fFoldedCount < itemCount) + fList.remove(0, itemCount - fFoldedCount - 1); + else if (fFoldedCount > itemCount) + for (int i = 0; i != fFoldedCount - itemCount; i++) + new TableItem(fList, SWT.NONE); + + // fill table + TableItem[] items = fList.getItems(); + for (int i = 0; i != fFoldedCount; i++) { + TableItem item = items[i]; + Label label = fLabels[fFilteredIndices[fFoldedIndices[i]]]; + + item.setText(label.string); + item.setImage(label.image); + } + + // select first item if any + if (fList.getItemCount() > 0) + fList.setSelection(0); + + fList.setRedraw(true); + fList.notifyListeners(SWT.Selection, new Event()); + } + +} \ No newline at end of file