Package org.eclipse.ui.dialogs

Source Code of org.eclipse.ui.dialogs.FilteredList$DefaultFilterMatcher

/*******************************************************************************
* Copyright (c) 2000, 2006 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*   IBM Corporation - initial API and implementation
*   Sebastian Davids <sdavids@gmx.de> - Fix for bug 19346 - Dialog font should be activated and used by other components.
*******************************************************************************/
package org.eclipse.ui.dialogs;

import java.util.Comparator;
import java.util.HashSet;
import java.util.Set;
import java.util.Vector;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.accessibility.Accessible;
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;
import org.eclipse.ui.internal.WorkbenchMessages;
import org.eclipse.ui.internal.misc.StringMatcher;
import org.eclipse.ui.internal.util.Util;
import org.eclipse.ui.progress.WorkbenchJob;

/**
* 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).
*
* @since 2.0
*/
public class FilteredList extends Composite {
  /**
   * The FilterMatcher is the interface used to check filtering criterea.
   */
  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);

    /**
     * @param element
     *            The element to test against.
     * @return <code>true</code> if the object matches the pattern,
     *         <code>false</code> otherwise. <code>setFilter()</code>
     *         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(fLabelProvider.getText(element));
    }
  }

  private Table fList;

  ILabelProvider fLabelProvider;

  private boolean fMatchEmptyString = true;

  private boolean fIgnoreCase;

  private boolean fAllowDuplicates;

  private String fFilter = ""; //$NON-NLS-1$

  private TwoArrayQuickSorter fSorter;

  Object[] fElements = new Object[0];

  Label[] fLabels;

  Vector fImages = new Vector();

  int[] fFoldedIndices;

  int fFoldedCount;

  int[] fFilteredIndices;

  int fFilteredCount;

  private FilterMatcher fFilterMatcher = new DefaultFilterMatcher();

  Comparator fComparator;

  TableUpdateJob fUpdateJob;

  /**
   * Label is a private class used for comparing list objects
   */
  private static class Label {
    /**
     * The string portion of the label.
     */
    public final String string;

    /**
     * The image portion of the label.
     */
    public final Image image;

    /**
     * Create a new instance of label.
     *
     * @param newString
     * @param image
     */
    public Label(String newString, Image image) {
      if (newString == null) {
        this.string = Util.ZERO_LENGTH_STRING;
      } else {
        this.string = newString;
      }
      this.image = image;
    }

    /**
     * Return whether or not the receiver is the same as label.
     *
     * @param label
     * @return boolean
     */
    public boolean equals(Label label) {
      if (label == null) {
        return false;
      }
      // If the string portions match (whether null or not), fall
      // through and check the image portion.
      if (string == null && label.string != null) {
        return false;
      }
      if ((string != null) && (!string.equals(label.string))) {
        return false;
      }
      if (image == null) {
        return label.image == null;
      }
      return image.equals(label.image);
    }
  }

  private final class LabelComparator implements Comparator {
    private boolean labelIgnoreCase;

    LabelComparator(boolean ignoreCase) {
      labelIgnoreCase = ignoreCase;
    }

    public int compare(Object left, Object right) {
      Label leftLabel = (Label) left;
      Label rightLabel = (Label) right;
      int value;
      if (fComparator == null) {
        value = labelIgnoreCase ? 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 filtered list.
   *
   * @param parent
   *            the parent composite
   * @param style
   *            the widget style
   * @param labelProvider
   *            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 labelProvider, 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.setFont(parent.getFont());
    fList.addDisposeListener(new DisposeListener() {
      public void widgetDisposed(DisposeEvent e) {
        fLabelProvider.dispose();
        if (fUpdateJob != null) {
          fUpdateJob.cancel();
        }
      }
    });
    fLabelProvider = labelProvider;
    fIgnoreCase = ignoreCase;
    fSorter = new TwoArrayQuickSorter(new LabelComparator(ignoreCase));
    fAllowDuplicates = allowDuplicates;
    fMatchEmptyString = 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 = fLabelProvider.getText(fElements[i]);
      Image image = fLabelProvider.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];
    fFoldedIndices = new int[length];
    updateList();
  }

  /**
   * Tests if the list (before folding and filtering) is empty.
   *
   * @return returns <code>true</code> if the list is empty,
   *         <code>false</code> otherwise.
   */
  public boolean isEmpty() {
    return (fElements == null) || (fElements.length == 0);
  }

  /**
   * Sets the filter matcher.
   *
   * @param filterMatcher
   */
  public void setFilterMatcher(FilterMatcher filterMatcher) {
    Assert.isNotNull(filterMatcher);
    fFilterMatcher = filterMatcher;
  }

  /**
   * Sets a custom comparator for sorting the list.
   *
   * @param comparator
   */
  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. Empty or null array removes selection.
   *
   * @param selection
   *            an array of indices specifying the selection.
   */
  public void setSelection(int[] selection) {
    if (selection == null || selection.length == 0) {
      fList.deselectAll();
    } else {
      // If there is no working update job, or the update job is ready to
      // accept selections, set the selection immediately.
      if (fUpdateJob == null) {
        fList.setSelection(selection);
        fList.notifyListeners(SWT.Selection, new Event());
      } else {
        // There is an update job doing the population of the list, so
        // it should update the selection.
        fUpdateJob.updateSelection(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
   * <code>getSelectionIndices()</code>.
   *
   * @return returns the index of the selection, -1 for no selection.
   */
  public int getSelectionIndex() {
    return fList.getSelectionIndex();
  }

  /**
   * Sets the selection of the list. Empty or null array removes selection.
   *
   * @param elements
   *            the array of elements to be selected.
   */
  public void setSelection(Object[] elements) {
    if (elements == null || elements.length == 0) {
      fList.deselectAll();
      return;
    }
    if (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;
      }
    }
    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
   * <code>setElements</code>. 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$
    updateList();
  }

  private void updateList() {
    fFilteredCount = filter();
    fFoldedCount = fold();
    if (fUpdateJob != null) {
      fUpdateJob.cancel();
    }
    fUpdateJob = new TableUpdateJob(fList, fFoldedCount);
    fUpdateJob.schedule();
  }

  /**
   * 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, <code>null</code>
   *         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;
    }
    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))
        && !fMatchEmptyString) {
      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;
  }

  private class TableUpdateJob extends WorkbenchJob {
    final Table fTable;

    final int fCount;

    private int currentIndex = 0;

    /*
     * Programmatic selections requested while this job was running.
     */
    int[] indicesToSelect;
   
    private boolean readyForSelection = false;

    /**
     * Create a new instance of a job used to update the table.
     *
     * @param table
     * @param count
     *            The number of items to update per running.
     */
    public TableUpdateJob(Table table, int count) {
      super(WorkbenchMessages.FilteredList_UpdateJobName);
      setSystem(true);
      fTable = table;
      fCount = count;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.ui.progress.UIJob#runInUIThread(org.eclipse.core.runtime.IProgressMonitor)
     */
public IStatus runInUIThread(IProgressMonitor monitor) {
            if (fTable.isDisposed()) {
        return Status.CANCEL_STATUS;
      }
            int itemCount = fTable.getItemCount();
                       
            // Remove excess items
            if (fCount < itemCount) {
                fTable.setRedraw(false);
                fTable.remove(fCount, itemCount - 1);
                fTable.setRedraw(true);
                itemCount = fTable.getItemCount();
            }
            // table empty -> no selection
            if (fCount == 0) {
                fTable.notifyListeners(SWT.Selection, new Event());
                return Status.OK_STATUS;
            }
            // How many we are going to do this time.
            int iterations = Math.min(10, fCount - currentIndex);
            for (int i = 0; i < iterations; i++) {
                if (monitor.isCanceled()) {
          return Status.CANCEL_STATUS;
        }
                final TableItem item = (currentIndex < itemCount) ? fTable
                        .getItem(currentIndex)
                        : new TableItem(fTable, SWT.NONE);
                final Label label = fLabels[fFilteredIndices[fFoldedIndices[currentIndex]]];
                item.setText(label.string);
                item.setImage(label.image);
                currentIndex++;
            }
            if (monitor.isCanceled()) {
        return Status.CANCEL_STATUS;
      }
            if (currentIndex < fCount) {
        schedule(100);
      } else {
                if (indicesToSelect == null) {
                   // Make a default selection in the table if there is none.
                  // If a selection has already been made, honor it.
                  // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=112146
                    if (fCount > 0) {
                      if (fTable.getSelectionIndices().length == 0) {
                        defaultSelect();
                      } else {
                        // There is a selection, but it likely hasn't changed since the
                        // job started.  Force a selection notification, since the
                        // items represented by the selection have changed.
              // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=119456
                        fTable.notifyListeners(SWT.Selection, new Event());
                      }
                    }
                } else {
                  // Set the selection as indicated.
                    selectAndNotify(indicesToSelect);
                }
                // This flag signifies that the selection can now be directly
                // updated in the widget.
                readyForSelection = true;
            }
            return Status.OK_STATUS;
        }
    /**
     * Update the selection for the supplied indices.
     *
     * @param indices
     */
    void updateSelection(final int[] indices) {
      indicesToSelect = indices;
      if (readyForSelection) {
        selectAndNotify(indices);
      }
    }

    /**
     * Select the first element if there is no selection
     */
    private void defaultSelect() {
      /**
       * Reset to the first selection if no index has been queued.
       */
      selectAndNotify(new int[] { 0 });
    }

    /**
     * Select the supplied indices and notify any listeners
     *
     * @param indices
     */
    private void selectAndNotify(final int[] indices) {
      // It is possible that the table was disposed
      // before the update finished. If so then leave
      if (fTable.isDisposed()) {
        return;
      }
      fTable.setSelection(indices);
      fTable.notifyListeners(SWT.Selection, new Event());
    }
  }

  /**
   * Returns whether or not duplicates are allowed.
   *
   * @return <code>true</code> indicates duplicates are allowed
   */
  public boolean getAllowDuplicates() {
    return fAllowDuplicates;
  }

  /**
   * Sets whether or not duplicates are allowed. If this value is set the
   * items should be set again for this value to take effect.
   *
   * @param allowDuplicates
   *            <code>true</code> indicates duplicates are allowed
   */
  public void setAllowDuplicates(boolean allowDuplicates) {
    this.fAllowDuplicates = allowDuplicates;
  }

  /**
   * Returns whether or not case should be ignored.
   *
   * @return <code>true</code> if case should be ignored
   */
  public boolean getIgnoreCase() {
    return fIgnoreCase;
  }

  /**
   * Sets whether or not case should be ignored If this value is set the items
   * should be set again for this value to take effect.
   *
   * @param ignoreCase
   *            <code>true</code> if case should be ignored
   */
  public void setIgnoreCase(boolean ignoreCase) {
    this.fIgnoreCase = ignoreCase;
  }

  /**
   * Returns whether empty filter strings should filter everything or nothing.
   *
   * @return <code>true</code> for the empty string to match all items,
   *         <code>false</code> to match none
   */
  public boolean getMatchEmptyString() {
    return fMatchEmptyString;
  }

  /**
   * Sets whether empty filter strings should filter everything or nothing. If
   * this value is set the items should be set again for this value to take
   * effect.
   *
   * @param matchEmptyString
   *            <code>true</code> for the empty string to match all items,
   *            <code>false</code> to match none
   */
  public void setMatchEmptyString(boolean matchEmptyString) {
    this.fMatchEmptyString = matchEmptyString;
  }

  /**
   * Returns the label provider for the items.
   *
   * @return the label provider
   */
  public ILabelProvider getLabelProvider() {
    return fLabelProvider;
  }

  /**
   * Sets the label provider. If this value is set the items should be set
   * again for this value to take effect.
   *
   * @param labelProvider
   *            the label provider
   */
  public void setLabelProvider(ILabelProvider labelProvider) {
    this.fLabelProvider = labelProvider;
  }
 
  /**
   * Returns the accessible object for the receiver.
   * If this is the first time this object is requested,
   * then the object is created and returned.
   *
   * @return the accessible object
   *
   * @exception SWTException <ul>
   *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
   *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
   * </ul>
   *
   * @see Accessible#addAccessibleListener
   * @see Accessible#addAccessibleControlListener
   *
   * @since 3.3
   */
  public Accessible getAccessible() {
    return fList.getAccessible();
  }
}
TOP

Related Classes of org.eclipse.ui.dialogs.FilteredList$DefaultFilterMatcher

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.