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());