]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.utils.ui/src/org/simantics/utils/ui/dialogs/ColumnFilteredItemsSelectionDialog.java
Fixed all line endings of the repository
[simantics/platform.git] / bundles / org.simantics.utils.ui / src / org / simantics / utils / ui / dialogs / ColumnFilteredItemsSelectionDialog.java
1 /*******************************************************************************
2  * Copyright (c) 2000, 2010 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  *  Willian Mitsuda <wmitsuda@gmail.com>
11  *     - Fix for bug 196553 - [Dialogs] Support IColorProvider/IFontProvider in FilteredItemsSelectionDialog
12  *  Peter Friese <peter.friese@gentleware.com>
13  *     - Fix for bug 208602 - [Dialogs] Open Type dialog needs accessible labels
14  *  Simon Muschel <smuschel@gmx.de> - bug 258493
15  *******************************************************************************/
16 package org.simantics.utils.ui.dialogs;
17
18 import java.io.IOException;
19 import java.io.StringReader;
20 import java.io.StringWriter;
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.Collections;
24 import java.util.Comparator;
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.Iterator;
28 import java.util.LinkedHashSet;
29 import java.util.List;
30 import java.util.Set;
31
32 import org.eclipse.core.commands.AbstractHandler;
33 import org.eclipse.core.commands.ExecutionEvent;
34 import org.eclipse.core.commands.IHandler;
35 import org.eclipse.core.runtime.Assert;
36 import org.eclipse.core.runtime.CoreException;
37 import org.eclipse.core.runtime.IProgressMonitor;
38 import org.eclipse.core.runtime.IStatus;
39 import org.eclipse.core.runtime.ListenerList;
40 import org.eclipse.core.runtime.NullProgressMonitor;
41 import org.eclipse.core.runtime.ProgressMonitorWrapper;
42 import org.eclipse.core.runtime.Status;
43 import org.eclipse.core.runtime.SubProgressMonitor;
44 import org.eclipse.core.runtime.jobs.Job;
45 import org.eclipse.jface.action.Action;
46 import org.eclipse.jface.action.ActionContributionItem;
47 import org.eclipse.jface.action.IAction;
48 import org.eclipse.jface.action.IMenuListener;
49 import org.eclipse.jface.action.IMenuManager;
50 import org.eclipse.jface.action.LegacyActionTools;
51 import org.eclipse.jface.action.MenuManager;
52 import org.eclipse.jface.dialogs.IDialogSettings;
53 import org.eclipse.jface.viewers.ColumnLabelProvider;
54 import org.eclipse.jface.viewers.ContentViewer;
55 import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider;
56 import org.eclipse.jface.viewers.DoubleClickEvent;
57 import org.eclipse.jface.viewers.IColorProvider;
58 import org.eclipse.jface.viewers.IContentProvider;
59 import org.eclipse.jface.viewers.IDoubleClickListener;
60 import org.eclipse.jface.viewers.IFontProvider;
61 import org.eclipse.jface.viewers.ILabelDecorator;
62 import org.eclipse.jface.viewers.ILabelProvider;
63 import org.eclipse.jface.viewers.ILabelProviderListener;
64 import org.eclipse.jface.viewers.ILazyContentProvider;
65 import org.eclipse.jface.viewers.ISelection;
66 import org.eclipse.jface.viewers.ISelectionChangedListener;
67 import org.eclipse.jface.viewers.IStructuredContentProvider;
68 import org.eclipse.jface.viewers.LabelProvider;
69 import org.eclipse.jface.viewers.LabelProviderChangedEvent;
70 import org.eclipse.jface.viewers.SelectionChangedEvent;
71 import org.eclipse.jface.viewers.StructuredSelection;
72 import org.eclipse.jface.viewers.StyledCellLabelProvider;
73 import org.eclipse.jface.viewers.StyledString;
74 import org.eclipse.jface.viewers.TableViewer;
75 import org.eclipse.jface.viewers.TableViewerColumn;
76 import org.eclipse.jface.viewers.Viewer;
77 import org.eclipse.jface.viewers.ViewerCell;
78 import org.eclipse.jface.viewers.ViewerFilter;
79 import org.eclipse.osgi.util.NLS;
80 import org.eclipse.swt.SWT;
81 import org.eclipse.swt.accessibility.ACC;
82 import org.eclipse.swt.accessibility.AccessibleAdapter;
83 import org.eclipse.swt.accessibility.AccessibleEvent;
84 import org.eclipse.swt.custom.CLabel;
85 import org.eclipse.swt.custom.ViewForm;
86 import org.eclipse.swt.events.KeyAdapter;
87 import org.eclipse.swt.events.KeyEvent;
88 import org.eclipse.swt.events.ModifyEvent;
89 import org.eclipse.swt.events.ModifyListener;
90 import org.eclipse.swt.events.MouseAdapter;
91 import org.eclipse.swt.events.MouseEvent;
92 import org.eclipse.swt.events.SelectionAdapter;
93 import org.eclipse.swt.events.SelectionEvent;
94 import org.eclipse.swt.events.TraverseEvent;
95 import org.eclipse.swt.events.TraverseListener;
96 import org.eclipse.swt.graphics.Color;
97 import org.eclipse.swt.graphics.Font;
98 import org.eclipse.swt.graphics.GC;
99 import org.eclipse.swt.graphics.Image;
100 import org.eclipse.swt.graphics.Point;
101 import org.eclipse.swt.graphics.Rectangle;
102 import org.eclipse.swt.layout.GridData;
103 import org.eclipse.swt.layout.GridLayout;
104 import org.eclipse.swt.widgets.Composite;
105 import org.eclipse.swt.widgets.Control;
106 import org.eclipse.swt.widgets.Display;
107 import org.eclipse.swt.widgets.Event;
108 import org.eclipse.swt.widgets.Label;
109 import org.eclipse.swt.widgets.Menu;
110 import org.eclipse.swt.widgets.Shell;
111 import org.eclipse.swt.widgets.Table;
112 import org.eclipse.swt.widgets.TableColumn;
113 import org.eclipse.swt.widgets.Text;
114 import org.eclipse.swt.widgets.ToolBar;
115 import org.eclipse.swt.widgets.ToolItem;
116 import org.eclipse.ui.ActiveShellExpression;
117 import org.eclipse.ui.IMemento;
118 import org.eclipse.ui.IWorkbenchCommandConstants;
119 import org.eclipse.ui.IWorkbenchPreferenceConstants;
120 import org.eclipse.ui.PlatformUI;
121 import org.eclipse.ui.WorkbenchException;
122 import org.eclipse.ui.XMLMemento;
123 import org.eclipse.ui.dialogs.SearchPattern;
124 import org.eclipse.ui.dialogs.SelectionStatusDialog;
125 import org.eclipse.ui.handlers.IHandlerActivation;
126 import org.eclipse.ui.handlers.IHandlerService;
127 import org.eclipse.ui.internal.IWorkbenchGraphicConstants;
128 import org.eclipse.ui.internal.WorkbenchImages;
129 import org.eclipse.ui.internal.WorkbenchMessages;
130 import org.eclipse.ui.internal.WorkbenchPlugin;
131 import org.eclipse.ui.progress.UIJob;
132 import org.eclipse.ui.statushandlers.StatusManager;
133
134 /**
135  * Shows a list of items to the user with a text entry field for a string
136  * pattern used to filter the list of items.
137  * 
138  * This is a copy from org.eclipse.ui.dialogs.FilteredItemsSelectionDialog
139  * with a hook for column creation and a fix to a (possible) bug that 
140  * prevented the empty pattern to be handled correctly. This version of the
141  * dialog also has the possibility to force an update of the contents if new
142  * elements are added.
143  * 
144  * TODO: Clean up warnings.
145  * 
146  * @author Janne Kauttio (modifications only)
147  * 
148  * @since 3.3
149  */
150 @SuppressWarnings({ "restriction", "rawtypes", "unchecked" })
151 public abstract class ColumnFilteredItemsSelectionDialog extends
152                 SelectionStatusDialog {
153
154         private static final String DIALOG_BOUNDS_SETTINGS = "DialogBoundsSettings"; //$NON-NLS-1$
155
156         private static final String SHOW_STATUS_LINE = "ShowStatusLine"; //$NON-NLS-1$
157
158         private static final String HISTORY_SETTINGS = "History"; //$NON-NLS-1$
159
160         private static final String DIALOG_HEIGHT = "DIALOG_HEIGHT"; //$NON-NLS-1$
161
162         private static final String DIALOG_WIDTH = "DIALOG_WIDTH"; //$NON-NLS-1$
163
164         /**
165          * Represents an empty selection in the pattern input field (used only for
166          * initial pattern).
167          */
168         public static final int NONE = 0;
169
170         /**
171          * Pattern input field selection where caret is at the beginning (used only
172          * for initial pattern).
173          */
174         public static final int CARET_BEGINNING = 1;
175
176         /**
177          * Represents a full selection in the pattern input field (used only for
178          * initial pattern).
179          */
180         public static final int FULL_SELECTION = 2;
181
182         private Text pattern;
183
184         private TableViewer viewer;
185
186         private DetailsContentViewer details;
187
188         /**
189          * It is a duplicate of a field in the CLabel class in DetailsContentViewer.
190          * It is maintained, because the <code>setDetailsLabelProvider()</code>
191          * could be called before content area is created.
192          */
193         private ILabelProvider detailsLabelProvider;
194
195         private ItemsListLabelProvider itemsListLabelProvider;
196
197         private MenuManager menuManager;
198
199         private MenuManager contextMenuManager;
200
201         private boolean multi;
202
203         private ToolBar toolBar;
204
205         private ToolItem toolItem;
206
207         private Label progressLabel;
208
209         private ToggleStatusLineAction toggleStatusLineAction;
210
211         private RemoveHistoryItemAction removeHistoryItemAction;
212
213         private ActionContributionItem removeHistoryActionContributionItem;
214
215         private IStatus status;
216
217         private RefreshCacheJob refreshCacheJob;
218
219         private RefreshProgressMessageJob refreshProgressMessageJob = new RefreshProgressMessageJob();
220
221         private Object[] currentSelection;
222
223         private ContentProvider contentProvider;
224
225         private FilterHistoryJob filterHistoryJob;
226
227         private FilterJob filterJob;
228
229         private ItemsFilter filter;
230
231         private List lastCompletedResult;
232
233         private ItemsFilter lastCompletedFilter;
234
235         private String initialPatternText;
236
237         private int selectionMode;
238
239         private ItemsListSeparator itemsListSeparator;
240
241         private static final String EMPTY_STRING = ""; //$NON-NLS-1$
242
243         private boolean refreshWithLastSelection = false;
244
245         private IHandlerActivation showViewHandler;
246
247         /**
248          * Creates a new instance of the class.
249          * 
250          * @param shell
251          *            shell to parent the dialog on
252          * @param multi
253          *            indicates whether dialog allows to select more than one
254          *            position in its list of items
255          */
256         public ColumnFilteredItemsSelectionDialog(Shell shell, boolean multi) {
257                 super(shell);
258                 this.multi = multi;
259                 filterHistoryJob = new FilterHistoryJob();
260                 filterJob = new FilterJob();
261                 contentProvider = new ContentProvider();
262                 refreshCacheJob = new RefreshCacheJob();
263                 itemsListSeparator = new ItemsListSeparator(WorkbenchMessages.FilteredItemsSelectionDialog_separatorLabel);
264                 selectionMode = NONE;
265         }
266
267         /**
268          * Creates a new instance of the class. Created dialog won't allow to select
269          * more than one item.
270          * 
271          * @param shell
272          *            shell to parent the dialog on
273          */
274         public ColumnFilteredItemsSelectionDialog(Shell shell) {
275                 this(shell, false);
276         }
277
278         /**
279          * Adds viewer filter to the dialog items list.
280          * 
281          * @param filter
282          *            the new filter
283          */
284         protected void addListFilter(ViewerFilter filter) {
285                 contentProvider.addFilter(filter);
286         }
287
288         /**
289          * Sets a new label provider for items in the list. If the label provider
290          * also implements {@link
291          * org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider
292          * .IStyledLabelProvider}, the style text labels provided by it will be used
293          * provided that the corresponding preference is set.
294          * 
295          * @see IWorkbenchPreferenceConstants#USE_COLORED_LABELS
296          * 
297          * @param listLabelProvider
298          *              the label provider for items in the list
299          */
300         public void setListLabelProvider(ILabelProvider listLabelProvider) {
301                 getItemsListLabelProvider().setProvider(listLabelProvider);
302         }
303
304         /**
305          * Returns the label decorator for selected items in the list.
306          * 
307          * @return the label decorator for selected items in the list
308          */
309         private ILabelDecorator getListSelectionLabelDecorator() {
310                 return getItemsListLabelProvider().getSelectionDecorator();
311         }
312
313         /**
314          * Sets the label decorator for selected items in the list.
315          * 
316          * @param listSelectionLabelDecorator
317          *            the label decorator for selected items in the list
318          */
319         public void setListSelectionLabelDecorator(
320                         ILabelDecorator listSelectionLabelDecorator) {
321                 getItemsListLabelProvider().setSelectionDecorator(
322                                 listSelectionLabelDecorator);
323         }
324
325         /**
326          * Returns the item list label provider.
327          * 
328          * @return the item list label provider
329          */
330         private ItemsListLabelProvider getItemsListLabelProvider() {
331                 if (itemsListLabelProvider == null) {
332                         itemsListLabelProvider = new ItemsListLabelProvider(
333                                         new LabelProvider(), null);
334                 }
335                 return itemsListLabelProvider;
336         }
337
338         /**
339          * Sets label provider for the details field.
340          * 
341          * For a single selection, the element sent to
342          * {@link ILabelProvider#getImage(Object)} and
343          * {@link ILabelProvider#getText(Object)} is the selected object, for
344          * multiple selection a {@link String} with amount of selected items is the
345          * element.
346          * 
347          * @see #getSelectedItems() getSelectedItems() can be used to retrieve
348          *      selected items and get the items count.
349          * 
350          * @param detailsLabelProvider
351          *            the label provider for the details field
352          */
353         public void setDetailsLabelProvider(ILabelProvider detailsLabelProvider) {
354                 this.detailsLabelProvider = detailsLabelProvider;
355                 if (details != null) {
356                         details.setLabelProvider(detailsLabelProvider);
357                 }
358         }
359
360         private ILabelProvider getDetailsLabelProvider() {
361                 if (detailsLabelProvider == null) {
362                         detailsLabelProvider = new LabelProvider();
363                 }
364                 return detailsLabelProvider;
365         }
366
367         /*
368          * (non-Javadoc)
369          * 
370          * @see org.eclipse.jface.window.Window#create()
371          */
372         public void create() {
373                 super.create();
374                 pattern.setFocus();
375         }
376
377         /**
378          * Restores dialog using persisted settings. The default implementation
379          * restores the status of the details line and the selection history.
380          * 
381          * @param settings
382          *            settings used to restore dialog
383          */
384         protected void restoreDialog(IDialogSettings settings) {
385                 boolean toggleStatusLine = true;
386
387                 if (settings.get(SHOW_STATUS_LINE) != null) {
388                         toggleStatusLine = settings.getBoolean(SHOW_STATUS_LINE);
389                 }
390
391                 toggleStatusLineAction.setChecked(toggleStatusLine);
392
393                 details.setVisible(toggleStatusLine);
394
395                 String setting = settings.get(HISTORY_SETTINGS);
396                 if (setting != null) {
397                         try {
398                                 IMemento memento = XMLMemento.createReadRoot(new StringReader(setting));
399                                 this.contentProvider.loadHistory(memento);
400                         } catch (WorkbenchException e) {
401                                 // Simply don't restore the settings
402                                 StatusManager.getManager().handle(new Status(
403                                                 IStatus.ERROR,
404                                                 PlatformUI.PLUGIN_ID,
405                                                 IStatus.ERROR,
406                                                 WorkbenchMessages.FilteredItemsSelectionDialog_restoreError,
407                                                 e));
408                         }
409                 }
410         }
411
412         /*
413          * (non-Javadoc)
414          * 
415          * @see org.eclipse.jface.window.Window#close()
416          */
417         public boolean close() {
418                 this.filterJob.cancel();
419                 this.refreshCacheJob.cancel();
420                 this.refreshProgressMessageJob.cancel();
421                 if (showViewHandler != null) {
422                         IHandlerService service = (IHandlerService) PlatformUI.getWorkbench().getService(IHandlerService.class);
423                         service.deactivateHandler(showViewHandler);
424                         showViewHandler.getHandler().dispose();
425                         showViewHandler = null;
426                 }
427                 if (menuManager != null)
428                         menuManager.dispose();
429                 if (contextMenuManager != null)
430                         contextMenuManager.dispose();
431                 storeDialog(getDialogSettings());
432                 return super.close();
433         }
434
435         /**
436          * Stores dialog settings.
437          * 
438          * @param settings
439          *            settings used to store dialog
440          */
441         protected void storeDialog(IDialogSettings settings) {
442                 settings.put(SHOW_STATUS_LINE, toggleStatusLineAction.isChecked());
443
444                 XMLMemento memento = XMLMemento.createWriteRoot(HISTORY_SETTINGS);
445                 this.contentProvider.saveHistory(memento);
446                 StringWriter writer = new StringWriter();
447                 try {
448                         memento.save(writer);
449                         settings.put(HISTORY_SETTINGS, writer.getBuffer().toString());
450                 } catch (IOException e) {
451                         // Simply don't store the settings
452                         StatusManager.getManager().handle(new Status(
453                                         IStatus.ERROR,
454                                         PlatformUI.PLUGIN_ID,
455                                         IStatus.ERROR,
456                                         WorkbenchMessages.FilteredItemsSelectionDialog_storeError,
457                                         e));
458                 }
459         }
460
461         /**
462          * Create a new header which is labelled by headerLabel.
463          * 
464          * @param parent
465          * @return Label the label of the header
466          */
467         private Label createHeader(Composite parent) {
468                 Composite header = new Composite(parent, SWT.NONE);
469
470                 GridLayout layout = new GridLayout();
471                 layout.numColumns = 2;
472                 layout.marginWidth = 0;
473                 layout.marginHeight = 0;
474                 header.setLayout(layout);
475
476                 Label headerLabel = new Label(header, SWT.NONE);
477                 headerLabel.setText((getMessage() != null && getMessage().trim().length() > 0) ? getMessage()
478                                 : WorkbenchMessages.FilteredItemsSelectionDialog_patternLabel);
479                 headerLabel.addTraverseListener(new TraverseListener() {
480                         public void keyTraversed(TraverseEvent e) {
481                                 if (e.detail == SWT.TRAVERSE_MNEMONIC && e.doit) {
482                                         e.detail = SWT.TRAVERSE_NONE;
483                                         pattern.setFocus();
484                                 }
485                         }
486                 });
487
488                 GridData gd = new GridData(GridData.FILL_HORIZONTAL);
489                 headerLabel.setLayoutData(gd);
490
491                 createViewMenu(header);
492                 header.setLayoutData(gd);
493                 return headerLabel;
494         }
495
496         /**
497          * Create the labels for the list and the progress. Return the list label.
498          * 
499          * @param parent
500          * @return Label
501          */
502         private Label createLabels(Composite parent) {
503                 Composite labels = new Composite(parent, SWT.NONE);
504
505                 GridLayout layout = new GridLayout();
506                 layout.numColumns = 2;
507                 layout.marginWidth = 0;
508                 layout.marginHeight = 0;
509                 labels.setLayout(layout);
510
511                 Label listLabel = new Label(labels, SWT.NONE);
512                 listLabel.setText(WorkbenchMessages.FilteredItemsSelectionDialog_listLabel);
513
514                 listLabel.addTraverseListener(new TraverseListener() {
515                         public void keyTraversed(TraverseEvent e) {
516                                 if (e.detail == SWT.TRAVERSE_MNEMONIC && e.doit) {
517                                         e.detail = SWT.TRAVERSE_NONE;
518                                         viewer.getTable().setFocus();
519                                 }
520                         }
521                 });
522
523                 GridData gd = new GridData(GridData.FILL_HORIZONTAL);
524                 listLabel.setLayoutData(gd);
525
526                 progressLabel = new Label(labels, SWT.RIGHT);
527                 progressLabel.setLayoutData(gd);
528
529                 labels.setLayoutData(gd);
530                 return listLabel;
531         }
532
533         private void createViewMenu(Composite parent) {
534                 toolBar = new ToolBar(parent, SWT.FLAT);
535                 toolItem = new ToolItem(toolBar, SWT.PUSH, 0);
536
537                 GridData data = new GridData();
538                 data.horizontalAlignment = GridData.END;
539                 toolBar.setLayoutData(data);
540
541                 toolBar.addMouseListener(new MouseAdapter() {
542                         public void mouseDown(MouseEvent e) {
543                                 showViewMenu();
544                         }
545                 });
546
547                 toolItem.setImage(WorkbenchImages.getImage(IWorkbenchGraphicConstants.IMG_LCL_VIEW_MENU));
548                 toolItem.setToolTipText(WorkbenchMessages.FilteredItemsSelectionDialog_menu);
549                 toolItem.addSelectionListener(new SelectionAdapter() {
550                         public void widgetSelected(SelectionEvent e) {
551                                 showViewMenu();
552                         }
553                 });
554
555                 menuManager = new MenuManager();
556
557                 fillViewMenu(menuManager);
558
559                 IHandlerService service = (IHandlerService) PlatformUI.getWorkbench().getService(IHandlerService.class);
560                 IHandler handler = new AbstractHandler() {
561                         public Object execute(ExecutionEvent event) {
562                                 showViewMenu();
563                                 return null;
564                         }
565                 };
566                 showViewHandler = service.activateHandler(
567                                 IWorkbenchCommandConstants.WINDOW_SHOW_VIEW_MENU, handler,
568                                 new ActiveShellExpression(getShell()));
569         }
570
571         /**
572          * Fills the menu of the dialog.
573          * 
574          * @param menuManager
575          *            the menu manager
576          */
577         protected void fillViewMenu(IMenuManager menuManager) {
578                 toggleStatusLineAction = new ToggleStatusLineAction();
579                 menuManager.add(toggleStatusLineAction);
580         }
581
582         private void showViewMenu() {
583                 Menu menu = menuManager.createContextMenu(getShell());
584                 Rectangle bounds = toolItem.getBounds();
585                 Point topLeft = new Point(bounds.x, bounds.y + bounds.height);
586                 topLeft = toolBar.toDisplay(topLeft);
587                 menu.setLocation(topLeft.x, topLeft.y);
588                 menu.setVisible(true);
589         }
590
591     /**
592      * Hook that allows to add actions to the context menu.
593          * <p>
594          * Subclasses may extend in order to add other actions.</p>
595      * 
596      * @param menuManager the context menu manager
597      * @since 3.5
598      */
599         protected void fillContextMenu(IMenuManager menuManager) {
600                 List selectedElements= ((StructuredSelection)viewer.getSelection()).toList();
601
602                 Object item= null;
603
604                 for (Iterator it= selectedElements.iterator(); it.hasNext();) {
605                         item= it.next();
606                         if (item instanceof ItemsListSeparator || !isHistoryElement(item)) {
607                                 return;
608                         }
609                 }
610
611                 if (selectedElements.size() > 0) {
612                         removeHistoryItemAction.setText(WorkbenchMessages.FilteredItemsSelectionDialog_removeItemsFromHistoryAction);
613
614                         menuManager.add(removeHistoryActionContributionItem);
615
616                 }
617         }
618
619         private void createPopupMenu() {
620                 removeHistoryItemAction = new RemoveHistoryItemAction();
621                 removeHistoryActionContributionItem = new ActionContributionItem(removeHistoryItemAction);
622
623                 contextMenuManager = new MenuManager();
624                 contextMenuManager.setRemoveAllWhenShown(true);
625                 contextMenuManager.addMenuListener(new IMenuListener() {
626                         public void menuAboutToShow(IMenuManager manager) {
627                                 fillContextMenu(manager);
628                         }
629                 });
630
631                 final Table table = viewer.getTable();
632                 Menu menu= contextMenuManager.createContextMenu(table);
633                 table.setMenu(menu);
634         }
635
636         /**
637          * Creates an extra content area, which will be located above the details.
638          * 
639          * @param parent
640          *            parent to create the dialog widgets in
641          * @return an extra content area
642          */
643         protected abstract Control createExtendedContentArea(Composite parent);
644
645         /*
646          * (non-Javadoc)
647          * 
648          * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite)
649          */
650         protected Control createDialogArea(Composite parent) {
651                 Composite dialogArea = (Composite) super.createDialogArea(parent);
652
653                 Composite content = new Composite(dialogArea, SWT.NONE);
654                 GridData gd = new GridData(GridData.FILL_BOTH);
655                 content.setLayoutData(gd);
656
657                 GridLayout layout = new GridLayout();
658                 layout.numColumns = 1;
659                 layout.marginWidth = 0;
660                 layout.marginHeight = 0;
661                 content.setLayout(layout);
662
663                 final Label headerLabel = createHeader(content);
664
665                 pattern = new Text(content, SWT.SINGLE | SWT.BORDER | SWT.SEARCH | SWT.ICON_CANCEL);
666                 pattern.getAccessible().addAccessibleListener(new AccessibleAdapter() {
667                         public void getName(AccessibleEvent e) {
668                                 e.result = LegacyActionTools.removeMnemonics(headerLabel.getText());
669                         }
670                 });
671                 gd = new GridData(GridData.FILL_HORIZONTAL);
672                 pattern.setLayoutData(gd);
673
674                 final Label listLabel = createLabels(content);
675
676                 viewer = new TableViewer(content, (multi ? SWT.MULTI : SWT.SINGLE)
677                                 | SWT.BORDER | SWT.V_SCROLL | SWT.VIRTUAL | SWT.FULL_SELECTION);
678                 viewer.getTable().getAccessible().addAccessibleListener(
679                                 new AccessibleAdapter() {
680                                         public void getName(AccessibleEvent e) {
681                                                 if (e.childID == ACC.CHILDID_SELF) {
682                                                         e.result = LegacyActionTools.removeMnemonics(listLabel.getText());
683                                                 }
684                                         }
685                                 });
686                 viewer.setContentProvider(contentProvider);
687                 
688                 // added column creation hook
689                 createColumns(viewer);
690                 
691                 // show headers etc. if columns were added by the subclass
692                 if (viewer.getTable().getColumnCount() > 0) {
693                         viewer.getTable().setLinesVisible(true);
694                         viewer.getTable().setHeaderVisible(true);
695                 }
696                 
697                 viewer.setInput(new Object[0]);
698                 viewer.setItemCount(contentProvider.getNumberOfElements());
699                 gd = new GridData(GridData.FILL_BOTH);
700                 applyDialogFont(viewer.getTable());
701                 gd.heightHint= viewer.getTable().getItemHeight() * 15;
702                 viewer.getTable().setLayoutData(gd);
703
704                 createPopupMenu();
705
706                 pattern.addModifyListener(new ModifyListener() {
707                         public void modifyText(ModifyEvent e) {
708                                 applyFilter();
709                         }
710                 });
711
712                 pattern.addKeyListener(new KeyAdapter() {
713                         public void keyPressed(KeyEvent e) {
714                                 if (e.keyCode == SWT.ARROW_DOWN) {
715                                         if (viewer.getTable().getItemCount() > 0) {
716                                                 viewer.getTable().setFocus();
717                                         }
718                                 }
719                         }
720                 });
721
722                 viewer.addSelectionChangedListener(new ISelectionChangedListener() {
723                         public void selectionChanged(SelectionChangedEvent event) {
724                                 StructuredSelection selection = (StructuredSelection) event.getSelection();
725                                 handleSelected(selection);
726                         }
727                 });
728
729                 viewer.addDoubleClickListener(new IDoubleClickListener() {
730                         public void doubleClick(DoubleClickEvent event) {
731                                 handleDoubleClick();
732                         }
733                 });
734
735                 viewer.getTable().addKeyListener(new KeyAdapter() {
736                         public void keyPressed(KeyEvent e) {
737
738                                 if (e.keyCode == SWT.DEL) {
739
740                                         List selectedElements = ((StructuredSelection) viewer.getSelection()).toList();
741
742                                         Object item = null;
743                                         boolean isSelectedHistory = true;
744
745                                         for (Iterator it = selectedElements.iterator(); it.hasNext();) {
746                                                 item = it.next();
747                                                 if (item instanceof ItemsListSeparator || !isHistoryElement(item)) {
748                                                         isSelectedHistory = false;
749                                                         break;
750                                                 }
751                                         }
752                                         if (isSelectedHistory)
753                                                 removeSelectedItems(selectedElements);
754
755                                 }
756
757                                 if (e.keyCode == SWT.ARROW_UP && (e.stateMask & SWT.SHIFT) != 0
758                                                 && (e.stateMask & SWT.CTRL) != 0) {
759                                         StructuredSelection selection = (StructuredSelection) viewer.getSelection();
760
761                                         if (selection.size() == 1) {
762                                                 Object element = selection.getFirstElement();
763                                                 if (element.equals(viewer.getElementAt(0))) {
764                                                         pattern.setFocus();
765                                                 }
766                                                 if (viewer.getElementAt(viewer.getTable().getSelectionIndex() - 1) instanceof ItemsListSeparator)
767                                                         viewer.getTable().setSelection(viewer.getTable().getSelectionIndex() - 1);
768                                                 viewer.getTable().notifyListeners(SWT.Selection, new Event());
769
770                                         }
771                                 }
772
773                                 if (e.keyCode == SWT.ARROW_DOWN
774                                                 && (e.stateMask & SWT.SHIFT) != 0
775                                                 && (e.stateMask & SWT.CTRL) != 0) {
776
777                                         if (viewer.getElementAt(viewer.getTable().getSelectionIndex() + 1) instanceof ItemsListSeparator)
778                                                 viewer.getTable().setSelection(viewer.getTable().getSelectionIndex() + 1);
779                                         viewer.getTable().notifyListeners(SWT.Selection, new Event());
780                                 }
781                         }
782                 });
783
784                 createExtendedContentArea(content);
785
786                 details = new DetailsContentViewer(content, SWT.BORDER | SWT.FLAT);
787                 details.setVisible(toggleStatusLineAction.isChecked());
788                 details.setContentProvider(new NullContentProvider());
789                 details.setLabelProvider(getDetailsLabelProvider());
790
791                 applyDialogFont(content);
792
793                 restoreDialog(getDialogSettings());
794
795                 if (initialPatternText != null) {
796                         pattern.setText(initialPatternText);
797                 }
798
799                 switch (selectionMode) {
800                 case CARET_BEGINNING:
801                         pattern.setSelection(0, 0);
802                         break;
803                 case FULL_SELECTION:
804                         pattern.setSelection(0, initialPatternText.length());
805                         break;
806                 }
807
808                 // apply filter even if pattern is empty (display history)
809                 applyFilter();
810
811                 return dialogArea;
812         }
813         
814         /**
815          * Override this method to add columns to the content area of this dialog.
816          * 
817          * Subclass implementation of this method should NOT call the super 
818          * implementation as this will break the individual column label providers
819          * 
820          * @param viewer
821          */
822         protected void createColumns(TableViewer viewer) {
823                 // no columns are added by default, just set the label provider for the viewer
824                 viewer.setLabelProvider(getItemsListLabelProvider());
825         }
826         
827         /**
828          * An utility method for adding a column to the TableViewer, should be called
829          * from inside createColumns.
830          * 
831          * @param viewer
832          * @param label
833          * @param width
834          * @param labelProvider
835          */
836         protected void createColumn(TableViewer viewer, String label, int width, ColumnLabelProvider labelProvider) {
837                 TableViewerColumn viewercol = new TableViewerColumn(viewer, SWT.LEFT);
838                 
839                 TableColumn col = viewercol.getColumn();
840                 col.setText(label);
841                 col.setWidth(width);
842                 col.setResizable(true);
843                 col.setMoveable(true);
844                 
845                 // TODO: should use the local label provider class instead but it 
846                 // should be made compatible with multiple columns first
847                 viewercol.setLabelProvider(labelProvider);
848         }
849
850         /**
851          * This method is a hook for subclasses to override default dialog behavior.
852          * The <code>handleDoubleClick()</code> method handles double clicks on
853          * the list of filtered elements.
854          * <p>
855          * Current implementation makes double-clicking on the list do the same as
856          * pressing <code>OK</code> button on the dialog.
857          */
858         protected void handleDoubleClick() {
859                 okPressed();
860         }
861
862         /**
863          * Refreshes the details field according to the current selection in the
864          * items list.
865          */
866         private void refreshDetails() {
867                 StructuredSelection selection = getSelectedItems();
868
869                 switch (selection.size()) {
870                 case 0:
871                         details.setInput(null);
872                         break;
873                 case 1:
874                         details.setInput(selection.getFirstElement());
875                         break;
876                 default:
877                         details.setInput(NLS.bind(
878                                         WorkbenchMessages.FilteredItemsSelectionDialog_nItemsSelected,
879                                         new Integer(selection.size())));
880                         break;
881                 }
882
883         }
884
885         /**
886          * Handle selection in the items list by updating labels of selected and
887          * unselected items and refresh the details field using the selection.
888          * 
889          * @param selection
890          *            the new selection
891          */
892         protected void handleSelected(StructuredSelection selection) {
893                 IStatus status = new Status(IStatus.OK, PlatformUI.PLUGIN_ID,
894                                 IStatus.OK, EMPTY_STRING, null);
895
896                 Object[] lastSelection = currentSelection;
897
898                 currentSelection = selection.toArray();
899
900                 if (selection.size() == 0) {
901                         status = new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID,
902                                         IStatus.ERROR, EMPTY_STRING, null);
903
904                         if (lastSelection != null && getListSelectionLabelDecorator() != null) {
905                                 viewer.update(lastSelection, null);
906                         }
907
908                         currentSelection = null;
909
910                 } else {
911                         status = new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID,
912                                         IStatus.ERROR, EMPTY_STRING, null);
913
914                         List items = selection.toList();
915
916                         Object item = null;
917                         IStatus tempStatus = null;
918
919                         for (Iterator it = items.iterator(); it.hasNext();) {
920                                 Object o = it.next();
921
922                                 if (o instanceof ItemsListSeparator) {
923                                         continue;
924                                 }
925
926                                 item = o;
927                                 tempStatus = validateItem(item);
928
929                                 if (tempStatus.isOK()) {
930                                         status = new Status(IStatus.OK, PlatformUI.PLUGIN_ID,
931                                                         IStatus.OK, EMPTY_STRING, null);
932                                 } else {
933                                         status = tempStatus;
934                                         // if any selected element is not valid status is set to
935                                         // ERROR
936                                         break;
937                                 }
938                         }
939
940                         if (lastSelection != null && getListSelectionLabelDecorator() != null) {
941                                 viewer.update(lastSelection, null);
942                         }
943
944                         if (getListSelectionLabelDecorator() != null) {
945                                 viewer.update(currentSelection, null);
946                         }
947                 }
948
949                 refreshDetails();
950                 updateStatus(status);
951         }
952
953         /*
954          * (non-Javadoc)
955          * 
956          * @see org.eclipse.jface.window.Dialog#getDialogBoundsSettings()
957          */
958         protected IDialogSettings getDialogBoundsSettings() {
959                 IDialogSettings settings = getDialogSettings();
960                 IDialogSettings section = settings.getSection(DIALOG_BOUNDS_SETTINGS);
961                 if (section == null) {
962                         section = settings.addNewSection(DIALOG_BOUNDS_SETTINGS);
963                         section.put(DIALOG_HEIGHT, 500);
964                         section.put(DIALOG_WIDTH, 600);
965                 }
966                 return section;
967         }
968
969         /**
970          * Returns the dialog settings. Returned object can't be null.
971          * 
972          * @return return dialog settings for this dialog
973          */
974         protected abstract IDialogSettings getDialogSettings();
975
976         /**
977          * Refreshes the dialog - has to be called in UI thread.
978          */
979         public void refresh() {
980                 if (viewer != null && !viewer.getTable().isDisposed()) {
981
982                         List lastRefreshSelection = ((StructuredSelection) viewer.getSelection()).toList();
983                         viewer.getTable().deselectAll();
984
985                         viewer.setItemCount(contentProvider.getNumberOfElements());
986                         viewer.refresh();
987
988                         if (viewer.getTable().getItemCount() > 0) {
989                                 // preserve previous selection
990                                 if (refreshWithLastSelection && lastRefreshSelection != null
991                                                 && lastRefreshSelection.size() > 0) {
992                                         viewer.setSelection(new StructuredSelection(
993                                                         lastRefreshSelection));
994                                 } else {
995                                         refreshWithLastSelection = true;
996                                         viewer.getTable().setSelection(0);
997                                         viewer.getTable().notifyListeners(SWT.Selection, new Event());
998                                 }
999                         } else {
1000                                 viewer.setSelection(StructuredSelection.EMPTY);
1001                         }
1002
1003                 }
1004
1005                 scheduleProgressMessageRefresh();
1006         }
1007
1008         /**
1009          * Updates the progress label.
1010          * 
1011          * @deprecated
1012          */
1013         public void updateProgressLabel() {
1014                 scheduleProgressMessageRefresh();
1015         }
1016
1017         /**
1018          * Notifies the content provider - fires filtering of content provider
1019          * elements. During the filtering, a separator between history and workspace
1020          * matches is added.
1021          * <p>
1022          * This is a long running operation and should be called in a job.
1023          * 
1024          * @param checkDuplicates
1025          *            <code>true</code> if data concerning elements duplication
1026          *            should be computed - it takes much more time than the standard
1027          *            filtering
1028          * @param monitor
1029          *            a progress monitor or <code>null</code> if no monitor is
1030          *            available
1031          */
1032         public void reloadCache(boolean checkDuplicates, IProgressMonitor monitor) {
1033                 if (viewer != null && !viewer.getTable().isDisposed() && contentProvider != null) {
1034                         contentProvider.reloadCache(checkDuplicates, monitor);
1035                 }
1036         }
1037
1038         /**
1039          * Schedule refresh job.
1040          */
1041         public void scheduleRefresh() {
1042                 refreshCacheJob.cancelAll();
1043                 refreshCacheJob.schedule();
1044         }
1045
1046         /**
1047          * Schedules progress message refresh.
1048          */
1049         public void scheduleProgressMessageRefresh() {
1050                 if (filterJob.getState() != Job.RUNNING && refreshProgressMessageJob.getState() != Job.RUNNING)
1051                         refreshProgressMessageJob.scheduleProgressRefresh(null);
1052         }
1053
1054         /*
1055          * (non-Javadoc)
1056          * 
1057          * @see org.eclipse.ui.dialogs.SelectionStatusDialog#computeResult()
1058          */
1059         protected void computeResult() {
1060
1061                 List selectedElements = ((StructuredSelection) viewer.getSelection())
1062                                 .toList();
1063
1064                 List objectsToReturn = new ArrayList();
1065
1066                 Object item = null;
1067
1068                 for (Iterator it = selectedElements.iterator(); it.hasNext();) {
1069                         item = it.next();
1070
1071                         if (!(item instanceof ItemsListSeparator)) {
1072                                 accessedHistoryItem(item);
1073                                 objectsToReturn.add(item);
1074                         }
1075                 }
1076
1077                 setResult(objectsToReturn);
1078         }
1079
1080         /*
1081          * @see org.eclipse.ui.dialogs.SelectionStatusDialog#updateStatus(org.eclipse.core.runtime.IStatus)
1082          */
1083         protected void updateStatus(IStatus status) {
1084                 this.status = status;
1085                 super.updateStatus(status);
1086         }
1087
1088         /*
1089          * @see Dialog#okPressed()
1090          */
1091         protected void okPressed() {
1092                 if (status != null && (status.isOK() || status.getCode() == IStatus.INFO)) {
1093                         super.okPressed();
1094                 }
1095         }
1096
1097         /**
1098          * Sets the initial pattern used by the filter. This text is copied into the
1099          * selection input on the dialog. A full selection is used in the pattern
1100          * input field.
1101          * 
1102          * @param text
1103          *            initial pattern for the filter
1104          * @see ColumnFilteredItemsSelectionDialog#FULL_SELECTION
1105          */
1106         public void setInitialPattern(String text) {
1107                 setInitialPattern(text, FULL_SELECTION);
1108         }
1109
1110         /**
1111          * Sets the initial pattern used by the filter. This text is copied into the
1112          * selection input on the dialog. The <code>selectionMode</code> is used
1113          * to choose selection type for the input field.
1114          * 
1115          * @param text
1116          *            initial pattern for the filter
1117          * @param selectionMode
1118          *            one of: {@link ColumnFilteredItemsSelectionDialog#NONE},
1119          *            {@link ColumnFilteredItemsSelectionDialog#CARET_BEGINNING},
1120          *            {@link ColumnFilteredItemsSelectionDialog#FULL_SELECTION}
1121          */
1122         public void setInitialPattern(String text, int selectionMode) {
1123                 this.initialPatternText = text;
1124                 this.selectionMode = selectionMode;
1125         }
1126
1127         /**
1128          * Gets initial pattern.
1129          * 
1130          * @return initial pattern, or <code>null</code> if initial pattern is not
1131          *         set
1132          */
1133         protected String getInitialPattern() {
1134                 return this.initialPatternText;
1135         }
1136
1137         /**
1138          * Returns the current selection.
1139          * 
1140          * @return the current selection
1141          */
1142         protected StructuredSelection getSelectedItems() {
1143
1144                 StructuredSelection selection = (StructuredSelection) viewer.getSelection();
1145
1146                 List selectedItems = selection.toList();
1147                 Object itemToRemove = null;
1148
1149                 for (Iterator it = selection.iterator(); it.hasNext();) {
1150                         Object item = it.next();
1151                         if (item instanceof ItemsListSeparator) {
1152                                 itemToRemove = item;
1153                                 break;
1154                         }
1155                 }
1156
1157                 if (itemToRemove == null)
1158                         return new StructuredSelection(selectedItems);
1159                 // Create a new selection without the collision
1160                 List newItems = new ArrayList(selectedItems);
1161                 newItems.remove(itemToRemove);
1162                 return new StructuredSelection(newItems);
1163
1164         }
1165
1166         /**
1167          * Validates the item. When items on the items list are selected or
1168          * deselected, it validates each item in the selection and the dialog status
1169          * depends on all validations.
1170          * 
1171          * @param item
1172          *            an item to be checked
1173          * @return status of the dialog to be set
1174          */
1175         protected abstract IStatus validateItem(Object item);
1176
1177         /**
1178          * Creates an instance of a filter.
1179          * 
1180          * @return a filter for items on the items list. Can be <code>null</code>,
1181          *         no filtering will be applied then, causing no item to be shown in
1182          *         the list.
1183          */
1184         protected abstract ItemsFilter createFilter();
1185
1186         /**
1187          * Applies the filter created by <code>createFilter()</code> method to the
1188          * items list. When new filter is different than previous one it will cause
1189          * refiltering.
1190          */
1191         protected void applyFilter() {
1192                 ItemsFilter newFilter = createFilter();
1193
1194                 // don't apply filtering for patterns which mean the same, for example:
1195                 // *a**b and ***a*b
1196                 if (filter != null && filter.equalsFilter(newFilter)) {
1197                         return;
1198                 }
1199                 
1200                 filterHistoryJob.cancel();
1201                 filterJob.cancel();
1202
1203                 this.filter = newFilter;
1204                 
1205                 if (this.filter != null) {
1206                         filterHistoryJob.schedule();
1207                 }
1208                 
1209         }
1210
1211         /**
1212          * Returns comparator to sort items inside content provider. Returned object
1213          * will be probably created as an anonymous class. Parameters passed to the
1214          * <code>compare(java.lang.Object, java.lang.Object)</code> are going to
1215          * be the same type as the one used in the content provider.
1216          * 
1217          * @return comparator to sort items content provider
1218          */
1219         protected abstract Comparator getItemsComparator();
1220
1221         /**
1222          * Fills the content provider with matching items.
1223          * 
1224          * @param contentProvider
1225          *            collector to add items to.
1226          *            {@link ColumnFilteredItemsSelectionDialog.AbstractContentProvider#add(Object, ColumnFilteredItemsSelectionDialog.ItemsFilter)}
1227          *            only adds items that pass the given <code>itemsFilter</code>.
1228          * @param itemsFilter
1229          *            the items filter
1230          * @param progressMonitor
1231          *            must be used to report search progress. The state of this
1232          *            progress monitor reflects the state of the filtering process.
1233          * @throws CoreException
1234          */
1235         protected abstract void fillContentProvider(
1236                         AbstractContentProvider contentProvider, ItemsFilter itemsFilter,
1237                         IProgressMonitor progressMonitor) throws CoreException;
1238         
1239         /**
1240          * Force a refresh of the content provider.
1241          */
1242         protected void forceRefresh() {
1243                 lastCompletedFilter = null;
1244                 lastCompletedResult = null;
1245                 filterHistoryJob.schedule();
1246         }
1247
1248         /**
1249          * Removes selected items from history.
1250          * 
1251          * @param items
1252          *            items to be removed
1253          */
1254         private void removeSelectedItems(List items) {
1255                 for (Iterator iter = items.iterator(); iter.hasNext();) {
1256                         Object item = iter.next();
1257                         removeHistoryItem(item);
1258                 }
1259                 refreshWithLastSelection = false;
1260                 contentProvider.refresh();
1261         }
1262
1263         /**
1264          * Removes an item from history.
1265          * 
1266          * @param item
1267          *            an item to remove
1268          * @return removed item
1269          */
1270         protected Object removeHistoryItem(Object item) {
1271                 return contentProvider.removeHistoryElement(item);
1272         }
1273
1274         /**
1275          * Adds item to history.
1276          * 
1277          * @param item
1278          *            the item to be added
1279          */
1280         protected void accessedHistoryItem(Object item) {
1281                 contentProvider.addHistoryElement(item);
1282         }
1283
1284         /**
1285          * Returns a history comparator.
1286          * 
1287          * @return decorated comparator
1288          */
1289         private Comparator getHistoryComparator() {
1290                 return new HistoryComparator();
1291         }
1292
1293         /**
1294          * Returns the history of selected elements.
1295          * 
1296          * @return history of selected elements, or <code>null</code> if it is not
1297          *         set
1298          */
1299         protected SelectionHistory getSelectionHistory() {
1300                 return this.contentProvider.getSelectionHistory();
1301         }
1302
1303         /**
1304          * Sets new history.
1305          * 
1306          * @param selectionHistory
1307          *            the history
1308          */
1309         protected void setSelectionHistory(SelectionHistory selectionHistory) {
1310                 if (this.contentProvider != null)
1311                         this.contentProvider.setSelectionHistory(selectionHistory);
1312         }
1313
1314         /**
1315          * Indicates whether the given item is a history item.
1316          * 
1317          * @param item
1318          *            the item to be investigated
1319          * @return <code>true</code> if the given item exists in history,
1320          *         <code>false</code> otherwise
1321          */
1322         public boolean isHistoryElement(Object item) {
1323                 return this.contentProvider.isHistoryElement(item);
1324         }
1325
1326         /**
1327          * Indicates whether the given item is a duplicate.
1328          * 
1329          * @param item
1330          *            the item to be investigated
1331          * @return <code>true</code> if the item is duplicate, <code>false</code>
1332          *         otherwise
1333          */
1334         public boolean isDuplicateElement(Object item) {
1335                 return this.contentProvider.isDuplicateElement(item);
1336         }
1337
1338         /**
1339          * Sets separator label
1340          * 
1341          * @param separatorLabel
1342          *            the label showed on separator
1343          */
1344         public void setSeparatorLabel(String separatorLabel) {
1345                 this.itemsListSeparator = new ItemsListSeparator(separatorLabel);
1346         }
1347
1348         /**
1349          * Returns name for then given object.
1350          * 
1351          * @param item
1352          *            an object from the content provider. Subclasses should pay
1353          *            attention to the passed argument. They should either only pass
1354          *            objects of a known type (one used in content provider) or make
1355          *            sure that passed parameter is the expected one (by type
1356          *            checking like <code>instanceof</code> inside the method).
1357          * @return name of the given item
1358          */
1359         public abstract String getElementName(Object item);
1360
1361         private class ToggleStatusLineAction extends Action {
1362
1363                 /**
1364                  * Creates a new instance of the class.
1365                  */
1366                 public ToggleStatusLineAction() {
1367                         super(WorkbenchMessages.FilteredItemsSelectionDialog_toggleStatusAction, IAction.AS_CHECK_BOX);
1368                 }
1369
1370                 public void run() {
1371                         details.setVisible(isChecked());
1372                 }
1373         }
1374
1375         /**
1376          * Only refreshes UI on the basis of an already sorted and filtered set of
1377          * items.
1378          * <p>
1379          * Standard invocation scenario:
1380          * <ol>
1381          * <li>filtering job (<code>FilterJob</code> class extending
1382          * <code>Job</code> class)</li>
1383          * <li>cache refresh without checking for duplicates (<code>RefreshCacheJob</code>
1384          * class extending <code>Job</code> class)</li>
1385          * <li>UI refresh (<code>RefreshJob</code> class extending
1386          * <code>UIJob</code> class)</li>
1387          * <li>cache refresh with checking for duplicates (<cod>CacheRefreshJob</code>
1388          * class extending <code>Job</code> class)</li>
1389          * <li>UI refresh (<code>RefreshJob</code> class extending <code>UIJob</code>
1390          * class)</li>
1391          * </ol>
1392          * The scenario is rather complicated, but it had to be applied, because:
1393          * <ul>
1394          * <li> refreshing cache is rather a long action and cannot be run in the UI -
1395          * cannot be run in a UIJob</li>
1396          * <li> refreshing cache checking for duplicates is twice as long as
1397          * refreshing cache without checking for duplicates; results of the search
1398          * could be displayed earlier</li>
1399          * <li> refreshing the UI have to be run in a UIJob</li>
1400          * </ul>
1401          * 
1402          * @see org.eclipse.ui.dialogs.FilteredItemsSelectionDialog.FilterJob
1403          * @see org.eclipse.ui.dialogs.FilteredItemsSelectionDialog.RefreshJob
1404          * @see org.eclipse.ui.dialogs.FilteredItemsSelectionDialog.RefreshCacheJob
1405          */
1406         private class RefreshJob extends UIJob {
1407
1408                 /**
1409                  * Creates a new instance of the class.
1410                  */
1411                 public RefreshJob() {
1412                         super(ColumnFilteredItemsSelectionDialog.this.getParentShell().getDisplay(),
1413                                         WorkbenchMessages.FilteredItemsSelectionDialog_refreshJob);
1414                         setSystem(true);
1415                 }
1416
1417                 /*
1418                  * (non-Javadoc)
1419                  * 
1420                  * @see org.eclipse.ui.progress.UIJob#runInUIThread(org.eclipse.core.runtime.IProgressMonitor)
1421                  */
1422                 public IStatus runInUIThread(IProgressMonitor monitor) {
1423                         if (monitor.isCanceled())
1424                                 return new Status(IStatus.OK, WorkbenchPlugin.PI_WORKBENCH,
1425                                                 IStatus.OK, EMPTY_STRING, null);
1426
1427                         if (ColumnFilteredItemsSelectionDialog.this != null) {
1428                                 ColumnFilteredItemsSelectionDialog.this.refresh();
1429                         }
1430
1431                         return new Status(IStatus.OK, PlatformUI.PLUGIN_ID, IStatus.OK,
1432                                         EMPTY_STRING, null);
1433                 }
1434
1435         }
1436
1437         /**
1438          * Refreshes the progress message cyclically with 500 milliseconds delay.
1439          * <code>RefreshProgressMessageJob</code> is strictly connected with
1440          * <code>GranualProgressMonitor</code> and use it to to get progress
1441          * message and to decide about break of cyclical refresh.
1442          */
1443         private class RefreshProgressMessageJob extends UIJob {
1444
1445                 private GranualProgressMonitor progressMonitor;
1446
1447                 /**
1448                  * Creates a new instance of the class.
1449                  */
1450                 public RefreshProgressMessageJob() {
1451                         super(ColumnFilteredItemsSelectionDialog.this.getParentShell().getDisplay(),
1452                                         WorkbenchMessages.FilteredItemsSelectionDialog_progressRefreshJob);
1453                         setSystem(true);
1454                 }
1455
1456                 /*
1457                  * (non-Javadoc)
1458                  * 
1459                  * @see org.eclipse.ui.progress.UIJob#runInUIThread(org.eclipse.core.runtime.IProgressMonitor)
1460                  */
1461                 public IStatus runInUIThread(IProgressMonitor monitor) {
1462
1463                         if (!progressLabel.isDisposed())
1464                                 progressLabel.setText(progressMonitor != null ? progressMonitor.getMessage() : EMPTY_STRING);
1465
1466                         if (progressMonitor == null || progressMonitor.isDone()) {
1467                                 return new Status(IStatus.CANCEL, PlatformUI.PLUGIN_ID,
1468                                                 IStatus.CANCEL, EMPTY_STRING, null);
1469                         }
1470
1471                         // Schedule cyclical with 500 milliseconds delay
1472                         schedule(500);
1473
1474                         return new Status(IStatus.OK, PlatformUI.PLUGIN_ID, IStatus.OK,
1475                                         EMPTY_STRING, null);
1476                 }
1477
1478                 /**
1479                  * Schedule progress refresh job.
1480                  * 
1481                  * @param progressMonitor
1482                  *            used during refresh progress label
1483                  */
1484                 public void scheduleProgressRefresh(
1485                                 GranualProgressMonitor progressMonitor) {
1486                         this.progressMonitor = progressMonitor;
1487                         // Schedule with initial delay to avoid flickering when the user
1488                         // types quickly
1489                         schedule(200);
1490                 }
1491
1492         }
1493
1494         /**
1495          * A job responsible for computing filtered items list presented using
1496          * <code>RefreshJob</code>.
1497          * 
1498          * @see ColumnFilteredItemsSelectionDialog.RefreshJob
1499          * 
1500          */
1501         private class RefreshCacheJob extends Job {
1502
1503                 private RefreshJob refreshJob = new RefreshJob();
1504
1505                 /**
1506                  * Creates a new instance of the class.
1507                  */
1508                 public RefreshCacheJob() {
1509                         super(WorkbenchMessages.FilteredItemsSelectionDialog_cacheRefreshJob);
1510                         setSystem(true);
1511                 }
1512
1513                 /**
1514                  * Stops the job and all sub-jobs.
1515                  */
1516                 public void cancelAll() {
1517                         cancel();
1518                         refreshJob.cancel();
1519                 }
1520
1521                 /*
1522                  * (non-Javadoc)
1523                  * 
1524                  * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
1525                  */
1526                 protected IStatus run(IProgressMonitor monitor) {
1527                         if (monitor.isCanceled()) {
1528                                 return new Status(IStatus.CANCEL, WorkbenchPlugin.PI_WORKBENCH,
1529                                                 IStatus.CANCEL, EMPTY_STRING, null);
1530                         }
1531
1532                         if (ColumnFilteredItemsSelectionDialog.this != null) {
1533                                 GranualProgressMonitor wrappedMonitor = new GranualProgressMonitor(monitor);
1534                                 ColumnFilteredItemsSelectionDialog.this.reloadCache(true, wrappedMonitor);
1535                         }
1536
1537                         if (!monitor.isCanceled()) {
1538                                 refreshJob.schedule();
1539                         }
1540
1541                         return new Status(IStatus.OK, PlatformUI.PLUGIN_ID, IStatus.OK,
1542                                         EMPTY_STRING, null);
1543
1544                 }
1545
1546                 /*
1547                  * (non-Javadoc)
1548                  * 
1549                  * @see org.eclipse.core.runtime.jobs.Job#canceling()
1550                  */
1551                 protected void canceling() {
1552                         super.canceling();
1553                         contentProvider.stopReloadingCache();
1554                 }
1555
1556         }
1557
1558         private class RemoveHistoryItemAction extends Action {
1559
1560                 /**
1561                  * Creates a new instance of the class.
1562                  */
1563                 public RemoveHistoryItemAction() {
1564                         super(WorkbenchMessages.FilteredItemsSelectionDialog_removeItemsFromHistoryAction);
1565                 }
1566
1567                 /*
1568                  * (non-Javadoc)
1569                  * 
1570                  * @see org.eclipse.jface.action.Action#run()
1571                  */
1572                 public void run() {
1573                         List selectedElements = ((StructuredSelection) viewer.getSelection()).toList();
1574                         removeSelectedItems(selectedElements);
1575                 }
1576         }
1577
1578         private static boolean showColoredLabels() {
1579                 return PlatformUI.getPreferenceStore().getBoolean(IWorkbenchPreferenceConstants.USE_COLORED_LABELS);
1580         }
1581
1582         private class ItemsListLabelProvider extends StyledCellLabelProvider
1583                         implements ILabelProviderListener {
1584                 private ILabelProvider provider;
1585
1586                 private ILabelDecorator selectionDecorator;
1587
1588                 // Need to keep our own list of listeners
1589                 private ListenerList listeners = new ListenerList();
1590
1591                 /**
1592                  * Creates a new instance of the class.
1593                  * 
1594                  * @param provider
1595                  *            the label provider for all items, not <code>null</code>
1596                  * @param selectionDecorator
1597                  *            the decorator for selected items, can be <code>null</code>
1598                  */
1599                 public ItemsListLabelProvider(ILabelProvider provider,
1600                                 ILabelDecorator selectionDecorator) {
1601                         Assert.isNotNull(provider);
1602                         this.provider = provider;
1603                         this.selectionDecorator = selectionDecorator;
1604
1605                         setOwnerDrawEnabled(showColoredLabels() && provider instanceof IStyledLabelProvider);
1606
1607                         provider.addListener(this);
1608
1609                         if (selectionDecorator != null) {
1610                                 selectionDecorator.addListener(this);
1611                         }
1612                 }
1613
1614                 /**
1615                  * Sets new selection decorator.
1616                  * 
1617                  * @param newSelectionDecorator
1618                  *            new label decorator for selected items in the list
1619                  */
1620                 public void setSelectionDecorator(ILabelDecorator newSelectionDecorator) {
1621                         if (selectionDecorator != null) {
1622                                 selectionDecorator.removeListener(this);
1623                                 selectionDecorator.dispose();
1624                         }
1625
1626                         selectionDecorator = newSelectionDecorator;
1627
1628                         if (selectionDecorator != null) {
1629                                 selectionDecorator.addListener(this);
1630                         }
1631                 }
1632
1633                 /**
1634                  * Gets selection decorator.
1635                  * 
1636                  * @return the label decorator for selected items in the list
1637                  */
1638                 public ILabelDecorator getSelectionDecorator() {
1639                         return selectionDecorator;
1640                 }
1641
1642                 /**
1643                  * Sets new label provider.
1644                  * 
1645                  * @param newProvider
1646                  *            new label provider for items in the list, not
1647                  *            <code>null</code>
1648                  */
1649                 public void setProvider(ILabelProvider newProvider) {
1650                         Assert.isNotNull(newProvider);
1651                         provider.removeListener(this);
1652                         provider.dispose();
1653
1654                         provider = newProvider;
1655
1656                         if (provider != null) {
1657                                 provider.addListener(this);
1658                         }
1659
1660                         setOwnerDrawEnabled(showColoredLabels() && provider instanceof IStyledLabelProvider);
1661                 }
1662
1663                 private Image getImage(Object element) {
1664                         if (element instanceof ItemsListSeparator) {
1665                                 return WorkbenchImages.getImage(IWorkbenchGraphicConstants.IMG_OBJ_SEPARATOR);
1666                         }
1667
1668                         return provider.getImage(element);
1669                 }
1670
1671                 private boolean isSelected(Object element) {
1672                         if (element != null && currentSelection != null) {
1673                                 for (int i = 0; i < currentSelection.length; i++) {
1674                                         if (element.equals(currentSelection[i]))
1675                                                 return true;
1676                                 }
1677                         }
1678                         return false;
1679                 }
1680
1681                 /*
1682                  * (non-Javadoc)
1683                  * 
1684                  * @see org.eclipse.jface.viewers.ILabelProvider#getText(java.lang.Object)
1685                  */
1686                 private String getText(Object element) {
1687                         if (element instanceof ItemsListSeparator) {
1688                                 return getSeparatorLabel(((ItemsListSeparator) element).getName());
1689                         }
1690
1691                         String str = provider.getText(element);
1692                         if (selectionDecorator != null && isSelected(element)) {
1693                                 return selectionDecorator.decorateText(str.toString(), element);
1694                         }
1695
1696                         return str;
1697                 }
1698
1699                 private StyledString getStyledText(Object element,
1700                                 IStyledLabelProvider provider) {
1701                         StyledString string = provider.getStyledText(element);
1702
1703                         if (selectionDecorator != null && isSelected(element)) {
1704                                 String decorated = selectionDecorator.decorateText(string.getString(), element);
1705                                 return StyledCellLabelProvider.styleDecoratedString(decorated, null, string);
1706                                 // no need to add colors when element is selected
1707                         }
1708                         return string;
1709                 }
1710
1711                 public void update(ViewerCell cell) {
1712                         Object element = cell.getElement();
1713
1714                         if (!(element instanceof ItemsListSeparator) && provider instanceof IStyledLabelProvider) {
1715                                 IStyledLabelProvider styledLabelProvider = (IStyledLabelProvider) provider;
1716                                 StyledString styledString = getStyledText(element, styledLabelProvider);
1717
1718                                 cell.setText(styledString.getString());
1719                                 cell.setStyleRanges(styledString.getStyleRanges());
1720                                 cell.setImage(styledLabelProvider.getImage(element));
1721                         } else {
1722                                 cell.setText(getText(element));
1723                                 cell.setImage(getImage(element));
1724                         }
1725                         cell.setFont(getFont(element));
1726                         cell.setForeground(getForeground(element));
1727                         cell.setBackground(getBackground(element));
1728
1729                         super.update(cell);
1730                 }
1731
1732                 private String getSeparatorLabel(String separatorLabel) {
1733                         Rectangle rect = viewer.getTable().getBounds();
1734
1735                         int borderWidth = viewer.getTable().computeTrim(0, 0, 0, 0).width;
1736
1737                         int imageWidth = WorkbenchImages.getImage(
1738                                         IWorkbenchGraphicConstants.IMG_OBJ_SEPARATOR).getBounds().width;
1739
1740                         int width = rect.width - borderWidth - imageWidth;
1741
1742                         GC gc = new GC(viewer.getTable());
1743                         gc.setFont(viewer.getTable().getFont());
1744
1745                         int fSeparatorWidth = gc.getAdvanceWidth('-');
1746                         int fMessageLength = gc.textExtent(separatorLabel).x;
1747
1748                         gc.dispose();
1749
1750                         StringBuffer dashes = new StringBuffer();
1751                         int chars = (((width - fMessageLength) / fSeparatorWidth) / 2) - 2;
1752                         for (int i = 0; i < chars; i++) {
1753                                 dashes.append('-');
1754                         }
1755
1756                         StringBuffer result = new StringBuffer();
1757                         result.append(dashes);
1758                         result.append(" " + separatorLabel + " "); //$NON-NLS-1$//$NON-NLS-2$
1759                         result.append(dashes);
1760                         return result.toString().trim();
1761                 }
1762
1763                 /*
1764                  * (non-Javadoc)
1765                  * 
1766                  * @see org.eclipse.jface.viewers.IBaseLabelProvider#addListener(org.eclipse.jface.viewers.ILabelProviderListener)
1767                  */
1768                 public void addListener(ILabelProviderListener listener) {
1769                         listeners.add(listener);
1770                 }
1771
1772                 /*
1773                  * (non-Javadoc)
1774                  * 
1775                  * @see org.eclipse.jface.viewers.IBaseLabelProvider#dispose()
1776                  */
1777                 public void dispose() {
1778                         provider.removeListener(this);
1779                         provider.dispose();
1780
1781                         if (selectionDecorator != null) {
1782                                 selectionDecorator.removeListener(this);
1783                                 selectionDecorator.dispose();
1784                         }
1785
1786                         super.dispose();
1787                 }
1788
1789                 /*
1790                  * (non-Javadoc)
1791                  * 
1792                  * @see org.eclipse.jface.viewers.IBaseLabelProvider#isLabelProperty(java.lang.Object,
1793                  *      java.lang.String)
1794                  */
1795                 public boolean isLabelProperty(Object element, String property) {
1796                         if (provider.isLabelProperty(element, property)) {
1797                                 return true;
1798                         }
1799                         if (selectionDecorator != null
1800                                         && selectionDecorator.isLabelProperty(element, property)) {
1801                                 return true;
1802                         }
1803                         return false;
1804                 }
1805
1806                 /*
1807                  * (non-Javadoc)
1808                  * 
1809                  * @see org.eclipse.jface.viewers.IBaseLabelProvider#removeListener(org.eclipse.jface.viewers.ILabelProviderListener)
1810                  */
1811                 public void removeListener(ILabelProviderListener listener) {
1812                         listeners.remove(listener);
1813                 }
1814
1815                 private Color getBackground(Object element) {
1816                         if (element instanceof ItemsListSeparator) {
1817                                 return null;
1818                         }
1819                         if (provider instanceof IColorProvider) {
1820                                 return ((IColorProvider) provider).getBackground(element);
1821                         }
1822                         return null;
1823                 }
1824
1825                 private Color getForeground(Object element) {
1826                         if (element instanceof ItemsListSeparator) {
1827                                 return Display.getCurrent().getSystemColor(
1828                                                 SWT.COLOR_WIDGET_NORMAL_SHADOW);
1829                         }
1830                         if (provider instanceof IColorProvider) {
1831                                 return ((IColorProvider) provider).getForeground(element);
1832                         }
1833                         return null;
1834                 }
1835
1836                 private Font getFont(Object element) {
1837                         if (element instanceof ItemsListSeparator) {
1838                                 return null;
1839                         }
1840                         if (provider instanceof IFontProvider) {
1841                                 return ((IFontProvider) provider).getFont(element);
1842                         }
1843                         return null;
1844                 }
1845
1846                 /*
1847                  * (non-Javadoc)
1848                  * 
1849                  * @see org.eclipse.jface.viewers.ILabelProviderListener#labelProviderChanged(org.eclipse.jface.viewers.LabelProviderChangedEvent)
1850                  */
1851                 public void labelProviderChanged(LabelProviderChangedEvent event) {
1852                         Object[] l = listeners.getListeners();
1853                         for (int i = 0; i < listeners.size(); i++) {
1854                                 ((ILabelProviderListener) l[i]).labelProviderChanged(event);
1855                         }
1856                 }
1857         }
1858
1859         /**
1860          * Used in ItemsListContentProvider, separates history and non-history
1861          * items.
1862          */
1863         private class ItemsListSeparator {
1864
1865                 private String name;
1866
1867                 /**
1868                  * Creates a new instance of the class.
1869                  * 
1870                  * @param name
1871                  *            the name of the separator
1872                  */
1873                 public ItemsListSeparator(String name) {
1874                         this.name = name;
1875                 }
1876
1877                 /**
1878                  * Returns the name of this separator.
1879                  * 
1880                  * @return the name of the separator
1881                  */
1882                 public String getName() {
1883                         return name;
1884                 }
1885         }
1886
1887         /**
1888          * GranualProgressMonitor is used for monitoring progress of filtering
1889          * process. It is used by <code>RefreshProgressMessageJob</code> to
1890          * refresh progress message. State of this monitor illustrates state of
1891          * filtering or cache refreshing process.
1892          * 
1893          */
1894         private class GranualProgressMonitor extends ProgressMonitorWrapper {
1895
1896                 private String name;
1897
1898                 private String subName;
1899
1900                 private int totalWork;
1901
1902                 private double worked;
1903
1904                 private boolean done;
1905
1906                 /**
1907                  * Creates instance of <code>GranualProgressMonitor</code>.
1908                  * 
1909                  * @param monitor
1910                  *            progress to be wrapped
1911                  */
1912                 public GranualProgressMonitor(IProgressMonitor monitor) {
1913                         super(monitor);
1914                 }
1915
1916                 /**
1917                  * Checks if filtering has been done
1918                  * 
1919                  * @return true if filtering work has been done false in other way
1920                  */
1921                 public boolean isDone() {
1922                         return done;
1923                 }
1924
1925                 /*
1926                  * (non-Javadoc)
1927                  * 
1928                  * @see org.eclipse.core.runtime.ProgressMonitorWrapper#setTaskName(java.lang.String)
1929                  */
1930                 public void setTaskName(String name) {
1931                         super.setTaskName(name);
1932                         this.name = name;
1933                         this.subName = null;
1934                 }
1935
1936                 /*
1937                  * (non-Javadoc)
1938                  * 
1939                  * @see org.eclipse.core.runtime.ProgressMonitorWrapper#subTask(java.lang.String)
1940                  */
1941                 public void subTask(String name) {
1942                         super.subTask(name);
1943                         this.subName = name;
1944                 }
1945
1946                 /*
1947                  * (non-Javadoc)
1948                  * 
1949                  * @see org.eclipse.core.runtime.ProgressMonitorWrapper#beginTask(java.lang.String,
1950                  *      int)
1951                  */
1952                 public void beginTask(String name, int totalWork) {
1953                         super.beginTask(name, totalWork);
1954                         if (this.name == null)
1955                                 this.name = name;
1956                         this.totalWork = totalWork;
1957                         refreshProgressMessageJob.scheduleProgressRefresh(this);
1958                 }
1959
1960                 /*
1961                  * (non-Javadoc)
1962                  * 
1963                  * @see org.eclipse.core.runtime.ProgressMonitorWrapper#worked(int)
1964                  */
1965                 public void worked(int work) {
1966                         super.worked(work);
1967                         internalWorked(work);
1968                 }
1969
1970                 /*
1971                  * (non-Javadoc)
1972                  * 
1973                  * @see org.eclipse.core.runtime.ProgressMonitorWrapper#done()
1974                  */
1975                 public void done() {
1976                         done = true;
1977                         super.done();
1978                 }
1979
1980                 /*
1981                  * (non-Javadoc)
1982                  * 
1983                  * @see org.eclipse.core.runtime.ProgressMonitorWrapper#setCanceled(boolean)
1984                  */
1985                 public void setCanceled(boolean b) {
1986                         done = b;
1987                         super.setCanceled(b);
1988                 }
1989
1990                 /*
1991                  * (non-Javadoc)
1992                  * 
1993                  * @see org.eclipse.core.runtime.ProgressMonitorWrapper#internalWorked(double)
1994                  */
1995                 public void internalWorked(double work) {
1996                         worked = worked + work;
1997                 }
1998
1999                 private String getMessage() {
2000                         if (done)
2001                                 return ""; //$NON-NLS-1$
2002
2003                         String message;
2004
2005                         if (name == null) {
2006                                 message = subName == null ? "" : subName; //$NON-NLS-1$
2007                         } else {
2008                                 message = subName == null ? name
2009                                                 : NLS.bind(WorkbenchMessages.FilteredItemsSelectionDialog_subtaskProgressMessage,
2010                                                                 new Object[] { name, subName });
2011                         }
2012                         if (totalWork == 0)
2013                                 return message;
2014
2015                         return NLS.bind(WorkbenchMessages.FilteredItemsSelectionDialog_taskProgressMessage,
2016                                         new Object[] { message, new Integer((int) ((worked * 100) / totalWork)) });
2017
2018                 }
2019
2020         }
2021
2022         /**
2023          * Filters items history and schedule filter job.
2024          */
2025         private class FilterHistoryJob extends Job {
2026
2027                 /**
2028                  * Filter used during the filtering process.
2029                  */
2030                 private ItemsFilter itemsFilter;
2031
2032                 /**
2033                  * Creates new instance of receiver.
2034                  */
2035                 public FilterHistoryJob() {
2036                         super(WorkbenchMessages.FilteredItemsSelectionDialog_jobLabel);
2037                         setSystem(true);
2038                 }
2039
2040                 /*
2041                  * (non-Javadoc)
2042                  * 
2043                  * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
2044                  */
2045                 protected IStatus run(IProgressMonitor monitor) {
2046                         this.itemsFilter = filter;
2047
2048                         contentProvider.reset();
2049
2050                         refreshWithLastSelection = false;
2051
2052                         contentProvider.addHistoryItems(itemsFilter);
2053
2054                         if (!(lastCompletedFilter != null && lastCompletedFilter.isSubFilter(this.itemsFilter)))
2055                                 contentProvider.refresh();
2056
2057                         filterJob.schedule();
2058
2059                         return Status.OK_STATUS;
2060                 }
2061
2062         }
2063
2064         /**
2065          * Filters items in indicated set and history. During filtering, it
2066          * refreshes the dialog (progress monitor and elements list).
2067          * 
2068          * Depending on the filter, <code>FilterJob</code> decides which kind of
2069          * search will be run inside <code>filterContent</code>. If the last
2070          * filtering is done (last completed filter), is not null, and the new
2071          * filter is a sub-filter ({@link ColumnFilteredItemsSelectionDialog.ItemsFilter#isSubFilter(ColumnFilteredItemsSelectionDialog.ItemsFilter)})
2072          * of the last, then <code>FilterJob</code> only filters in the cache. If
2073          * it is the first filtering or the new filter isn't a sub-filter of the
2074          * last one, a full search is run.
2075          */
2076         private class FilterJob extends Job {
2077
2078                 /**
2079                  * Filter used during the filtering process.
2080                  */
2081                 protected ItemsFilter itemsFilter;
2082
2083                 /**
2084                  * Creates new instance of FilterJob
2085                  */
2086                 public FilterJob() {
2087                         super(WorkbenchMessages.FilteredItemsSelectionDialog_jobLabel);
2088                         setSystem(true);
2089                 }
2090
2091                 /*
2092                  * (non-Javadoc)
2093                  * 
2094                  * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
2095                  */
2096                 protected final IStatus run(IProgressMonitor parent) {
2097                         GranualProgressMonitor monitor = new GranualProgressMonitor(parent);
2098                         return doRun(monitor);
2099                 }
2100
2101                 /**
2102                  * Executes job using the given filtering progress monitor. A hook for
2103                  * subclasses.
2104                  * 
2105                  * @param monitor
2106                  *            progress monitor
2107                  * @return result of the execution
2108                  */
2109                 protected IStatus doRun(GranualProgressMonitor monitor) {
2110                         try {
2111                                 internalRun(monitor);
2112                         } catch (CoreException e) {
2113                                 cancel();
2114                                 return new Status(
2115                                                 IStatus.ERROR,
2116                                                 PlatformUI.PLUGIN_ID,
2117                                                 IStatus.ERROR,
2118                                                 WorkbenchMessages.FilteredItemsSelectionDialog_jobError,
2119                                                 e);
2120                         }
2121                         return Status.OK_STATUS;
2122                 }
2123
2124                 /**
2125                  * Main method for the job.
2126                  * 
2127                  * @param monitor
2128                  * @throws CoreException
2129                  */
2130                 private void internalRun(GranualProgressMonitor monitor)
2131                                 throws CoreException {
2132                         try {
2133                                 if (monitor.isCanceled())
2134                                         return;
2135
2136                                 this.itemsFilter = filter;
2137
2138                                 // why is the content not filtered if the patter is empty? 
2139                                 // this makes no sense since the search pattern is able to
2140                                 // handle the empty pattern just fine, and even has settings
2141                                 // for what to do in this case
2142                                 //if (filter.getPattern().length() != 0) {
2143                                 //      filterContent(monitor);
2144                                 //}
2145                                 
2146                                 filterContent(monitor);
2147
2148                                 if (monitor.isCanceled())
2149                                         return;
2150
2151                                 contentProvider.refresh();
2152                         } finally {
2153                                 monitor.done();
2154                         }
2155                 }
2156
2157                 /**
2158                  * Filters items.
2159                  * 
2160                  * @param monitor
2161                  *            for monitoring progress
2162                  * @throws CoreException
2163                  */
2164                 protected void filterContent(GranualProgressMonitor monitor)
2165                                 throws CoreException {
2166
2167                         if (lastCompletedFilter != null
2168                                         && lastCompletedFilter.isSubFilter(this.itemsFilter)) {
2169
2170                                 int length = lastCompletedResult.size() / 500;
2171                                 monitor.beginTask(WorkbenchMessages.FilteredItemsSelectionDialog_cacheSearchJob_taskName, length);
2172
2173                                 for (int pos = 0; pos < lastCompletedResult.size(); pos++) {
2174
2175                                         Object item = lastCompletedResult.get(pos);
2176                                         if (monitor.isCanceled())
2177                                                 break;
2178                                         contentProvider.add(item, itemsFilter);
2179
2180                                         if ((pos % 500) == 0) {
2181                                                 monitor.worked(1);
2182                                         }
2183                                 }
2184
2185                         } else {
2186
2187                                 lastCompletedFilter = null;
2188                                 lastCompletedResult = null;
2189
2190                                 SubProgressMonitor subMonitor = null;
2191                                 if (monitor != null) {
2192                                         monitor.beginTask(WorkbenchMessages.FilteredItemsSelectionDialog_searchJob_taskName, 100);
2193                                         subMonitor = new SubProgressMonitor(monitor, 95);
2194
2195                                 }
2196
2197                                 fillContentProvider(contentProvider, itemsFilter, subMonitor);
2198
2199                                 if (monitor != null && !monitor.isCanceled()) {
2200                                         monitor.worked(2);
2201                                         contentProvider.rememberResult(itemsFilter);
2202                                         monitor.worked(3);
2203                                 }
2204                         }
2205
2206                 }
2207
2208         }
2209
2210         /**
2211          * History stores a list of key, object pairs. The list is bounded at a
2212          * certain size. If the list exceeds this size the oldest element is removed
2213          * from the list. An element can be added/renewed with a call to
2214          * <code>accessed(Object)</code>.
2215          * <p>
2216          * The history can be stored to/loaded from an XML file.
2217          */
2218         protected static abstract class SelectionHistory {
2219
2220                 private static final String DEFAULT_ROOT_NODE_NAME = "historyRootNode"; //$NON-NLS-1$
2221
2222                 private static final String DEFAULT_INFO_NODE_NAME = "infoNode"; //$NON-NLS-1$
2223
2224                 private static final int MAX_HISTORY_SIZE = 60;
2225
2226                 private final Set historyList;
2227
2228                 private final String rootNodeName;
2229
2230                 private final String infoNodeName;
2231
2232                 private SelectionHistory(String rootNodeName, String infoNodeName) {
2233
2234                         historyList = Collections.synchronizedSet(new LinkedHashSet() {
2235
2236                                 private static final long serialVersionUID = 0L;
2237
2238                                 /*
2239                                  * (non-Javadoc)
2240                                  * 
2241                                  * @see java.util.LinkedList#add(java.lang.Object)
2242                                  */
2243                                 public boolean add(Object arg0) {
2244                                         if (this.size() >= MAX_HISTORY_SIZE) {
2245                                                 Iterator iterator = this.iterator();
2246                                                 iterator.next();
2247                                                 iterator.remove();
2248                                         }
2249                                         return super.add(arg0);
2250                                 }
2251
2252                         });
2253
2254                         this.rootNodeName = rootNodeName;
2255                         this.infoNodeName = infoNodeName;
2256                 }
2257
2258                 /**
2259                  * Creates new instance of <code>SelectionHistory</code>.
2260                  */
2261                 public SelectionHistory() {
2262                         this(DEFAULT_ROOT_NODE_NAME, DEFAULT_INFO_NODE_NAME);
2263                 }
2264
2265                 /**
2266                  * Adds object to history.
2267                  * 
2268                  * @param object
2269                  *            the item to be added to the history
2270                  */
2271                 public synchronized void accessed(Object object) {
2272                         historyList.remove(object);
2273                         historyList.add(object);
2274                 }
2275
2276                 /**
2277                  * Returns <code>true</code> if history contains object.
2278                  * 
2279                  * @param object
2280                  *            the item for which check will be executed
2281                  * @return <code>true</code> if history contains object
2282                  *         <code>false</code> in other way
2283                  */
2284                 public synchronized boolean contains(Object object) {
2285                         return historyList.contains(object);
2286                 }
2287
2288                 /**
2289                  * Returns <code>true</code> if history is empty.
2290                  * 
2291                  * @return <code>true</code> if history is empty
2292                  */
2293                 public synchronized boolean isEmpty() {
2294                         return historyList.isEmpty();
2295                 }
2296
2297                 /**
2298                  * Remove element from history.
2299                  * 
2300                  * @param element
2301                  *            to remove form the history
2302                  * @return <code>true</code> if this list contained the specified
2303                  *         element
2304                  */
2305                 public synchronized boolean remove(Object element) {
2306                         return historyList.remove(element);
2307                 }
2308
2309                 /**
2310                  * Load history elements from memento.
2311                  * 
2312                  * @param memento
2313                  *            memento from which the history will be retrieved
2314                  */
2315                 public void load(IMemento memento) {
2316
2317                         XMLMemento historyMemento = (XMLMemento) memento
2318                                         .getChild(rootNodeName);
2319
2320                         if (historyMemento == null) {
2321                                 return;
2322                         }
2323
2324                         IMemento[] mementoElements = historyMemento
2325                                         .getChildren(infoNodeName);
2326                         for (int i = 0; i < mementoElements.length; ++i) {
2327                                 IMemento mementoElement = mementoElements[i];
2328                                 Object object = restoreItemFromMemento(mementoElement);
2329                                 if (object != null) {
2330                                         historyList.add(object);
2331                                 }
2332                         }
2333                 }
2334
2335                 /**
2336                  * Save history elements to memento.
2337                  * 
2338                  * @param memento
2339                  *            memento to which the history will be added
2340                  */
2341                 public void save(IMemento memento) {
2342
2343                         IMemento historyMemento = memento.createChild(rootNodeName);
2344
2345                         Object[] items = getHistoryItems();
2346                         for (int i = 0; i < items.length; i++) {
2347                                 Object item = items[i];
2348                                 IMemento elementMemento = historyMemento
2349                                                 .createChild(infoNodeName);
2350                                 storeItemToMemento(item, elementMemento);
2351                         }
2352
2353                 }
2354
2355                 /**
2356                  * Gets array of history items.
2357                  * 
2358                  * @return array of history elements
2359                  */
2360                 public synchronized Object[] getHistoryItems() {
2361                         return historyList.toArray();
2362                 }
2363
2364                 /**
2365                  * Creates an object using given memento.
2366                  * 
2367                  * @param memento
2368                  *            memento used for creating new object
2369                  * 
2370                  * @return the restored object
2371                  */
2372                 protected abstract Object restoreItemFromMemento(IMemento memento);
2373
2374                 /**
2375                  * Store object in <code>IMemento</code>.
2376                  * 
2377                  * @param item
2378                  *            the item to store
2379                  * @param memento
2380                  *            the memento to store to
2381                  */
2382                 protected abstract void storeItemToMemento(Object item, IMemento memento);
2383
2384         }
2385
2386         /**
2387          * Filters elements using SearchPattern by comparing the names of items with
2388          * the filter pattern.
2389          */
2390         protected abstract class ItemsFilter {
2391
2392                 protected SearchPattern patternMatcher;
2393
2394                 /**
2395                  * Creates new instance of ItemsFilter.
2396                  */
2397                 public ItemsFilter() {
2398                         this(new SearchPattern());
2399                 }
2400
2401                 /**
2402                  * Creates new instance of ItemsFilter.
2403                  * 
2404                  * @param searchPattern
2405                  *            the pattern to be used when filtering
2406                  */
2407                 public ItemsFilter(SearchPattern searchPattern) {
2408                         patternMatcher = searchPattern;
2409                         String stringPattern = ""; //$NON-NLS-1$
2410                         if (pattern != null && !pattern.getText().equals("*")) { //$NON-NLS-1$
2411                                 stringPattern = pattern.getText();
2412                         }
2413                         patternMatcher.setPattern(stringPattern);
2414                 }
2415
2416                 /**
2417                  * Check if the given filter is a sub-filter of this filter. The default
2418                  * implementation checks if the <code>SearchPattern</code> from the
2419                  * given filter is a sub-pattern of the one from this filter.
2420                  * <p>
2421                  * <i>WARNING: This method is <b>not</b> defined in reading order, i.e.
2422                  * <code>a.isSubFilter(b)</code> is <code>true</code> iff
2423                  * <code>b</code> is a sub-filter of <code>a</code>, and not
2424                  * vice-versa. </i>
2425                  * </p>
2426                  * 
2427                  * @param filter
2428                  *            the filter to be checked, or <code>null</code>
2429                  * @return <code>true</code> if the given filter is sub-filter of this
2430                  *         filter, <code>false</code> if the given filter isn't a
2431                  *         sub-filter or is <code>null</code>
2432                  * 
2433                  * @see org.eclipse.ui.dialogs.SearchPattern#isSubPattern(org.eclipse.ui.dialogs.SearchPattern)
2434                  */
2435                 public boolean isSubFilter(ItemsFilter filter) {
2436                         if (filter != null) {
2437                                 return this.patternMatcher.isSubPattern(filter.patternMatcher);
2438                         }
2439                         return false;
2440                 }
2441
2442                 /**
2443                  * Checks whether the provided filter is equal to the current filter.
2444                  * The default implementation checks if <code>SearchPattern</code>
2445                  * from current filter is equal to the one from provided filter.
2446                  * 
2447                  * @param filter
2448                  *            filter to be checked, or <code>null</code>
2449                  * @return <code>true</code> if the given filter is equal to current
2450                  *         filter, <code>false</code> if given filter isn't equal to
2451                  *         current one or if it is <code>null</code>
2452                  * 
2453                  * @see org.eclipse.ui.dialogs.SearchPattern#equalsPattern(org.eclipse.ui.dialogs.SearchPattern)
2454                  */
2455                 public boolean equalsFilter(ItemsFilter filter) {
2456                         if (filter != null
2457                                         && filter.patternMatcher.equalsPattern(this.patternMatcher)) {
2458                                 return true;
2459                         }
2460                         return false;
2461                 }
2462
2463                 /**
2464                  * Checks whether the pattern's match rule is camel case.
2465                  * 
2466                  * @return <code>true</code> if pattern's match rule is camel case,
2467                  *         <code>false</code> otherwise
2468                  */
2469                 public boolean isCamelCasePattern() {
2470                         return patternMatcher.getMatchRule() == SearchPattern.RULE_CAMELCASE_MATCH;
2471                 }
2472
2473                 /**
2474                  * Returns the pattern string.
2475                  * 
2476                  * @return pattern for this filter
2477                  * 
2478                  * @see SearchPattern#getPattern()
2479                  */
2480                 public String getPattern() {
2481                         return patternMatcher.getPattern();
2482                 }
2483
2484                 /**
2485                  * Returns the rule to apply for matching keys.
2486                  * 
2487                  * @return an implementation-specific match rule
2488                  * 
2489                  * @see SearchPattern#getMatchRule() for match rules returned by the
2490                  *      default implementation
2491                  */
2492                 public int getMatchRule() {
2493                         return patternMatcher.getMatchRule();
2494                 }
2495
2496                 /**
2497                  * Matches text with filter.
2498                  * 
2499                  * @param text
2500                  *            the text to match with the filter
2501                  * @return <code>true</code> if text matches with filter pattern,
2502                  *         <code>false</code> otherwise
2503                  */
2504                 protected boolean matches(String text) {
2505                         return patternMatcher.matches(text);
2506                 }
2507
2508                 /**
2509                  * General method for matching raw name pattern. Checks whether current
2510                  * pattern is prefix of name provided item.
2511                  * 
2512                  * @param item
2513                  *            item to check
2514                  * @return <code>true</code> if current pattern is a prefix of name
2515                  *         provided item, <code>false</code> if item's name is shorter
2516                  *         than prefix or sequences of characters don't match.
2517                  */
2518                 public boolean matchesRawNamePattern(Object item) {
2519                         String prefix = patternMatcher.getPattern();
2520                         String text = getElementName(item);
2521
2522                         if (text == null)
2523                                 return false;
2524
2525                         int textLength = text.length();
2526                         int prefixLength = prefix.length();
2527                         if (textLength < prefixLength) {
2528                                 return false;
2529                         }
2530                         for (int i = prefixLength - 1; i >= 0; i--) {
2531                                 if (Character.toLowerCase(prefix.charAt(i)) != Character
2532                                                 .toLowerCase(text.charAt(i)))
2533                                         return false;
2534                         }
2535                         return true;
2536                 }
2537
2538                 /**
2539                  * Matches an item against filter conditions.
2540                  * 
2541                  * @param item
2542                  * @return <code>true<code> if item matches against filter conditions, <code>false</code>
2543                  *         otherwise
2544                  */
2545                 public abstract boolean matchItem(Object item);
2546
2547                 /**
2548                  * Checks consistency of an item. Item is inconsistent if was changed or
2549                  * removed.
2550                  * 
2551                  * @param item
2552                  * @return <code>true</code> if item is consistent, <code>false</code>
2553                  *         if item is inconsistent
2554                  */
2555                 public abstract boolean isConsistentItem(Object item);
2556
2557         }
2558
2559         /**
2560          * An interface to content providers for
2561          * <code>FilterItemsSelectionDialog</code>.
2562          */
2563         protected abstract class AbstractContentProvider {
2564                 /**
2565                  * Adds the item to the content provider iff the filter matches the
2566                  * item. Otherwise does nothing.
2567                  * 
2568                  * @param item
2569                  *            the item to add
2570                  * @param itemsFilter
2571                  *            the filter
2572                  * 
2573                  * @see ColumnFilteredItemsSelectionDialog.ItemsFilter#matchItem(Object)
2574                  */
2575                 public abstract void add(Object item, ItemsFilter itemsFilter);
2576         }
2577
2578         /**
2579          * Collects filtered elements. Contains one synchronized, sorted set for
2580          * collecting filtered elements. All collected elements are sorted using
2581          * comparator. Comparator is returned by getElementComparator() method.
2582          * Implementation of <code>ItemsFilter</code> is used to filter elements.
2583          * The key function of filter used in to filtering is
2584          * <code>matchElement(Object item)</code>.
2585          * <p>
2586          * The <code>ContentProvider</code> class also provides item filtering
2587          * methods. The filtering has been moved from the standard TableView
2588          * <code>getFilteredItems()</code> method to content provider, because
2589          * <code>ILazyContentProvider</code> and virtual tables are used. This
2590          * class is responsible for adding a separator below history items and
2591          * marking each items as duplicate if its name repeats more than once on the
2592          * filtered list.
2593          */
2594         private class ContentProvider extends AbstractContentProvider implements
2595                         IStructuredContentProvider, ILazyContentProvider {
2596
2597                 private SelectionHistory selectionHistory;
2598
2599                 /**
2600                  * Raw result of the searching (unsorted, unfiltered).
2601                  * <p>
2602                  * Standard object flow:
2603                  * <code>items -> lastSortedItems -> lastFilteredItems</code>
2604                  */
2605                 private Set items;
2606
2607                 /**
2608                  * Items that are duplicates.
2609                  */
2610                 private Set duplicates;
2611
2612                 /**
2613                  * List of <code>ViewerFilter</code>s to be used during filtering
2614                  */
2615                 private List filters;
2616
2617                 /**
2618                  * Result of the last filtering.
2619                  * <p>
2620                  * Standard object flow:
2621                  * <code>items -> lastSortedItems -> lastFilteredItems</code>
2622                  */
2623                 private List lastFilteredItems;
2624
2625                 /**
2626                  * Result of the last sorting.
2627                  * <p>
2628                  * Standard object flow:
2629                  * <code>items -> lastSortedItems -> lastFilteredItems</code>
2630                  */
2631                 private List lastSortedItems;
2632
2633                 /**
2634                  * Used for <code>getFilteredItems()</code> method canceling (when the
2635                  * job that invoked the method was canceled).
2636                  * <p>
2637                  * Method canceling could be based (only) on monitor canceling
2638                  * unfortunately sometimes the method <code>getFilteredElements()</code>
2639                  * could be run with a null monitor, the <code>reset</code> flag have
2640                  * to be left intact.
2641                  */
2642                 private boolean reset;
2643
2644                 /**
2645                  * Creates new instance of <code>ContentProvider</code>.
2646                  */
2647                 public ContentProvider() {
2648                         this.items = Collections.synchronizedSet(new HashSet(2048));
2649                         this.duplicates = Collections.synchronizedSet(new HashSet(256));
2650                         this.lastFilteredItems = new ArrayList();
2651                         this.lastSortedItems = Collections.synchronizedList(new ArrayList(2048));
2652                 }
2653
2654                 /**
2655                  * Sets selection history.
2656                  * 
2657                  * @param selectionHistory
2658                  *            The selectionHistory to set.
2659                  */
2660                 public void setSelectionHistory(SelectionHistory selectionHistory) {
2661                         this.selectionHistory = selectionHistory;
2662                 }
2663
2664                 /**
2665                  * @return Returns the selectionHistory.
2666                  */
2667                 public SelectionHistory getSelectionHistory() {
2668                         return selectionHistory;
2669                 }
2670
2671                 /**
2672                  * Removes all content items and resets progress message.
2673                  */
2674                 public void reset() {
2675                         reset = true;
2676                         this.items.clear();
2677                         this.duplicates.clear();
2678                         this.lastSortedItems.clear();
2679                 }
2680
2681                 /**
2682                  * Stops reloading cache - <code>getFilteredItems()</code> method.
2683                  */
2684                 public void stopReloadingCache() {
2685                         reset = true;
2686                 }
2687
2688                 /**
2689                  * Adds filtered item.
2690                  * 
2691                  * @param item
2692                  * @param itemsFilter
2693                  */
2694                 public void add(Object item, ItemsFilter itemsFilter) {
2695                         if (itemsFilter == filter) {
2696                                 if (itemsFilter != null) {
2697                                         if (itemsFilter.matchItem(item)) {
2698                                                 this.items.add(item);
2699                                         }
2700                                 } else {
2701                                         this.items.add(item);
2702                                 }
2703                         }
2704                 }
2705
2706                 /**
2707                  * Add all history items to <code>contentProvider</code>.
2708                  * 
2709                  * @param itemsFilter
2710                  */
2711                 public void addHistoryItems(ItemsFilter itemsFilter) {
2712                         if (this.selectionHistory != null) {
2713                                 Object[] items = this.selectionHistory.getHistoryItems();
2714                                 for (int i = 0; i < items.length; i++) {
2715                                         Object item = items[i];
2716                                         if (itemsFilter == filter) {
2717                                                 if (itemsFilter != null) {
2718                                                         if (itemsFilter.matchItem(item)) {
2719                                                                 if (itemsFilter.isConsistentItem(item)) {
2720                                                                         this.items.add(item);
2721                                                                 } else {
2722                                                                         this.selectionHistory.remove(item);
2723                                                                 }
2724                                                         }
2725                                                 }
2726                                         }
2727                                 }
2728                         }
2729                 }
2730
2731                 /**
2732                  * Refresh dialog.
2733                  */
2734                 public void refresh() {
2735                         scheduleRefresh();
2736                 }
2737
2738                 /**
2739                  * Removes items from history and refreshes the view.
2740                  * 
2741                  * @param item
2742                  *            to remove
2743                  * 
2744                  * @return removed item
2745                  */
2746                 public Object removeHistoryElement(Object item) {
2747                         if (this.selectionHistory != null)
2748                                 this.selectionHistory.remove(item);
2749                         if (filter == null || filter.getPattern().length() == 0) {
2750                                 items.remove(item);
2751                                 duplicates.remove(item);
2752                                 this.lastSortedItems.remove(item);
2753                         }
2754
2755                         synchronized (lastSortedItems) {
2756                                 Collections.sort(lastSortedItems, getHistoryComparator());
2757                         }
2758                         return item;
2759                 }
2760
2761                 /**
2762                  * Adds item to history and refresh view.
2763                  * 
2764                  * @param item
2765                  *            to add
2766                  */
2767                 public void addHistoryElement(Object item) {
2768                         if (this.selectionHistory != null)
2769                                 this.selectionHistory.accessed(item);
2770                         if (filter == null || !filter.matchItem(item)) {
2771                                 this.items.remove(item);
2772                                 this.duplicates.remove(item);
2773                                 this.lastSortedItems.remove(item);
2774                         }
2775                         synchronized (lastSortedItems) {
2776                                 Collections.sort(lastSortedItems, getHistoryComparator());
2777                         }
2778                         this.refresh();
2779                 }
2780
2781                 /**
2782                  * @param item
2783                  * @return <code>true</code> if given item is part of the history
2784                  */
2785                 public boolean isHistoryElement(Object item) {
2786                         if (this.selectionHistory != null) {
2787                                 return this.selectionHistory.contains(item);
2788                         }
2789                         return false;
2790                 }
2791
2792                 /**
2793                  * Sets/unsets given item as duplicate.
2794                  * 
2795                  * @param item
2796                  *            item to change
2797                  * 
2798                  * @param isDuplicate
2799                  *            duplicate flag
2800                  */
2801                 public void setDuplicateElement(Object item, boolean isDuplicate) {
2802                         if (this.items.contains(item)) {
2803                                 if (isDuplicate)
2804                                         this.duplicates.add(item);
2805                                 else
2806                                         this.duplicates.remove(item);
2807                         }
2808                 }
2809
2810                 /**
2811                  * Indicates whether given item is a duplicate.
2812                  * 
2813                  * @param item
2814                  *            item to check
2815                  * @return <code>true</code> if item is duplicate
2816                  */
2817                 public boolean isDuplicateElement(Object item) {
2818                         return duplicates.contains(item);
2819                 }
2820
2821                 /**
2822                  * Load history from memento.
2823                  * 
2824                  * @param memento
2825                  *            memento from which the history will be retrieved
2826                  */
2827                 public void loadHistory(IMemento memento) {
2828                         if (this.selectionHistory != null)
2829                                 this.selectionHistory.load(memento);
2830                 }
2831
2832                 /**
2833                  * Save history to memento.
2834                  * 
2835                  * @param memento
2836                  *            memento to which the history will be added
2837                  */
2838                 public void saveHistory(IMemento memento) {
2839                         if (this.selectionHistory != null)
2840                                 this.selectionHistory.save(memento);
2841                 }
2842
2843                 /**
2844                  * Gets sorted items.
2845                  * 
2846                  * @return sorted items
2847                  */
2848                 private Object[] getSortedItems() {
2849                         if (lastSortedItems.size() != items.size()) {
2850                                 synchronized (lastSortedItems) {
2851                                         lastSortedItems.clear();
2852                                         lastSortedItems.addAll(items);
2853                                         Collections.sort(lastSortedItems, getHistoryComparator());
2854                                 }
2855                         }
2856                         return lastSortedItems.toArray();
2857                 }
2858
2859                 /**
2860                  * Remember result of filtering.
2861                  * 
2862                  * @param itemsFilter
2863                  */
2864                 public void rememberResult(ItemsFilter itemsFilter) {
2865                         List itemsList = Collections.synchronizedList(Arrays
2866                                         .asList(getSortedItems()));
2867                         // synchronization
2868                         if (itemsFilter == filter) {
2869                                 lastCompletedFilter = itemsFilter;
2870                                 lastCompletedResult = itemsList;
2871                         }
2872
2873                 }
2874
2875                 /*
2876                  * (non-Javadoc)
2877                  * 
2878                  * @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object)
2879                  */
2880                 public Object[] getElements(Object inputElement) {
2881                         return lastFilteredItems.toArray();
2882                 }
2883
2884                 public int getNumberOfElements() {
2885                         return lastFilteredItems.size();
2886                 }
2887
2888                 /*
2889                  * (non-Javadoc)
2890                  * 
2891                  * @see org.eclipse.jface.viewers.IContentProvider#dispose()
2892                  */
2893                 public void dispose() {
2894                 }
2895
2896                 /*
2897                  * (non-Javadoc)
2898                  * 
2899                  * @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer,
2900                  *      java.lang.Object, java.lang.Object)
2901                  */
2902                 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
2903                 }
2904
2905                 /*
2906                  * (non-Javadoc)
2907                  * 
2908                  * @see org.eclipse.jface.viewers.ILazyContentProvider#updateElement(int)
2909                  */
2910                 public void updateElement(int index) {
2911
2912                         ColumnFilteredItemsSelectionDialog.this.viewer.replace((lastFilteredItems
2913                                         .size() > index) ? lastFilteredItems.get(index) : null,
2914                                         index);
2915
2916                 }
2917
2918                 /**
2919                  * Main method responsible for getting the filtered items and checking
2920                  * for duplicates. It is based on the
2921                  * {@link ColumnFilteredItemsSelectionDialog.ContentProvider#getFilteredItems(Object, IProgressMonitor)}.
2922                  * 
2923                  * @param checkDuplicates
2924                  *            <code>true</code> if data concerning elements
2925                  *            duplication should be computed - it takes much more time
2926                  *            than standard filtering
2927                  * 
2928                  * @param monitor
2929                  *            progress monitor
2930                  */
2931                 public void reloadCache(boolean checkDuplicates,
2932                                 IProgressMonitor monitor) {
2933
2934                         reset = false;
2935
2936                         if (monitor != null) {
2937                                 // the work is divided into two actions of the same length
2938                                 int totalWork = checkDuplicates ? 200 : 100;
2939
2940                                 monitor.beginTask(WorkbenchMessages.FilteredItemsSelectionDialog_cacheRefreshJob, totalWork);
2941                         }
2942
2943                         // the TableViewer's root (the input) is treated as parent
2944
2945                         lastFilteredItems = Arrays.asList(getFilteredItems(viewer.getInput(),
2946                                         monitor != null ? new SubProgressMonitor(monitor, 100) : null));
2947
2948                         if (reset || (monitor != null && monitor.isCanceled())) {
2949                                 if (monitor != null)
2950                                         monitor.done();
2951                                 return;
2952                         }
2953
2954                         if (checkDuplicates) {
2955                                 checkDuplicates(monitor);
2956                         }
2957                         if (monitor != null)
2958                                 monitor.done();
2959                 }
2960
2961                 private void checkDuplicates(IProgressMonitor monitor) {
2962                         synchronized (lastFilteredItems) {
2963                                 IProgressMonitor subMonitor = null;
2964                                 int reportEvery = lastFilteredItems.size() / 20;
2965                                 if (monitor != null) {
2966                                         subMonitor = new SubProgressMonitor(monitor, 100);
2967                                         subMonitor.beginTask(WorkbenchMessages.FilteredItemsSelectionDialog_cacheRefreshJob_checkDuplicates, 5);
2968                                 }
2969                                 HashMap helperMap = new HashMap();
2970                                 for (int i = 0; i < lastFilteredItems.size(); i++) {
2971                                         if (reset || (subMonitor != null && subMonitor.isCanceled()))
2972                                                 return;
2973                                         Object item = lastFilteredItems.get(i);
2974
2975                                         if (!(item instanceof ItemsListSeparator)) {
2976                                                 Object previousItem = helperMap.put(getElementName(item), item);
2977                                                 if (previousItem != null) {
2978                                                         setDuplicateElement(previousItem, true);
2979                                                         setDuplicateElement(item, true);
2980                                                 } else {
2981                                                         setDuplicateElement(item, false);
2982                                                 }
2983                                         }
2984
2985                                         if (subMonitor != null && reportEvery != 0
2986                                                         && (i + 1) % reportEvery == 0)
2987                                                 subMonitor.worked(1);
2988                                 }
2989                                 helperMap.clear();
2990                         }
2991                 }
2992
2993                 /**
2994                  * Returns an array of items filtered using the provided
2995                  * <code>ViewerFilter</code>s with a separator added.
2996                  * 
2997                  * @param parent
2998                  *            the parent
2999                  * @param monitor
3000                  *            progress monitor, can be <code>null</code>
3001                  * @return an array of filtered items
3002                  */
3003                 protected Object[] getFilteredItems(Object parent,
3004                                 IProgressMonitor monitor) {
3005                         int ticks = 100;
3006                         if (monitor == null) {
3007                                 monitor = new NullProgressMonitor();
3008                         }
3009
3010                         monitor.beginTask(WorkbenchMessages.FilteredItemsSelectionDialog_cacheRefreshJob_getFilteredElements, ticks);
3011                         if (filters != null) {
3012                                 ticks /= (filters.size() + 2);
3013                         } else {
3014                                 ticks /= 2;
3015                         }
3016
3017                         // get already sorted array
3018                         Object[] filteredElements = getSortedItems();
3019
3020                         monitor.worked(ticks);
3021
3022                         // filter the elements using provided ViewerFilters
3023                         if (filters != null && filteredElements != null) {
3024                                 for (Iterator iter = filters.iterator(); iter.hasNext();) {
3025                                         ViewerFilter f = (ViewerFilter) iter.next();
3026                                         filteredElements = f.filter(viewer, parent, filteredElements);
3027                                         monitor.worked(ticks);
3028                                 }
3029                         }
3030
3031                         if (filteredElements == null || monitor.isCanceled()) {
3032                                 monitor.done();
3033                                 return new Object[0];
3034                         }
3035
3036                         ArrayList preparedElements = new ArrayList();
3037                         boolean hasHistory = false;
3038
3039                         if (filteredElements.length > 0) {
3040                                 if (isHistoryElement(filteredElements[0])) {
3041                                         hasHistory = true;
3042                                 }
3043                         }
3044
3045                         int reportEvery = filteredElements.length / ticks;
3046
3047                         // add separator
3048                         for (int i = 0; i < filteredElements.length; i++) {
3049                                 Object item = filteredElements[i];
3050
3051                                 if (hasHistory && !isHistoryElement(item)) {
3052                                         preparedElements.add(itemsListSeparator);
3053                                         hasHistory = false;
3054                                 }
3055
3056                                 preparedElements.add(item);
3057
3058                                 if (reportEvery != 0 && ((i + 1) % reportEvery == 0)) {
3059                                         monitor.worked(1);
3060                                 }
3061                         }
3062
3063                         monitor.done();
3064
3065                         return preparedElements.toArray();
3066                 }
3067
3068                 /**
3069                  * Adds a filter to this content provider. For an example usage of such
3070                  * filters look at the project <code>org.eclipse.ui.ide</code>, class
3071                  * <code>org.eclipse.ui.dialogs.FilteredResourcesSelectionDialog.CustomWorkingSetFilter</code>.
3072                  * 
3073                  * 
3074                  * @param filter
3075                  *            the filter to be added
3076                  */
3077                 public void addFilter(ViewerFilter filter) {
3078                         if (filters == null) {
3079                                 filters = new ArrayList();
3080                         }
3081                         filters.add(filter);
3082                         // currently filters are only added when dialog is restored
3083                         // if it is changed, refreshing the whole TableViewer should be
3084                         // added
3085                 }
3086
3087         }
3088
3089         /**
3090          * A content provider that does nothing.
3091          */
3092         private class NullContentProvider implements IContentProvider {
3093
3094                 /*
3095                  * (non-Javadoc)
3096                  * 
3097                  * @see org.eclipse.jface.viewers.IContentProvider#dispose()
3098                  */
3099                 public void dispose() {
3100                 }
3101
3102                 /*
3103                  * (non-Javadoc)
3104                  * 
3105                  * @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer,
3106                  *      java.lang.Object, java.lang.Object)
3107                  */
3108                 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
3109                 }
3110
3111         }
3112
3113         /**
3114          * DetailsContentViewer objects are wrappers for labels.
3115          * DetailsContentViewer provides means to change label's image and text when
3116          * the attached LabelProvider is updated.
3117          */
3118         private class DetailsContentViewer extends ContentViewer {
3119
3120                 private CLabel label;
3121
3122                 /**
3123                  * Unfortunately, it was impossible to delegate displaying border to
3124                  * label. The <code>ViewForm</code> is used because
3125                  * <code>CLabel</code> displays shadow when border is present.
3126                  */
3127                 private ViewForm viewForm;
3128
3129                 /**
3130                  * Constructs a new instance of this class given its parent and a style
3131                  * value describing its behavior and appearance.
3132                  * 
3133                  * @param parent
3134                  *            the parent component
3135                  * @param style
3136                  *            SWT style bits
3137                  */
3138                 public DetailsContentViewer(Composite parent, int style) {
3139                         viewForm = new ViewForm(parent, style);
3140                         GridData gd = new GridData(GridData.FILL_HORIZONTAL);
3141                         gd.horizontalSpan = 2;
3142                         viewForm.setLayoutData(gd);
3143                         label = new CLabel(viewForm, SWT.FLAT);
3144                         label.setFont(parent.getFont());
3145                         viewForm.setContent(label);
3146                         hookControl(label);
3147                 }
3148
3149                 /**
3150                  * Shows/hides the content viewer.
3151                  * 
3152                  * @param visible
3153                  *            if the content viewer should be visible.
3154                  */
3155                 public void setVisible(boolean visible) {
3156                         GridData gd = (GridData) viewForm.getLayoutData();
3157                         gd.exclude = !visible;
3158                         viewForm.getParent().layout();
3159                 }
3160
3161                 /*
3162                  * (non-Javadoc)
3163                  * 
3164                  * @see org.eclipse.jface.viewers.Viewer#inputChanged(java.lang.Object,
3165                  *      java.lang.Object)
3166                  */
3167                 protected void inputChanged(Object input, Object oldInput) {
3168                         if (oldInput == null) {
3169                                 if (input == null) {
3170                                         return;
3171                                 }
3172                                 refresh();
3173                                 return;
3174                         }
3175
3176                         refresh();
3177
3178                 }
3179
3180                 /*
3181                  * (non-Javadoc)
3182                  * 
3183                  * @see org.eclipse.jface.viewers.ContentViewer#handleLabelProviderChanged(org.eclipse.jface.viewers.LabelProviderChangedEvent)
3184                  */
3185                 protected void handleLabelProviderChanged(
3186                                 LabelProviderChangedEvent event) {
3187                         if (event != null) {
3188                                 refresh(event.getElements());
3189                         }
3190                 }
3191
3192                 /*
3193                  * (non-Javadoc)
3194                  * 
3195                  * @see org.eclipse.jface.viewers.Viewer#getControl()
3196                  */
3197                 public Control getControl() {
3198                         return label;
3199                 }
3200
3201                 /*
3202                  * (non-Javadoc)
3203                  * 
3204                  * @see org.eclipse.jface.viewers.Viewer#getSelection()
3205                  */
3206                 public ISelection getSelection() {
3207                         // not supported
3208                         return null;
3209                 }
3210
3211                 /*
3212                  * (non-Javadoc)
3213                  * 
3214                  * @see org.eclipse.jface.viewers.Viewer#refresh()
3215                  */
3216                 public void refresh() {
3217                         Object input = this.getInput();
3218                         if (input != null) {
3219                                 ILabelProvider labelProvider = (ILabelProvider) getLabelProvider();
3220                                 doRefresh(labelProvider.getText(input), labelProvider
3221                                                 .getImage(input));
3222                         } else {
3223                                 doRefresh(null, null);
3224                         }
3225                 }
3226
3227                 /**
3228                  * Sets the given text and image to the label.
3229                  * 
3230                  * @param text
3231                  *            the new text or null
3232                  * @param image
3233                  *            the new image
3234                  */
3235                 private void doRefresh(String text, Image image) {
3236                         if ( text != null ) {
3237                                 text = LegacyActionTools.escapeMnemonics(text);
3238                         }
3239                         label.setText(text);
3240                         label.setImage(image);
3241                 }
3242
3243                 /*
3244                  * (non-Javadoc)
3245                  * 
3246                  * @see org.eclipse.jface.viewers.Viewer#setSelection(org.eclipse.jface.viewers.ISelection,
3247                  *      boolean)
3248                  */
3249                 public void setSelection(ISelection selection, boolean reveal) {
3250                         // not supported
3251                 }
3252
3253                 /**
3254                  * Refreshes the label if currently chosen element is on the list.
3255                  * 
3256                  * @param objs
3257                  *            list of changed object
3258                  */
3259                 private void refresh(Object[] objs) {
3260                         if (objs == null || getInput() == null) {
3261                                 return;
3262                         }
3263                         Object input = getInput();
3264                         for (int i = 0; i < objs.length; i++) {
3265                                 if (objs[i].equals(input)) {
3266                                         refresh();
3267                                         break;
3268                                 }
3269                         }
3270                 }
3271         }
3272
3273         /**
3274          * Compares items according to the history.
3275          */
3276         private class HistoryComparator implements Comparator {
3277
3278                 /*
3279                  * (non-Javadoc)
3280                  * 
3281                  * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
3282                  */
3283                 public int compare(Object o1, Object o2) {
3284                         boolean h1 = isHistoryElement(o1);
3285                         boolean h2 = isHistoryElement(o2);
3286                         if (h1 == h2)
3287                                 return getItemsComparator().compare(o1, o2);
3288
3289                         if (h1)
3290                                 return -2;
3291                         if (h2)
3292                                 return +2;
3293
3294                         return 0;
3295                 }
3296
3297         }
3298         
3299
3300         /**
3301          * Get the control where the search pattern is entered. Any filtering should
3302          * be done using an {@link ItemsFilter}. This control should only be
3303          * accessed for listeners that wish to handle events that do not affect
3304          * filtering such as custom traversal.
3305          * 
3306          * @return Control or <code>null</code> if the pattern control has not
3307          *         been created.
3308          */
3309         public Control getPatternControl() {
3310                 return pattern;
3311         }
3312
3313 }
3314