Project

General

Profile

Download (119 KB) Statistics
| Branch: | Tag: | Revision:
1
// $Id$
2
/**
3
* Copyright (C) 2016 EDIT
4
* European Distributed Institute of Taxonomy
5
* http://www.e-taxonomy.eu
6
*
7
* The contents of this file are subject to the Mozilla Public License Version 1.1
8
* See LICENSE.TXT at the top of this package for the full license terms.
9
*/
10
package eu.etaxonomy.taxeditor.ui.dialog.selection;
11

    
12
import java.io.IOException;
13
import java.io.StringReader;
14
import java.io.StringWriter;
15
import java.util.ArrayList;
16
import java.util.Arrays;
17
import java.util.Collections;
18
import java.util.Comparator;
19
import java.util.HashMap;
20
import java.util.HashSet;
21
import java.util.Iterator;
22
import java.util.LinkedHashSet;
23
import java.util.List;
24
import java.util.Set;
25

    
26
import org.eclipse.core.commands.AbstractHandler;
27
import org.eclipse.core.commands.ExecutionEvent;
28
import org.eclipse.core.commands.IHandler;
29
import org.eclipse.core.runtime.Assert;
30
import org.eclipse.core.runtime.CoreException;
31
import org.eclipse.core.runtime.IProgressMonitor;
32
import org.eclipse.core.runtime.IStatus;
33
import org.eclipse.core.runtime.ListenerList;
34
import org.eclipse.core.runtime.NullProgressMonitor;
35
import org.eclipse.core.runtime.ProgressMonitorWrapper;
36
import org.eclipse.core.runtime.Status;
37
import org.eclipse.core.runtime.SubProgressMonitor;
38
import org.eclipse.core.runtime.jobs.Job;
39
import org.eclipse.jface.action.Action;
40
import org.eclipse.jface.action.ActionContributionItem;
41
import org.eclipse.jface.action.IAction;
42
import org.eclipse.jface.action.IMenuListener;
43
import org.eclipse.jface.action.IMenuManager;
44
import org.eclipse.jface.action.LegacyActionTools;
45
import org.eclipse.jface.action.MenuManager;
46
import org.eclipse.jface.dialogs.IDialogSettings;
47
import org.eclipse.jface.viewers.ContentViewer;
48
import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider;
49
import org.eclipse.jface.viewers.DoubleClickEvent;
50
import org.eclipse.jface.viewers.IColorProvider;
51
import org.eclipse.jface.viewers.IContentProvider;
52
import org.eclipse.jface.viewers.IDoubleClickListener;
53
import org.eclipse.jface.viewers.IFontProvider;
54
import org.eclipse.jface.viewers.ILabelDecorator;
55
import org.eclipse.jface.viewers.ILabelProvider;
56
import org.eclipse.jface.viewers.ILabelProviderListener;
57
import org.eclipse.jface.viewers.ILazyContentProvider;
58
import org.eclipse.jface.viewers.ISelection;
59
import org.eclipse.jface.viewers.ISelectionChangedListener;
60
import org.eclipse.jface.viewers.IStructuredContentProvider;
61
import org.eclipse.jface.viewers.LabelProvider;
62
import org.eclipse.jface.viewers.LabelProviderChangedEvent;
63
import org.eclipse.jface.viewers.SelectionChangedEvent;
64
import org.eclipse.jface.viewers.StructuredSelection;
65
import org.eclipse.jface.viewers.StyledCellLabelProvider;
66
import org.eclipse.jface.viewers.StyledString;
67
import org.eclipse.jface.viewers.TableViewer;
68
import org.eclipse.jface.viewers.Viewer;
69
import org.eclipse.jface.viewers.ViewerCell;
70
import org.eclipse.jface.viewers.ViewerFilter;
71
import org.eclipse.osgi.util.NLS;
72
import org.eclipse.swt.SWT;
73
import org.eclipse.swt.accessibility.ACC;
74
import org.eclipse.swt.accessibility.AccessibleAdapter;
75
import org.eclipse.swt.accessibility.AccessibleEvent;
76
import org.eclipse.swt.custom.CLabel;
77
import org.eclipse.swt.custom.ViewForm;
78
import org.eclipse.swt.events.KeyAdapter;
79
import org.eclipse.swt.events.KeyEvent;
80
import org.eclipse.swt.events.ModifyEvent;
81
import org.eclipse.swt.events.ModifyListener;
82
import org.eclipse.swt.events.MouseAdapter;
83
import org.eclipse.swt.events.MouseEvent;
84
import org.eclipse.swt.events.SelectionAdapter;
85
import org.eclipse.swt.events.SelectionEvent;
86
import org.eclipse.swt.events.TraverseEvent;
87
import org.eclipse.swt.events.TraverseListener;
88
import org.eclipse.swt.graphics.Color;
89
import org.eclipse.swt.graphics.Font;
90
import org.eclipse.swt.graphics.GC;
91
import org.eclipse.swt.graphics.Image;
92
import org.eclipse.swt.graphics.Point;
93
import org.eclipse.swt.graphics.Rectangle;
94
import org.eclipse.swt.layout.GridData;
95
import org.eclipse.swt.layout.GridLayout;
96
import org.eclipse.swt.widgets.Composite;
97
import org.eclipse.swt.widgets.Control;
98
import org.eclipse.swt.widgets.Display;
99
import org.eclipse.swt.widgets.Event;
100
import org.eclipse.swt.widgets.Label;
101
import org.eclipse.swt.widgets.Menu;
102
import org.eclipse.swt.widgets.Shell;
103
import org.eclipse.swt.widgets.Table;
104
import org.eclipse.swt.widgets.Text;
105
import org.eclipse.swt.widgets.ToolBar;
106
import org.eclipse.swt.widgets.ToolItem;
107
import org.eclipse.ui.ActiveShellExpression;
108
import org.eclipse.ui.IMemento;
109
import org.eclipse.ui.IWorkbenchCommandConstants;
110
import org.eclipse.ui.IWorkbenchPreferenceConstants;
111
import org.eclipse.ui.PlatformUI;
112
import org.eclipse.ui.WorkbenchException;
113
import org.eclipse.ui.XMLMemento;
114
import org.eclipse.ui.dialogs.FilteredItemsSelectionDialog;
115
import org.eclipse.ui.dialogs.SearchPattern;
116
import org.eclipse.ui.dialogs.SelectionStatusDialog;
117
import org.eclipse.ui.handlers.IHandlerActivation;
118
import org.eclipse.ui.handlers.IHandlerService;
119
import org.eclipse.ui.internal.IWorkbenchGraphicConstants;
120
import org.eclipse.ui.internal.WorkbenchImages;
121
import org.eclipse.ui.internal.WorkbenchMessages;
122
import org.eclipse.ui.internal.WorkbenchPlugin;
123
import org.eclipse.ui.progress.UIJob;
124
import org.eclipse.ui.statushandlers.StatusManager;
125

    
126
/**
127
 * @author k.luther
128
 * @date 10.06.2016
129
 *
130
 */
