KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > ui > dialogs > FilteredList


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

12 package org.eclipse.ui.dialogs;
13
14 import java.util.Comparator JavaDoc;
15 import java.util.HashSet JavaDoc;
16 import java.util.Set JavaDoc;
17 import java.util.Vector JavaDoc;
18
19 import org.eclipse.core.runtime.Assert;
20 import org.eclipse.core.runtime.IProgressMonitor;
21 import org.eclipse.core.runtime.IStatus;
22 import org.eclipse.core.runtime.Status;
23 import org.eclipse.jface.viewers.ILabelProvider;
24 import org.eclipse.swt.SWT;
25 import org.eclipse.swt.SWTException;
26 import org.eclipse.swt.accessibility.Accessible;
27 import org.eclipse.swt.events.DisposeEvent;
28 import org.eclipse.swt.events.DisposeListener;
29 import org.eclipse.swt.events.SelectionListener;
30 import org.eclipse.swt.graphics.Image;
31 import org.eclipse.swt.layout.GridData;
32 import org.eclipse.swt.layout.GridLayout;
33 import org.eclipse.swt.widgets.Composite;
34 import org.eclipse.swt.widgets.Event;
35 import org.eclipse.swt.widgets.Table;
36 import org.eclipse.swt.widgets.TableItem;
37 import org.eclipse.ui.internal.WorkbenchMessages;
38 import org.eclipse.ui.internal.misc.StringMatcher;
39 import org.eclipse.ui.internal.util.Util;
40 import org.eclipse.ui.progress.WorkbenchJob;
41
42 /**
43  * A composite widget which holds a list of elements for user selection. The
44  * elements are sorted alphabetically. Optionally, the elements can be filtered
45  * and duplicate entries can be hidden (folding).
46  *
47  * @since 2.0
48  */