131
public abstract class CdmFilteredItemsSelectionDialog extends SelectionStatusDialog {
132

    
133

    
134

    
135
        private static final String DIALOG_BOUNDS_SETTINGS = "DialogBoundsSettings"; //$NON-NLS-1$
136

    
137
        private static final String SHOW_STATUS_LINE = "ShowStatusLine"; //$NON-NLS-1$
138

    
139
        private static final String HISTORY_SETTINGS = "History"; //$NON-NLS-1$
140

    
141
        private static final String DIALOG_HEIGHT = "DIALOG_HEIGHT"; //$NON-NLS-1$
142

    
143
        private static final String DIALOG_WIDTH = "DIALOG_WIDTH"; //$NON-NLS-1$
144

    
145
        /**
146
         * Represents an empty selection in the pattern input field (used only for
147
         * initial pattern).
148
         */
149
        public static final int NONE = 0;
150

    
151
        /**
152
         * Pattern input field selection where caret is at the beginning (used only
153
         * for initial pattern).
154
         */
155
        public static final int CARET_BEGINNING = 1;
156

    
157
        /**
158
         * Represents a full selection in the pattern input field (used only for
159
         * initial pattern).
160
         */
161
        public static final int FULL_SELECTION = 2;
162

    
163
        private Text pattern;
164

    
165
        private TableViewer list;
166

    
167
        private DetailsContentViewer details;
168

    
169
        /**
170
         * It is a duplicate of a field in the CLabel class in DetailsContentViewer.
171
         * It is maintained, because the <code>setDetailsLabelProvider()</code>
172
         * could be called before content area is created.
173
         */
174
        private ILabelProvider detailsLabelProvider;
175

    
176
        private ItemsListLabelProvider itemsListLabelProvider;
177

    
178
        private MenuManager menuManager;
179

    
180
        private MenuManager contextMenuManager;
181

    
182
        private final boolean multi;
183

    
184
        private ToolBar toolBar;
185

    
186
        private ToolItem toolItem;
187

    
188
        private Label progressLabel;
189

    
190
        private ToggleStatusLineAction toggleStatusLineAction;
191

    
192
        private RemoveHistoryItemAction removeHistoryItemAction;
193

    
194
        private ActionContributionItem removeHistoryActionContributionItem;
195

    
196
        private IStatus status;
197

    
198
        private final RefreshCacheJob refreshCacheJob;
199

    
200
        private final RefreshProgressMessageJob refreshProgressMessageJob = new RefreshProgressMessageJob();
201

    
202
        private Object[] currentSelection;
203

    
204
        private final ContentProvider contentProvider;
205

    
206
        private final FilterHistoryJob filterHistoryJob;
207

    
208
        private final FilterJob filterJob;
209

    
210
        private ItemsFilter filter;
211

    
212
        private List lastCompletedResult;
213

    
214
        private ItemsFilter lastCompletedFilter;
215

    
216
        private String initialPatternText;
217

    
218
        private int selectionMode;
219

    
220
        private ItemsListSeparator itemsListSeparator;
221

    
222
        private static final String EMPTY_STRING = ""; //$NON-NLS-1$
223

    
224
        private boolean refreshWithLastSelection = false;
225

    
226
        private IHandlerActivation showViewHandler;
227

    
228
        /**
229
         * Creates a new instance of the class.
230
         *
231
         * @param shell
232
         *            shell to parent the dialog on
233
         * @param multi
234
         *            indicates whether dialog allows to select more than one
235
         *            position in its list of items
236
         */
237
        public CdmFilteredItemsSelectionDialog(Shell shell, boolean multi) {
238
            super(shell);
239
            this.multi = multi;
240
            filterHistoryJob = new FilterHistoryJob();
241
            filterJob = new FilterJob();
242
            contentProvider = new ContentProvider();
243
            refreshCacheJob = new RefreshCacheJob();
244
            itemsListSeparator = new ItemsListSeparator(
245
                    WorkbenchMessages.FilteredItemsSelectionDialog_separatorLabel);
246
            selectionMode = NONE;
247
        }
248

    
249
        /**
250
         * Creates a new instance of the class. Created dialog won't allow to select
251
         * more than one item.
252
         *
253
         * @param shell
254
         *            shell to parent the dialog on
255
         */
256
        public CdmFilteredItemsSelectionDialog(Shell shell) {
257
            this(shell, false);
258
        }
259

    
260
        /**
261
         * Adds viewer filter to the dialog items list.
262
         *
263
         * @param filter
264
         *            the new filter
265
         */
266
        protected void addListFilter(ViewerFilter filter) {
267
            contentProvider.addFilter(filter);
268
        }
269

    
270
        /**
271
         * Sets a new label provider for items in the list. If the label provider
272
         * also implements {@link
273
         * org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider
274
         * .IStyledLabelProvider}, the style text labels provided by it will be used
275
         * provided that the corresponding preference is set.
276
         *
277
         * @see IWorkbenchPreferenceConstants#USE_COLORED_LABELS
278
         *
279
         * @param listLabelProvider
280
         *      the label provider for items in the list
281
         */
282
        public void setListLabelProvider(ILabelProvider listLabelProvider) {
283
            getItemsListLabelProvider().setProvider(listLabelProvider);
284
        }
285

    
286
        /**
287
         * Returns the label decorator for selected items in the list.
288
         *
289
         * @return the label decorator for selected items in the list
290
         */
291
        private ILabelDecorator getListSelectionLabelDecorator() {
292
            return getItemsListLabelProvider().getSelectionDecorator();
293
        }
294

    
295
        /**
296
         * Sets the label decorator for selected items in the list.
297
         *
298
         * @param listSelectionLabelDecorator
299
         *            the label decorator for selected items in the list
300
         */
301
        public void setListSelectionLabelDecorator(
302
                ILabelDecorator listSelectionLabelDecorator) {
303
            getItemsListLabelProvider().setSelectionDecorator(
304
                    listSelectionLabelDecorator);
305
        }
306

    
307
        /**
308
         * Returns the item list label provider.
309
         *
310
         * @return the item list label provider
311
         */
312
        private ItemsListLabelProvider getItemsListLabelProvider() {
313
            if (itemsListLabelProvider == null) {
314
                itemsListLabelProvider = new ItemsListLabelProvider(
315
                        new LabelProvider(), null);
316
            }
317
            return itemsListLabelProvider;
318
        }
319

    
320
        /**
321
         * Sets label provider for the details field.
322
         *
323
         * For a single selection, the element sent to
324
         * {@link ILabelProvider#getImage(Object)} and
325
         * {@link ILabelProvider#getText(Object)} is the selected object, for
326
         * multiple selection a {@link String} with amount of selected items is the
327
         * element.
328
         *
329
         * @see #getSelectedItems() getSelectedItems() can be used to retrieve
330
         *      selected items and get the items count.
331
         *
332
         * @param detailsLabelProvider
333
         *            the label provider for the details field
334
         */
335
        public void setDetailsLabelProvider(ILabelProvider detailsLabelProvider) {
336
            this.detailsLabelProvider = detailsLabelProvider;
337
            if (details != null) {
338
                details.setLabelProvider(detailsLabelProvider);
339
            }
340
        }
341

    
342
        private ILabelProvider getDetailsLabelProvider() {
343
            if (detailsLabelProvider == null) {
344
                detailsLabelProvider = new LabelProvider();
345
            }
346
            return detailsLabelProvider;
347
        }
348

    
349
        /*
350
         * (non-Javadoc)
351
         *
352
         * @see org.eclipse.jface.window.Window#create()
353
         */
354
        @Override
355
        public void create() {
356
            super.create();
357
            pattern.setFocus();
358
        }
359

    
360
        /**
361
         * Restores dialog using persisted settings. The default implementation
362
         * restores the status of the details line and the selection history.
363
         *
364
         * @param settings
365
         *            settings used to restore dialog
366
         */
367
        protected void restoreDialog(IDialogSettings settings) {
368
            boolean toggleStatusLine = true;
369

    
370
            if (settings.get(SHOW_STATUS_LINE) != null) {
371
                toggleStatusLine = settings.getBoolean(SHOW_STATUS_LINE);
372
            }
373

    
374
            toggleStatusLineAction.setChecked(toggleStatusLine);
375

    
376
            details.setVisible(toggleStatusLine);
377

    
378
            String setting = settings.get(HISTORY_SETTINGS);
379
            if (setting != null) {
380
                try {
381
                    IMemento memento = XMLMemento.createReadRoot(new StringReader(
382
                            setting));
383
                    this.contentProvider.loadHistory(memento);
384
                } catch (WorkbenchException e) {
385
                    // Simply don't restore the settings
386
                    StatusManager
387
                            .getManager()
388
                            .handle(
389
                                    new Status(
390
                                            IStatus.ERROR,
391
                                            PlatformUI.PLUGIN_ID,
392
                                            IStatus.ERROR,
393
                                            WorkbenchMessages.FilteredItemsSelectionDialog_restoreError,
394
                                            e));
395
                }
396
            }
397
        }
398

    
399
        /*
400
         * (non-Javadoc)
401
         *
402
         * @see org.eclipse.jface.window.Window#close()
403
         */
404
        @Override
405
        public boolean close() {
406
            this.filterJob.cancel();
407
            this.refreshCacheJob.cancel();
408
            this.refreshProgressMessageJob.cancel();
409
            if (showViewHandler != null) {
410
                IHandlerService service = (IHandlerService) PlatformUI
411
                        .getWorkbench().getService(IHandlerService.class);
412
                service.deactivateHandler(showViewHandler);
413
                showViewHandler.getHandler().dispose();
414
                showViewHandler = null;
415
            }
416
            if (menuManager != null) {
417
                menuManager.dispose();
418
            }
419
            if (contextMenuManager != null) {
420
                contextMenuManager.dispose();
421
            }
422
            storeDialog(getDialogSettings());
423
            return super.close();
424
        }
425

    
426
        /**
427
         * Stores dialog settings.
428
         *
429
         * @param settings
430
         *            settings used to store dialog
431
         */
432
        protected void storeDialog(IDialogSettings settings) {
433
            settings.put(SHOW_STATUS_LINE, toggleStatusLineAction.isChecked());
434

    
435
            XMLMemento memento = XMLMemento.createWriteRoot(HISTORY_SETTINGS);
436
            this.contentProvider.saveHistory(memento);
437
            StringWriter writer = new StringWriter();
438
            try {
439
                memento.save(writer);
440
                settings.put(HISTORY_SETTINGS, writer.getBuffer().toString());
441
            } catch (IOException e) {
442
                // Simply don't store the settings
443
                StatusManager
444
                        .getManager()
445
                        .handle(
446
                                new Status(
447
                                        IStatus.ERROR,
448
                                        PlatformUI.PLUGIN_ID,
449
                                        IStatus.ERROR,
450
                                        WorkbenchMessages.FilteredItemsSelectionDialog_storeError,
451
                                        e));
452
            }
453
        }
454

    
455
        /**
456
         * Create a new header which is labelled by headerLabel.
457
         *
458
         * @param parent
459
         * @return Label the label of the header
460
         */
461
        private Label createHeader(Composite parent) {
462
            Composite header = new Composite(parent, SWT.NONE);
463

    
464
            GridLayout layout = new GridLayout();
465
            layout.numColumns = 2;
466
            layout.marginWidth = 0;
467
            layout.marginHeight = 0;
468
            header.setLayout(layout);
469

    
470
            Label headerLabel = new Label(header, SWT.NONE);
471
            headerLabel.setText((getMessage() != null && getMessage().trim()
472
                    .length() > 0) ? getMessage()
473
                    : WorkbenchMessages.FilteredItemsSelectionDialog_patternLabel);
474
            headerLabel.addTraverseListener(new TraverseListener() {
475
                @Override
476
                public void keyTraversed(TraverseEvent e) {
477
                    if (e.detail == SWT.TRAVERSE_MNEMONIC && e.doit) {
478
                        e.detail = SWT.TRAVERSE_NONE;
479
                        pattern.setFocus();
480
                    }
481
                }
482
            });
483

    
484
            GridData gd = new GridData(GridData.FILL_HORIZONTAL);
485
            headerLabel.setLayoutData(gd);
486

    
487
            createViewMenu(header);
488
            header.setLayoutData(gd);
489
            return headerLabel;
490
        }
491

    
492
        /**
493
         * Create the labels for the list and the progress. Return the list label.
494
         *
495
         * @param parent
496
         * @return Label
497
         */
498
        private Label createLabels(Composite parent) {
499
            Composite labels = new Composite(parent, SWT.NONE);
500

    
501
            GridLayout layout = new GridLayout();
502
            layout.numColumns = 2;
503
            layout.marginWidth = 0;
504
            layout.marginHeight = 0;
505
            labels.setLayout(layout);
506

    
507
            Label listLabel = new Label(labels, SWT.NONE);
508
            listLabel
509
                    .setText(WorkbenchMessages.FilteredItemsSelectionDialog_listLabel);
510

    
511
            listLabel.addTraverseListener(new TraverseListener() {
512
                @Override
513
                public void keyTraversed(TraverseEvent e) {
514
                    if (e.detail == SWT.TRAVERSE_MNEMONIC && e.doit) {
515
                        e.detail = SWT.TRAVERSE_NONE;
516
                        list.getTable().setFocus();
517
                    }
518
                }
519
            });
520

    
521
            GridData gd = new GridData(GridData.FILL_HORIZONTAL);
522
            listLabel.setLayoutData(gd);
523

    
524
            progressLabel = new Label(labels, SWT.RIGHT);
525
            progressLabel.setLayoutData(gd);
526

    
527
            labels.setLayoutData(gd);
528
            return listLabel;
529
        }
530

    
531
        private void createViewMenu(Composite parent) {
532
            toolBar = new ToolBar(parent, SWT.FLAT);
533
            toolItem = new ToolItem(toolBar, SWT.PUSH, 0);
534

    
535
            GridData data = new GridData();
536
            data.horizontalAlignment = GridData.END;
537
            toolBar.setLayoutData(data);
538

    
539
            toolBar.addMouseListener(new MouseAdapter() {
540
                @Override
541
                public void mouseDown(MouseEvent e) {
542
                    showViewMenu();
543
                }
544
            });
545

    
546
            toolItem.setImage(WorkbenchImages
547
                    .getImage(IWorkbenchGraphicConstants.IMG_LCL_VIEW_MENU));
548
            toolItem
549
                    .setToolTipText(WorkbenchMessages.FilteredItemsSelectionDialog_menu);
550
            toolItem.addSelectionListener(new SelectionAdapter() {
551
                @Override
552
                public void widgetSelected(SelectionEvent e) {
553
                    showViewMenu();
554
                }
555
            });
556

    
557
            menuManager = new MenuManager();
558

    
559
            fillViewMenu(menuManager);
560

    
561
            IHandlerService service = (IHandlerService) PlatformUI.getWorkbench()
562
                    .getService(IHandlerService.class);
563
            IHandler handler = new AbstractHandler() {
564
                @Override
565
                public Object execute(ExecutionEvent event) {
566
                    showViewMenu();
567
                    return null;
568
                }
569
            };
570
            showViewHandler = service.activateHandler(
571
                    IWorkbenchCommandConstants.WINDOW_SHOW_VIEW_MENU, handler,
572
                    new ActiveShellExpression(getShell()));
573
        }
574

    
575
        /**
576
         * Fills the menu of the dialog.
577
         *
578
         * @param menuManager
579
         *            the menu manager
580
         */
581
        protected void fillViewMenu(IMenuManager menuManager) {
582
            toggleStatusLineAction = new ToggleStatusLineAction();
583
            menuManager.add(toggleStatusLineAction);
584
        }
585

    
586
        private void showViewMenu() {
587
            Menu menu = menuManager.createContextMenu(getShell());
588
            Rectangle bounds = toolItem.getBounds();
589
            Point topLeft = new Point(bounds.x, bounds.y + bounds.height);
590
            topLeft = toolBar.toDisplay(topLeft);
591
            menu.setLocation(topLeft.x, topLeft.y);
592
            menu.setVisible(true);
593
        }
594

    
595
        /**
596
         * Hook that allows to add actions to the context menu.
597
         * <p>
598
         * Subclasses may extend in order to add other actions.</p>
599
         *
600
         * @param menuManager the context menu manager
601
         * @since 3.5
602
         */
603
        protected void fillContextMenu(IMenuManager menuManager) {
604
            List selectedElements= ((StructuredSelection)list.getSelection()).toList();
605

    
606
            Object item= null;
607

    
608
            for (Iterator it= selectedElements.iterator(); it.hasNext();) {
609
                item= it.next();
610
                if (item instanceof ItemsListSeparator || !isHistoryElement(item)) {
611
                    return;
612
                }
613
            }
614

    
615
            if (selectedElements.size() > 0) {
616
                removeHistoryItemAction.setText(WorkbenchMessages.FilteredItemsSelectionDialog_removeItemsFromHistoryAction);
617

    
618
                menuManager.add(removeHistoryActionContributionItem);
619

    
620
            }
621
        }
622

    
623
        private void createPopupMenu() {
624
            removeHistoryItemAction = new RemoveHistoryItemAction();
625
            removeHistoryActionContributionItem = new ActionContributionItem(
626
                    removeHistoryItemAction);
627

    
628
            contextMenuManager = new MenuManager();
629
            contextMenuManager.setRemoveAllWhenShown(true);
630
            contextMenuManager.addMenuListener(new IMenuListener() {
631
                @Override
632
                public void menuAboutToShow(IMenuManager manager) {
633
                    fillContextMenu(manager);
634
                }
635
            });
636

    
637
            final Table table = list.getTable();
638
            Menu menu= contextMenuManager.createContextMenu(table);
639
            table.setMenu(menu);
640
        }
641

    
642
        /**
643
         * Creates an extra content area, which will be located above the details.
644
         *
645
         * @param parent
646
         *            parent to create the dialog widgets in
647
         * @return an extra content area
648
         */
649
        protected abstract Control createExtendedContentArea(Composite parent);
650

    
651
        /*
652
         * (non-Javadoc)
653
         *
654
         * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite)
655
         */
656
        @Override
657
        protected Control createDialogArea(Composite parent) {
658
            Composite dialogArea = (Composite) super.createDialogArea(parent);
659

    
660
            Composite content = new Composite(dialogArea, SWT.NONE);
661
            GridData gd = new GridData(GridData.FILL_BOTH);
662
            content.setLayoutData(gd);
663

    
664
            GridLayout layout = new GridLayout();
665
            layout.numColumns = 1;
666
            layout.marginWidth = 0;
667
            layout.marginHeight = 0;
668
            content.setLayout(layout);
669

    
670
            final Label headerLabel = createHeader(content);
671

    
672
            pattern = new Text(content, SWT.SINGLE | SWT.BORDER | SWT.SEARCH | SWT.ICON_CANCEL);
673
            pattern.getAccessible().addAccessibleListener(new AccessibleAdapter() {
674
                @Override
675
                public void getName(AccessibleEvent e) {
676
                    e.result = LegacyActionTools.removeMnemonics(headerLabel
677
                            .getText());
678
                }
679
            });
680
            gd = new GridData(GridData.FILL_HORIZONTAL);
681
            pattern.setLayoutData(gd);
682

    
683
            final Label listLabel = createLabels(content);
684

    
685
            list = new TableViewer(content, (multi ? SWT.MULTI : SWT.SINGLE)
686
                    | SWT.BORDER | SWT.V_SCROLL | SWT.VIRTUAL);
687
            list.getTable().getAccessible().addAccessibleListener(
688
                    new AccessibleAdapter() {
689
                        @Override
690
                        public void getName(AccessibleEvent e) {
691
                            if (e.childID == ACC.CHILDID_SELF) {
692
                                e.result = LegacyActionTools
693
                                        .removeMnemonics(listLabel.getText());
694
                            }
695
                        }
696
                    });
697
            list.setContentProvider(contentProvider);
698
            list.setLabelProvider(getItemsListLabelProvider());
699
            list.setInput(new Object[0]);
700
            list.setItemCount(contentProvider.getNumberOfElements());
701
            gd = new GridData(GridData.FILL_BOTH);
702
            applyDialogFont(list.getTable());
703
            gd.heightHint= list.getTable().getItemHeight() * 15;
704
            list.getTable().setLayoutData(gd);
705

    
706
            createPopupMenu();
707

    
708
            pattern.addModifyListener(new ModifyListener() {
709
                @Override
710
                public void modifyText(ModifyEvent e) {
711
                    applyFilter();
712
                }
713
            });
714

    
715
            pattern.addKeyListener(new KeyAdapter() {
716
                @Override
717
                public void keyPressed(KeyEvent e) {
718
                    if (e.keyCode == SWT.ARROW_DOWN) {
719
                        if (list.getTable().getItemCount() > 0) {
720
                            list.getTable().setFocus();
721
                        }
722
                    }
723
                }
724
            });
725

    
726
            list.addSelectionChangedListener(new ISelectionChangedListener() {
727
                @Override
728
                public void selectionChanged(SelectionChangedEvent event) {
729
                    StructuredSelection selection = (StructuredSelection) event
730
                            .getSelection();
731
                    handleSelected(selection);
732
                }
733
            });
734

    
735
            list.addDoubleClickListener(new IDoubleClickListener() {
736
                @Override
737
                public void doubleClick(DoubleClickEvent event) {
738
                    handleDoubleClick();
739
                }
740
            });
741

    
742
            list.getTable().addKeyListener(new KeyAdapter() {
743
                @Override
744
                public void keyPressed(KeyEvent e) {
745

    
746
                    if (e.keyCode == SWT.DEL) {
747

    
748
                        List selectedElements = ((StructuredSelection) list
749
                                .getSelection()).toList();
750

    
751
                        Object item = null;
752
                        boolean isSelectedHistory = true;
753

    
754
                        for (Iterator it = selectedElements.iterator(); it
755
                                .hasNext();) {
756
                            item = it.next();
757
                            if (item instanceof ItemsListSeparator
758
                                    || !isHistoryElement(item)) {
759
                                isSelectedHistory = false;
760
                                break;
761
                            }
762
                        }
763
                        if (isSelectedHistory) {
764
                            removeSelectedItems(selectedElements);
765
                        }
766

    
767
                    }
768

    
769
                    if (e.keyCode == SWT.ARROW_UP && (e.stateMask & SWT.SHIFT) != 0
770
                            && (e.stateMask & SWT.CTRL) != 0) {
771
                        StructuredSelection selection = (StructuredSelection) list
772
                                .getSelection();
773

    
774
                        if (selection.size() == 1) {
775
                            Object element = selection.getFirstElement();
776
                            if (element.equals(list.getElementAt(0))) {
777
                                pattern.setFocus();
778
                            }
779
                            if (list.getElementAt(list.getTable()
780
                                    .getSelectionIndex() - 1) instanceof ItemsListSeparator) {
781
                                list.getTable().setSelection(
782
                                        list.getTable().getSelectionIndex() - 1);
783
                            }
784
                            list.getTable().notifyListeners(SWT.Selection,
785
                                    new Event());
786

    
787
                        }
788
                    }
789

    
790
                    if (e.keyCode == SWT.ARROW_DOWN
791
                            && (e.stateMask & SWT.SHIFT) != 0
792
                            && (e.stateMask & SWT.CTRL) != 0) {
793

    
794
                        if (list
795
                                .getElementAt(list.getTable().getSelectionIndex() + 1) instanceof ItemsListSeparator) {
796
                            list.getTable().setSelection(
797
                                    list.getTable().getSelectionIndex() + 1);
798
                        }
799
                        list.getTable().notifyListeners(SWT.Selection, new Event());
800
                    }
801

    
802
                }
803
            });
804

    
805
            createExtendedContentArea(content);
806

    
807
            details = new DetailsContentViewer(content, SWT.BORDER | SWT.FLAT);
808
            details.setVisible(toggleStatusLineAction.isChecked());
809
            details.setContentProvider(new NullContentProvider());
810
            details.setLabelProvider(getDetailsLabelProvider());
811

    
812
            applyDialogFont(content);
813

    
814
            restoreDialog(getDialogSettings());
815

    
816
            if (initialPatternText != null) {
817
                pattern.setText(initialPatternText);
818
            }
819

    
820
            switch (selectionMode) {
821
            case CARET_BEGINNING:
822
                pattern.setSelection(0, 0);
823
                break;
824
            case FULL_SELECTION:
825
                pattern.setSelection(0, initialPatternText.length());
826
                break;
827
            }
828

    
829
            // apply filter even if pattern is empty (display history)
830
            applyFilter();
831

    
832
            return dialogArea;
833
        }
834

    
835
        /**
836
         * This method is a hook for subclasses to override default dialog behavior.
837
         * The <code>handleDoubleClick()</code> method handles double clicks on
838
         * the list of filtered elements.
839
         * <p>
840
         * Current implementation makes double-clicking on the list do the same as
841
         * pressing <code>OK</code> button on the dialog.
842
         */
843
        protected void handleDoubleClick() {
844
            okPressed();
845
        }
846

    
847
        /**
848
         * Refreshes the details field according to the current selection in the
849
         * items list.
850
         */
851
        private void refreshDetails() {
852
            StructuredSelection selection = getSelectedItems();
853

    
854
            switch (selection.size()) {
855
            case 0:
856
                details.setInput(null);
857
                break;
858
            case 1:
859
                details.setInput(selection.getFirstElement());
860
                break;
861
            default:
862
                details
863
                        .setInput(NLS
864
                                .bind(
865
                                        WorkbenchMessages.FilteredItemsSelectionDialog_nItemsSelected,
866
                                        new Integer(selection.size())));
867
                break;
868
            }
869

    
870
        }
871

    
872
        /**
873
         * Handle selection in the items list by updating labels of selected and
874
         * unselected items and refresh the details field using the selection.
875
         *
876
         * @param selection
877
         *            the new selection
878
         */
879
        protected void handleSelected(StructuredSelection selection) {
880
            IStatus status = new Status(IStatus.OK, PlatformUI.PLUGIN_ID,
881
                    IStatus.OK, EMPTY_STRING, null);
882

    
883
            Object[] lastSelection = currentSelection;
884

    
885
            currentSelection = selection.toArray();
886

    
887
            if (selection.size() == 0) {
888
                status = new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID,
889
                        IStatus.ERROR, EMPTY_STRING, null);
890

    
891
                if (lastSelection != null
892
                        && getListSelectionLabelDecorator() != null) {
893
                    list.update(lastSelection, null);
894
                }
895

    
896
                currentSelection = null;
897

    
898
            } else {
899
                status = new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID,
900
                        IStatus.ERROR, EMPTY_STRING, null);
901

    
902
                List items = selection.toList();
903

    
904
                Object item = null;
905
                IStatus tempStatus = null;
906

    
907
                for (Iterator it = items.iterator(); it.hasNext();) {
908
                    Object o = it.next();
909

    
910
                    if (o instanceof ItemsListSeparator) {
911
                        continue;
912
                    }
913

    
914
                    item = o;
915
                    tempStatus = validateItem(item);
916

    
917
                    if (tempStatus.isOK()) {
918
                        status = new Status(IStatus.OK, PlatformUI.PLUGIN_ID,
919
                                IStatus.OK, EMPTY_STRING, null);
920
                    } else {
921
                        status = tempStatus;
922
                        // if any selected element is not valid status is set to
923
                        // ERROR
924
                        break;
925
                    }
926
                }
927

    
928
                if (lastSelection != null
929
                        && getListSelectionLabelDecorator() != null) {
930
                    list.update(lastSelection, null);
931
                }
932

    
933
                if (getListSelectionLabelDecorator() != null) {
934
                    list.update(currentSelection, null);
935
                }
936
            }
937

    
938
            refreshDetails();
939
            updateStatus(status);
940
        }
941

    
942
        /*
943
         * (non-Javadoc)
944
         *
945
         * @see org.eclipse.jface.window.Dialog#getDialogBoundsSettings()
946
         */
947
        @Override
948
        protected IDialogSettings getDialogBoundsSettings() {
949
            IDialogSettings settings = getDialogSettings();
950
            IDialogSettings section = settings.getSection(DIALOG_BOUNDS_SETTINGS);
951
            if (section == null) {
952
                section = settings.addNewSection(DIALOG_BOUNDS_SETTINGS);
953
                section.put(DIALOG_HEIGHT, 500);
954
                section.put(DIALOG_WIDTH, 600);
955
            }
956
            return section;
957
        }
958

    
959
        /**
960
         * Returns the dialog settings. Returned object can't be null.
961
         *
962
         * @return return dialog settings for this dialog
963
         */
964
        protected abstract IDialogSettings getDialogSettings();
965

    
966
        /**
967
         * Refreshes the dialog - has to be called in UI thread.
968
         */
969
        public void refresh() {
970
            if (list != null && !list.getTable().isDisposed()) {
971

    
972
                List lastRefreshSelection = ((StructuredSelection) list
973
                        .getSelection()).toList();
974
                list.getTable().deselectAll();
975

    
976
                list.setItemCount(contentProvider.getNumberOfElements());
977
                list.refresh();
978

    
979
                if (list.getTable().getItemCount() > 0) {
980
                    // preserve previous selection
981
                    if (refreshWithLastSelection && lastRefreshSelection != null
982
                            && lastRefreshSelection.size() > 0) {
983
                        list.setSelection(new StructuredSelection(
984
                                lastRefreshSelection));
985
                    } else {
986
                        refreshWithLastSelection = true;
987
                        list.getTable().setSelection(0);
988
                        list.getTable().notifyListeners(SWT.Selection, new Event());
989
                    }
990
                } else {
991
                    list.setSelection(StructuredSelection.EMPTY);
992
                }
993

    
994
            }
995

    
996
            scheduleProgressMessageRefresh();
997
        }
998

    
999
        /**
1000
         * Updates the progress label.
1001
         *
1002
         * @deprecated
1003
         */
1004
        @Deprecated
1005
        public void updateProgressLabel() {
1006
            scheduleProgressMessageRefresh();
1007
        }
1008

    
1009
        /**
1010
         * Notifies the content provider - fires filtering of content provider
1011
         * elements. During the filtering, a separator between history and workspace
1012
         * matches is added.
1013
         * <p>
1014
         * This is a long running operation and should be called in a job.
1015
         *
1016
         * @param checkDuplicates
1017
         *            <code>true</code> if data concerning elements duplication
1018
         *            should be computed - it takes much more time than the standard
1019
         *            filtering
1020
         * @param monitor
1021
         *            a progress monitor or <code>null</code> if no monitor is
1022
         *            available
1023
         */
1024
        public void reloadCache(boolean checkDuplicates, IProgressMonitor monitor) {
1025
            if (list != null && !list.getTable().isDisposed()
1026
                    && contentProvider != null) {
1027
                contentProvider.reloadCache(checkDuplicates, monitor);
1028
            }
1029
        }
1030

    
1031
        /**
1032
         * Schedule refresh job.
1033
         */
1034
        public void scheduleRefresh() {
1035
            refreshCacheJob.cancelAll();
1036
            refreshCacheJob.schedule();
1037
        }
1038

    
1039
        /**
1040
         * Schedules progress message refresh.
1041
         */
1042
        public void scheduleProgressMessageRefresh() {
1043
            if (filterJob.getState() != Job.RUNNING
1044
                    && refreshProgressMessageJob.getState() != Job.RUNNING) {
1045
                refreshProgressMessageJob.scheduleProgressRefresh(null);
1046
            }
1047
        }
1048

    
1049
        /*
1050
         * (non-Javadoc)
1051
         *
1052
         * @see org.eclipse.ui.dialogs.SelectionStatusDialog#computeResult()
1053
         */
1054
        @Override
1055
        protected void computeResult() {
1056

    
1057
            List selectedElements = ((StructuredSelection) list.getSelection())
1058
                    .toList();
1059

    
1060
            List objectsToReturn = new ArrayList();
1061

    
1062
            Object item = null;
1063

    
1064
            for (Iterator it = selectedElements.iterator(); it.hasNext();) {
1065
                item = it.next();
1066

    
1067
                if (!(item instanceof ItemsListSeparator)) {
1068
                    accessedHistoryItem(item);
1069
                    objectsToReturn.add(item);
1070
                }
1071
            }
1072

    
1073
            setResult(objectsToReturn);
1074
        }
1075

    
1076
        /*
1077
         * @see org.eclipse.ui.dialogs.SelectionStatusDialog#updateStatus(org.eclipse.core.runtime.IStatus)
1078
         */
1079
        @Override
1080
        protected void updateStatus(IStatus status) {
1081
            this.status = status;
1082
            super.updateStatus(status);
1083
        }
1084

    
1085
        /*
1086
         * @see Dialog#okPressed()
1087
         */
1088
        @Override
1089
        protected void okPressed() {
1090
            if (status != null
1091
                    && (status.isOK() || status.getCode() == IStatus.INFO)) {
1092
                super.okPressed();
1093
            }
1094
        }
1095

    
1096
        /**
1097
         * Sets the initial pattern used by the filter. This text is copied into the
1098
         * selection input on the dialog. A full selection is used in the pattern
1099
         * input field.
1100
         *
1101
         * @param text
1102
         *            initial pattern for the filter
1103
         * @see FilteredItemsSelectionDialog#FULL_SELECTION
1104
         */
1105
        public void setInitialPattern(String text) {
1106
            setInitialPattern(text, FULL_SELECTION);
1107
        }
1108

    
1109
        /**
1110
         * Sets the initial pattern used by the filter. This text is copied into the
1111
         * selection input on the dialog. The <code>selectionMode</code> is used
1112
         * to choose selection type for the input field.
1113
         *
1114
         * @param text
1115
         *            initial pattern for the filter
1116
         * @param selectionMode
1117
         *            one of: {@link FilteredItemsSelectionDialog#NONE},
1118
         *            {@link FilteredItemsSelectionDialog#CARET_BEGINNING},
1119
         *            {@link FilteredItemsSelectionDialog#FULL_SELECTION}
1120
         */
1121
        public void setInitialPattern(String text, int selectionMode) {
1122
            this.initialPatternText = text;
1123
            this.selectionMode = selectionMode;
1124
        }
1125

    
1126
        /**
1127
         * Gets initial pattern.
1128
         *
1129
         * @return initial pattern, or <code>null</code> if initial pattern is not
1130
         *         set
1131
         */
1132
        protected String getInitialPattern() {
1133
            return this.initialPatternText;
1134
        }
1135

    
1136
        /**
1137
         * Returns the current selection.
1138
         *
1139
         * @return the current selection
1140
         */
1141
        protected StructuredSelection getSelectedItems() {
1142

    
1143
            StructuredSelection selection = (StructuredSelection) list
1144
                    .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
            }
1160
            // Create a new selection without the collision
1161
            List newItems = new ArrayList(selectedItems);
1162
            newItems.remove(itemToRemove);
1163
            return new StructuredSelection(newItems);
1164

    
1165
        }
1166

    
1167
        /**
1168
         * Validates the item. When items on the items list are selected or
1169
         * deselected, it validates each item in the selection and the dialog status
1170
         * depends on all validations.
1171
         *
1172
         * @param item
1173
         *            an item to be checked
1174
         * @return status of the dialog to be set
1175
         */
1176
        protected abstract IStatus validateItem(Object item);
1177

    
1178
        /**
1179
         * Creates an instance of a filter.
1180
         *
1181
         * @return a filter for items on the items list. Can be <code>null</code>,
1182
         *         no filtering will be applied then, causing no item to be shown in
1183
         *         the list.
1184
         */
1185
        protected abstract ItemsFilter createFilter();
1186

    
1187
        /**
1188
         * Applies the filter created by <code>createFilter()</code> method to the
1189
         * items list. When new filter is different than previous one it will cause
1190
         * refiltering.
1191
         */
1192
        protected void applyFilter() {
1193

    
1194
            ItemsFilter newFilter = createFilter();
1195

    
1196
            // don't apply filtering for patterns which mean the same, for example:
1197
            // *a**b and ***a*b
1198
            if (filter != null && filter.equalsFilter(newFilter)) {
1199
                return;
1200
            }
1201

    
1202
            filterHistoryJob.cancel();
1203
            filterJob.cancel();
1204

    
1205
            this.filter = newFilter;
1206

    
1207
            if (this.filter != null) {
1208
                filterHistoryJob.schedule();
1209
            }
1210
        }
1211

    
1212
        /**
1213
         * Returns comparator to sort items inside content provider. Returned object
1214
         * will be probably created as an anonymous class. Parameters passed to the
1215
         * <code>compare(java.lang.Object, java.lang.Object)</code> are going to
1216
         * be the same type as the one used in the content provider.
1217
         *
1218
         * @return comparator to sort items content provider
1219
         */
1220
        protected abstract Comparator getItemsComparator();