49 public class FilteredList extends Composite {
50     /**
51      * The FilterMatcher is the interface used to check filtering criterea.
52      */

53     public interface FilterMatcher {
54         /**
55          * Sets the filter.
56          *
57          * @param pattern
58          * the filter pattern.
59          * @param ignoreCase
60          * a flag indicating whether pattern matching is case
61          * insensitive or not.
62          * @param ignoreWildCards
63          * a flag indicating whether wildcard characters are
64          * interpreted or not.
65          */

66         void setFilter(String JavaDoc pattern, boolean ignoreCase,
67                 boolean ignoreWildCards);
68
69         /**
70          * @param element
71          * The element to test against.
72          * @return <code>true</code> if the object matches the pattern,
73          * <code>false</code> otherwise. <code>setFilter()</code>
74          * must have been called at least once prior to a call to this
75          * method.
76          */

77         boolean match(Object JavaDoc element);
78     }
79
80     private class DefaultFilterMatcher implements FilterMatcher {
81         private StringMatcher fMatcher;
82
83         public void setFilter(String JavaDoc pattern, boolean ignoreCase,
84                 boolean ignoreWildCards) {
85             fMatcher = new StringMatcher(pattern + '*', ignoreCase,
86                     ignoreWildCards);
87         }
88
89         public boolean match(Object JavaDoc element) {
90             return fMatcher.match(fLabelProvider.getText(element));
91         }
92     }
93
94     private Table fList;
95
96     ILabelProvider fLabelProvider;
97
98     private boolean fMatchEmptyString = true;
99
100     private boolean fIgnoreCase;
101
102     private boolean fAllowDuplicates;
103
104     private String JavaDoc fFilter = ""; //$NON-NLS-1$
105

106     private TwoArrayQuickSorter fSorter;
107
108     Object JavaDoc[] fElements = new Object JavaDoc[0];
109
110     Label[] fLabels;
111
112     Vector JavaDoc fImages = new Vector JavaDoc();
113
114     int[] fFoldedIndices;
115
116     int fFoldedCount;
117
118     int[] fFilteredIndices;
119
120     int fFilteredCount;
121
122     private FilterMatcher fFilterMatcher = new DefaultFilterMatcher();
123
124     Comparator JavaDoc fComparator;
125
126     TableUpdateJob fUpdateJob;
127
128     /**
129      * Label is a private class used for comparing list objects
130      */

131     private static class Label {
132         /**
133          * The string portion of the label.
134          */

135         public final String JavaDoc string;
136
137         /**
138          * The image portion of the label.
139          */

140         public final Image image;
141
142         /**
143          * Create a new instance of label.
144          *
145          * @param newString
146          * @param image
147          */

148         public Label(String JavaDoc newString, Image image) {
149             if (newString == null) {
150                 this.string = Util.ZERO_LENGTH_STRING;
151             } else {
152                 this.string = newString;
153             }
154             this.image = image;
155         }
156
157         /**
158          * Return whether or not the receiver is the same as label.
159          *
160          * @param label
161          * @return boolean
162          */

163         public boolean equals(Label label) {
164             if (label == null) {
165                 return false;
166             }
167             // If the string portions match (whether null or not), fall
168
// through and check the image portion.
169
if (string == null && label.string != null) {
170                 return false;
171             }
172             if ((string != null) && (!string.equals(label.string))) {
173                 return false;
174             }
175             if (image == null) {
176                 return label.image == null;
177             }
178             return image.equals(label.image);
179         }
180     }
181
182     private final class LabelComparator implements Comparator JavaDoc {
183         private boolean labelIgnoreCase;
184
185         LabelComparator(boolean ignoreCase) {
186             labelIgnoreCase = ignoreCase;
187         }
188
189         public int compare(Object JavaDoc left, Object JavaDoc right) {
190             Label leftLabel = (Label) left;
191             Label rightLabel = (Label) right;
192             int value;
193             if (fComparator == null) {
194                 value = labelIgnoreCase ? leftLabel.string
195                         .compareToIgnoreCase(rightLabel.string)
196                         : leftLabel.string.compareTo(rightLabel.string);
197             } else {
198                 value = fComparator
199                         .compare(leftLabel.string, rightLabel.string);
200             }
201             if (value != 0) {
202                 return value;
203             }
204             // images are allowed to be null
205
if (leftLabel.image == null) {
206                 return (rightLabel.image == null) ? 0 : -1;
207             } else if (rightLabel.image == null) {
208                 return +1;
209             } else {
210                 return fImages.indexOf(leftLabel.image)
211                         - fImages.indexOf(rightLabel.image);
212             }
213         }
214     }
215
216     /**
217      * Constructs a new filtered list.
218      *
219      * @param parent
220      * the parent composite
221      * @param style
222      * the widget style
223      * @param labelProvider
224      * the label renderer
225      * @param ignoreCase
226      * specifies whether sorting and folding is case sensitive
227      * @param allowDuplicates
228      * specifies whether folding of duplicates is desired
229      * @param matchEmptyString
230      * specifies whether empty filter strings should filter
231      * everything or nothing
232      */

233     public FilteredList(Composite parent, int style,
234             ILabelProvider labelProvider, boolean ignoreCase,
235             boolean allowDuplicates, boolean matchEmptyString) {
236         super(parent, SWT.NONE);
237         GridLayout layout = new GridLayout();
238         layout.marginHeight = 0;
239         layout.marginWidth = 0;
240         setLayout(layout);
241         fList = new Table(this, style);
242         fList.setLayoutData(new GridData(GridData.FILL_BOTH));
243         fList.setFont(parent.getFont());
244         fList.addDisposeListener(new DisposeListener() {
245             public void widgetDisposed(DisposeEvent e) {
246                 fLabelProvider.dispose();
247                 if (fUpdateJob != null) {
248                     fUpdateJob.cancel();
249                 }
250             }
251         });
252         fLabelProvider = labelProvider;
253         fIgnoreCase = ignoreCase;
254         fSorter = new TwoArrayQuickSorter(new LabelComparator(ignoreCase));
255         fAllowDuplicates = allowDuplicates;
256         fMatchEmptyString = matchEmptyString;
257     }
258
259     /**
260      * Sets the list of elements.
261      *
262      * @param elements
263      * the elements to be shown in the list.
264      */

265     public void setElements(Object JavaDoc[] elements) {
266         if (elements == null) {
267             fElements = new Object JavaDoc[0];
268         } else {
269             // copy list for sorting
270
fElements = new Object JavaDoc[elements.length];
271             System.arraycopy(elements, 0, fElements, 0, elements.length);
272         }
273         int length = fElements.length;
274         // fill labels
275
fLabels = new Label[length];
276         Set JavaDoc imageSet = new HashSet JavaDoc();
277         for (int i = 0; i != length; i++) {
278             String JavaDoc text = fLabelProvider.getText(fElements[i]);
279             Image image = fLabelProvider.getImage(fElements[i]);
280             fLabels[i] = new Label(text, image);
281             imageSet.add(image);
282         }
283         fImages.clear();
284         fImages.addAll(imageSet);
285         fSorter.sort(fLabels, fElements);
286         fFilteredIndices = new int[length];
287         fFoldedIndices = new int[length];
288         updateList();
289     }
290
291     /**
292      * Tests if the list (before folding and filtering) is empty.
293      *
294      * @return returns <code>true</code> if the list is empty,
295      * <code>false</code> otherwise.
296      */

297     public boolean isEmpty() {
298         return (fElements == null) || (fElements.length == 0);
299     }
300
301     /**
302      * Sets the filter matcher.
303      *
304      * @param filterMatcher
305      */

306     public void setFilterMatcher(FilterMatcher filterMatcher) {
307         Assert.isNotNull(filterMatcher);
308         fFilterMatcher = filterMatcher;
309     }
310
311     /**
312      * Sets a custom comparator for sorting the list.
313      *
314      * @param comparator
315      */

316     public void setComparator(Comparator JavaDoc comparator) {
317         Assert.isNotNull(comparator);
318         fComparator = comparator;
319     }
320
321     /**
322      * Adds a selection listener to the list.
323      *
324      * @param listener
325      * the selection listener to be added.
326      */

327     public void addSelectionListener(SelectionListener listener) {
328         fList.addSelectionListener(listener);
329     }
330
331     /**
332      * Removes a selection listener from the list.
333      *
334      * @param listener
335      * the selection listener to be removed.
336      */

337     public void removeSelectionListener(SelectionListener listener) {
338         fList.removeSelectionListener(listener);
339     }
340
341     /**
342      * Sets the selection of the list. Empty or null array removes selection.
343      *
344      * @param selection
345      * an array of indices specifying the selection.
346      */

347     public void setSelection(int[] selection) {
348         if (selection == null || selection.length == 0) {
349             fList.deselectAll();
350         } else {
351             // If there is no working update job, or the update job is ready to
352
// accept selections, set the selection immediately.
353
if (fUpdateJob == null) {
354                 fList.setSelection(selection);
355                 fList.notifyListeners(SWT.Selection, new Event());
356             } else {
357                 // There is an update job doing the population of the list, so
358
// it should update the selection.
359
fUpdateJob.updateSelection(selection);
360             }
361         }
362     }
363
364     /**
365      * Returns the selection of the list.
366      *
367      * @return returns an array of indices specifying the current selection.
368      */

369     public int[] getSelectionIndices() {
370         return fList.getSelectionIndices();
371     }
372
373     /**
374      * Returns the selection of the list. This is a convenience function for
375      * <code>getSelectionIndices()</code>.
376      *
377      * @return returns the index of the selection, -1 for no selection.
378      */

379     public int getSelectionIndex() {
380         return fList.getSelectionIndex();
381     }
382
383     /**
384      * Sets the selection of the list. Empty or null array removes selection.
385      *
386      * @param elements
387      * the array of elements to be selected.
388      */

389     public void setSelection(Object JavaDoc[] elements) {
390         if (elements == null || elements.length == 0) {
391             fList.deselectAll();
392             return;
393         }
394         if (fElements == null) {
395             return;
396         }
397         // fill indices
398
int[] indices = new int[elements.length];
399         for (int i = 0; i != elements.length; i++) {
400             int j;
401             for (j = 0; j != fFoldedCount; j++) {
402                 int max = (j == fFoldedCount - 1) ? fFilteredCount
403                         : fFoldedIndices[j + 1];
404                 int l;
405                 for (l = fFoldedIndices[j]; l != max; l++) {
406                     // found matching element?
407
if (fElements[fFilteredIndices[l]].equals(elements[i])) {
408                         indices[i] = j;
409                         break;
410                     }
411                 }
412                 if (l != max) {
413                     break;
414                 }
415             }
416             // not found
417
if (j == fFoldedCount) {
418                 indices[i] = 0;
419             }
420         }
421         setSelection(indices);
422     }
423
424     /**
425      * Returns an array of the selected elements. The type of the elements
426      * returned in the list are the same as the ones passed with
427      * <code>setElements</code>. The array does not contain the rendered
428      * strings.
429      *
430      * @return returns the array of selected elements.
431      */

432     public Object JavaDoc[] getSelection() {
433         if (fList.isDisposed() || (fList.getSelectionCount() == 0)) {
434             return new Object JavaDoc[0];
435         }
436         int[] indices = fList.getSelectionIndices();
437         Object JavaDoc[] elements = new Object JavaDoc[indices.length];
438         for (int i = 0; i != indices.length; i++) {
439             elements[i] = fElements[fFilteredIndices[fFoldedIndices[indices[i]]]];
440         }
441         return elements;
442     }
443
444     /**
445      * Sets the filter pattern. Current only prefix filter patterns are
446      * supported.
447      *
448      * @param filter
449      * the filter pattern.
450      */

451     public void setFilter(String JavaDoc filter) {
452         fFilter = (filter == null) ? "" : filter; //$NON-NLS-1$
453
updateList();
454     }
455
456     private void updateList() {
457         fFilteredCount = filter();
458         fFoldedCount = fold();
459         if (fUpdateJob != null) {
460             fUpdateJob.cancel();
461         }
462         fUpdateJob = new TableUpdateJob(fList, fFoldedCount);
463         fUpdateJob.schedule();
464     }
465
466     /**
467      * Returns the filter pattern.
468      *
469      * @return returns the filter pattern.
470      */

471     public String JavaDoc getFilter() {
472         return fFilter;
473     }
474
475     /**
476      * Returns all elements which are folded together to one entry in the list.
477      *
478      * @param index
479      * the index selecting the entry in the list.
480      * @return returns an array of elements folded together, <code>null</code>
481      * if index is out of range.
482      */

483     public Object JavaDoc[] getFoldedElements(int index) {
484         if ((index < 0) || (index >= fFoldedCount)) {
485             return null;
486         }
487         int start = fFoldedIndices[index];
488         int count = (index == fFoldedCount - 1) ? fFilteredCount - start
489                 : fFoldedIndices[index + 1] - start;
490         Object JavaDoc[] elements = new Object JavaDoc[count];
491         for (int i = 0; i != count; i++) {
492             elements[i] = fElements[fFilteredIndices[start + i]];
493         }
494         return elements;
495     }
496
497     /*
498      * Folds duplicate entries. Two elements are considered as a pair of
499      * duplicates if they coiincide in the rendered string and image. @return
500      * returns the number of elements after folding.
501      */

502     private int fold() {
503         if (fAllowDuplicates) {
504             for (int i = 0; i != fFilteredCount; i++) {
505                 fFoldedIndices[i] = i; // identity mapping
506
}
507             return fFilteredCount;
508         }
509         int k = 0;
510         Label last = null;
511         for (int i = 0; i != fFilteredCount; i++) {
512             int j = fFilteredIndices[i];
513             Label current = fLabels[j];
514             if (!current.equals(last)) {
515                 fFoldedIndices[k] = i;
516                 k++;
517                 last = current;
518             }
519         }
520         return k;
521     }
522
523     /*
524      * Filters the list with the filter pattern. @return returns the number of
525      * elements after filtering.
526      */

527     private int filter() {
528         if (((fFilter == null) || (fFilter.length() == 0))
529                 && !fMatchEmptyString) {
530             return 0;
531         }
532         fFilterMatcher.setFilter(fFilter.trim(), fIgnoreCase, false);
533         int k = 0;
534         for (int i = 0; i != fElements.length; i++) {
535             if (fFilterMatcher.match(fElements[i])) {
536                 fFilteredIndices[k++] = i;
537             }
538         }
539         return k;
540     }
541
542     private class TableUpdateJob extends WorkbenchJob {
543         final Table fTable;
544
545         final int fCount;
546
547         private int currentIndex = 0;
548
549         /*
550          * Programmatic selections requested while this job was running.
551          */

552         int[] indicesToSelect;
553         
554         private boolean readyForSelection = false;
555
556         /**
557          * Create a new instance of a job used to update the table.
558          *
559          * @param table
560          * @param count
561          * The number of items to update per running.
562          */

563         public TableUpdateJob(Table table, int count) {
564             super(WorkbenchMessages.FilteredList_UpdateJobName);
565             setSystem(true);
566             fTable = table;
567             fCount = count;
568         }
569
570         /*
571          * (non-Javadoc)
572          *
573          * @see org.eclipse.ui.progress.UIJob#runInUIThread(org.eclipse.core.runtime.IProgressMonitor)
574          */

575 public IStatus runInUIThread(IProgressMonitor monitor) {
576             if (fTable.isDisposed()) {
577                 return Status.CANCEL_STATUS;
578             }
579             int itemCount = fTable.getItemCount();
580                         
581             // Remove excess items
582
if (fCount < itemCount) {
583                 fTable.setRedraw(false);
584                 fTable.remove(fCount, itemCount - 1);
585                 fTable.setRedraw(true);
586                 itemCount = fTable.getItemCount();
587             }
588             // table empty -> no selection
589
if (fCount == 0) {
590                 fTable.notifyListeners(SWT.Selection, new Event());
591                 return Status.OK_STATUS;
592             }
593             // How many we are going to do this time.
594
int iterations = Math.min(10, fCount - currentIndex);
595             for (int i = 0; i < iterations; i++) {
596                 if (monitor.isCanceled()) {
597                     return Status.CANCEL_STATUS;
598                 }
599                 final TableItem item = (currentIndex < itemCount) ? fTable
600                         .getItem(currentIndex)
601                         : new TableItem(fTable, SWT.NONE);
602                 final Label label = fLabels[fFilteredIndices[fFoldedIndices[currentIndex]]];
603                 item.setText(label.string);
604                 item.setImage(label.image);
605                 currentIndex++;
606             }
607             if (monitor.isCanceled()) {
608                 return Status.CANCEL_STATUS;
609             }
610             if (currentIndex < fCount) {
611                 schedule(100);
612             } else {
613                 if (indicesToSelect == null) {
614                     // Make a default selection in the table if there is none.
615
// If a selection has already been made, honor it.
616
// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=112146
617
if (fCount > 0) {
618                         if (fTable.getSelectionIndices().length == 0) {
619                             defaultSelect();
620                         } else {
621                             // There is a selection, but it likely hasn't changed since the
622
// job started. Force a selection notification, since the
623
// items represented by the selection have changed.
624
// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=119456
625
fTable.notifyListeners(SWT.Selection, new Event());
626                         }
627                     }
628                 } else {
629                     // Set the selection as indicated.
630
selectAndNotify(indicesToSelect);
631                 }
632                 // This flag signifies that the selection can now be directly
633
// updated in the widget.
634
readyForSelection = true;
635             }
636             return Status.OK_STATUS;
637         }
638         /**
639          * Update the selection for the supplied indices.
640          *
641          * @param indices
642          */

643         void updateSelection(final int[] indices) {
644             indicesToSelect = indices;
645             if (readyForSelection) {
646                 selectAndNotify(indices);
647             }
648         }
649
650         /**
651          * Select the first element if there is no selection
652          */

653         private void defaultSelect() {
654             /**
655              * Reset to the first selection if no index has been queued.
656              */

657             selectAndNotify(new int[] { 0 });
658         }
659
660         /**
661          * Select the supplied indices and notify any listeners
662          *
663          * @param indices
664          */

665         private void selectAndNotify(final int[] indices) {
666             // It is possible that the table was disposed
667
// before the update finished. If so then leave
668
if (fTable.isDisposed()) {
669                 return;
670             }
671             fTable.setSelection(indices);
672             fTable.notifyListeners(SWT.Selection, new Event());
673         }
674     }
675
676     /**
677      * Returns whether or not duplicates are allowed.
678      *
679      * @return <code>true</code> indicates duplicates are allowed
680      */

681     public boolean getAllowDuplicates() {
682         return fAllowDuplicates;
683     }
684
685     /**
686      * Sets whether or not duplicates are allowed. If this value is set the
687      * items should be set again for this value to take effect.
688      *
689      * @param allowDuplicates
690      * <code>true</code> indicates duplicates are allowed
691      */

692     public void setAllowDuplicates(boolean allowDuplicates) {
693         this.fAllowDuplicates = allowDuplicates;
694     }
695
696     /**
697      * Returns whether or not case should be ignored.
698      *
699      * @return <code>true</code> if case should be ignored
700      */

701     public boolean getIgnoreCase() {
702         return fIgnoreCase;
703     }
704
705     /**
706      * Sets whether or not case should be ignored If this value is set the items
707      * should be set again for this value to take effect.
708      *
709      * @param ignoreCase
710      * <code>true</code> if case should be ignored
711      */

712     public void setIgnoreCase(boolean ignoreCase) {
713         this.fIgnoreCase = ignoreCase;
714     }
715
716     /**
717      * Returns whether empty filter strings should filter everything or nothing.
718      *
719      * @return <code>true</code> for the empty string to match all items,
720      * <code>false</code> to match none
721      */

722     public boolean getMatchEmptyString() {
723         return fMatchEmptyString;
724     }
725
726     /**
727      * Sets whether empty filter strings should filter everything or nothing. If
728      * this value is set the items should be set again for this value to take
729      * effect.
730      *
731      * @param matchEmptyString
732      * <code>true</code> for the empty string to match all items,
733      * <code>false</code> to match none
734      */

735     public void setMatchEmptyString(boolean matchEmptyString) {
736         this.fMatchEmptyString = matchEmptyString;
737     }
738
739     /**
740      * Returns the label provider for the items.
741      *
742      * @return the label provider
743      */

744     public ILabelProvider getLabelProvider() {
745         return fLabelProvider;
746     }
747
748     /**
749      * Sets the label provider. If this value is set the items should be set
750      * again for this value to take effect.
751      *
752      * @param labelProvider
753      * the label provider
754      */

755     public void setLabelProvider(ILabelProvider labelProvider) {
756         this.fLabelProvider = labelProvider;
757     }
758     
759     /**
760      * Returns the accessible object for the receiver.
761      * If this is the first time this object is requested,
762      * then the object is created and returned.
763      *
764      * @return the accessible object
765      *
766      * @exception SWTException <ul>
767      * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
768      * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
769      * </ul>
770      *
771      * @see Accessible#addAccessibleListener
772      * @see Accessible#addAccessibleControlListener
773      *
774      * @since 3.3
775      */

776     public Accessible getAccessible() {
777         return fList.getAccessible();
778     }
779 }
780
Popular Tags