1221

    
1222
        /**
1223
         * Fills the content provider with matching items.
1224
         *
1225
         * @param contentProvider
1226
         *            collector to add items to.
1227
         *            {@link FilteredItemsSelectionDialog.AbstractContentProvider#add(Object, FilteredItemsSelectionDialog.ItemsFilter)}
1228
         *            only adds items that pass the given <code>itemsFilter</code>.
1229
         * @param itemsFilter
1230
         *            the items filter
1231
         * @param progressMonitor
1232
         *            must be used to report search progress. The state of this
1233
         *            progress monitor reflects the state of the filtering process.
1234
         * @throws CoreException
1235
         */
1236
        protected abstract void fillContentProvider(
1237
                AbstractContentProvider contentProvider, ItemsFilter itemsFilter,
1238
                IProgressMonitor progressMonitor) throws CoreException;
1239

    
1240
        /**
1241
         * Removes selected items from history.
1242
         *
1243
         * @param items
1244
         *            items to be removed
1245
         */
1246
        private void removeSelectedItems(List items) {
1247
            for (Iterator iter = items.iterator(); iter.hasNext();) {
1248
                Object item = iter.next();
1249
                removeHistoryItem(item);
1250
            }
1251
            refreshWithLastSelection = false;
1252
            contentProvider.refresh();
1253
        }
1254

    
1255
        /**
1256
         * Removes an item from history.
1257
         *
1258
         * @param item
1259
         *            an item to remove
1260
         * @return removed item
1261
         */
1262
        protected Object removeHistoryItem(Object item) {
1263
            return contentProvider.removeHistoryElement(item);
1264
        }
1265

    
1266
        /**
1267
         * Adds item to history.
1268
         *
1269
         * @param item
1270
         *            the item to be added
1271
         */
1272
        protected void accessedHistoryItem(Object item) {
1273
            contentProvider.addHistoryElement(item);
1274
        }
1275

    
1276
        /**
1277
         * Returns a history comparator.
1278
         *
1279
         * @return decorated comparator
1280
         */
1281
        private Comparator getHistoryComparator() {
1282
            return new HistoryComparator();
1283
        }
1284

    
1285
        /**
1286
         * Returns the history of selected elements.
1287
         *
1288
         * @return history of selected elements, or <code>null</code> if it is not
1289
         *         set
1290
         */
1291
        protected SelectionHistory getSelectionHistory() {
1292
            return this.contentProvider.getSelectionHistory();
1293
        }
1294

    
1295
        /**
1296
         * Sets new history.
1297
         *
1298
         * @param selectionHistory
1299
         *            the history
1300
         */
1301
        protected void setSelectionHistory(SelectionHistory selectionHistory) {
1302
            if (this.contentProvider != null) {
1303
                this.contentProvider.setSelectionHistory(selectionHistory);
1304
            }
1305
        }
1306

    
1307
        /**
1308
         * Indicates whether the given item is a history item.
1309
         *
1310
         * @param item
1311
         *            the item to be investigated
1312
         * @return <code>true</code> if the given item exists in history,
1313
         *         <code>false</code> otherwise
1314
         */
1315
        public boolean isHistoryElement(Object item) {
1316
            return this.contentProvider.isHistoryElement(item);
1317
        }
1318

    
1319
        /**
1320
         * Indicates whether the given item is a duplicate.
1321
         *
1322
         * @param item
1323
         *            the item to be investigated
1324
         * @return <code>true</code> if the item is duplicate, <code>false</code>
1325
         *         otherwise
1326
         */
1327
        public boolean isDuplicateElement(Object item) {
1328
            return this.contentProvider.isDuplicateElement(item);
1329
        }
1330

    
1331
        /**
1332
         * Sets separator label
1333
         *
1334
         * @param separatorLabel
1335
         *            the label showed on separator
1336
         */
1337
        public void setSeparatorLabel(String separatorLabel) {
1338
            this.itemsListSeparator = new ItemsListSeparator(separatorLabel);
1339
        }
1340

    
1341
        /**
1342
         * Returns name for then given object.
1343
         *
1344
         * @param item
1345
         *            an object from the content provider. Subclasses should pay
1346
         *            attention to the passed argument. They should either only pass
1347
         *            objects of a known type (one used in content provider) or make
1348
         *            sure that passed parameter is the expected one (by type
1349
         *            checking like <code>instanceof</code> inside the method).
1350
         * @return name of the given item
1351
         */
1352
        public abstract String getElementName(Object item);
1353

    
1354
        private class ToggleStatusLineAction extends Action {
1355

    
1356
            /**
1357
             * Creates a new instance of the class.
1358
             */
1359
            public ToggleStatusLineAction() {
1360
                super(
1361
                        WorkbenchMessages.FilteredItemsSelectionDialog_toggleStatusAction,
1362
                        IAction.AS_CHECK_BOX);
1363
            }
1364

    
1365
            @Override
1366
            public void run() {
1367
                details.setVisible(isChecked());
1368
            }
1369
        }
1370

    
1371
        /**
1372
         * Only refreshes UI on the basis of an already sorted and filtered set of
1373
         * items.
1374
         * <p>
1375
         * Standard invocation scenario:
1376
         * <ol>
1377
         * <li>filtering job (<code>FilterJob</code> class extending
1378
         * <code>Job</code> class)</li>
1379
         * <li>cache refresh without checking for duplicates (<code>RefreshCacheJob</code>
1380
         * class extending <code>Job</code> class)</li>
1381
         * <li>UI refresh (<code>RefreshJob</code> class extending
1382
         * <code>UIJob</code> class)</li>
1383
         * <li>cache refresh with checking for duplicates (<cod>CacheRefreshJob</code>
1384
         * class extending <code>Job</code> class)</li>
1385
         * <li>UI refresh (<code>RefreshJob</code> class extending <code>UIJob</code>
1386
         * class)</li>
1387
         * </ol>
1388
         * The scenario is rather complicated, but it had to be applied, because:
1389
         * <ul>
1390
         * <li> refreshing cache is rather a long action and cannot be run in the UI -
1391
         * cannot be run in a UIJob</li>
1392
         * <li> refreshing cache checking for duplicates is twice as long as
1393
         * refreshing cache without checking for duplicates; results of the search
1394
         * could be displayed earlier</li>
1395
         * <li> refreshing the UI have to be run in a UIJob</li>
1396
         * </ul>
1397
         *
1398
         * @see org.eclipse.ui.dialogs.FilteredItemsSelectionDialog.FilterJob
1399
         * @see org.eclipse.ui.dialogs.FilteredItemsSelectionDialog.RefreshJob
1400
         * @see org.eclipse.ui.dialogs.FilteredItemsSelectionDialog.RefreshCacheJob
1401
         */
1402
        private class RefreshJob extends UIJob {
1403

    
1404
            /**
1405
             * Creates a new instance of the class.
1406
             */
1407
            public RefreshJob() {
1408
                super(CdmFilteredItemsSelectionDialog.this.getParentShell()
1409
                        .getDisplay(),
1410
                        WorkbenchMessages.FilteredItemsSelectionDialog_refreshJob);
1411
                setSystem(true);
1412
            }
1413

    
1414
            /*
1415
             * (non-Javadoc)
1416
             *
1417
             * @see org.eclipse.ui.progress.UIJob#runInUIThread(org.eclipse.core.runtime.IProgressMonitor)
1418
             */
1419
            @Override
1420
            public IStatus runInUIThread(IProgressMonitor monitor) {
1421
                if (monitor.isCanceled()) {
1422
                    return new Status(IStatus.OK, WorkbenchPlugin.PI_WORKBENCH,
1423
                            IStatus.OK, EMPTY_STRING, null);
1424
                }
1425

    
1426
                if (CdmFilteredItemsSelectionDialog.this != null) {
1427
                    CdmFilteredItemsSelectionDialog.this.refresh();
1428
                }
1429

    
1430
                return new Status(IStatus.OK, PlatformUI.PLUGIN_ID, IStatus.OK,
1431
                        EMPTY_STRING, null);
1432
            }
1433

    
1434
        }
1435

    
1436
        /**
1437
         * Refreshes the progress message cyclically with 500 milliseconds delay.
1438
         * <code>RefreshProgressMessageJob</code> is strictly connected with
1439
         * <code>GranualProgressMonitor</code> and use it to to get progress
1440
         * message and to decide about break of cyclical refresh.
1441
         */
1442
        private class RefreshProgressMessageJob extends UIJob {
1443

    
1444
            private GranualProgressMonitor progressMonitor;
1445

    
1446
            /**
1447
             * Creates a new instance of the class.
1448
             */
1449
            public RefreshProgressMessageJob() {
1450
                super(
1451
                        CdmFilteredItemsSelectionDialog.this.getParentShell()
1452
                                .getDisplay(),
1453
                        WorkbenchMessages.FilteredItemsSelectionDialog_progressRefreshJob);
1454
                setSystem(true);
1455
            }
1456

    
1457
            /*
1458
             * (non-Javadoc)
1459
             *
1460
             * @see org.eclipse.ui.progress.UIJob#runInUIThread(org.eclipse.core.runtime.IProgressMonitor)
1461
             */
1462
            @Override
1463
            public IStatus runInUIThread(IProgressMonitor monitor) {
1464

    
1465
                if (!progressLabel.isDisposed()) {
1466
                    progressLabel.setText(progressMonitor != null ? progressMonitor
1467
                            .getMessage() : EMPTY_STRING);
1468
                }
1469

    
1470
                if (progressMonitor == null || progressMonitor.isDone()) {
1471
                    return new Status(IStatus.CANCEL, PlatformUI.PLUGIN_ID,
1472
                            IStatus.CANCEL, EMPTY_STRING, null);
1473
                }
1474

    
1475
                // Schedule cyclical with 500 milliseconds delay
1476
                schedule(500);
1477

    
1478
                return new Status(IStatus.OK, PlatformUI.PLUGIN_ID, IStatus.OK,
1479
                        EMPTY_STRING, null);
1480
            }
1481

    
1482
            /**
1483
             * Schedule progress refresh job.
1484
             *
1485
             * @param progressMonitor
1486
             *            used during refresh progress label
1487
             */
1488
            public void scheduleProgressRefresh(
1489
                    GranualProgressMonitor progressMonitor) {
1490
                this.progressMonitor = progressMonitor;
1491
                // Schedule with initial delay to avoid flickering when the user
1492
                // types quickly
1493
                schedule(200);
1494
            }
1495

    
1496
        }
1497

    
1498
        /**
1499
         * A job responsible for computing filtered items list presented using
1500
         * <code>RefreshJob</code>.
1501
         *
1502
         * @see FilteredItemsSelectionDialog.RefreshJob
1503
         *
1504
         */
1505
        private class RefreshCacheJob extends Job {
1506

    
1507
            private final RefreshJob refreshJob = new RefreshJob();
1508

    
1509
            /**
1510
             * Creates a new instance of the class.
1511
             */
1512
            public RefreshCacheJob() {
1513
                super(
1514
                        WorkbenchMessages.FilteredItemsSelectionDialog_cacheRefreshJob);
1515
                setSystem(true);
1516
            }
1517

    
1518
            /**
1519
             * Stops the job and all sub-jobs.
1520
             */
1521
            public void cancelAll() {
1522
                cancel();
1523
                refreshJob.cancel();
1524
            }
1525

    
1526
            /*
1527
             * (non-Javadoc)
1528
             *
1529
             * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
1530
             */
1531
            @Override
1532
            protected IStatus run(IProgressMonitor monitor) {
1533
                if (monitor.isCanceled()) {
1534
                    return new Status(IStatus.CANCEL, WorkbenchPlugin.PI_WORKBENCH,
1535
                            IStatus.CANCEL, EMPTY_STRING, null);
1536
                }
1537

    
1538
                if (CdmFilteredItemsSelectionDialog.this != null) {
1539
                    GranualProgressMonitor wrappedMonitor = new GranualProgressMonitor(
1540
                            monitor);
1541
                    CdmFilteredItemsSelectionDialog.this.reloadCache(true,
1542
                            wrappedMonitor);
1543
                }
1544

    
1545
                if (!monitor.isCanceled()) {
1546
                    refreshJob.schedule();
1547
                }
1548

    
1549
                return new Status(IStatus.OK, PlatformUI.PLUGIN_ID, IStatus.OK,
1550
                        EMPTY_STRING, null);
1551

    
1552
            }
1553

    
1554
            /*
1555
             * (non-Javadoc)
1556
             *
1557
             * @see org.eclipse.core.runtime.jobs.Job#canceling()
1558
             */
1559
            @Override
1560
            protected void canceling() {
1561
                super.canceling();
1562
                contentProvider.stopReloadingCache();
1563
            }
1564

    
1565
        }
1566

    
1567
        private class RemoveHistoryItemAction extends Action {
1568

    
1569
            /**
1570
             * Creates a new instance of the class.
1571
             */
1572
            public RemoveHistoryItemAction() {
1573
                super(
1574
                        WorkbenchMessages.FilteredItemsSelectionDialog_removeItemsFromHistoryAction);
1575
            }
1576

    
1577
            /*
1578
             * (non-Javadoc)
1579
             *
1580
             * @see org.eclipse.jface.action.Action#run()
1581
             */
1582
            @Override
1583
            public void run() {
1584
                List selectedElements = ((StructuredSelection) list.getSelection())
1585
                        .toList();
1586
                removeSelectedItems(selectedElements);
1587
            }
1588
        }
1589

    
1590
        private static boolean showColoredLabels() {
1591
            return PlatformUI.getPreferenceStore().getBoolean(IWorkbenchPreferenceConstants.USE_COLORED_LABELS);
1592
        }
1593

    
1594
        private class ItemsListLabelProvider extends StyledCellLabelProvider
1595
                implements ILabelProviderListener {
1596
            private ILabelProvider provider;
1597

    
1598
            private ILabelDecorator selectionDecorator;
1599

    
1600
            // Need to keep our own list of listeners
1601
            private final ListenerList listeners = new ListenerList();
1602

    
1603
            /**
1604
             * Creates a new instance of the class.
1605
             *
1606
             * @param provider
1607
             *            the label provider for all items, not <code>null</code>
1608
             * @param selectionDecorator
1609
             *            the decorator for selected items, can be <code>null</code>
1610
             */
1611
            public ItemsListLabelProvider(ILabelProvider provider,
1612
                    ILabelDecorator selectionDecorator) {
1613
                Assert.isNotNull(provider);
1614
                this.provider = provider;
1615
                this.selectionDecorator = selectionDecorator;
1616

    
1617
                setOwnerDrawEnabled(showColoredLabels() && provider instanceof IStyledLabelProvider);
1618

    
1619
                provider.addListener(this);
1620

    
1621
                if (selectionDecorator != null) {
1622
                    selectionDecorator.addListener(this);
1623
                }
1624
            }
1625

    
1626
            /**
1627
             * Sets new selection decorator.
1628
             *
1629
             * @param newSelectionDecorator
1630
             *            new label decorator for selected items in the list
1631
             */
1632
            public void setSelectionDecorator(ILabelDecorator newSelectionDecorator) {
1633
                if (selectionDecorator != null) {
1634
                    selectionDecorator.removeListener(this);
1635
                    selectionDecorator.dispose();
1636
                }
1637

    
1638
                selectionDecorator = newSelectionDecorator;
1639

    
1640
                if (selectionDecorator != null) {
1641
                    selectionDecorator.addListener(this);
1642
                }
1643
            }
1644

    
1645
            /**
1646
             * Gets selection decorator.
1647
             *
1648
             * @return the label decorator for selected items in the list
1649
             */
1650
            public ILabelDecorator getSelectionDecorator() {
1651
                return selectionDecorator;
1652
            }
1653

    
1654
            /**
1655
             * Sets new label provider.
1656
             *
1657
             * @param newProvider
1658
             *            new label provider for items in the list, not
1659
             *            <code>null</code>
1660
             */
1661
            public void setProvider(ILabelProvider newProvider) {
1662
                Assert.isNotNull(newProvider);
1663
                provider.removeListener(this);
1664
                provider.dispose();
1665

    
1666
                provider = newProvider;
1667

    
1668
                if (provider != null) {
1669
                    provider.addListener(this);
1670
                }
1671

    
1672
                setOwnerDrawEnabled(showColoredLabels() && provider instanceof IStyledLabelProvider);
1673
            }
1674

    
1675
            private Image getImage(Object element) {
1676
                if (element instanceof ItemsListSeparator) {
1677
                    return WorkbenchImages
1678
                            .getImage(IWorkbenchGraphicConstants.IMG_OBJ_SEPARATOR);
1679
                }
1680

    
1681
                return provider.getImage(element);
1682
            }
1683

    
1684
            private boolean isSelected(Object element) {
1685
                if (element != null && currentSelection != null) {
1686
                    for (int i = 0; i < currentSelection.length; i++) {
1687
                        if (element.equals(currentSelection[i])) {
1688
                            return true;
1689
                        }
1690
                    }
1691
                }
1692
                return false;
1693
            }
1694

    
1695
            /*
1696
             * (non-Javadoc)
1697
             *
1698
             * @see org.eclipse.jface.viewers.ILabelProvider#getText(java.lang.Object)
1699
             */
1700
            private String getText(Object element) {
1701
                if (element instanceof ItemsListSeparator) {
1702
                    return getSeparatorLabel(((ItemsListSeparator) element)
1703
                            .getName());
1704
                }
1705

    
1706
                String str = provider.getText(element);
1707
                if (selectionDecorator != null && isSelected(element)) {
1708
                    return selectionDecorator.decorateText(str.toString(), element);
1709
                }
1710

    
1711
                return str;
1712
            }
1713

    
1714
            private StyledString getStyledText(Object element,
1715
                    IStyledLabelProvider provider) {
1716
                StyledString string = provider.getStyledText(element);
1717

    
1718
                if (selectionDecorator != null && isSelected(element)) {
1719
                    String decorated = selectionDecorator.decorateText(string
1720
                            .getString(), element);
1721
                    return StyledCellLabelProvider.styleDecoratedString(decorated, null, string);
1722
                    // no need to add colors when element is selected
1723
                }
1724
                return string;
1725
            }
1726

    
1727
            @Override
1728
            public void update(ViewerCell cell) {
1729
                Object element = cell.getElement();
1730

    
1731
                if (!(element instanceof ItemsListSeparator)
1732
                        && provider instanceof IStyledLabelProvider) {
1733
                    IStyledLabelProvider styledLabelProvider = (IStyledLabelProvider) provider;
1734
                    StyledString styledString = getStyledText(element,
1735
                            styledLabelProvider);
1736

    
1737
                    cell.setText(styledString.getString());
1738
                    cell.setStyleRanges(styledString.getStyleRanges());
1739
                    cell.setImage(styledLabelProvider.getImage(element));
1740
                } else {
1741
                    cell.setText(getText(element));
1742
                    cell.setImage(getImage(element));
1743
                }
1744
                cell.setFont(getFont(element));
1745
                cell.setForeground(getForeground(element));
1746
                cell.setBackground(getBackground(element));
1747

    
1748
                super.update(cell);
1749
            }
1750

    
1751
            private String getSeparatorLabel(String separatorLabel) {
1752
                Rectangle rect = list.getTable().getBounds();
1753

    
1754
                int borderWidth = list.getTable().computeTrim(0, 0, 0, 0).width;
1755

    
1756
                int imageWidth = WorkbenchImages.getImage(
1757
                        IWorkbenchGraphicConstants.IMG_OBJ_SEPARATOR).getBounds().width;
1758

    
1759
                int width = rect.width - borderWidth - imageWidth;
1760

    
1761
                GC gc = new GC(list.getTable());
1762
                gc.setFont(list.getTable().getFont());
1763

    
1764
                int fSeparatorWidth = gc.getAdvanceWidth('-');
1765
                int fMessageLength = gc.textExtent(separatorLabel).x;
1766

    
1767
                gc.dispose();
1768

    
1769
                StringBuffer dashes = new StringBuffer();
1770
                int chars = (((width - fMessageLength) / fSeparatorWidth) / 2) - 2;
1771
                for (int i = 0; i < chars; i++) {
1772
                    dashes.append('-');
1773
                }
1774

    
1775
                StringBuffer result = new StringBuffer();
1776
                result.append(dashes);
1777
                result.append(" " + separatorLabel + " "); //$NON-NLS-1$//$NON-NLS-2$
1778
                result.append(dashes);
1779
                return result.toString().trim();
1780
            }
1781

    
1782
            /*
1783
             * (non-Javadoc)
1784
             *
1785
             * @see org.eclipse.jface.viewers.IBaseLabelProvider#addListener(org.eclipse.jface.viewers.ILabelProviderListener)
1786
             */
1787
            @Override
1788
            public void addListener(ILabelProviderListener listener) {
1789
                listeners.add(listener);
1790
            }
1791

    
1792
            /*
1793
             * (non-Javadoc)
1794
             *
1795
             * @see org.eclipse.jface.viewers.IBaseLabelProvider#dispose()
1796
             */
1797
            @Override
1798
            public void dispose() {
1799
                provider.removeListener(this);
1800
                provider.dispose();
1801

    
1802
                if (selectionDecorator != null) {
1803
                    selectionDecorator.removeListener(this);
1804
                    selectionDecorator.dispose();
1805
                }
1806

    
1807
                super.dispose();
1808
            }
1809

    
1810
            /*
1811
             * (non-Javadoc)
1812
             *
1813
             * @see org.eclipse.jface.viewers.IBaseLabelProvider#isLabelProperty(java.lang.Object,
1814
             *      java.lang.String)
1815
             */
1816
            @Override
1817
            public boolean isLabelProperty(Object element, String property) {
1818
                if (provider.isLabelProperty(element, property)) {
1819
                    return true;
1820
                }
1821
                if (selectionDecorator != null
1822
                        && selectionDecorator.isLabelProperty(element, property)) {
1823
                    return true;
1824
                }
1825
                return false;
1826
            }
1827

    
1828
            /*
1829
             * (non-Javadoc)
1830
             *
1831
             * @see org.eclipse.jface.viewers.IBaseLabelProvider#removeListener(org.eclipse.jface.viewers.ILabelProviderListener)
1832
             */
1833
            @Override
1834
            public void removeListener(ILabelProviderListener listener) {
1835
                listeners.remove(listener);
1836
            }
1837

    
1838
            private Color getBackground(Object element) {
1839
                if (element instanceof ItemsListSeparator) {
1840
                    return null;
1841
                }
1842
                if (provider instanceof IColorProvider) {
1843
                    return ((IColorProvider) provider).getBackground(element);
1844
                }
1845
                return null;
1846
            }
1847

    
1848
            private Color getForeground(Object element) {
1849
                if (element instanceof ItemsListSeparator) {
1850
                    return Display.getCurrent().getSystemColor(
1851
                            SWT.COLOR_WIDGET_NORMAL_SHADOW);
1852
                }
1853
                if (provider instanceof IColorProvider) {
1854
                    return ((IColorProvider) provider).getForeground(element);
1855
                }
1856
                return null;
1857
            }
1858

    
1859
            private Font getFont(Object element) {
1860
                if (element instanceof ItemsListSeparator) {
1861
                    return null;
1862
                }
1863
                if (provider instanceof IFontProvider) {
1864
                    return ((IFontProvider) provider).getFont(element);
1865
                }
1866
                return null;
1867
            }
1868

    
1869
            /*
1870
             * (non-Javadoc)
1871
             *
1872
             * @see org.eclipse.jface.viewers.ILabelProviderListener#labelProviderChanged(org.eclipse.jface.viewers.LabelProviderChangedEvent)
1873
             */
1874
            @Override
1875
            public void labelProviderChanged(LabelProviderChangedEvent event) {
1876
                Object[] l = listeners.getListeners();
1877
                for (int i = 0; i < listeners.size(); i++) {
1878
                    ((ILabelProviderListener) l[i]).labelProviderChanged(event);
1879
                }
1880
            }
1881
        }
1882

    
1883
        /**
1884
         * Used in ItemsListContentProvider, separates history and non-history
1885
         * items.
1886
         */
1887
        private class ItemsListSeparator {
1888

    
1889
            private final String name;
1890

    
1891
            /**
1892
             * Creates a new instance of the class.
1893
             *
1894
             * @param name
1895
             *            the name of the separator
1896
             */
1897
            public ItemsListSeparator(String name) {
1898
                this.name = name;
1899
            }
1900

    
1901
            /**
1902
             * Returns the name of this separator.
1903
             *
1904
             * @return the name of the separator
1905
             */
1906
            public String getName() {
1907
                return name;
1908
            }
1909
        }
1910

    
1911
        /**
1912
         * GranualProgressMonitor is used for monitoring progress of filtering
1913
         * process. It is used by <code>RefreshProgressMessageJob</code> to
1914
         * refresh progress message. State of this monitor illustrates state of
1915
         * filtering or cache refreshing process.
1916
         *
1917
         */
1918
        private class GranualProgressMonitor extends ProgressMonitorWrapper {
1919

    
1920
            private String name;
1921

    
1922
            private String subName;
1923

    
1924
            private int totalWork;
1925

    
1926
            private double worked;
1927

    
1928
            private boolean done;
1929

    
1930
            /**
1931
             * Creates instance of <code>GranualProgressMonitor</code>.
1932
             *
1933
             * @param monitor
1934
             *            progress to be wrapped
1935
             */
1936
            public GranualProgressMonitor(IProgressMonitor monitor) {
1937
                super(monitor);
1938
            }
1939

    
1940
            /**
1941
             * Checks if filtering has been done
1942
             *
1943
             * @return true if filtering work has been done false in other way
1944
             */
1945
            public boolean isDone() {
1946
                return done;
1947
            }
1948

    
1949
            /*
1950
             * (non-Javadoc)
1951
             *
1952
             * @see org.eclipse.core.runtime.ProgressMonitorWrapper#setTaskName(java.lang.String)
1953
             */
1954
            @Override
1955
            public void setTaskName(String name) {
1956
                super.setTaskName(name);
1957
                this.name = name;
1958
                this.subName = null;
1959
            }
1960

    
1961
            /*
1962
             * (non-Javadoc)
1963
             *
1964
             * @see org.eclipse.core.runtime.ProgressMonitorWrapper#subTask(java.lang.String)
1965
             */
1966
            @Override
1967
            public void subTask(String name) {
1968
                super.subTask(name);
1969
                this.subName = name;
1970
            }
1971

    
1972
            /*
1973
             * (non-Javadoc)
1974
             *
1975
             * @see org.eclipse.core.runtime.ProgressMonitorWrapper#beginTask(java.lang.String,
1976
             *      int)
1977
             */
1978
            @Override
1979
            public void beginTask(String name, int totalWork) {
1980
                super.beginTask(name, totalWork);
1981
                if (this.name == null) {
1982
                    this.name = name;
1983
                }
1984
                this.totalWork = totalWork;
1985
                refreshProgressMessageJob.scheduleProgressRefresh(this);
1986
            }
1987

    
1988
            /*
1989
             * (non-Javadoc)
1990
             *
1991
             * @see org.eclipse.core.runtime.ProgressMonitorWrapper#worked(int)
1992
             */
1993
            @Override
1994
            public void worked(int work) {
1995
                super.worked(work);
1996
                internalWorked(work);
1997
            }
1998

    
1999
            /*
2000
             * (non-Javadoc)
2001
             *
2002
             * @see org.eclipse.core.runtime.ProgressMonitorWrapper#done()
2003
             */
2004
            @Override
2005
            public void done() {
2006
                done = true;
2007
                super.done();
2008
            }
2009

    
2010
            /*
2011
             * (non-Javadoc)
2012
             *
2013
             * @see org.eclipse.core.runtime.ProgressMonitorWrapper#setCanceled(boolean)
2014
             */
2015
            @Override
2016
            public void setCanceled(boolean b) {
2017
                done = b;
2018
                super.setCanceled(b);
2019
            }
2020

    
2021
            /*
2022
             * (non-Javadoc)
2023
             *
2024
             * @see org.eclipse.core.runtime.ProgressMonitorWrapper#internalWorked(double)
2025
             */
2026
            @Override
2027
            public void internalWorked(double work) {
2028
                worked = worked + work;
2029
            }
2030

    
2031
            private String getMessage() {
2032
                if (done) {
2033
                    return ""; //$NON-NLS-1$
2034
                }
2035

    
2036
                String message;
2037

    
2038
                if (name == null) {
2039
                    message = subName == null ? "" : subName; //$NON-NLS-1$
2040
                } else {
2041
                    message = subName == null ? name
2042
                            : NLS
2043
                                    .bind(
2044
                                            WorkbenchMessages.FilteredItemsSelectionDialog_subtaskProgressMessage,
2045
                                            new Object[] { name, subName });
2046
                }
2047
                if (totalWork == 0) {
2048
                    return message;
2049
                }
2050

    
2051
                return NLS
2052
                        .bind(
2053
                                WorkbenchMessages.FilteredItemsSelectionDialog_taskProgressMessage,
2054
                                new Object[] {
2055
                                        message,
2056
                                        new Integer(
2057
                                                (int) ((worked * 100) / totalWork)) });
2058

    
2059
            }
2060

    
2061
        }
2062

    
2063
        /**
2064
         * Filters items history and schedule filter job.
2065
         */
2066
        private class FilterHistoryJob extends Job {
2067

    
2068
            /**
2069
             * Filter used during the filtering process.
2070
             */
2071
            private ItemsFilter itemsFilter;
2072

    
2073
            /**
2074
             * Creates new instance of receiver.
2075
             */
2076
            public FilterHistoryJob() {
2077
                super(WorkbenchMessages.FilteredItemsSelectionDialog_jobLabel);
2078
                setSystem(true);
2079
            }
2080

    
2081
            /*
2082
             * (non-Javadoc)
2083
             *
2084
             * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
2085
             */
2086
            @Override
2087
            protected IStatus run(IProgressMonitor monitor) {
2088

    
2089
                this.itemsFilter = filter;
2090

    
2091
                contentProvider.reset();
2092

    
2093
                refreshWithLastSelection = false;
2094

    
2095
                contentProvider.addHistoryItems(itemsFilter);
2096

    
2097
                if (!(lastCompletedFilter != null && lastCompletedFilter
2098
                        .isSubFilter(this.itemsFilter))) {
2099
                    contentProvider.refresh();
2100
                }
2101

    
2102
                filterJob.schedule();
2103

    
2104
                return Status.OK_STATUS;
2105
            }
2106

    
2107
        }
2108

    
2109
        /**
2110
         * Filters items in indicated set and history. During filtering, it
2111
         * refreshes the dialog (progress monitor and elements list).
2112
         *
2113
         * Depending on the filter, <code>FilterJob</code> decides which kind of
2114
         * search will be run inside <code>filterContent</code>. If the last
2115
         * filtering is done (last completed filter), is not null, and the new
2116
         * filter is a sub-filter ({@link FilteredItemsSelectionDialog.ItemsFilter#isSubFilter(FilteredItemsSelectionDialog.ItemsFilter)})
2117
         * of the last, then <code>FilterJob</code> only filters in the cache. If
2118
         * it is the first filtering or the new filter isn't a sub-filter of the
2119
         * last one, a full search is run.
2120
         */
2121
        private class FilterJob extends Job {
2122

    
2123
            /**
2124
             * Filter used during the filtering process.
2125
             */
2126
            protected ItemsFilter itemsFilter;
2127

    
2128
            /**
2129
             * Creates new instance of FilterJob
2130
             */
2131
            public FilterJob() {
2132
                super(WorkbenchMessages.FilteredItemsSelectionDialog_jobLabel);
2133
                setSystem(true);
2134
            }
2135

    
2136
            /*
2137
             * (non-Javadoc)
2138
             *
2139
             * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
2140
             */
2141
            @Override
2142
            protected final IStatus run(IProgressMonitor parent) {
2143
                GranualProgressMonitor monitor = new GranualProgressMonitor(parent);
2144
                return doRun(monitor);
2145
            }
2146

    
2147
            /**
2148
             * Executes job using the given filtering progress monitor. A hook for
2149
             * subclasses.
2150
             *
2151
             * @param monitor
2152
             *            progress monitor
2153
             * @return result of the execution
2154
             */
2155
            protected IStatus doRun(GranualProgressMonitor monitor) {
2156
                try {
2157
                    internalRun(monitor);
2158
                } catch (CoreException e) {
2159
                    cancel();
2160
                    return new Status(
2161
                            IStatus.ERROR,
2162
                            PlatformUI.PLUGIN_ID,
2163
                            IStatus.ERROR,
2164
                            WorkbenchMessages.FilteredItemsSelectionDialog_jobError,
2165
                            e);
2166
                }
2167
                return Status.OK_STATUS;
2168
            }
2169

    
2170
            /**
2171
             * Main method for the job.
2172
             *
2173
             * @param monitor
2174
             * @throws CoreException
2175
             */
2176
            private void internalRun(GranualProgressMonitor monitor)
2177
                    throws CoreException {
2178
                try {
2179
                    if (monitor.isCanceled()) {
2180
                        return;
2181
                    }
2182

    
2183
                    this.itemsFilter = filter;
2184

    
2185
                    if (filter.getPattern().length() != 0) {
2186
                        filterContent(monitor);
2187
                    }
2188

    
2189
                    if (monitor.isCanceled()) {
2190
                        return;
2191
                    }
2192

    
2193
                    contentProvider.refresh();
2194
                } finally {
2195
                    monitor.done();
2196
                }
2197
            }
2198

    
2199
            /**
2200
             * Filters items.
2201
             *
2202
             * @param monitor
2203
             *            for monitoring progress
2204
             * @throws CoreException
2205
             */
2206
            protected void filterContent(GranualProgressMonitor monitor)
2207
                    throws CoreException {
2208

    
2209
//                if (lastCompletedFilter != null
2210
//                        && lastCompletedFilter.isSubFilter(this.itemsFilter)) {
2211
//
2212
//                    int length = lastCompletedResult.size() / 500;
2213
//                    monitor
2214
//                            .beginTask(
2215
//                                    WorkbenchMessages.FilteredItemsSelectionDialog_cacheSearchJob_taskName,
2216
//                                    length);
2217
//
2218
//                    for (int pos = 0; pos < lastCompletedResult.size(); pos++) {
2219
//
2220
//                        Object item = lastCompletedResult.get(pos);
2221
//                        if (monitor.isCanceled()) {
2222
//                            break;
2223
//                        }
2224
//                        contentProvider.add(item, itemsFilter);
2225
//
2226
//                        if ((pos % 500) == 0) {
2227
//                            monitor.worked(1);
2228
//                        }
2229
//                    }
2230
//
2231
//                } else {
2232

    
2233
                    lastCompletedFilter = null;
2234
                    lastCompletedResult = null;
2235

    
2236
                    SubProgressMonitor subMonitor = null;
2237
                    if (monitor != null) {
2238
                        monitor
2239
                                .beginTask(
2240
                                        WorkbenchMessages.FilteredItemsSelectionDialog_searchJob_taskName,
2241
                                        100);
2242
                        subMonitor = new SubProgressMonitor(monitor, 95);
2243

    
2244
                    }
2245

    
2246
                    fillContentProvider(contentProvider, itemsFilter, subMonitor);
2247

    
2248
                    if (monitor != null && !monitor.isCanceled()) {
2249
                        monitor.worked(2);
2250
                        contentProvider.rememberResult(itemsFilter);
2251
                        monitor.worked(3);
2252
                    }
2253
                //}
2254

    
2255
            }
2256

    
2257
        }
2258

    
2259
        /**
2260
         * History stores a list of key, object pairs. The list is bounded at a
2261
         * certain size. If the list exceeds this size the oldest element is removed
2262
         * from the list. An element can be added/renewed with a call to
2263
         * <code>accessed(Object)</code>.
2264
         * <p>
2265
         * The history can be stored to/loaded from an XML file.
2266
         */
2267
        protected static abstract class SelectionHistory {
2268

    
2269
            private static final String DEFAULT_ROOT_NODE_NAME = "historyRootNode"; //$NON-NLS-1$
2270

    
2271
            private static final String DEFAULT_INFO_NODE_NAME = "infoNode"; //$NON-NLS-1$
2272

    
2273
            private static final int MAX_HISTORY_SIZE = 60;
2274

    
2275
            private final Set historyList;
2276

    
2277
            private final String rootNodeName;
2278

    
2279
            private final String infoNodeName;
2280

    
2281
            private SelectionHistory(String rootNodeName, String infoNodeName) {
2282

    
2283
                historyList = Collections.synchronizedSet(new LinkedHashSet() {
2284

    
2285
                    private static final long serialVersionUID = 0L;
2286

    
2287
                    /*
2288
                     * (non-Javadoc)
2289
                     *
2290
                     * @see java.util.LinkedList#add(java.lang.Object)
2291
                     */
2292
                    @Override
2293
                    public boolean add(Object arg0) {
2294
                        if (this.size() >= MAX_HISTORY_SIZE) {
2295
                            Iterator iterator = this.iterator();
2296
                            iterator.next();
2297
                            iterator.remove();
2298
                        }
2299
                        return super.add(arg0);
2300
                    }
2301

    
2302
                });
2303

    
2304
                this.rootNodeName = rootNodeName;
2305
                this.infoNodeName = infoNodeName;
2306
            }
2307

    
2308
            /**
2309
             * Creates new instance of <code>SelectionHistory</code>.
2310
             */
2311
            public SelectionHistory() {
2312
                this(DEFAULT_ROOT_NODE_NAME, DEFAULT_INFO_NODE_NAME);
2313
            }
2314

    
2315
            /**
2316
             * Adds object to history.
2317
             *
2318
             * @param object
2319
             *            the item to be added to the history
2320
             */
2321
            public synchronized void accessed(Object object) {
2322
                historyList.remove(object);
2323
                historyList.add(object);
2324
            }
2325

    
2326
            /**
2327
             * Returns <code>true</code> if history contains object.
2328
             *
2329
             * @param object
2330
             *            the item for which check will be executed
2331
             * @return <code>true</code> if history contains object
2332
             *         <code>false</code> in other way
2333
             */
2334
            public synchronized boolean contains(Object object) {
2335
                return historyList.contains(object);
2336
            }
2337

    
2338
            /**
2339
             * Returns <code>true</code> if history is empty.
2340
             *
2341
             * @return <code>true</code> if history is empty
2342
             */
2343
            public synchronized boolean isEmpty() {
2344
                return historyList.isEmpty();
2345
            }
2346

    
2347
            /**
2348
             * Remove element from history.
2349
             *
2350
             * @param element
2351
             *            to remove form the history
2352
             * @return <code>true</code> if this list contained the specified
2353
             *         element
2354
             */
2355
            public synchronized boolean remove(Object element) {
2356
                return historyList.remove(element);
2357
            }
2358

    
2359
            /**
2360
             * Load history elements from memento.
2361
             *
2362
             * @param memento
2363
             *            memento from which the history will be retrieved
2364
             */
2365
            public void load(IMemento memento) {
2366

    
2367
                XMLMemento historyMemento = (XMLMemento) memento
2368
                        .getChild(rootNodeName);
2369

    
2370
                if (historyMemento == null) {
2371
                    return;
2372
                }
2373

    
2374
                IMemento[] mementoElements = historyMemento
2375
                        .getChildren(infoNodeName);
2376
                for (int i = 0; i < mementoElements.length; ++i) {
2377
                    IMemento mementoElement = mementoElements[i];
2378
                    Object object = restoreItemFromMemento(mementoElement);
2379
                    if (object != null) {
2380
                        historyList.add(object);
2381
                    }
2382
                }
2383
            }
2384

    
2385
            /**
2386
             * Save history elements to memento.
2387
             *
2388
             * @param memento
2389
             *            memento to which the history will be added
2390
             */
2391
            public void save(IMemento memento) {
2392

    
2393
                IMemento historyMemento = memento.createChild(rootNodeName);
2394

    
2395
                Object[] items = getHistoryItems();
2396
                for (int i = 0; i < items.length; i++) {
2397
                    Object item = items[i];
2398
                    IMemento elementMemento = historyMemento
2399
                            .createChild(infoNodeName);
2400
                    storeItemToMemento(item, elementMemento);
2401
                }
2402

    
2403
            }
2404

    
2405
            /**
2406
             * Gets array of history items.
2407
             *
2408
             * @return array of history elements
2409
             */
2410
            public synchronized Object[] getHistoryItems() {
2411
                return historyList.toArray();
2412
            }
2413

    
2414
            /**
2415
             * Creates an object using given memento.
2416
             *
2417
             * @param memento
2418
             *            memento used for creating new object
2419
             *
2420
             * @return the restored object
2421
             */
2422
            protected abstract Object restoreItemFromMemento(IMemento memento);
2423

    
2424
            /**
2425
             * Store object in <code>IMemento</code>.
2426
             *
2427
             * @param item
2428
             *            the item to store
2429
             * @param memento
2430
             *            the memento to store to
2431
             */
2432
            protected abstract void storeItemToMemento(Object item, IMemento memento);
2433

    
2434
        }
2435

    
2436
        /**
2437
         * Filters elements using SearchPattern by comparing the names of items with
2438
         * the filter pattern.
2439
         */
2440
        protected abstract class ItemsFilter {
2441

    
2442
            protected SearchPattern patternMatcher;
2443

    
2444
            /**
2445
             * Creates new instance of ItemsFilter.
2446
             */
2447
            public ItemsFilter() {
2448
                this(new SearchPattern());
2449
            }
2450

    
2451
            /**
2452
             * Creates new instance of ItemsFilter.
2453
             *
2454
             * @param searchPattern
2455
             *            the pattern to be used when filtering
2456
             */
2457
            public ItemsFilter(SearchPattern searchPattern) {
2458
                patternMatcher = searchPattern;
2459
                String stringPattern = ""; //$NON-NLS-1$
2460
                if (pattern != null && !pattern.getText().equals("*")) { //$NON-NLS-1$
2461
                    stringPattern = pattern.getText();
2462
                }
2463
                patternMatcher.setPattern(stringPattern);
2464
            }
2465

    
2466
            /**
2467
             * Check if the given filter is a sub-filter of this filter. The default
2468
             * implementation checks if the <code>SearchPattern</code> from the
2469
             * given filter is a sub-pattern of the one from this filter.
2470
             * <p>
2471
             * <i>WARNING: This method is <b>not</b> defined in reading order, i.e.
2472
             * <code>a.isSubFilter(b)</code> is <code>true</code> iff
2473
             * <code>b</code> is a sub-filter of <code>a</code>, and not
2474
             * vice-versa. </i>
2475
             * </p>
2476
             *
2477
             * @param filter
2478
             *            the filter to be checked, or <code>null</code>
2479
             * @return <code>true</code> if the given filter is sub-filter of this
2480
             *         filter, <code>false</code> if the given filter isn't a
2481
             *         sub-filter or is <code>null</code>
2482
             *
2483
             * @see org.eclipse.ui.dialogs.SearchPattern#isSubPattern(org.eclipse.ui.dialogs.SearchPattern)
2484
             */
2485
            public boolean isSubFilter(ItemsFilter filter) {
2486
                if (filter != null) {
2487
                    return this.patternMatcher.isSubPattern(filter.patternMatcher);
2488
                }
2489
                return false;
2490
            }
2491

    
2492
            /**
2493
             * Checks whether the provided filter is equal to the current filter.
2494
             * The default implementation checks if <code>SearchPattern</code>
2495
             * from current filter is equal to the one from provided filter.
2496
             *
2497
             * @param filter
2498
             *            filter to be checked, or <code>null</code>
2499
             * @return <code>true</code> if the given filter is equal to current
2500
             *         filter, <code>false</code> if given filter isn't equal to
2501
             *         current one or if it is <code>null</code>
2502
             *
2503
             * @see org.eclipse.ui.dialogs.SearchPattern#equalsPattern(org.eclipse.ui.dialogs.SearchPattern)
2504
             */
2505
            public boolean equalsFilter(ItemsFilter filter) {
2506
                if (filter != null
2507
                        && filter.patternMatcher.equalsPattern(this.patternMatcher)) {
2508
                    return true;
2509
                }
2510
                return false;
2511
            }
2512

    
2513
            /**
2514
             * Checks whether the pattern's match rule is camel case.
2515
             *
2516
             * @return <code>true</code> if pattern's match rule is camel case,
2517
             *         <code>false</code> otherwise
2518
             */
2519
            public boolean isCamelCasePattern() {
2520
                return patternMatcher.getMatchRule() == SearchPattern.RULE_CAMELCASE_MATCH;
2521
            }
2522

    
2523
            /**
2524
             * Returns the pattern string.
2525
             *
2526
             * @return pattern for this filter
2527
             *
2528
             * @see SearchPattern#getPattern()
2529
             */
2530
            public String getPattern() {
2531
                return patternMatcher.getPattern();
2532
            }
2533

    
2534
            /**
2535
             * Returns the rule to apply for matching keys.
2536
             *
2537
             * @return an implementation-specific match rule
2538
             *
2539
             * @see SearchPattern#getMatchRule() for match rules returned by the
2540
             *      default implementation
2541
             */
2542
            public int getMatchRule() {
2543
                return patternMatcher.getMatchRule();
2544
            }
2545

    
2546
            /**
2547
             * Matches text with filter.
2548
             *
2549
             * @param text
2550
             *            the text to match with the filter
2551
             * @return <code>true</code> if text matches with filter pattern,
2552
             *         <code>false</code> otherwise
2553
             */
2554
            protected boolean matches(String text) {
2555
                return patternMatcher.matches(text);
2556
            }
2557

    
2558
            /**
2559
             * General method for matching raw name pattern. Checks whether current
2560
             * pattern is prefix of name provided item.
2561
             *
2562
             * @param item
2563
             *            item to check
2564
             * @return <code>true</code> if current pattern is a prefix of name
2565
             *         provided item, <code>false</code> if item's name is shorter
2566
             *         than prefix or sequences of characters don't match.
2567
             */
2568
            public boolean matchesRawNamePattern(Object item) {
2569
                String prefix = patternMatcher.getPattern();
2570
                String text = getElementName(item);
2571

    
2572
                if (text == null) {
2573
                    return false;
2574
                }
2575

    
2576
                int textLength = text.length();
2577
                int prefixLength = prefix.length();
2578
                if (textLength < prefixLength) {
2579
                    return false;
2580
                }
2581
                for (int i = prefixLength - 1; i >= 0; i--) {
2582
                    if (Character.toLowerCase(prefix.charAt(i)) != Character
2583
                            .toLowerCase(text.charAt(i))) {
2584
                        return false;
2585
                    }
2586
                }
2587
                return true;
2588
            }
2589

    
2590
            /**
2591
             * Matches an item against filter conditions.
2592
             *
2593
             * @param item
2594
             * @return <code>true<code> if item matches against filter conditions, <code>false</code>
2595
             *         otherwise
2596
             */
2597
            public abstract boolean matchItem(Object item);
2598

    
2599
            /**
2600
             * Checks consistency of an item. Item is inconsistent if was changed or
2601
             * removed.
2602
             *
2603
             * @param item
2604
             * @return <code>true</code> if item is consistent, <code>false</code>
2605
             *         if item is inconsistent
2606
             */
2607
            public abstract boolean isConsistentItem(Object item);
2608

    
2609
        }
2610

    
2611
        /**
2612
         * An interface to content providers for
2613
         * <code>FilterItemsSelectionDialog</code>.
2614
         */
2615
        protected abstract class AbstractContentProvider {
2616
            /**
2617
             * Adds the item to the content provider iff the filter matches the
2618
             * item. Otherwise does nothing.
2619
             *
2620
             * @param item
2621
             *            the item to add
2622
             * @param itemsFilter
2623
             *            the filter
2624
             *
2625
             * @see FilteredItemsSelectionDialog.ItemsFilter#matchItem(Object)
2626
             */
2627
            public abstract void add(Object item, ItemsFilter itemsFilter);
2628
        }
2629

    
2630
        /**
2631
         * Collects filtered elements. Contains one synchronized, sorted set for
2632
         * collecting filtered elements. All collected elements are sorted using
2633
         * comparator. Comparator is returned by getElementComparator() method.
2634
         * Implementation of <code>ItemsFilter</code> is used to filter elements.
2635
         * The key function of filter used in to filtering is
2636
         * <code>matchElement(Object item)</code>.
2637
         * <p>
2638
         * The <code>ContentProvider</code> class also provides item filtering
2639
         * methods. The filtering has been moved from the standard TableView
2640
         * <code>getFilteredItems()</code> method to content provider, because
2641
         * <code>ILazyContentProvider</code> and virtual tables are used. This
2642
         * class is responsible for adding a separator below history items and
2643
         * marking each items as duplicate if its name repeats more than once on the
2644
         * filtered list.
2645
         */
2646
        private class ContentProvider extends AbstractContentProvider implements
2647
                IStructuredContentProvider, ILazyContentProvider {
2648

    
2649
            private SelectionHistory selectionHistory;
2650

    
2651
            /**
2652
             * Raw result of the searching (unsorted, unfiltered).
2653
             * <p>
2654
             * Standard object flow:
2655
             * <code>items -> lastSortedItems -> lastFilteredItems</code>
2656
             */
2657
            private final Set items;
2658

    
2659
            /**
2660
             * Items that are duplicates.
2661
             */
2662
            private final Set duplicates;
2663

    
2664
            /**
2665
             * List of <code>ViewerFilter</code>s to be used during filtering
2666
             */
2667
            private List filters;
2668

    
2669
            /**
2670
             * Result of the last filtering.
2671
             * <p>
2672
             * Standard object flow:
2673
             * <code>items -> lastSortedItems -> lastFilteredItems</code>
2674
             */
2675
            private List lastFilteredItems;
2676

    
2677
            /**
2678
             * Result of the last sorting.
2679
             * <p>
2680
             * Standard object flow:
2681
             * <code>items -> lastSortedItems -> lastFilteredItems</code>
2682
             */
2683
            private final List lastSortedItems;
2684

    
2685
            /**
2686
             * Used for <code>getFilteredItems()</code> method canceling (when the
2687
             * job that invoked the method was canceled).
2688
             * <p>
2689
             * Method canceling could be based (only) on monitor canceling
2690
             * unfortunately sometimes the method <code>getFilteredElements()</code>
2691
             * could be run with a null monitor, the <code>reset</code> flag have
2692
             * to be left intact.
2693
             */
2694
            private boolean reset;
2695

    
2696
            /**
2697
             * Creates new instance of <code>ContentProvider</code>.
2698
             */
2699
            public ContentProvider() {
2700
                this.items = Collections.synchronizedSet(new HashSet(2048));
2701
                this.duplicates = Collections.synchronizedSet(new HashSet(256));
2702
                this.lastFilteredItems = new ArrayList();
2703
                this.lastSortedItems = Collections.synchronizedList(new ArrayList(
2704
                        2048));
2705
            }
2706

    
2707
            /**
2708
             * Sets selection history.
2709
             *
2710
             * @param selectionHistory
2711
             *            The selectionHistory to set.
2712
             */
2713
            public void setSelectionHistory(SelectionHistory selectionHistory) {
2714
                this.selectionHistory = selectionHistory;
2715
            }
2716

    
2717
            /**
2718
             * @return Returns the selectionHistory.
2719
             */
2720
            public SelectionHistory getSelectionHistory() {
2721
                return selectionHistory;
2722
            }
2723

    
2724
            /**
2725
             * Removes all content items and resets progress message.
2726
             */
2727
            public void reset() {
2728
                reset = true;
2729
                this.items.clear();
2730
                this.duplicates.clear();
2731
                this.lastSortedItems.clear();
2732
            }
2733

    
2734
            /**
2735
             * Stops reloading cache - <code>getFilteredItems()</code> method.
2736
             */
2737
            public void stopReloadingCache() {
2738
                reset = true;
2739
            }
2740

    
2741
            /**
2742
             * Adds filtered item.
2743
             *
2744
             * @param item
2745
             * @param itemsFilter
2746
             */
2747
            @Override
2748
            public void add(Object item, ItemsFilter itemsFilter) {
2749
                if (itemsFilter == filter) {
2750
                    if (itemsFilter != null) {
2751
                        if (itemsFilter.matchItem(item)) {
2752
                            this.items.add(item);
2753
                        }
2754
                    } else {
2755
                        this.items.add(item);
2756
                    }
2757
                }
2758
            }
2759

    
2760
            /**
2761
             * Add all history items to <code>contentProvider</code>.
2762
             *
2763
             * @param itemsFilter
2764
             */
2765
            public void addHistoryItems(ItemsFilter itemsFilter) {
2766
                if (this.selectionHistory != null) {
2767
                    Object[] items = this.selectionHistory.getHistoryItems();
2768
                    for (int i = 0; i < items.length; i++) {
2769
                        Object item = items[i];
2770
                        if (itemsFilter == filter) {
2771
                            if (itemsFilter != null) {
2772
                                if (itemsFilter.matchItem(item)) {
2773
                                    if (itemsFilter.isConsistentItem(item)) {
2774
                                        this.items.add(item);
2775
                                    } else {
2776
                                        this.selectionHistory.remove(item);
2777
                                    }
2778
                                }
2779
                            }
2780
                        }
2781
                    }
2782
                }
2783
            }
2784

    
2785
            /**
2786
             * Refresh dialog.
2787
             */
2788
            public void refresh() {
2789
                scheduleRefresh();
2790
            }
2791

    
2792
            /**
2793
             * Removes items from history and refreshes the view.
2794
             *
2795
             * @param item
2796
             *            to remove
2797
             *
2798
             * @return removed item
2799
             */
2800
            public Object removeHistoryElement(Object item) {
2801
                if (this.selectionHistory != null) {
2802
                    this.selectionHistory.remove(item);
2803
                }
2804
                if (filter == null || filter.getPattern().length() == 0) {
2805
                    items.remove(item);
2806
                    duplicates.remove(item);
2807
                    this.lastSortedItems.remove(item);
2808
                }
2809

    
2810
                synchronized (lastSortedItems) {
2811
                    Collections.sort(lastSortedItems, getHistoryComparator());
2812
                }
2813
                return item;
2814
            }
2815

    
2816
            /**
2817
             * Adds item to history and refresh view.
2818
             *
2819
             * @param item
2820
             *            to add
2821
             */
2822
            public void addHistoryElement(Object item) {
2823
                if (this.selectionHistory != null) {
2824
                    this.selectionHistory.accessed(item);
2825
                }
2826
                if (filter == null || !filter.matchItem(item)) {
2827
                    this.items.remove(item);
2828
                    this.duplicates.remove(item);
2829
                    this.lastSortedItems.remove(item);
2830
                }
2831
                synchronized (lastSortedItems) {
2832
                    Collections.sort(lastSortedItems, getHistoryComparator());
2833
                }
2834
                this.refresh();
2835
            }
2836

    
2837
            /**
2838
             * @param item
2839
             * @return <code>true</code> if given item is part of the history
2840
             */
2841
            public boolean isHistoryElement(Object item) {
2842
                if (this.selectionHistory != null) {
2843
                    return this.selectionHistory.contains(item);
2844
                }
2845
                return false;
2846
            }
2847

    
2848
            /**
2849
             * Sets/unsets given item as duplicate.
2850
             *
2851
             * @param item
2852
             *            item to change
2853
             *
2854
             * @param isDuplicate
2855
             *            duplicate flag
2856
             */
2857
            public void setDuplicateElement(Object item, boolean isDuplicate) {
2858
                if (this.items.contains(item)) {
2859
                    if (isDuplicate) {
2860
                        this.duplicates.add(item);
2861
                    } else {
2862
                        this.duplicates.remove(item);
2863
                    }
2864
                }
2865
            }
2866

    
2867
            /**
2868
             * Indicates whether given item is a duplicate.
2869
             *
2870
             * @param item
2871
             *            item to check
2872
             * @return <code>true</code> if item is duplicate
2873
             */
2874
            public boolean isDuplicateElement(Object item) {
2875
                return duplicates.contains(item);
2876
            }
2877

    
2878
            /**
2879
             * Load history from memento.
2880
             *
2881
             * @param memento
2882
             *            memento from which the history will be retrieved
2883
             */
2884
            public void loadHistory(IMemento memento) {
2885
                if (this.selectionHistory != null) {
2886
                    this.selectionHistory.load(memento);
2887
                }
2888
            }
2889

    
2890
            /**
2891
             * Save history to memento.
2892
             *
2893
             * @param memento
2894
             *            memento to which the history will be added
2895
             */
2896
            public void saveHistory(IMemento memento) {
2897
                if (this.selectionHistory != null) {
2898
                    this.selectionHistory.save(memento);
2899
                }
2900
            }
2901

    
2902
            /**
2903
             * Gets sorted items.
2904
             *
2905
             * @return sorted items
2906
             */
2907
            private Object[] getSortedItems() {
2908
                if (lastSortedItems.size() != items.size()) {
2909
                    synchronized (lastSortedItems) {
2910
                        lastSortedItems.clear();
2911
                        lastSortedItems.addAll(items);
2912
                        Collections.sort(lastSortedItems, getHistoryComparator());
2913
                    }
2914
                }
2915
                return lastSortedItems.toArray();
2916
            }
2917

    
2918
            /**
2919
             * Remember result of filtering.
2920
             *
2921
             * @param itemsFilter
2922
             */
2923
            public void rememberResult(ItemsFilter itemsFilter) {
2924
                List itemsList = Collections.synchronizedList(Arrays
2925
                        .asList(getSortedItems()));
2926
                // synchronization
2927
                if (itemsFilter == filter) {
2928
                    lastCompletedFilter = itemsFilter;
2929
                    lastCompletedResult = itemsList;
2930
                }
2931

    
2932
            }
2933

    
2934
            /*
2935
             * (non-Javadoc)
2936
             *
2937
             * @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object)
2938
             */
2939
            @Override
2940
            public Object[] getElements(Object inputElement) {
2941
                return lastFilteredItems.toArray();
2942
            }
2943

    
2944
            public int getNumberOfElements() {
2945
                return lastFilteredItems.size();
2946
            }
2947

    
2948
            /*
2949
             * (non-Javadoc)
2950
             *
2951
             * @see org.eclipse.jface.viewers.IContentProvider#dispose()
2952
             */
2953
            @Override
2954
            public void dispose() {
2955
            }
2956

    
2957
            /*
2958
             * (non-Javadoc)
2959
             *
2960
             * @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer,
2961
             *      java.lang.Object, java.lang.Object)
2962
             */
2963
            @Override
2964
            public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
2965
            }
2966

    
2967
            /*
2968
             * (non-Javadoc)
2969
             *
2970
             * @see org.eclipse.jface.viewers.ILazyContentProvider#updateElement(int)
2971
             */
2972
            @Override
2973
            public void updateElement(int index) {
2974

    
2975
                CdmFilteredItemsSelectionDialog.this.list.replace((lastFilteredItems
2976
                        .size() > index) ? lastFilteredItems.get(index) : null,
2977
                        index);
2978

    
2979
            }
2980

    
2981
            /**
2982
             * Main method responsible for getting the filtered items and checking
2983
             * for duplicates. It is based on the
2984
             * {@link FilteredItemsSelectionDialog.ContentProvider#getFilteredItems(Object, IProgressMonitor)}.
2985
             *
2986
             * @param checkDuplicates
2987
             *            <code>true</code> if data concerning elements
2988
             *            duplication should be computed - it takes much more time
2989
             *            than standard filtering
2990
             *
2991
             * @param monitor
2992
             *            progress monitor
2993
             */
2994
            public void reloadCache(boolean checkDuplicates,
2995
                    IProgressMonitor monitor) {
2996

    
2997
                reset = false;
2998

    
2999
                if (monitor != null) {
3000
                    // the work is divided into two actions of the same length
3001
                    int totalWork = checkDuplicates ? 200 : 100;
3002

    
3003
                    monitor
3004
                            .beginTask(
3005
                                    WorkbenchMessages.FilteredItemsSelectionDialog_cacheRefreshJob,
3006
                                    totalWork);
3007
                }
3008

    
3009
                // the TableViewer's root (the input) is treated as parent
3010

    
3011
                lastFilteredItems = Arrays.asList(getFilteredItems(list.getInput(),
3012
                        monitor != null ? new SubProgressMonitor(monitor, 100)
3013
                                : null));
3014

    
3015
                if (reset || (monitor != null && monitor.isCanceled())) {
3016
                    if (monitor != null) {
3017
                        monitor.done();
3018
                    }
3019
                    return;
3020
                }
3021

    
3022
                if (checkDuplicates) {
3023
                    checkDuplicates(monitor);
3024
                }
3025
                if (monitor != null) {
3026
                    monitor.done();
3027
                }
3028
            }
3029

    
3030
            private void checkDuplicates(IProgressMonitor monitor) {
3031
                synchronized (lastFilteredItems) {
3032
                    IProgressMonitor subMonitor = null;
3033
                    int reportEvery = lastFilteredItems.size() / 20;
3034
                    if (monitor != null) {
3035
                        subMonitor = new SubProgressMonitor(monitor, 100);
3036
                        subMonitor
3037
                                .beginTask(
3038
                                        WorkbenchMessages.FilteredItemsSelectionDialog_cacheRefreshJob_checkDuplicates,
3039
                                        5);
3040
                    }
3041
                    HashMap helperMap = new HashMap();
3042
                    for (int i = 0; i < lastFilteredItems.size(); i++) {
3043
                        if (reset
3044
                                || (subMonitor != null && subMonitor.isCanceled())) {
3045
                            return;
3046
                        }
3047
                        Object item = lastFilteredItems.get(i);
3048

    
3049
                        if (!(item instanceof ItemsListSeparator)) {
3050
                            Object previousItem = helperMap.put(
3051
                                    getElementName(item), item);
3052
                            if (previousItem != null) {
3053
                                setDuplicateElement(previousItem, true);
3054
                                setDuplicateElement(item, true);
3055
                            } else {
3056
                                setDuplicateElement(item, false);
3057
                            }
3058
                        }
3059

    
3060
                        if (subMonitor != null && reportEvery != 0
3061
                                && (i + 1) % reportEvery == 0) {
3062
                            subMonitor.worked(1);
3063
                        }
3064
                    }
3065
                    helperMap.clear();
3066
                }
3067
            }
3068

    
3069
            /**
3070
             * Returns an array of items filtered using the provided
3071
             * <code>ViewerFilter</code>s with a separator added.
3072
             *
3073
             * @param parent
3074
             *            the parent
3075
             * @param monitor
3076
             *            progress monitor, can be <code>null</code>
3077
             * @return an array of filtered items
3078
             */
3079
            protected Object[] getFilteredItems(Object parent,
3080
                    IProgressMonitor monitor) {
3081
                int ticks = 100;
3082
                if (monitor == null) {
3083
                    monitor = new NullProgressMonitor();
3084
                }
3085

    
3086
                monitor
3087
                        .beginTask(
3088
                                WorkbenchMessages.FilteredItemsSelectionDialog_cacheRefreshJob_getFilteredElements,
3089
                                ticks);
3090
                if (filters != null) {
3091
                    ticks /= (filters.size() + 2);
3092
                } else {
3093
                    ticks /= 2;
3094
                }
3095

    
3096
                // get already sorted array
3097
                Object[] filteredElements = getSortedItems();
3098

    
3099
                monitor.worked(ticks);
3100

    
3101
                // filter the elements using provided ViewerFilters
3102
                if (filters != null && filteredElements != null) {
3103
                    for (Iterator iter = filters.iterator(); iter.hasNext();) {
3104
                        ViewerFilter f = (ViewerFilter) iter.next();
3105
                        filteredElements = f.filter(list, parent, filteredElements);
3106
                        monitor.worked(ticks);
3107
                    }
3108
                }
3109

    
3110
                if (filteredElements == null || monitor.isCanceled()) {
3111
                    monitor.done();
3112
                    return new Object[0];
3113
                }
3114

    
3115
                ArrayList preparedElements = new ArrayList();
3116
                boolean hasHistory = false;
3117

    
3118
                if (filteredElements.length > 0) {
3119
                    if (isHistoryElement(filteredElements[0])) {
3120
                        hasHistory = true;
3121
                    }
3122
                }
3123

    
3124
                int reportEvery = filteredElements.length / ticks;
3125

    
3126
                // add separator
3127
                for (int i = 0; i < filteredElements.length; i++) {
3128
                    Object item = filteredElements[i];
3129

    
3130
                    if (hasHistory && !isHistoryElement(item)) {
3131
                        preparedElements.add(itemsListSeparator);
3132
                        hasHistory = false;
3133
                    }
3134

    
3135
                    preparedElements.add(item);
3136

    
3137
                    if (reportEvery != 0 && ((i + 1) % reportEvery == 0)) {
3138
                        monitor.worked(1);
3139
                    }
3140
                }
3141

    
3142
                monitor.done();
3143

    
3144
                return preparedElements.toArray();
3145
            }
3146

    
3147
            /**
3148
             * Adds a filter to this content provider. For an example usage of such
3149
             * filters look at the project <code>org.eclipse.ui.ide</code>, class
3150
             * <code>org.eclipse.ui.dialogs.FilteredResourcesSelectionDialog.CustomWorkingSetFilter</code>.
3151
             *
3152
             *
3153
             * @param filter
3154
             *            the filter to be added
3155
             */
3156
            public void addFilter(ViewerFilter filter) {
3157
                if (filters == null) {
3158
                    filters = new ArrayList();
3159
                }
3160
                filters.add(filter);
3161
                // currently filters are only added when dialog is restored
3162
                // if it is changed, refreshing the whole TableViewer should be
3163
                // added
3164
            }
3165

    
3166
        }
3167

    
3168
        /**
3169
         * A content provider that does nothing.
3170
         */
3171
        private class NullContentProvider implements IContentProvider {
3172

    
3173
            /*
3174
             * (non-Javadoc)
3175
             *
3176
             * @see org.eclipse.jface.viewers.IContentProvider#dispose()
3177
             */
3178
            @Override
3179
            public void dispose() {
3180
            }
3181

    
3182
            /*
3183
             * (non-Javadoc)
3184
             *
3185
             * @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer,
3186
             *      java.lang.Object, java.lang.Object)
3187
             */
3188
            @Override
3189
            public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
3190
            }
3191

    
3192
        }
3193

    
3194
        /**
3195
         * DetailsContentViewer objects are wrappers for labels.
3196
         * DetailsContentViewer provides means to change label's image and text when
3197
         * the attached LabelProvider is updated.
3198
         */
3199
        private class DetailsContentViewer extends ContentViewer {
3200

    
3201
            private final CLabel label;
3202

    
3203
            /**
3204
             * Unfortunately, it was impossible to delegate displaying border to
3205
             * label. The <code>ViewForm</code> is used because
3206
             * <code>CLabel</code> displays shadow when border is present.
3207
             */
3208
            private final ViewForm viewForm;
3209

    
3210
            /**
3211
             * Constructs a new instance of this class given its parent and a style
3212
             * value describing its behavior and appearance.
3213
             *
3214
             * @param parent
3215
             *            the parent component
3216
             * @param style
3217
             *            SWT style bits
3218
             */
3219
            public DetailsContentViewer(Composite parent, int style) {
3220
                viewForm = new ViewForm(parent, style);
3221
                GridData gd = new GridData(GridData.FILL_HORIZONTAL);
3222
                gd.horizontalSpan = 2;
3223
                viewForm.setLayoutData(gd);
3224
                label = new CLabel(viewForm, SWT.FLAT);
3225
                label.setFont(parent.getFont());
3226
                viewForm.setContent(label);
3227
                hookControl(label);
3228
            }
3229

    
3230
            /**
3231
             * Shows/hides the content viewer.
3232
             *
3233
             * @param visible
3234
             *            if the content viewer should be visible.
3235
             */
3236
            public void setVisible(boolean visible) {
3237
                GridData gd = (GridData) viewForm.getLayoutData();
3238
                gd.exclude = !visible;
3239
                viewForm.getParent().layout();
3240
            }
3241

    
3242
            /*
3243
             * (non-Javadoc)
3244
             *
3245
             * @see org.eclipse.jface.viewers.Viewer#inputChanged(java.lang.Object,
3246
             *      java.lang.Object)
3247
             */
3248
            @Override
3249
            protected void inputChanged(Object input, Object oldInput) {
3250
                if (oldInput == null) {
3251
                    if (input == null) {
3252
                        return;
3253
                    }
3254
                    refresh();
3255
                    return;
3256
                }
3257

    
3258
                refresh();
3259

    
3260
            }
3261

    
3262
            /*
3263
             * (non-Javadoc)
3264
             *
3265
             * @see org.eclipse.jface.viewers.ContentViewer#handleLabelProviderChanged(org.eclipse.jface.viewers.LabelProviderChangedEvent)
3266
             */
3267
            @Override
3268
            protected void handleLabelProviderChanged(
3269
                    LabelProviderChangedEvent event) {
3270
                if (event != null) {
3271
                    refresh(event.getElements());
3272
                }
3273
            }
3274

    
3275
            /*
3276
             * (non-Javadoc)
3277
             *
3278
             * @see org.eclipse.jface.viewers.Viewer#getControl()
3279
             */
3280
            @Override
3281
            public Control getControl() {
3282
                return label;
3283
            }
3284

    
3285
            /*
3286
             * (non-Javadoc)
3287
             *
3288
             * @see org.eclipse.jface.viewers.Viewer#getSelection()
3289
             */
3290
            @Override
3291
            public ISelection getSelection() {
3292
                // not supported
3293
                return null;
3294
            }
3295

    
3296
            /*
3297
             * (non-Javadoc)
3298
             *
3299
             * @see org.eclipse.jface.viewers.Viewer#refresh()
3300
             */
3301
            @Override
3302
            public void refresh() {
3303
                Object input = this.getInput();
3304
                if (input != null) {
3305
                    ILabelProvider labelProvider = (ILabelProvider) getLabelProvider();
3306
                    doRefresh(labelProvider.getText(input), labelProvider
3307
                            .getImage(input));
3308
                } else {
3309
                    doRefresh(null, null);
3310
                }
3311
            }
3312

    
3313
            /**
3314
             * Sets the given text and image to the label.
3315
             *
3316
             * @param text
3317
             *            the new text or null
3318
             * @param image
3319
             *            the new image
3320
             */
3321
            private void doRefresh(String text, Image image) {
3322
                if ( text != null ) {
3323
                    text = LegacyActionTools.escapeMnemonics(text);
3324
                }
3325
                label.setText(text);
3326
                label.setImage(image);
3327
            }
3328

    
3329
            /*
3330
             * (non-Javadoc)
3331
             *
3332
             * @see org.eclipse.jface.viewers.Viewer#setSelection(org.eclipse.jface.viewers.ISelection,
3333
             *      boolean)
3334
             */
3335
            @Override
3336
            public void setSelection(ISelection selection, boolean reveal) {
3337
                // not supported
3338
            }
3339

    
3340
            /**
3341
             * Refreshes the label if currently chosen element is on the list.
3342
             *
3343
             * @param objs
3344
             *            list of changed object
3345
             */
3346
            private void refresh(Object[] objs) {
3347
                if (objs == null || getInput() == null) {
3348
                    return;
3349
                }
3350
                Object input = getInput();
3351
                for (int i = 0; i < objs.length; i++) {
3352
                    if (objs[i].equals(input)) {
3353
                        refresh();
3354
                        break;
3355
                    }
3356
                }
3357
            }
3358
        }
3359

    
3360
        /**
3361
         * Compares items according to the history.
3362
         */
3363
        private class HistoryComparator implements Comparator {
3364

    
3365
            /*
3366
             * (non-Javadoc)
3367
             *
3368
             * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
3369
             */
3370
            @Override
3371
            public int compare(Object o1, Object o2) {
3372
                boolean h1 = isHistoryElement(o1);
3373
                boolean h2 = isHistoryElement(o2);
3374
                if (h1 == h2) {
3375
                    return getItemsComparator().compare(o1, o2);
3376
                }
3377

    
3378
                if (h1) {
3379
                    return -2;
3380
                }
3381
                if (h2) {
3382
                    return +2;
3383
                }
3384

    
3385
                return 0;
3386
            }
3387

    
3388
        }
3389

    
3390

    
3391
        /**
3392
         * Get the control where the search pattern is entered. Any filtering should
3393
         * be done using an {@link ItemsFilter}. This control should only be
3394
         * accessed for listeners that wish to handle events that do not affect
3395
         * filtering such as custom traversal.
3396
         *
3397
         * @return Control or <code>null</code> if the pattern control has not
3398
         *         been created.
3399
         */
3400
        public Control getPatternControl() {
3401
            return pattern;
3402
        }
3403

    
3404
    }
3405

    
3406

    
(5-5/32)