Project

General

Profile

Download (120 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
 * This is a class copied from FilteredItemsSelectionDialog and adapted to the Cdm use case. The original
131
 * dialog gets all items when opening the dialog and then apply the filter on all items.
132
 * In our case we need a possibility to filter the items already when getting them from the DB, so we adapt the
133
 * original class
134
 * original description:
135
 * Shows a list of items to the user with a text entry field for a string
136
 * pattern used to filter the list of items.
137
 *
138
 *
139
 */
140

    
141
public abstract class CdmFilteredItemsSelectionDialog extends SelectionStatusDialog {
142

    
143

    
144

    
145
        private static final String DIALOG_BOUNDS_SETTINGS = "DialogBoundsSettings"; //$NON-NLS-1$
146

    
147
        private static final String SHOW_STATUS_LINE = "ShowStatusLine"; //$NON-NLS-1$
148

    
149
        private static final String HISTORY_SETTINGS = "History"; //$NON-NLS-1$
150

    
151
        private static final String DIALOG_HEIGHT = "DIALOG_HEIGHT"; //$NON-NLS-1$
152

    
153
        private static final String DIALOG_WIDTH = "DIALOG_WIDTH"; //$NON-NLS-1$
154

    
155
        /**
156
         * Represents an empty selection in the pattern input field (used only for
157
         * initial pattern).
158
         */
159
        public static final int NONE = 0;
160

    
161
        /**
162
         * Pattern input field selection where caret is at the beginning (used only
163
         * for initial pattern).
164
         */
165
        public static final int CARET_BEGINNING = 1;
166

    
167
        /**
168
         * Represents a full selection in the pattern input field (used only for
169
         * initial pattern).
170
         */
171
        public static final int FULL_SELECTION = 2;
172

    
173
        private Text pattern;
174

    
175
        private TableViewer list;
176

    
177
        private DetailsContentViewer details;
178

    
179
        /**
180
         * It is a duplicate of a field in the CLabel class in DetailsContentViewer.
181
         * It is maintained, because the <code>setDetailsLabelProvider()</code>
182
         * could be called before content area is created.
183
         */
184
        private ILabelProvider detailsLabelProvider;
185

    
186
        private ItemsListLabelProvider itemsListLabelProvider;
187

    
188
        private MenuManager menuManager;
189

    
190
        private MenuManager contextMenuManager;
191

    
192
        private final boolean multi;
193

    
194
        private ToolBar toolBar;
195

    
196
        private ToolItem toolItem;
197

    
198
        private Label progressLabel;
199

    
200
        private ToggleStatusLineAction toggleStatusLineAction;
201

    
202
        private RemoveHistoryItemAction removeHistoryItemAction;
203

    
204
        private ActionContributionItem removeHistoryActionContributionItem;
205

    
206
        private IStatus status;
207

    
208
        private final RefreshCacheJob refreshCacheJob;
209

    
210
        private final RefreshProgressMessageJob refreshProgressMessageJob = new RefreshProgressMessageJob();
211

    
212
        private Object[] currentSelection;
213

    
214
        private final ContentProvider contentProvider;
215

    
216
        private final FilterHistoryJob filterHistoryJob;
217

    
218
        private final FilterJob filterJob;
219

    
220
        private ItemsFilter filter;
221

    
222
        private List lastCompletedResult;
223

    
224
        private ItemsFilter lastCompletedFilter;
225

    
226
        private String initialPatternText;
227

    
228
        private int selectionMode;
229

    
230
        private ItemsListSeparator itemsListSeparator;
231

    
232
        private static final String EMPTY_STRING = ""; //$NON-NLS-1$
233

    
234
        private boolean refreshWithLastSelection = false;
235

    
236
        private IHandlerActivation showViewHandler;
237

    
238
        /**
239
         * Creates a new instance of the class.
240
         *
241
         * @param shell
242
         *            shell to parent the dialog on
243
         * @param multi
244
         *            indicates whether dialog allows to select more than one
245
         *            position in its list of items
246
         */
247
        public CdmFilteredItemsSelectionDialog(Shell shell, boolean multi) {
248
            super(shell);
249
            this.multi = multi;
250
            filterHistoryJob = new FilterHistoryJob();
251
            filterJob = new FilterJob();
252
            contentProvider = new ContentProvider();
253
            refreshCacheJob = new RefreshCacheJob();
254
            itemsListSeparator = new ItemsListSeparator(
255
                    WorkbenchMessages.FilteredItemsSelectionDialog_separatorLabel);
256
            selectionMode = NONE;
257
        }
258

    
259
        /**
260
         * Creates a new instance of the class. Created dialog won't allow to select
261
         * more than one item.
262
         *
263
         * @param shell
264
         *            shell to parent the dialog on
265
         */
266
        public CdmFilteredItemsSelectionDialog(Shell shell) {
267
            this(shell, false);
268
        }
269

    
270
        /**
271
         * Adds viewer filter to the dialog items list.
272
         *
273
         * @param filter
274
         *            the new filter
275
         */
276
        protected void addListFilter(ViewerFilter filter) {
277
            contentProvider.addFilter(filter);
278
        }
279

    
280
        /**
281
         * Sets a new label provider for items in the list. If the label provider
282
         * also implements {@link
283
         * org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider
284
         * .IStyledLabelProvider}, the style text labels provided by it will be used
285
         * provided that the corresponding preference is set.
286
         *
287
         * @see IWorkbenchPreferenceConstants#USE_COLORED_LABELS
288
         *
289
         * @param listLabelProvider
290
         *      the label provider for items in the list
291
         */
292
        public void setListLabelProvider(ILabelProvider listLabelProvider) {
293
            getItemsListLabelProvider().setProvider(listLabelProvider);
294
        }
295

    
296
        /**
297
         * Returns the label decorator for selected items in the list.
298
         *
299
         * @return the label decorator for selected items in the list
300
         */
301
        private ILabelDecorator getListSelectionLabelDecorator() {
302
            return getItemsListLabelProvider().getSelectionDecorator();
303
        }
304

    
305
        /**
306
         * Sets the label decorator for selected items in the list.
307
         *
308
         * @param listSelectionLabelDecorator
309
         *            the label decorator for selected items in the list
310
         */
311
        public void setListSelectionLabelDecorator(
312
                ILabelDecorator listSelectionLabelDecorator) {
313
            getItemsListLabelProvider().setSelectionDecorator(
314
                    listSelectionLabelDecorator);
315
        }
316

    
317
        /**
318
         * Returns the item list label provider.
319
         *
320
         * @return the item list label provider
321
         */
322
        private ItemsListLabelProvider getItemsListLabelProvider() {
323
            if (itemsListLabelProvider == null) {
324
                itemsListLabelProvider = new ItemsListLabelProvider(
325
                        new LabelProvider(), null);
326
            }
327
            return itemsListLabelProvider;
328
        }
329

    
330
        /**
331
         * Sets label provider for the details field.
332
         *
333
         * For a single selection, the element sent to
334
         * {@link ILabelProvider#getImage(Object)} and
335
         * {@link ILabelProvider#getText(Object)} is the selected object, for
336
         * multiple selection a {@link String} with amount of selected items is the
337
         * element.
338
         *
339
         * @see #getSelectedItems() getSelectedItems() can be used to retrieve
340
         *      selected items and get the items count.
341
         *
342
         * @param detailsLabelProvider
343
         *            the label provider for the details field
344
         */
345
        public void setDetailsLabelProvider(ILabelProvider detailsLabelProvider) {
346
            this.detailsLabelProvider = detailsLabelProvider;
347
            if (details != null) {
348
                details.setLabelProvider(detailsLabelProvider);
349
            }
350
        }
351

    
352
        private ILabelProvider getDetailsLabelProvider() {
353
            if (detailsLabelProvider == null) {
354
                detailsLabelProvider = new LabelProvider();
355
            }
356
            return detailsLabelProvider;
357
        }
358

    
359
        /*
360
         * (non-Javadoc)
361
         *
362
         * @see org.eclipse.jface.window.Window#create()
363
         */
364
        @Override
365
        public void create() {
366
            super.create();
367
            pattern.setFocus();
368
        }
369

    
370
        /**
371
         * Restores dialog using persisted settings. The default implementation
372
         * restores the status of the details line and the selection history.
373
         *
374
         * @param settings
375
         *            settings used to restore dialog
376
         */
377
        protected void restoreDialog(IDialogSettings settings) {
378
            boolean toggleStatusLine = true;
379

    
380
            if (settings.get(SHOW_STATUS_LINE) != null) {
381
                toggleStatusLine = settings.getBoolean(SHOW_STATUS_LINE);
382
            }
383

    
384
            toggleStatusLineAction.setChecked(toggleStatusLine);
385

    
386
            details.setVisible(toggleStatusLine);
387

    
388
            String setting = settings.get(HISTORY_SETTINGS);
389
            if (setting != null) {
390
                try {
391
                    IMemento memento = XMLMemento.createReadRoot(new StringReader(
392
                            setting));
393
                    this.contentProvider.loadHistory(memento);
394
                } catch (WorkbenchException e) {
395
                    // Simply don't restore the settings
396
                    StatusManager
397
                            .getManager()
398
                            .handle(
399
                                    new Status(
400
                                            IStatus.ERROR,
401
                                            PlatformUI.PLUGIN_ID,
402
                                            IStatus.ERROR,
403
                                            WorkbenchMessages.FilteredItemsSelectionDialog_restoreError,
404
                                            e));
405
                }
406
            }
407
        }
408

    
409
        /*
410
         * (non-Javadoc)
411
         *
412
         * @see org.eclipse.jface.window.Window#close()
413
         */
414
        @Override
415
        public boolean close() {
416
            this.filterJob.cancel();
417
            this.refreshCacheJob.cancel();
418
            this.refreshProgressMessageJob.cancel();
419
            if (showViewHandler != null) {
420
                IHandlerService service = (IHandlerService) PlatformUI
421
                        .getWorkbench().getService(IHandlerService.class);
422
                service.deactivateHandler(showViewHandler);
423
                showViewHandler.getHandler().dispose();
424
                showViewHandler = null;
425
            }
426
            if (menuManager != null) {
427
                menuManager.dispose();
428
            }
429
            if (contextMenuManager != null) {
430
                contextMenuManager.dispose();
431
            }
432
            storeDialog(getDialogSettings());
433
            return super.close();
434
        }
435

    
436
        /**
437
         * Stores dialog settings.
438
         *
439
         * @param settings
440
         *            settings used to store dialog
441
         */
442
        protected void storeDialog(IDialogSettings settings) {
443
            settings.put(SHOW_STATUS_LINE, toggleStatusLineAction.isChecked());
444

    
445
            XMLMemento memento = XMLMemento.createWriteRoot(HISTORY_SETTINGS);
446
            this.contentProvider.saveHistory(memento);
447
            StringWriter writer = new StringWriter();
448
            try {
449
                memento.save(writer);
450
                settings.put(HISTORY_SETTINGS, writer.getBuffer().toString());
451
            } catch (IOException e) {
452
                // Simply don't store the settings
453
                StatusManager
454
                        .getManager()
455
                        .handle(
456
                                new Status(
457
                                        IStatus.ERROR,
458
                                        PlatformUI.PLUGIN_ID,
459
                                        IStatus.ERROR,
460
                                        WorkbenchMessages.FilteredItemsSelectionDialog_storeError,
461
                                        e));
462
            }
463
        }
464

    
465
        /**
466
         * Create a new header which is labelled by headerLabel.
467
         *
468
         * @param parent
469
         * @return Label the label of the header
470
         */
471
        private Label createHeader(Composite parent) {
472
            Composite header = new Composite(parent, SWT.NONE);
473

    
474
            GridLayout layout = new GridLayout();
475
            layout.numColumns = 2;
476
            layout.marginWidth = 0;
477
            layout.marginHeight = 0;
478
            header.setLayout(layout);
479

    
480
            Label headerLabel = new Label(header, SWT.NONE);
481
            headerLabel.setText((getMessage() != null && getMessage().trim()
482
                    .length() > 0) ? getMessage()
483
                    : WorkbenchMessages.FilteredItemsSelectionDialog_patternLabel);
484
            headerLabel.addTraverseListener(new TraverseListener() {
485
                @Override
486
                public void keyTraversed(TraverseEvent e) {
487
                    if (e.detail == SWT.TRAVERSE_MNEMONIC && e.doit) {
488
                        e.detail = SWT.TRAVERSE_NONE;
489
                        pattern.setFocus();
490
                    }
491
                }
492
            });
493

    
494
            GridData gd = new GridData(GridData.FILL_HORIZONTAL);
495
            headerLabel.setLayoutData(gd);
496

    
497
            createViewMenu(header);
498
            header.setLayoutData(gd);
499
            return headerLabel;
500
        }
501

    
502
        /**
503
         * Create the labels for the list and the progress. Return the list label.
504
         *
505
         * @param parent
506
         * @return Label
507
         */
508
        private Label createLabels(Composite parent) {
509
            Composite labels = new Composite(parent, SWT.NONE);
510

    
511
            GridLayout layout = new GridLayout();
512
            layout.numColumns = 2;
513
            layout.marginWidth = 0;
514
            layout.marginHeight = 0;
515
            labels.setLayout(layout);
516

    
517
            Label listLabel = new Label(labels, SWT.NONE);
518
            listLabel
519
                    .setText(WorkbenchMessages.FilteredItemsSelectionDialog_listLabel);
520

    
521
            listLabel.addTraverseListener(new TraverseListener() {
522
                @Override
523
                public void keyTraversed(TraverseEvent e) {
524
                    if (e.detail == SWT.TRAVERSE_MNEMONIC && e.doit) {
525
                        e.detail = SWT.TRAVERSE_NONE;
526
                        list.getTable().setFocus();
527
                    }
528
                }
529
            });
530

    
531
            GridData gd = new GridData(GridData.FILL_HORIZONTAL);
532
            listLabel.setLayoutData(gd);
533

    
534
            progressLabel = new Label(labels, SWT.RIGHT);
535
            progressLabel.setLayoutData(gd);
536

    
537
            labels.setLayoutData(gd);
538
            return listLabel;
539
        }
540

    
541
        private void createViewMenu(Composite parent) {
542
            toolBar = new ToolBar(parent, SWT.FLAT);
543
            toolItem = new ToolItem(toolBar, SWT.PUSH, 0);
544

    
545
            GridData data = new GridData();
546
            data.horizontalAlignment = GridData.END;
547
            toolBar.setLayoutData(data);
548

    
549
            toolBar.addMouseListener(new MouseAdapter() {
550
                @Override
551
                public void mouseDown(MouseEvent e) {
552
                    showViewMenu();
553
                }
554
            });
555

    
556
            toolItem.setImage(WorkbenchImages
557
                    .getImage(IWorkbenchGraphicConstants.IMG_LCL_VIEW_MENU));
558
            toolItem
559
                    .setToolTipText(WorkbenchMessages.FilteredItemsSelectionDialog_menu);
560
            toolItem.addSelectionListener(new SelectionAdapter() {
561
                @Override
562
                public void widgetSelected(SelectionEvent e) {
563
                    showViewMenu();
564
                }
565
            });
566

    
567
            menuManager = new MenuManager();
568

    
569
            fillViewMenu(menuManager);
570

    
571
            IHandlerService service = (IHandlerService) PlatformUI.getWorkbench()
572
                    .getService(IHandlerService.class);
573
            IHandler handler = new AbstractHandler() {
574
                @Override
575
                public Object execute(ExecutionEvent event) {
576
                    showViewMenu();
577
                    return null;
578
                }
579
            };
580
            showViewHandler = service.activateHandler(
581
                    IWorkbenchCommandConstants.WINDOW_SHOW_VIEW_MENU, handler,
582
                    new ActiveShellExpression(getShell()));
583
        }
584

    
585
        /**
586
         * Fills the menu of the dialog.
587
         *
588
         * @param menuManager
589
         *            the menu manager
590
         */
591
        protected void fillViewMenu(IMenuManager menuManager) {
592
            toggleStatusLineAction = new ToggleStatusLineAction();
593
            menuManager.add(toggleStatusLineAction);
594
        }
595

    
596
        private void showViewMenu() {
597
            Menu menu = menuManager.createContextMenu(getShell());
598
            Rectangle bounds = toolItem.getBounds();
599
            Point topLeft = new Point(bounds.x, bounds.y + bounds.height);
600
            topLeft = toolBar.toDisplay(topLeft);
601
            menu.setLocation(topLeft.x, topLeft.y);
602
            menu.setVisible(true);
603
        }
604

    
605
        /**
606
         * Hook that allows to add actions to the context menu.
607
         * <p>
608
         * Subclasses may extend in order to add other actions.</p>
609
         *
610
         * @param menuManager the context menu manager
611
         * @since 3.5
612
         */
613
        protected void fillContextMenu(IMenuManager menuManager) {
614
            List selectedElements= ((StructuredSelection)list.getSelection()).toList();
615

    
616
            Object item= null;
617

    
618
            for (Iterator it= selectedElements.iterator(); it.hasNext();) {
619
                item= it.next();
620
                if (item instanceof ItemsListSeparator || !isHistoryElement(item)) {
621
                    return;
622
                }
623
            }
624

    
625
            if (selectedElements.size() > 0) {
626
                removeHistoryItemAction.setText(WorkbenchMessages.FilteredItemsSelectionDialog_removeItemsFromHistoryAction);
627

    
628
                menuManager.add(removeHistoryActionContributionItem);
629

    
630
            }
631
        }
632

    
633
        private void createPopupMenu() {
634
            removeHistoryItemAction = new RemoveHistoryItemAction();
635
            removeHistoryActionContributionItem = new ActionContributionItem(
636
                    removeHistoryItemAction);
637

    
638
            contextMenuManager = new MenuManager();
639
            contextMenuManager.setRemoveAllWhenShown(true);
640
            contextMenuManager.addMenuListener(new IMenuListener() {
641
                @Override
642
                public void menuAboutToShow(IMenuManager manager) {
643
                    fillContextMenu(manager);
644
                }
645
            });
646

    
647
            final Table table = list.getTable();
648
            Menu menu= contextMenuManager.createContextMenu(table);
649
            table.setMenu(menu);
650
        }
651

    
652
        /**
653
         * Creates an extra content area, which will be located above the details.
654
         *
655
         * @param parent
656
         *            parent to create the dialog widgets in
657
         * @return an extra content area
658
         */
659
        protected abstract Control createExtendedContentArea(Composite parent);
660

    
661
        /*
662
         * (non-Javadoc)
663
         *
664
         * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite)
665
         */
666
        @Override
667
        protected Control createDialogArea(Composite parent) {
668
            Composite dialogArea = (Composite) super.createDialogArea(parent);
669

    
670
            Composite content = new Composite(dialogArea, SWT.NONE);
671
            GridData gd = new GridData(GridData.FILL_BOTH);
672
            content.setLayoutData(gd);
673

    
674
            GridLayout layout = new GridLayout();
675
            layout.numColumns = 1;
676
            layout.marginWidth = 0;
677
            layout.marginHeight = 0;
678
            content.setLayout(layout);
679

    
680
            final Label headerLabel = createHeader(content);
681

    
682
            pattern = new Text(content, SWT.SINGLE | SWT.BORDER | SWT.SEARCH | SWT.ICON_CANCEL);
683
            pattern.getAccessible().addAccessibleListener(new AccessibleAdapter() {
684
                @Override
685
                public void getName(AccessibleEvent e) {
686
                    e.result = LegacyActionTools.removeMnemonics(headerLabel
687
                            .getText());
688
                }
689
            });
690
            gd = new GridData(GridData.FILL_HORIZONTAL);
691
            pattern.setLayoutData(gd);
692

    
693
            final Label listLabel = createLabels(content);
694

    
695
            list = new TableViewer(content, (multi ? SWT.MULTI : SWT.SINGLE)
696
                    | SWT.BORDER | SWT.V_SCROLL | SWT.VIRTUAL);
697
            list.getTable().getAccessible().addAccessibleListener(
698
                    new AccessibleAdapter() {
699
                        @Override
700
                        public void getName(AccessibleEvent e) {
701
                            if (e.childID == ACC.CHILDID_SELF) {
702
                                e.result = LegacyActionTools
703
                                        .removeMnemonics(listLabel.getText());
704
                            }
705
                        }
706
                    });
707
            list.setContentProvider(contentProvider);
708
            list.setLabelProvider(getItemsListLabelProvider());
709
            list.setInput(new Object[0]);
710
            list.setItemCount(contentProvider.getNumberOfElements());
711
            gd = new GridData(GridData.FILL_BOTH);
712
            applyDialogFont(list.getTable());
713
            gd.heightHint= list.getTable().getItemHeight() * 15;
714
            list.getTable().setLayoutData(gd);
715

    
716
            createPopupMenu();
717

    
718
            pattern.addModifyListener(new ModifyListener() {
719
                @Override
720
                public void modifyText(ModifyEvent e) {
721
                    applyFilter();
722
                }
723
            });
724

    
725
            pattern.addKeyListener(new KeyAdapter() {
726
                @Override
727
                public void keyPressed(KeyEvent e) {
728
                    if (e.keyCode == SWT.ARROW_DOWN) {
729
                        if (list.getTable().getItemCount() > 0) {
730
                            list.getTable().setFocus();
731
                        }
732
                    }
733
                }
734
            });
735

    
736
            list.addSelectionChangedListener(new ISelectionChangedListener() {
737
                @Override
738
                public void selectionChanged(SelectionChangedEvent event) {
739
                    StructuredSelection selection = (StructuredSelection) event
740
                            .getSelection();
741
                    handleSelected(selection);
742
                }
743
            });
744

    
745
            list.addDoubleClickListener(new IDoubleClickListener() {
746
                @Override
747
                public void doubleClick(DoubleClickEvent event) {
748
                    handleDoubleClick();
749
                }
750
            });
751

    
752
            list.getTable().addKeyListener(new KeyAdapter() {
753
                @Override
754
                public void keyPressed(KeyEvent e) {
755

    
756
                    if (e.keyCode == SWT.DEL) {
757

    
758
                        List selectedElements = ((StructuredSelection) list
759
                                .getSelection()).toList();
760

    
761
                        Object item = null;
762
                        boolean isSelectedHistory = true;
763

    
764
                        for (Iterator it = selectedElements.iterator(); it
765
                                .hasNext();) {
766
                            item = it.next();
767
                            if (item instanceof ItemsListSeparator
768
                                    || !isHistoryElement(item)) {
769
                                isSelectedHistory = false;
770
                                break;
771
                            }
772
                        }
773
                        if (isSelectedHistory) {
774
                            removeSelectedItems(selectedElements);
775
                        }
776

    
777
                    }
778

    
779
                    if (e.keyCode == SWT.ARROW_UP && (e.stateMask & SWT.SHIFT) != 0
780
                            && (e.stateMask & SWT.CTRL) != 0) {
781
                        StructuredSelection selection = (StructuredSelection) list
782
                                .getSelection();
783

    
784
                        if (selection.size() == 1) {
785
                            Object element = selection.getFirstElement();
786
                            if (element.equals(list.getElementAt(0))) {
787
                                pattern.setFocus();
788
                            }
789
                            if (list.getElementAt(list.getTable()
790
                                    .getSelectionIndex() - 1) instanceof ItemsListSeparator) {
791
                                list.getTable().setSelection(
792
                                        list.getTable().getSelectionIndex() - 1);
793
                            }
794
                            list.getTable().notifyListeners(SWT.Selection,
795
                                    new Event());
796

    
797
                        }
798
                    }
799

    
800
                    if (e.keyCode == SWT.ARROW_DOWN
801
                            && (e.stateMask & SWT.SHIFT) != 0
802
                            && (e.stateMask & SWT.CTRL) != 0) {
803

    
804
                        if (list
805
                                .getElementAt(list.getTable().getSelectionIndex() + 1) instanceof ItemsListSeparator) {
806
                            list.getTable().setSelection(
807
                                    list.getTable().getSelectionIndex() + 1);
808
                        }
809
                        list.getTable().notifyListeners(SWT.Selection, new Event());
810
                    }
811

    
812
                }
813
            });
814

    
815
            createExtendedContentArea(content);
816

    
817
            details = new DetailsContentViewer(content, SWT.BORDER | SWT.FLAT);
818
            details.setVisible(toggleStatusLineAction.isChecked());
819
            details.setContentProvider(new NullContentProvider());
820
            details.setLabelProvider(getDetailsLabelProvider());
821

    
822
            applyDialogFont(content);
823

    
824
            restoreDialog(getDialogSettings());
825

    
826
            if (initialPatternText != null) {
827
                pattern.setText(initialPatternText);
828
            }
829

    
830
            switch (selectionMode) {
831
            case CARET_BEGINNING:
832
                pattern.setSelection(0, 0);
833
                break;
834
            case FULL_SELECTION:
835
                pattern.setSelection(0, initialPatternText.length());
836
                break;
837
            }
838

    
839
            // apply filter even if pattern is empty (display history)
840
            applyFilter();
841

    
842
            return dialogArea;
843
        }
844

    
845
        /**
846
         * This method is a hook for subclasses to override default dialog behavior.
847
         * The <code>handleDoubleClick()</code> method handles double clicks on
848
         * the list of filtered elements.
849
         * <p>
850
         * Current implementation makes double-clicking on the list do the same as
851
         * pressing <code>OK</code> button on the dialog.
852
         */
853
        protected void handleDoubleClick() {
854
            okPressed();
855
        }
856

    
857
        /**
858
         * Refreshes the details field according to the current selection in the
859
         * items list.
860
         */
861
        private void refreshDetails() {
862
            StructuredSelection selection = getSelectedItems();
863

    
864
            switch (selection.size()) {
865
            case 0:
866
                details.setInput(null);
867
                break;
868
            case 1:
869
                details.setInput(selection.getFirstElement());
870
                break;
871
            default:
872
                details
873
                        .setInput(NLS
874
                                .bind(
875
                                        WorkbenchMessages.FilteredItemsSelectionDialog_nItemsSelected,
876
                                        new Integer(selection.size())));
877
                break;
878
            }
879

    
880
        }
881

    
882
        /**
883
         * Handle selection in the items list by updating labels of selected and
884
         * unselected items and refresh the details field using the selection.
885
         *
886
         * @param selection
887
         *            the new selection
888
         */
889
        protected void handleSelected(StructuredSelection selection) {
890
            IStatus status = new Status(IStatus.OK, PlatformUI.PLUGIN_ID,
891
                    IStatus.OK, EMPTY_STRING, null);
892

    
893
            Object[] lastSelection = currentSelection;
894

    
895
            currentSelection = selection.toArray();
896

    
897
            if (selection.size() == 0) {
898
                status = new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID,
899
                        IStatus.ERROR, EMPTY_STRING, null);
900

    
901
                if (lastSelection != null
902
                        && getListSelectionLabelDecorator() != null) {
903
                    list.update(lastSelection, null);
904
                }
905

    
906
                currentSelection = null;
907

    
908
            } else {
909
                status = new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID,
910
                        IStatus.ERROR, EMPTY_STRING, null);
911

    
912
                List items = selection.toList();
913

    
914
                Object item = null;
915
                IStatus tempStatus = null;
916

    
917
                for (Iterator it = items.iterator(); it.hasNext();) {
918
                    Object o = it.next();
919

    
920
                    if (o instanceof ItemsListSeparator) {
921
                        continue;
922
                    }
923

    
924
                    item = o;
925
                    tempStatus = validateItem(item);
926

    
927
                    if (tempStatus.isOK()) {
928
                        status = new Status(IStatus.OK, PlatformUI.PLUGIN_ID,
929
                                IStatus.OK, EMPTY_STRING, null);
930
                    } else {
931
                        status = tempStatus;
932
                        // if any selected element is not valid status is set to
933
                        // ERROR
934
                        break;
935
                    }
936
                }
937

    
938
                if (lastSelection != null
939
                        && getListSelectionLabelDecorator() != null) {
940
                    list.update(lastSelection, null);
941
                }
942

    
943
                if (getListSelectionLabelDecorator() != null) {
944
                    list.update(currentSelection, null);
945
                }
946
            }
947

    
948
            refreshDetails();
949
            updateStatus(status);
950
        }
951

    
952
        /*
953
         * (non-Javadoc)
954
         *
955
         * @see org.eclipse.jface.window.Dialog#getDialogBoundsSettings()
956
         */
957
        @Override
958
        protected IDialogSettings getDialogBoundsSettings() {
959
            IDialogSettings settings = getDialogSettings();
960
            IDialogSettings section = settings.getSection(DIALOG_BOUNDS_SETTINGS);
961
            if (section == null) {
962
                section = settings.addNewSection(DIALOG_BOUNDS_SETTINGS);
963
                section.put(DIALOG_HEIGHT, 500);
964
                section.put(DIALOG_WIDTH, 600);
965
            }
966
            return section;
967
        }
968

    
969
        /**
970
         * Returns the dialog settings. Returned object can't be null.
971
         *
972
         * @return return dialog settings for this dialog
973
         */
974
        protected abstract IDialogSettings getDialogSettings();
975

    
976
        /**
977
         * Refreshes the dialog - has to be called in UI thread.
978
         */
979
        public void refresh() {
980
            if (list != null && !list.getTable().isDisposed()) {
981

    
982
                List lastRefreshSelection = ((StructuredSelection) list
983
                        .getSelection()).toList();
984
                list.getTable().deselectAll();
985

    
986
                list.setItemCount(contentProvider.getNumberOfElements());
987
                list.refresh();
988

    
989
                if (list.getTable().getItemCount() > 0) {
990
                    // preserve previous selection
991
                    if (refreshWithLastSelection && lastRefreshSelection != null
992
                            && lastRefreshSelection.size() > 0) {
993
                        list.setSelection(new StructuredSelection(
994
                                lastRefreshSelection));
995
                    } else {
996
                        refreshWithLastSelection = true;
997
                        list.getTable().setSelection(0);
998
                        list.getTable().notifyListeners(SWT.Selection, new Event());
999
                    }
1000
                } else {
1001
                    list.setSelection(StructuredSelection.EMPTY);
1002
                }
1003

    
1004
            }
1005

    
1006
            scheduleProgressMessageRefresh();
1007
        }
1008

    
1009
        /**
1010
         * Updates the progress label.
1011
         *
1012
         * @deprecated
1013
         */
1014
        @Deprecated
1015
        public void updateProgressLabel() {
1016
            scheduleProgressMessageRefresh();
1017
        }
1018

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

    
1041
        /**
1042
         * Schedule refresh job.
1043
         */
1044
        public void scheduleRefresh() {
1045
            refreshCacheJob.cancelAll();
1046
            refreshCacheJob.schedule();
1047
        }
1048

    
1049
        /**
1050
         * Schedules progress message refresh.
1051
         */
1052
        public void scheduleProgressMessageRefresh() {
1053
            if (filterJob.getState() != Job.RUNNING
1054
                    && refreshProgressMessageJob.getState() != Job.RUNNING) {
1055
                refreshProgressMessageJob.scheduleProgressRefresh(null);
1056
            }
1057
        }
1058

    
1059
        /*
1060
         * (non-Javadoc)
1061
         *
1062
         * @see org.eclipse.ui.dialogs.SelectionStatusDialog#computeResult()
1063
         */
1064
        @Override
1065
        protected void computeResult() {
1066

    
1067
            List selectedElements = ((StructuredSelection) list.getSelection())
1068
                    .toList();
1069

    
1070
            List objectsToReturn = new ArrayList();
1071

    
1072
            Object item = null;
1073

    
1074
            for (Iterator it = selectedElements.iterator(); it.hasNext();) {
1075
                item = it.next();
1076

    
1077
                if (!(item instanceof ItemsListSeparator)) {
1078
                    accessedHistoryItem(item);
1079
                    objectsToReturn.add(item);
1080
                }
1081
            }
1082

    
1083
            setResult(objectsToReturn);
1084
        }
1085

    
1086
        /*
1087
         * @see org.eclipse.ui.dialogs.SelectionStatusDialog#updateStatus(org.eclipse.core.runtime.IStatus)
1088
         */
1089
        @Override
1090
        protected void updateStatus(IStatus status) {
1091
            this.status = status;
1092
            super.updateStatus(status);
1093
        }
1094

    
1095
        /*
1096
         * @see Dialog#okPressed()
1097
         */
1098
        @Override
1099
        protected void okPressed() {
1100
            if (status != null
1101
                    && (status.isOK() || status.getCode() == IStatus.INFO)) {
1102
                super.okPressed();
1103
            }
1104
        }
1105

    
1106
        /**
1107
         * Sets the initial pattern used by the filter. This text is copied into the
1108
         * selection input on the dialog. A full selection is used in the pattern
1109
         * input field.
1110
         *
1111
         * @param text
1112
         *            initial pattern for the filter
1113
         * @see FilteredItemsSelectionDialog#FULL_SELECTION
1114
         */
1115
        public void setInitialPattern(String text) {
1116
            setInitialPattern(text, FULL_SELECTION);
1117
        }
1118

    
1119
        /**
1120
         * Sets the initial pattern used by the filter. This text is copied into the
1121
         * selection input on the dialog. The <code>selectionMode</code> is used
1122
         * to choose selection type for the input field.
1123
         *
1124
         * @param text
1125
         *            initial pattern for the filter
1126
         * @param selectionMode
1127
         *            one of: {@link FilteredItemsSelectionDialog#NONE},
1128
         *            {@link FilteredItemsSelectionDialog#CARET_BEGINNING},
1129
         *            {@link FilteredItemsSelectionDialog#FULL_SELECTION}
1130
         */
1131
        public void setInitialPattern(String text, int selectionMode) {
1132
            this.initialPatternText = text;
1133
            this.selectionMode = selectionMode;
1134
        }
1135

    
1136
        /**
1137
         * Gets initial pattern.
1138
         *
1139
         * @return initial pattern, or <code>null</code> if initial pattern is not
1140
         *         set
1141
         */
1142
        protected String getInitialPattern() {
1143
            return this.initialPatternText;
1144
        }
1145

    
1146
        /**
1147
         * Returns the current selection.
1148
         *
1149
         * @return the current selection
1150
         */
1151
        protected StructuredSelection getSelectedItems() {
1152

    
1153
            StructuredSelection selection = (StructuredSelection) list
1154
                    .getSelection();
1155

    
1156
            List selectedItems = selection.toList();
1157
            Object itemToRemove = null;
1158

    
1159
            for (Iterator it = selection.iterator(); it.hasNext();) {
1160
                Object item = it.next();
1161
                if (item instanceof ItemsListSeparator) {
1162
                    itemToRemove = item;
1163
                    break;
1164
                }
1165
            }
1166

    
1167
            if (itemToRemove == null) {
1168
                return new StructuredSelection(selectedItems);
1169
            }
1170
            // Create a new selection without the collision
1171
            List newItems = new ArrayList(selectedItems);
1172
            newItems.remove(itemToRemove);
1173
            return new StructuredSelection(newItems);
1174

    
1175
        }
1176

    
1177
        /**
1178
         * Validates the item. When items on the items list are selected or
1179
         * deselected, it validates each item in the selection and the dialog status
1180
         * depends on all validations.
1181
         *
1182
         * @param item
1183
         *            an item to be checked
1184
         * @return status of the dialog to be set
1185
         */
1186
        protected abstract IStatus validateItem(Object item);
1187

    
1188
        /**
1189
         * Creates an instance of a filter.
1190
         *
1191
         * @return a filter for items on the items list. Can be <code>null</code>,
1192
         *         no filtering will be applied then, causing no item to be shown in
1193
         *         the list.
1194
         */
1195
        protected abstract ItemsFilter createFilter();
1196

    
1197
        /**
1198
         *
1199
         * Applies the filter created by <code>createFilter()</code> method to the
1200
         * items list. When new filter is different than previous one it will cause
1201
         * refiltering.
1202
         */
1203
        protected void applyFilter() {
1204
            // to get an already filtered selection of the database we added the initModel() method here.
1205
            initModel();
1206
            ItemsFilter newFilter = createFilter();
1207

    
1208
            // don't apply filtering for patterns which mean the same, for example:
1209
            // *a**b and ***a*b
1210
            if (filter != null && filter.equalsFilter(newFilter)) {
1211
                return;
1212
            }
1213

    
1214
            filterHistoryJob.cancel();
1215
            filterJob.cancel();
1216

    
1217
            this.filter = newFilter;
1218

    
1219
            if (this.filter != null) {
1220
                filterHistoryJob.schedule();
1221
            }
1222
        }
1223

    
1224
        /**
1225
         * Returns comparator to sort items inside content provider. Returned object
1226
         * will be probably created as an anonymous class. Parameters passed to the
1227
         * <code>compare(java.lang.Object, java.lang.Object)</code> are going to
1228
         * be the same type as the one used in the content provider.
1229
         *
1230
         * @return comparator to sort items content provider
1231
         */
1232
        protected abstract Comparator getItemsComparator();
1233

    
1234
        /**
1235
         * Fills the content provider with matching items.
1236
         *
1237
         * @param contentProvider
1238
         *            collector to add items to.
1239
         *            {@link FilteredItemsSelectionDialog.AbstractContentProvider#add(Object, FilteredItemsSelectionDialog.ItemsFilter)}
1240
         *            only adds items that pass the given <code>itemsFilter</code>.
1241
         * @param itemsFilter
1242
         *            the items filter
1243
         * @param progressMonitor
1244
         *            must be used to report search progress. The state of this
1245
         *            progress monitor reflects the state of the filtering process.
1246
         * @throws CoreException
1247
         */
1248
        protected abstract void fillContentProvider(
1249
                AbstractContentProvider contentProvider, ItemsFilter itemsFilter,
1250
                IProgressMonitor progressMonitor) throws CoreException;
1251

    
1252
        /**
1253
         * Removes selected items from history.
1254
         *
1255
         * @param items
1256
         *            items to be removed
1257
         */
1258
        private void removeSelectedItems(List items) {
1259
            for (Iterator iter = items.iterator(); iter.hasNext();) {
1260
                Object item = iter.next();
1261
                removeHistoryItem(item);
1262
            }
1263
            refreshWithLastSelection = false;
1264
            contentProvider.refresh();
1265
        }
1266

    
1267
        /**
1268
         * Removes an item from history.
1269
         *
1270
         * @param item
1271
         *            an item to remove
1272
         * @return removed item
1273
         */
1274
        protected Object removeHistoryItem(Object item) {
1275
            return contentProvider.removeHistoryElement(item);
1276
        }
1277

    
1278
        /**
1279
         * Adds item to history.
1280
         *
1281
         * @param item
1282
         *            the item to be added
1283
         */
1284
        protected void accessedHistoryItem(Object item) {
1285
            contentProvider.addHistoryElement(item);
1286
        }
1287

    
1288
        /**
1289
         * Returns a history comparator.
1290
         *
1291
         * @return decorated comparator
1292
         */
1293
        private Comparator getHistoryComparator() {
1294
            return new HistoryComparator();
1295
        }
1296

    
1297
        /**
1298
         * Returns the history of selected elements.
1299
         *
1300
         * @return history of selected elements, or <code>null</code> if it is not
1301
         *         set
1302
         */
1303
        protected SelectionHistory getSelectionHistory() {
1304
            return this.contentProvider.getSelectionHistory();
1305
        }
1306

    
1307
        /**
1308
         * Sets new history.
1309
         *
1310
         * @param selectionHistory
1311
         *            the history
1312
         */
1313
        protected void setSelectionHistory(SelectionHistory selectionHistory) {
1314
            if (this.contentProvider != null) {
1315
                this.contentProvider.setSelectionHistory(selectionHistory);
1316
            }
1317
        }
1318

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

    
1331
        /**
1332
         * Indicates whether the given item is a duplicate.
1333
         *
1334
         * @param item
1335
         *            the item to be investigated
1336
         * @return <code>true</code> if the item is duplicate, <code>false</code>
1337
         *         otherwise
1338
         */
1339
        public boolean isDuplicateElement(Object item) {
1340
            return this.contentProvider.isDuplicateElement(item);
1341
        }
1342

    
1343
        /**
1344
         * Sets separator label
1345
         *
1346
         * @param separatorLabel
1347
         *            the label showed on separator
1348
         */
1349
        public void setSeparatorLabel(String separatorLabel) {
1350
            this.itemsListSeparator = new ItemsListSeparator(separatorLabel);
1351
        }
1352

    
1353
        /**
1354
         * Returns name for then given object.
1355
         *
1356
         * @param item
1357
         *            an object from the content provider. Subclasses should pay
1358
         *            attention to the passed argument. They should either only pass
1359
         *            objects of a known type (one used in content provider) or make
1360
         *            sure that passed parameter is the expected one (by type
1361
         *            checking like <code>instanceof</code> inside the method).
1362
         * @return name of the given item
1363
         */
1364
        public abstract String getElementName(Object item);
1365

    
1366
        private class ToggleStatusLineAction extends Action {
1367

    
1368
            /**
1369
             * Creates a new instance of the class.
1370
             */
1371
            public ToggleStatusLineAction() {
1372
                super(
1373
                        WorkbenchMessages.FilteredItemsSelectionDialog_toggleStatusAction,
1374
                        IAction.AS_CHECK_BOX);
1375
            }
1376

    
1377
            @Override
1378
            public void run() {
1379
                details.setVisible(isChecked());
1380
            }
1381
        }
1382

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

    
1416
            /**
1417
             * Creates a new instance of the class.
1418
             */
1419
            public RefreshJob() {
1420
                super(CdmFilteredItemsSelectionDialog.this.getParentShell()
1421
                        .getDisplay(),
1422
                        WorkbenchMessages.FilteredItemsSelectionDialog_refreshJob);
1423
                setSystem(true);
1424
            }
1425

    
1426
            /*
1427
             * (non-Javadoc)
1428
             *
1429
             * @see org.eclipse.ui.progress.UIJob#runInUIThread(org.eclipse.core.runtime.IProgressMonitor)
1430
             */
1431
            @Override
1432
            public IStatus runInUIThread(IProgressMonitor monitor) {
1433
                if (monitor.isCanceled()) {
1434
                    return new Status(IStatus.OK, WorkbenchPlugin.PI_WORKBENCH,
1435
                            IStatus.OK, EMPTY_STRING, null);
1436
                }
1437

    
1438
                if (CdmFilteredItemsSelectionDialog.this != null) {
1439
                    CdmFilteredItemsSelectionDialog.this.refresh();
1440
                }
1441

    
1442
                return new Status(IStatus.OK, PlatformUI.PLUGIN_ID, IStatus.OK,
1443
                        EMPTY_STRING, null);
1444
            }
1445

    
1446
        }
1447

    
1448
        /**
1449
         * Refreshes the progress message cyclically with 500 milliseconds delay.
1450
         * <code>RefreshProgressMessageJob</code> is strictly connected with
1451
         * <code>GranualProgressMonitor</code> and use it to to get progress
1452
         * message and to decide about break of cyclical refresh.
1453
         */
1454
        private class RefreshProgressMessageJob extends UIJob {
1455

    
1456
            private GranualProgressMonitor progressMonitor;
1457

    
1458
            /**
1459
             * Creates a new instance of the class.
1460
             */
1461
            public RefreshProgressMessageJob() {
1462
                super(
1463
                        CdmFilteredItemsSelectionDialog.this.getParentShell()
1464
                                .getDisplay(),
1465
                        WorkbenchMessages.FilteredItemsSelectionDialog_progressRefreshJob);
1466
                setSystem(true);
1467
            }
1468

    
1469
            /*
1470
             * (non-Javadoc)
1471
             *
1472
             * @see org.eclipse.ui.progress.UIJob#runInUIThread(org.eclipse.core.runtime.IProgressMonitor)
1473
             */
1474
            @Override
1475
            public IStatus runInUIThread(IProgressMonitor monitor) {
1476

    
1477
                if (!progressLabel.isDisposed()) {
1478
                    progressLabel.setText(progressMonitor != null ? progressMonitor
1479
                            .getMessage() : EMPTY_STRING);
1480
                }
1481

    
1482
                if (progressMonitor == null || progressMonitor.isDone()) {
1483
                    return new Status(IStatus.CANCEL, PlatformUI.PLUGIN_ID,
1484
                            IStatus.CANCEL, EMPTY_STRING, null);
1485
                }
1486

    
1487
                // Schedule cyclical with 500 milliseconds delay
1488
                schedule(500);
1489

    
1490
                return new Status(IStatus.OK, PlatformUI.PLUGIN_ID, IStatus.OK,
1491
                        EMPTY_STRING, null);
1492
            }
1493

    
1494
            /**
1495
             * Schedule progress refresh job.
1496
             *
1497
             * @param progressMonitor
1498
             *            used during refresh progress label
1499
             */
1500
            public void scheduleProgressRefresh(
1501
                    GranualProgressMonitor progressMonitor) {
1502
                this.progressMonitor = progressMonitor;
1503
                // Schedule with initial delay to avoid flickering when the user
1504
                // types quickly
1505
                schedule(200);
1506
            }
1507

    
1508
        }
1509

    
1510
        /**
1511
         * A job responsible for computing filtered items list presented using
1512
         * <code>RefreshJob</code>.
1513
         *
1514
         * @see FilteredItemsSelectionDialog.RefreshJob
1515
         *
1516
         */
1517
        private class RefreshCacheJob extends Job {
1518

    
1519
            private final RefreshJob refreshJob = new RefreshJob();
1520

    
1521
            /**
1522
             * Creates a new instance of the class.
1523
             */
1524
            public RefreshCacheJob() {
1525
                super(
1526
                        WorkbenchMessages.FilteredItemsSelectionDialog_cacheRefreshJob);
1527
                setSystem(true);
1528
            }
1529

    
1530
            /**
1531
             * Stops the job and all sub-jobs.
1532
             */
1533
            public void cancelAll() {
1534
                cancel();
1535
                refreshJob.cancel();
1536
            }
1537

    
1538
            /*
1539
             * (non-Javadoc)
1540
             *
1541
             * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
1542
             */
1543
            @Override
1544
            protected IStatus run(IProgressMonitor monitor) {
1545
                if (monitor.isCanceled()) {
1546
                    return new Status(IStatus.CANCEL, WorkbenchPlugin.PI_WORKBENCH,
1547
                            IStatus.CANCEL, EMPTY_STRING, null);
1548
                }
1549

    
1550
                if (CdmFilteredItemsSelectionDialog.this != null) {
1551
                    GranualProgressMonitor wrappedMonitor = new GranualProgressMonitor(
1552
                            monitor);
1553
                    CdmFilteredItemsSelectionDialog.this.reloadCache(true,
1554
                            wrappedMonitor);
1555
                }
1556

    
1557
                if (!monitor.isCanceled()) {
1558
                    refreshJob.schedule();
1559
                }
1560

    
1561
                return new Status(IStatus.OK, PlatformUI.PLUGIN_ID, IStatus.OK,
1562
                        EMPTY_STRING, null);
1563

    
1564
            }
1565

    
1566
            /*
1567
             * (non-Javadoc)
1568
             *
1569
             * @see org.eclipse.core.runtime.jobs.Job#canceling()
1570
             */
1571
            @Override
1572
            protected void canceling() {
1573
                super.canceling();
1574
                contentProvider.stopReloadingCache();
1575
            }
1576

    
1577
        }
1578

    
1579
        private class RemoveHistoryItemAction extends Action {
1580

    
1581
            /**
1582
             * Creates a new instance of the class.
1583
             */
1584
            public RemoveHistoryItemAction() {
1585
                super(
1586
                        WorkbenchMessages.FilteredItemsSelectionDialog_removeItemsFromHistoryAction);
1587
            }
1588

    
1589
            /*
1590
             * (non-Javadoc)
1591
             *
1592
             * @see org.eclipse.jface.action.Action#run()
1593
             */
1594
            @Override
1595
            public void run() {
1596
                List selectedElements = ((StructuredSelection) list.getSelection())
1597
                        .toList();
1598
                removeSelectedItems(selectedElements);
1599
            }
1600
        }
1601

    
1602
        private static boolean showColoredLabels() {
1603
            return PlatformUI.getPreferenceStore().getBoolean(IWorkbenchPreferenceConstants.USE_COLORED_LABELS);
1604
        }
1605

    
1606
        private class ItemsListLabelProvider extends StyledCellLabelProvider
1607
                implements ILabelProviderListener {
1608
            private ILabelProvider provider;
1609

    
1610
            private ILabelDecorator selectionDecorator;
1611

    
1612
            // Need to keep our own list of listeners
1613
            private final ListenerList listeners = new ListenerList();
1614

    
1615
            /**
1616
             * Creates a new instance of the class.
1617
             *
1618
             * @param provider
1619
             *            the label provider for all items, not <code>null</code>
1620
             * @param selectionDecorator
1621
             *            the decorator for selected items, can be <code>null</code>
1622
             */
1623
            public ItemsListLabelProvider(ILabelProvider provider,
1624
                    ILabelDecorator selectionDecorator) {
1625
                Assert.isNotNull(provider);
1626
                this.provider = provider;
1627
                this.selectionDecorator = selectionDecorator;
1628

    
1629
                setOwnerDrawEnabled(showColoredLabels() && provider instanceof IStyledLabelProvider);
1630

    
1631
                provider.addListener(this);
1632

    
1633
                if (selectionDecorator != null) {
1634
                    selectionDecorator.addListener(this);
1635
                }
1636
            }
1637

    
1638
            /**
1639
             * Sets new selection decorator.
1640
             *
1641
             * @param newSelectionDecorator
1642
             *            new label decorator for selected items in the list
1643
             */
1644
            public void setSelectionDecorator(ILabelDecorator newSelectionDecorator) {
1645
                if (selectionDecorator != null) {
1646
                    selectionDecorator.removeListener(this);
1647
                    selectionDecorator.dispose();
1648
                }
1649

    
1650
                selectionDecorator = newSelectionDecorator;
1651

    
1652
                if (selectionDecorator != null) {
1653
                    selectionDecorator.addListener(this);
1654
                }
1655
            }
1656

    
1657
            /**
1658
             * Gets selection decorator.
1659
             *
1660
             * @return the label decorator for selected items in the list
1661
             */
1662
            public ILabelDecorator getSelectionDecorator() {
1663
                return selectionDecorator;
1664
            }
1665

    
1666
            /**
1667
             * Sets new label provider.
1668
             *
1669
             * @param newProvider
1670
             *            new label provider for items in the list, not
1671
             *            <code>null</code>
1672
             */
1673
            public void setProvider(ILabelProvider newProvider) {
1674
                Assert.isNotNull(newProvider);
1675
                provider.removeListener(this);
1676
                provider.dispose();
1677

    
1678
                provider = newProvider;
1679

    
1680
                if (provider != null) {
1681
                    provider.addListener(this);
1682
                }
1683

    
1684
                setOwnerDrawEnabled(showColoredLabels() && provider instanceof IStyledLabelProvider);
1685
            }
1686

    
1687
            private Image getImage(Object element) {
1688
                if (element instanceof ItemsListSeparator) {
1689
                    return WorkbenchImages
1690
                            .getImage(IWorkbenchGraphicConstants.IMG_OBJ_SEPARATOR);
1691
                }
1692

    
1693
                return provider.getImage(element);
1694
            }
1695

    
1696
            private boolean isSelected(Object element) {
1697
                if (element != null && currentSelection != null) {
1698
                    for (int i = 0; i < currentSelection.length; i++) {
1699
                        if (element.equals(currentSelection[i])) {
1700
                            return true;
1701
                        }
1702
                    }
1703
                }
1704
                return false;
1705
            }
1706

    
1707
            /*
1708
             * (non-Javadoc)
1709
             *
1710
             * @see org.eclipse.jface.viewers.ILabelProvider#getText(java.lang.Object)
1711
             */
1712
            private String getText(Object element) {
1713
                if (element instanceof ItemsListSeparator) {
1714
                    return getSeparatorLabel(((ItemsListSeparator) element)
1715
                            .getName());
1716
                }
1717

    
1718
                String str = provider.getText(element);
1719
                if (selectionDecorator != null && isSelected(element)) {
1720
                    return selectionDecorator.decorateText(str.toString(), element);
1721
                }
1722

    
1723
                return str;
1724
            }
1725

    
1726
            private StyledString getStyledText(Object element,
1727
                    IStyledLabelProvider provider) {
1728
                StyledString string = provider.getStyledText(element);
1729

    
1730
                if (selectionDecorator != null && isSelected(element)) {
1731
                    String decorated = selectionDecorator.decorateText(string
1732
                            .getString(), element);
1733
                    return StyledCellLabelProvider.styleDecoratedString(decorated, null, string);
1734
                    // no need to add colors when element is selected
1735
                }
1736
                return string;
1737
            }
1738

    
1739
            @Override
1740
            public void update(ViewerCell cell) {
1741
                Object element = cell.getElement();
1742

    
1743
                if (!(element instanceof ItemsListSeparator)
1744
                        && provider instanceof IStyledLabelProvider) {
1745
                    IStyledLabelProvider styledLabelProvider = (IStyledLabelProvider) provider;
1746
                    StyledString styledString = getStyledText(element,
1747
                            styledLabelProvider);
1748

    
1749
                    cell.setText(styledString.getString());
1750
                    cell.setStyleRanges(styledString.getStyleRanges());
1751
                    cell.setImage(styledLabelProvider.getImage(element));
1752
                } else {
1753
                    cell.setText(getText(element));
1754
                    cell.setImage(getImage(element));
1755
                }
1756
                cell.setFont(getFont(element));
1757
                cell.setForeground(getForeground(element));
1758
                cell.setBackground(getBackground(element));
1759

    
1760
                super.update(cell);
1761
            }
1762

    
1763
            private String getSeparatorLabel(String separatorLabel) {
1764
                Rectangle rect = list.getTable().getBounds();
1765

    
1766
                int borderWidth = list.getTable().computeTrim(0, 0, 0, 0).width;
1767

    
1768
                int imageWidth = WorkbenchImages.getImage(
1769
                        IWorkbenchGraphicConstants.IMG_OBJ_SEPARATOR).getBounds().width;
1770

    
1771
                int width = rect.width - borderWidth - imageWidth;
1772

    
1773
                GC gc = new GC(list.getTable());
1774
                gc.setFont(list.getTable().getFont());
1775

    
1776
                int fSeparatorWidth = gc.getAdvanceWidth('-');
1777
                int fMessageLength = gc.textExtent(separatorLabel).x;
1778

    
1779
                gc.dispose();
1780

    
1781
                StringBuffer dashes = new StringBuffer();
1782
                int chars = (((width - fMessageLength) / fSeparatorWidth) / 2) - 2;
1783
                for (int i = 0; i < chars; i++) {
1784
                    dashes.append('-');
1785
                }
1786

    
1787
                StringBuffer result = new StringBuffer();
1788
                result.append(dashes);
1789
                result.append(" " + separatorLabel + " "); //$NON-NLS-1$//$NON-NLS-2$
1790
                result.append(dashes);
1791
                return result.toString().trim();
1792
            }
1793

    
1794
            /*
1795
             * (non-Javadoc)
1796
             *
1797
             * @see org.eclipse.jface.viewers.IBaseLabelProvider#addListener(org.eclipse.jface.viewers.ILabelProviderListener)
1798
             */
1799
            @Override
1800
            public void addListener(ILabelProviderListener listener) {
1801
                listeners.add(listener);
1802
            }
1803

    
1804
            /*
1805
             * (non-Javadoc)
1806
             *
1807
             * @see org.eclipse.jface.viewers.IBaseLabelProvider#dispose()
1808
             */
1809
            @Override
1810
            public void dispose() {
1811
                provider.removeListener(this);
1812
                provider.dispose();
1813

    
1814
                if (selectionDecorator != null) {
1815
                    selectionDecorator.removeListener(this);
1816
                    selectionDecorator.dispose();
1817
                }
1818

    
1819
                super.dispose();
1820
            }
1821

    
1822
            /*
1823
             * (non-Javadoc)
1824
             *
1825
             * @see org.eclipse.jface.viewers.IBaseLabelProvider#isLabelProperty(java.lang.Object,
1826
             *      java.lang.String)
1827
             */
1828
            @Override
1829
            public boolean isLabelProperty(Object element, String property) {
1830
                if (provider.isLabelProperty(element, property)) {
1831
                    return true;
1832
                }
1833
                if (selectionDecorator != null
1834
                        && selectionDecorator.isLabelProperty(element, property)) {
1835
                    return true;
1836
                }
1837
                return false;
1838
            }
1839

    
1840
            /*
1841
             * (non-Javadoc)
1842
             *
1843
             * @see org.eclipse.jface.viewers.IBaseLabelProvider#removeListener(org.eclipse.jface.viewers.ILabelProviderListener)
1844
             */
1845
            @Override
1846
            public void removeListener(ILabelProviderListener listener) {
1847
                listeners.remove(listener);
1848
            }
1849

    
1850
            private Color getBackground(Object element) {
1851
                if (element instanceof ItemsListSeparator) {
1852
                    return null;
1853
                }
1854
                if (provider instanceof IColorProvider) {
1855
                    return ((IColorProvider) provider).getBackground(element);
1856
                }
1857
                return null;
1858
            }
1859

    
1860
            private Color getForeground(Object element) {
1861
                if (element instanceof ItemsListSeparator) {
1862
                    return Display.getCurrent().getSystemColor(
1863
                            SWT.COLOR_WIDGET_NORMAL_SHADOW);
1864
                }
1865
                if (provider instanceof IColorProvider) {
1866
                    return ((IColorProvider) provider).getForeground(element);
1867
                }
1868
                return null;
1869
            }
1870

    
1871
            private Font getFont(Object element) {
1872
                if (element instanceof ItemsListSeparator) {
1873
                    return null;
1874
                }
1875
                if (provider instanceof IFontProvider) {
1876
                    return ((IFontProvider) provider).getFont(element);
1877
                }
1878
                return null;
1879
            }
1880

    
1881
            /*
1882
             * (non-Javadoc)
1883
             *
1884
             * @see org.eclipse.jface.viewers.ILabelProviderListener#labelProviderChanged(org.eclipse.jface.viewers.LabelProviderChangedEvent)
1885
             */
1886
            @Override
1887
            public void labelProviderChanged(LabelProviderChangedEvent event) {
1888
                Object[] l = listeners.getListeners();
1889
                for (int i = 0; i < listeners.size(); i++) {
1890
                    ((ILabelProviderListener) l[i]).labelProviderChanged(event);
1891
                }
1892
            }
1893
        }
1894

    
1895
        /**
1896
         * Used in ItemsListContentProvider, separates history and non-history
1897
         * items.
1898
         */
1899
        private class ItemsListSeparator {
1900

    
1901
            private final String name;
1902

    
1903
            /**
1904
             * Creates a new instance of the class.
1905
             *
1906
             * @param name
1907
             *            the name of the separator
1908
             */
1909
            public ItemsListSeparator(String name) {
1910
                this.name = name;
1911
            }
1912

    
1913
            /**
1914
             * Returns the name of this separator.
1915
             *
1916
             * @return the name of the separator
1917
             */
1918
            public String getName() {
1919
                return name;
1920
            }
1921
        }
1922

    
1923
        /**
1924
         * GranualProgressMonitor is used for monitoring progress of filtering
1925
         * process. It is used by <code>RefreshProgressMessageJob</code> to
1926
         * refresh progress message. State of this monitor illustrates state of
1927
         * filtering or cache refreshing process.
1928
         *
1929
         */
1930
        private class GranualProgressMonitor extends ProgressMonitorWrapper {
1931

    
1932
            private String name;
1933

    
1934
            private String subName;
1935

    
1936
            private int totalWork;
1937

    
1938
            private double worked;
1939

    
1940
            private boolean done;
1941

    
1942
            /**
1943
             * Creates instance of <code>GranualProgressMonitor</code>.
1944
             *
1945
             * @param monitor
1946
             *            progress to be wrapped
1947
             */
1948
            public GranualProgressMonitor(IProgressMonitor monitor) {
1949
                super(monitor);
1950
            }
1951

    
1952
            /**
1953
             * Checks if filtering has been done
1954
             *
1955
             * @return true if filtering work has been done false in other way
1956
             */
1957
            public boolean isDone() {
1958
                return done;
1959
            }
1960

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

    
1973
            /*
1974
             * (non-Javadoc)
1975
             *
1976
             * @see org.eclipse.core.runtime.ProgressMonitorWrapper#subTask(java.lang.String)
1977
             */
1978
            @Override
1979
            public void subTask(String name) {
1980
                super.subTask(name);
1981
                this.subName = name;
1982
            }
1983

    
1984
            /*
1985
             * (non-Javadoc)
1986
             *
1987
             * @see org.eclipse.core.runtime.ProgressMonitorWrapper#beginTask(java.lang.String,
1988
             *      int)
1989
             */
1990
            @Override
1991
            public void beginTask(String name, int totalWork) {
1992
                super.beginTask(name, totalWork);
1993
                if (this.name == null) {
1994
                    this.name = name;
1995
                }
1996
                this.totalWork = totalWork;
1997
                refreshProgressMessageJob.scheduleProgressRefresh(this);
1998
            }
1999

    
2000
            /*
2001
             * (non-Javadoc)
2002
             *
2003
             * @see org.eclipse.core.runtime.ProgressMonitorWrapper#worked(int)
2004
             */
2005
            @Override
2006
            public void worked(int work) {
2007
                super.worked(work);
2008
                internalWorked(work);
2009
            }
2010

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

    
2022
            /*
2023
             * (non-Javadoc)
2024
             *
2025
             * @see org.eclipse.core.runtime.ProgressMonitorWrapper#setCanceled(boolean)
2026
             */
2027
            @Override
2028
            public void setCanceled(boolean b) {
2029
                done = b;
2030
                super.setCanceled(b);
2031
            }
2032

    
2033
            /*
2034
             * (non-Javadoc)
2035
             *
2036
             * @see org.eclipse.core.runtime.ProgressMonitorWrapper#internalWorked(double)
2037
             */
2038
            @Override
2039
            public void internalWorked(double work) {
2040
                worked = worked + work;
2041
            }
2042

    
2043
            private String getMessage() {
2044
                if (done) {
2045
                    return ""; //$NON-NLS-1$
2046
                }
2047

    
2048
                String message;
2049

    
2050
                if (name == null) {
2051
                    message = subName == null ? "" : subName; //$NON-NLS-1$
2052
                } else {
2053
                    message = subName == null ? name
2054
                            : NLS
2055
                                    .bind(
2056
                                            WorkbenchMessages.FilteredItemsSelectionDialog_subtaskProgressMessage,
2057
                                            new Object[] { name, subName });
2058
                }
2059
                if (totalWork == 0) {
2060
                    return message;
2061
                }
2062

    
2063
                return NLS
2064
                        .bind(
2065
                                WorkbenchMessages.FilteredItemsSelectionDialog_taskProgressMessage,
2066
                                new Object[] {
2067
                                        message,
2068
                                        new Integer(
2069
                                                (int) ((worked * 100) / totalWork)) });
2070

    
2071
            }
2072

    
2073
        }
2074

    
2075
        /**
2076
         * Filters items history and schedule filter job.
2077
         */
2078
        private class FilterHistoryJob extends Job {
2079

    
2080
            /**
2081
             * Filter used during the filtering process.
2082
             */
2083
            private ItemsFilter itemsFilter;
2084

    
2085
            /**
2086
             * Creates new instance of receiver.
2087
             */
2088
            public FilterHistoryJob() {
2089
                super(WorkbenchMessages.FilteredItemsSelectionDialog_jobLabel);
2090
                setSystem(true);
2091
            }
2092

    
2093
            /*
2094
             * (non-Javadoc)
2095
             *
2096
             * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
2097
             */
2098
            @Override
2099
            protected IStatus run(IProgressMonitor monitor) {
2100

    
2101
                this.itemsFilter = filter;
2102

    
2103
                contentProvider.reset();
2104

    
2105
                refreshWithLastSelection = false;
2106

    
2107
                contentProvider.addHistoryItems(itemsFilter);
2108

    
2109
                if (!(lastCompletedFilter != null && lastCompletedFilter
2110
                        .isSubFilter(this.itemsFilter))) {
2111
                    contentProvider.refresh();
2112
                }
2113

    
2114
                filterJob.schedule();
2115

    
2116
                return Status.OK_STATUS;
2117
            }
2118

    
2119
        }
2120

    
2121
        /**
2122
         * Filters items in indicated set and history. During filtering, it
2123
         * refreshes the dialog (progress monitor and elements list).
2124
         *
2125
         * Depending on the filter, <code>FilterJob</code> decides which kind of
2126
         * search will be run inside <code>filterContent</code>. If the last
2127
         * filtering is done (last completed filter), is not null, and the new
2128
         * filter is a sub-filter ({@link FilteredItemsSelectionDialog.ItemsFilter#isSubFilter(FilteredItemsSelectionDialog.ItemsFilter)})
2129
         * of the last, then <code>FilterJob</code> only filters in the cache. If
2130
         * it is the first filtering or the new filter isn't a sub-filter of the
2131
         * last one, a full search is run.
2132
         */
2133
        private class FilterJob extends Job {
2134

    
2135
            /**
2136
             * Filter used during the filtering process.
2137
             */
2138
            protected ItemsFilter itemsFilter;
2139

    
2140
            /**
2141
             * Creates new instance of FilterJob
2142
             */
2143
            public FilterJob() {
2144
                super(WorkbenchMessages.FilteredItemsSelectionDialog_jobLabel);
2145
                setSystem(true);
2146
            }
2147

    
2148
            /*
2149
             * (non-Javadoc)
2150
             *
2151
             * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
2152
             */
2153
            @Override
2154
            protected final IStatus run(IProgressMonitor parent) {
2155
                GranualProgressMonitor monitor = new GranualProgressMonitor(parent);
2156
                return doRun(monitor);
2157
            }
2158

    
2159
            /**
2160
             * Executes job using the given filtering progress monitor. A hook for
2161
             * subclasses.
2162
             *
2163
             * @param monitor
2164
             *            progress monitor
2165
             * @return result of the execution
2166
             */
2167
            protected IStatus doRun(GranualProgressMonitor monitor) {
2168
                try {
2169
                    internalRun(monitor);
2170
                } catch (CoreException e) {
2171
                    cancel();
2172
                    return new Status(
2173
                            IStatus.ERROR,
2174
                            PlatformUI.PLUGIN_ID,
2175
                            IStatus.ERROR,
2176
                            WorkbenchMessages.FilteredItemsSelectionDialog_jobError,
2177
                            e);
2178
                }
2179
                return Status.OK_STATUS;
2180
            }
2181

    
2182
            /**
2183
             * Main method for the job.
2184
             *
2185
             * @param monitor
2186
             * @throws CoreException
2187
             */
2188
            private void internalRun(GranualProgressMonitor monitor)
2189
                    throws CoreException {
2190
                try {
2191
                    if (monitor.isCanceled()) {
2192
                        return;
2193
                    }
2194

    
2195
                    this.itemsFilter = filter;
2196

    
2197
                    if (filter.getPattern().length() != 0) {
2198
                        filterContent(monitor);
2199
                    }
2200

    
2201
                    if (monitor.isCanceled()) {
2202
                        return;
2203
                    }
2204

    
2205
                    contentProvider.refresh();
2206
                } finally {
2207
                    monitor.done();
2208
                }
2209
            }
2210

    
2211
            /**
2212
             * Filters items.
2213
             *
2214
             * @param monitor
2215
             *            for monitoring progress
2216
             * @throws CoreException
2217
             */
2218
            protected void filterContent(GranualProgressMonitor monitor)
2219
                    throws CoreException {
2220

    
2221
//                if (lastCompletedFilter != null
2222
//                        && lastCompletedFilter.isSubFilter(this.itemsFilter)) {
2223
//
2224
//                    int length = lastCompletedResult.size() / 500;
2225
//                    monitor
2226
//                            .beginTask(
2227
//                                    WorkbenchMessages.FilteredItemsSelectionDialog_cacheSearchJob_taskName,
2228
//                                    length);
2229
//
2230
//                    for (int pos = 0; pos < lastCompletedResult.size(); pos++) {
2231
//
2232
//                        Object item = lastCompletedResult.get(pos);
2233
//                        if (monitor.isCanceled()) {
2234
//                            break;
2235
//                        }
2236
//                        contentProvider.add(item, itemsFilter);
2237
//
2238
//                        if ((pos % 500) == 0) {
2239
//                            monitor.worked(1);
2240
//                        }
2241
//                    }
2242
//
2243
//                } else {
2244

    
2245
                    lastCompletedFilter = null;
2246
                    lastCompletedResult = null;
2247

    
2248
                    SubProgressMonitor subMonitor = null;
2249
                    if (monitor != null) {
2250
                        monitor
2251
                                .beginTask(
2252
                                        WorkbenchMessages.FilteredItemsSelectionDialog_searchJob_taskName,
2253
                                        100);
2254
                        subMonitor = new SubProgressMonitor(monitor, 95);
2255

    
2256
                    }
2257

    
2258
                    fillContentProvider(contentProvider, itemsFilter, subMonitor);
2259

    
2260
                    if (monitor != null && !monitor.isCanceled()) {
2261
                        monitor.worked(2);
2262
                        contentProvider.rememberResult(itemsFilter);
2263
                        monitor.worked(3);
2264
                    }
2265
                //}
2266

    
2267
            }
2268

    
2269
        }
2270

    
2271
        /**
2272
         * History stores a list of key, object pairs. The list is bounded at a
2273
         * certain size. If the list exceeds this size the oldest element is removed
2274
         * from the list. An element can be added/renewed with a call to
2275
         * <code>accessed(Object)</code>.
2276
         * <p>
2277
         * The history can be stored to/loaded from an XML file.
2278
         */
2279
        protected static abstract class SelectionHistory {
2280

    
2281
            private static final String DEFAULT_ROOT_NODE_NAME = "historyRootNode"; //$NON-NLS-1$
2282

    
2283
            private static final String DEFAULT_INFO_NODE_NAME = "infoNode"; //$NON-NLS-1$
2284

    
2285
            private static final int MAX_HISTORY_SIZE = 60;
2286

    
2287
            private final Set historyList;
2288

    
2289
            private final String rootNodeName;
2290

    
2291
            private final String infoNodeName;
2292

    
2293
            private SelectionHistory(String rootNodeName, String infoNodeName) {
2294

    
2295
                historyList = Collections.synchronizedSet(new LinkedHashSet() {
2296

    
2297
                    private static final long serialVersionUID = 0L;
2298

    
2299
                    /*
2300
                     * (non-Javadoc)
2301
                     *
2302
                     * @see java.util.LinkedList#add(java.lang.Object)
2303
                     */
2304
                    @Override
2305
                    public boolean add(Object arg0) {
2306
                        if (this.size() >= MAX_HISTORY_SIZE) {
2307
                            Iterator iterator = this.iterator();
2308
                            iterator.next();
2309
                            iterator.remove();
2310
                        }
2311
                        return super.add(arg0);
2312
                    }
2313

    
2314
                });
2315

    
2316
                this.rootNodeName = rootNodeName;
2317
                this.infoNodeName = infoNodeName;
2318
            }
2319

    
2320
            /**
2321
             * Creates new instance of <code>SelectionHistory</code>.
2322
             */
2323
            public SelectionHistory() {
2324
                this(DEFAULT_ROOT_NODE_NAME, DEFAULT_INFO_NODE_NAME);
2325
            }
2326

    
2327
            /**
2328
             * Adds object to history.
2329
             *
2330
             * @param object
2331
             *            the item to be added to the history
2332
             */
2333
            public synchronized void accessed(Object object) {
2334
                historyList.remove(object);
2335
                historyList.add(object);
2336
            }
2337

    
2338
            /**
2339
             * Returns <code>true</code> if history contains object.
2340
             *
2341
             * @param object
2342
             *            the item for which check will be executed
2343
             * @return <code>true</code> if history contains object
2344
             *         <code>false</code> in other way
2345
             */
2346
            public synchronized boolean contains(Object object) {
2347
                return historyList.contains(object);
2348
            }
2349

    
2350
            /**
2351
             * Returns <code>true</code> if history is empty.
2352
             *
2353
             * @return <code>true</code> if history is empty
2354
             */
2355
            public synchronized boolean isEmpty() {
2356
                return historyList.isEmpty();
2357
            }
2358

    
2359
            /**
2360
             * Remove element from history.
2361
             *
2362
             * @param element
2363
             *            to remove form the history
2364
             * @return <code>true</code> if this list contained the specified
2365
             *         element
2366
             */
2367
            public synchronized boolean remove(Object element) {
2368
                return historyList.remove(element);
2369
            }
2370

    
2371
            /**
2372
             * Load history elements from memento.
2373
             *
2374
             * @param memento
2375
             *            memento from which the history will be retrieved
2376
             */
2377
            public void load(IMemento memento) {
2378

    
2379
                XMLMemento historyMemento = (XMLMemento) memento
2380
                        .getChild(rootNodeName);
2381

    
2382
                if (historyMemento == null) {
2383
                    return;
2384
                }
2385

    
2386
                IMemento[] mementoElements = historyMemento
2387
                        .getChildren(infoNodeName);
2388
                for (int i = 0; i < mementoElements.length; ++i) {
2389
                    IMemento mementoElement = mementoElements[i];
2390
                    Object object = restoreItemFromMemento(mementoElement);
2391
                    if (object != null) {
2392
                        historyList.add(object);
2393
                    }
2394
                }
2395
            }
2396

    
2397
            /**
2398
             * Save history elements to memento.
2399
             *
2400
             * @param memento
2401
             *            memento to which the history will be added
2402
             */
2403
            public void save(IMemento memento) {
2404

    
2405
                IMemento historyMemento = memento.createChild(rootNodeName);
2406

    
2407
                Object[] items = getHistoryItems();
2408
                for (int i = 0; i < items.length; i++) {
2409
                    Object item = items[i];
2410
                    IMemento elementMemento = historyMemento
2411
                            .createChild(infoNodeName);
2412
                    storeItemToMemento(item, elementMemento);
2413
                }
2414

    
2415
            }
2416

    
2417
            /**
2418
             * Gets array of history items.
2419
             *
2420
             * @return array of history elements
2421
             */
2422
            public synchronized Object[] getHistoryItems() {
2423
                return historyList.toArray();
2424
            }
2425

    
2426
            /**
2427
             * Creates an object using given memento.
2428
             *
2429
             * @param memento
2430
             *            memento used for creating new object
2431
             *
2432
             * @return the restored object
2433
             */
2434
            protected abstract Object restoreItemFromMemento(IMemento memento);
2435

    
2436
            /**
2437
             * Store object in <code>IMemento</code>.
2438
             *
2439
             * @param item
2440
             *            the item to store
2441
             * @param memento
2442
             *            the memento to store to
2443
             */
2444
            protected abstract void storeItemToMemento(Object item, IMemento memento);
2445

    
2446
        }
2447

    
2448
        /**
2449
         * Filters elements using SearchPattern by comparing the names of items with
2450
         * the filter pattern.
2451
         */
2452
        protected abstract class ItemsFilter {
2453

    
2454
            protected SearchPattern patternMatcher;
2455

    
2456
            /**
2457
             * Creates new instance of ItemsFilter.
2458
             */
2459
            public ItemsFilter() {
2460
                this(new SearchPattern());
2461
            }
2462

    
2463
            /**
2464
             * Creates new instance of ItemsFilter.
2465
             *
2466
             * @param searchPattern
2467
             *            the pattern to be used when filtering
2468
             */
2469
            public ItemsFilter(SearchPattern searchPattern) {
2470
                patternMatcher = searchPattern;
2471
                String stringPattern = ""; //$NON-NLS-1$
2472
                if (pattern != null && !pattern.getText().equals("*")) { //$NON-NLS-1$
2473
                    stringPattern = pattern.getText();
2474
                }
2475
                patternMatcher.setPattern(stringPattern);
2476
            }
2477

    
2478
            /**
2479
             * Check if the given filter is a sub-filter of this filter. The default
2480
             * implementation checks if the <code>SearchPattern</code> from the
2481
             * given filter is a sub-pattern of the one from this filter.
2482
             * <p>
2483
             * <i>WARNING: This method is <b>not</b> defined in reading order, i.e.
2484
             * <code>a.isSubFilter(b)</code> is <code>true</code> iff
2485
             * <code>b</code> is a sub-filter of <code>a</code>, and not
2486
             * vice-versa. </i>
2487
             * </p>
2488
             *
2489
             * @param filter
2490
             *            the filter to be checked, or <code>null</code>
2491
             * @return <code>true</code> if the given filter is sub-filter of this
2492
             *         filter, <code>false</code> if the given filter isn't a
2493
             *         sub-filter or is <code>null</code>
2494
             *
2495
             * @see org.eclipse.ui.dialogs.SearchPattern#isSubPattern(org.eclipse.ui.dialogs.SearchPattern)
2496
             */
2497
            public boolean isSubFilter(ItemsFilter filter) {
2498
                if (filter != null) {
2499
                    return this.patternMatcher.isSubPattern(filter.patternMatcher);
2500
                }
2501
                return false;
2502
            }
2503

    
2504
            /**
2505
             * Checks whether the provided filter is equal to the current filter.
2506
             * The default implementation checks if <code>SearchPattern</code>
2507
             * from current filter is equal to the one from provided filter.
2508
             *
2509
             * @param filter
2510
             *            filter to be checked, or <code>null</code>
2511
             * @return <code>true</code> if the given filter is equal to current
2512
             *         filter, <code>false</code> if given filter isn't equal to
2513
             *         current one or if it is <code>null</code>
2514
             *
2515
             * @see org.eclipse.ui.dialogs.SearchPattern#equalsPattern(org.eclipse.ui.dialogs.SearchPattern)
2516
             */
2517
            public boolean equalsFilter(ItemsFilter filter) {
2518
                if (filter != null
2519
                        && filter.patternMatcher.equalsPattern(this.patternMatcher)) {
2520
                    return true;
2521
                }
2522
                return false;
2523
            }
2524

    
2525
            /**
2526
             * Checks whether the pattern's match rule is camel case.
2527
             *
2528
             * @return <code>true</code> if pattern's match rule is camel case,
2529
             *         <code>false</code> otherwise
2530
             */
2531
            public boolean isCamelCasePattern() {
2532
                return patternMatcher.getMatchRule() == SearchPattern.RULE_CAMELCASE_MATCH;
2533
            }
2534

    
2535
            /**
2536
             * Returns the pattern string.
2537
             *
2538
             * @return pattern for this filter
2539
             *
2540
             * @see SearchPattern#getPattern()
2541
             */
2542
            public String getPattern() {
2543
                return patternMatcher.getPattern();
2544
            }
2545

    
2546
            /**
2547
             * Returns the rule to apply for matching keys.
2548
             *
2549
             * @return an implementation-specific match rule
2550
             *
2551
             * @see SearchPattern#getMatchRule() for match rules returned by the
2552
             *      default implementation
2553
             */
2554
            public int getMatchRule() {
2555
                return patternMatcher.getMatchRule();
2556
            }
2557

    
2558
            /**
2559
             * Matches text with filter.
2560
             *
2561
             * @param text
2562
             *            the text to match with the filter
2563
             * @return <code>true</code> if text matches with filter pattern,
2564
             *         <code>false</code> otherwise
2565
             */
2566
            protected boolean matches(String text) {
2567
                return patternMatcher.matches(text);
2568
            }
2569

    
2570
            /**
2571
             * General method for matching raw name pattern. Checks whether current
2572
             * pattern is prefix of name provided item.
2573
             *
2574
             * @param item
2575
             *            item to check
2576
             * @return <code>true</code> if current pattern is a prefix of name
2577
             *         provided item, <code>false</code> if item's name is shorter
2578
             *         than prefix or sequences of characters don't match.
2579
             */
2580
            public boolean matchesRawNamePattern(Object item) {
2581
                String prefix = patternMatcher.getPattern();
2582
                String text = getElementName(item);
2583

    
2584
                if (text == null) {
2585
                    return false;
2586
                }
2587

    
2588
                int textLength = text.length();
2589
                int prefixLength = prefix.length();
2590
                if (textLength < prefixLength) {
2591
                    return false;
2592
                }
2593
                for (int i = prefixLength - 1; i >= 0; i--) {
2594
                    if (Character.toLowerCase(prefix.charAt(i)) != Character
2595
                            .toLowerCase(text.charAt(i))) {
2596
                        return false;
2597
                    }
2598
                }
2599
                return true;
2600
            }
2601

    
2602
            /**
2603
             * Matches an item against filter conditions.
2604
             *
2605
             * @param item
2606
             * @return <code>true<code> if item matches against filter conditions, <code>false</code>
2607
             *         otherwise
2608
             */
2609
            public abstract boolean matchItem(Object item);
2610

    
2611
            /**
2612
             * Checks consistency of an item. Item is inconsistent if was changed or
2613
             * removed.
2614
             *
2615
             * @param item
2616
             * @return <code>true</code> if item is consistent, <code>false</code>
2617
             *         if item is inconsistent
2618
             */
2619
            public abstract boolean isConsistentItem(Object item);
2620

    
2621
        }
2622

    
2623
        /**
2624
         * An interface to content providers for
2625
         * <code>FilterItemsSelectionDialog</code>.
2626
         */
2627
        protected abstract class AbstractContentProvider {
2628
            /**
2629
             * Adds the item to the content provider iff the filter matches the
2630
             * item. Otherwise does nothing.
2631
             *
2632
             * @param item
2633
             *            the item to add
2634
             * @param itemsFilter
2635
             *            the filter
2636
             *
2637
             * @see FilteredItemsSelectionDialog.ItemsFilter#matchItem(Object)
2638
             */
2639
            public abstract void add(Object item, ItemsFilter itemsFilter);
2640
        }
2641

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

    
2661
            private SelectionHistory selectionHistory;
2662

    
2663
            /**
2664
             * Raw result of the searching (unsorted, unfiltered).
2665
             * <p>
2666
             * Standard object flow:
2667
             * <code>items -> lastSortedItems -> lastFilteredItems</code>
2668
             */
2669
            private final Set items;
2670

    
2671
            /**
2672
             * Items that are duplicates.
2673
             */
2674
            private final Set duplicates;
2675

    
2676
            /**
2677
             * List of <code>ViewerFilter</code>s to be used during filtering
2678
             */
2679
            private List filters;
2680

    
2681
            /**
2682
             * Result of the last filtering.
2683
             * <p>
2684
             * Standard object flow:
2685
             * <code>items -> lastSortedItems -> lastFilteredItems</code>
2686
             */
2687
            private List lastFilteredItems;
2688

    
2689
            /**
2690
             * Result of the last sorting.
2691
             * <p>
2692
             * Standard object flow:
2693
             * <code>items -> lastSortedItems -> lastFilteredItems</code>
2694
             */
2695
            private final List lastSortedItems;
2696

    
2697
            /**
2698
             * Used for <code>getFilteredItems()</code> method canceling (when the
2699
             * job that invoked the method was canceled).
2700
             * <p>
2701
             * Method canceling could be based (only) on monitor canceling
2702
             * unfortunately sometimes the method <code>getFilteredElements()</code>
2703
             * could be run with a null monitor, the <code>reset</code> flag have
2704
             * to be left intact.
2705
             */
2706
            private boolean reset;
2707

    
2708
            /**
2709
             * Creates new instance of <code>ContentProvider</code>.
2710
             */
2711
            public ContentProvider() {
2712
                this.items = Collections.synchronizedSet(new HashSet(2048));
2713
                this.duplicates = Collections.synchronizedSet(new HashSet(256));
2714
                this.lastFilteredItems = new ArrayList();
2715
                this.lastSortedItems = Collections.synchronizedList(new ArrayList(
2716
                        2048));
2717
            }
2718

    
2719
            /**
2720
             * Sets selection history.
2721
             *
2722
             * @param selectionHistory
2723
             *            The selectionHistory to set.
2724
             */
2725
            public void setSelectionHistory(SelectionHistory selectionHistory) {
2726
                this.selectionHistory = selectionHistory;
2727
            }
2728

    
2729
            /**
2730
             * @return Returns the selectionHistory.
2731
             */
2732
            public SelectionHistory getSelectionHistory() {
2733
                return selectionHistory;
2734
            }
2735

    
2736
            /**
2737
             * Removes all content items and resets progress message.
2738
             */
2739
            public void reset() {
2740
                reset = true;
2741
                this.items.clear();
2742
                this.duplicates.clear();
2743
                this.lastSortedItems.clear();
2744
            }
2745

    
2746
            /**
2747
             * Stops reloading cache - <code>getFilteredItems()</code> method.
2748
             */
2749
            public void stopReloadingCache() {
2750
                reset = true;
2751
            }
2752

    
2753
            /**
2754
             * Adds filtered item.
2755
             *
2756
             * @param item
2757
             * @param itemsFilter
2758
             */
2759
            @Override
2760
            public void add(Object item, ItemsFilter itemsFilter) {
2761
                if (itemsFilter == filter) {
2762
                    if (itemsFilter != null) {
2763
                        if (itemsFilter.matchItem(item)) {
2764
                            this.items.add(item);
2765
                        }
2766
                    } else {
2767
                        this.items.add(item);
2768
                    }
2769
                }
2770
            }
2771

    
2772
            /**
2773
             * Add all history items to <code>contentProvider</code>.
2774
             *
2775
             * @param itemsFilter
2776
             */
2777
            public void addHistoryItems(ItemsFilter itemsFilter) {
2778
                if (this.selectionHistory != null) {
2779
                    Object[] items = this.selectionHistory.getHistoryItems();
2780
                    for (int i = 0; i < items.length; i++) {
2781
                        Object item = items[i];
2782
                        if (itemsFilter == filter) {
2783
                            if (itemsFilter != null) {
2784
                                if (itemsFilter.matchItem(item)) {
2785
                                    if (itemsFilter.isConsistentItem(item)) {
2786
                                        this.items.add(item);
2787
                                    } else {
2788
                                        this.selectionHistory.remove(item);
2789
                                    }
2790
                                }
2791
                            }
2792
                        }
2793
                    }
2794
                }
2795
            }
2796

    
2797
            /**
2798
             * Refresh dialog.
2799
             */
2800
            public void refresh() {
2801
                scheduleRefresh();
2802
            }
2803

    
2804
            /**
2805
             * Removes items from history and refreshes the view.
2806
             *
2807
             * @param item
2808
             *            to remove
2809
             *
2810
             * @return removed item
2811
             */
2812
            public Object removeHistoryElement(Object item) {
2813
                if (this.selectionHistory != null) {
2814
                    this.selectionHistory.remove(item);
2815
                }
2816
                if (filter == null || filter.getPattern().length() == 0) {
2817
                    items.remove(item);
2818
                    duplicates.remove(item);
2819
                    this.lastSortedItems.remove(item);
2820
                }
2821

    
2822
                synchronized (lastSortedItems) {
2823
                    Collections.sort(lastSortedItems, getHistoryComparator());
2824
                }
2825
                return item;
2826
            }
2827

    
2828
            /**
2829
             * Adds item to history and refresh view.
2830
             *
2831
             * @param item
2832
             *            to add
2833
             */
2834
            public void addHistoryElement(Object item) {
2835
                if (this.selectionHistory != null) {
2836
                    this.selectionHistory.accessed(item);
2837
                }
2838
                if (filter == null || !filter.matchItem(item)) {
2839
                    this.items.remove(item);
2840
                    this.duplicates.remove(item);
2841
                    this.lastSortedItems.remove(item);
2842
                }
2843
                synchronized (lastSortedItems) {
2844
                    Collections.sort(lastSortedItems, getHistoryComparator());
2845
                }
2846
                this.refresh();
2847
            }
2848

    
2849
            /**
2850
             * @param item
2851
             * @return <code>true</code> if given item is part of the history
2852
             */
2853
            public boolean isHistoryElement(Object item) {
2854
                if (this.selectionHistory != null) {
2855
                    return this.selectionHistory.contains(item);
2856
                }
2857
                return false;
2858
            }
2859

    
2860
            /**
2861
             * Sets/unsets given item as duplicate.
2862
             *
2863
             * @param item
2864
             *            item to change
2865
             *
2866
             * @param isDuplicate
2867
             *            duplicate flag
2868
             */
2869
            public void setDuplicateElement(Object item, boolean isDuplicate) {
2870
                if (this.items.contains(item)) {
2871
                    if (isDuplicate) {
2872
                        this.duplicates.add(item);
2873
                    } else {
2874
                        this.duplicates.remove(item);
2875
                    }
2876
                }
2877
            }
2878

    
2879
            /**
2880
             * Indicates whether given item is a duplicate.
2881
             *
2882
             * @param item
2883
             *            item to check
2884
             * @return <code>true</code> if item is duplicate
2885
             */
2886
            public boolean isDuplicateElement(Object item) {
2887
                return duplicates.contains(item);
2888
            }
2889

    
2890
            /**
2891
             * Load history from memento.
2892
             *
2893
             * @param memento
2894
             *            memento from which the history will be retrieved
2895
             */
2896
            public void loadHistory(IMemento memento) {
2897
                if (this.selectionHistory != null) {
2898
                    this.selectionHistory.load(memento);
2899
                }
2900
            }
2901

    
2902
            /**
2903
             * Save history to memento.
2904
             *
2905
             * @param memento
2906
             *            memento to which the history will be added
2907
             */
2908
            public void saveHistory(IMemento memento) {
2909
                if (this.selectionHistory != null) {
2910
                    this.selectionHistory.save(memento);
2911
                }
2912
            }
2913

    
2914
            /**
2915
             * Gets sorted items.
2916
             *
2917
             * @return sorted items
2918
             */
2919
            private Object[] getSortedItems() {
2920
                if (lastSortedItems.size() != items.size()) {
2921
                    synchronized (lastSortedItems) {
2922
                        lastSortedItems.clear();
2923
                        lastSortedItems.addAll(items);
2924
                        Collections.sort(lastSortedItems, getHistoryComparator());
2925
                    }
2926
                }
2927
                return lastSortedItems.toArray();
2928
            }
2929

    
2930
            /**
2931
             * Remember result of filtering.
2932
             *
2933
             * @param itemsFilter
2934
             */
2935
            public void rememberResult(ItemsFilter itemsFilter) {
2936
                List itemsList = Collections.synchronizedList(Arrays
2937
                        .asList(getSortedItems()));
2938
                // synchronization
2939
                if (itemsFilter == filter) {
2940
                    lastCompletedFilter = itemsFilter;
2941
                    lastCompletedResult = itemsList;
2942
                }
2943

    
2944
            }
2945

    
2946
            /*
2947
             * (non-Javadoc)
2948
             *
2949
             * @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object)
2950
             */
2951
            @Override
2952
            public Object[] getElements(Object inputElement) {
2953
                //return items.toArray();
2954
                return lastFilteredItems.toArray();
2955
            }
2956

    
2957
            public int getNumberOfElements() {
2958

    
2959
               return lastFilteredItems.size();
2960
            }
2961

    
2962
            /*
2963
             * (non-Javadoc)
2964
             *
2965
             * @see org.eclipse.jface.viewers.IContentProvider#dispose()
2966
             */
2967
            @Override
2968
            public void dispose() {
2969
            }
2970

    
2971
            /*
2972
             * (non-Javadoc)
2973
             *
2974
             * @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer,
2975
             *      java.lang.Object, java.lang.Object)
2976
             */
2977
            @Override
2978
            public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
2979
            }
2980

    
2981
            /*
2982
             * (non-Javadoc)
2983
             *
2984
             * @see org.eclipse.jface.viewers.ILazyContentProvider#updateElement(int)
2985
             */
2986
            @Override
2987
            public void updateElement(int index) {
2988

    
2989
                CdmFilteredItemsSelectionDialog.this.list.replace((lastFilteredItems
2990
                        .size() > index) ? lastFilteredItems.get(index) : null,
2991
                        index);
2992

    
2993
            }
2994

    
2995
            /**
2996
             * Main method responsible for getting the filtered items and checking
2997
             * for duplicates. It is based on the
2998
             * {@link FilteredItemsSelectionDialog.ContentProvider#getFilteredItems(Object, IProgressMonitor)}.
2999
             *
3000
             * @param checkDuplicates
3001
             *            <code>true</code> if data concerning elements
3002
             *            duplication should be computed - it takes much more time
3003
             *            than standard filtering
3004
             *
3005
             * @param monitor
3006
             *            progress monitor
3007
             */
3008
            public void reloadCache(boolean checkDuplicates,
3009
                    IProgressMonitor monitor) {
3010

    
3011
                reset = false;
3012

    
3013
                if (monitor != null) {
3014
                    // the work is divided into two actions of the same length
3015
                    int totalWork = checkDuplicates ? 200 : 100;
3016

    
3017
                    monitor
3018
                            .beginTask(
3019
                                    WorkbenchMessages.FilteredItemsSelectionDialog_cacheRefreshJob,
3020
                                    totalWork);
3021
                }
3022

    
3023
                // the TableViewer's root (the input) is treated as parent
3024

    
3025
                lastFilteredItems = Arrays.asList(getFilteredItems(list.getInput(),
3026
                        monitor != null ? new SubProgressMonitor(monitor, 100)
3027
                                : null));
3028

    
3029
                if (reset || (monitor != null && monitor.isCanceled())) {
3030
                    if (monitor != null) {
3031
                        monitor.done();
3032
                    }
3033
                    return;
3034
                }
3035

    
3036
                if (checkDuplicates) {
3037
                    checkDuplicates(monitor);
3038
                }
3039
                if (monitor != null) {
3040
                    monitor.done();
3041
                }
3042
            }
3043

    
3044
            private void checkDuplicates(IProgressMonitor monitor) {
3045
                synchronized (lastFilteredItems) {
3046
                    IProgressMonitor subMonitor = null;
3047
                    int reportEvery = lastFilteredItems.size() / 20;
3048
                    if (monitor != null) {
3049
                        subMonitor = new SubProgressMonitor(monitor, 100);
3050
                        subMonitor
3051
                                .beginTask(
3052
                                        WorkbenchMessages.FilteredItemsSelectionDialog_cacheRefreshJob_checkDuplicates,
3053
                                        5);
3054
                    }
3055
                    HashMap helperMap = new HashMap();
3056
                    for (int i = 0; i < lastFilteredItems.size(); i++) {
3057
                        if (reset
3058
                                || (subMonitor != null && subMonitor.isCanceled())) {
3059
                            return;
3060
                        }
3061
                        Object item = lastFilteredItems.get(i);
3062

    
3063
                        if (!(item instanceof ItemsListSeparator)) {
3064
                            Object previousItem = helperMap.put(
3065
                                    getElementName(item), item);
3066
                            if (previousItem != null) {
3067
                                setDuplicateElement(previousItem, true);
3068
                                setDuplicateElement(item, true);
3069
                            } else {
3070
                                setDuplicateElement(item, false);
3071
                            }
3072
                        }
3073

    
3074
                        if (subMonitor != null && reportEvery != 0
3075
                                && (i + 1) % reportEvery == 0) {
3076
                            subMonitor.worked(1);
3077
                        }
3078
                    }
3079
                    helperMap.clear();
3080
                }
3081
            }
3082

    
3083
            /**
3084
             * Returns an array of items filtered using the provided
3085
             * <code>ViewerFilter</code>s with a separator added.
3086
             *
3087
             * @param parent
3088
             *            the parent
3089
             * @param monitor
3090
             *            progress monitor, can be <code>null</code>
3091
             * @return an array of filtered items
3092
             */
3093
            protected Object[] getFilteredItems(Object parent,
3094
                    IProgressMonitor monitor) {
3095
                int ticks = 100;
3096
                if (monitor == null) {
3097
                    monitor = new NullProgressMonitor();
3098
                }
3099

    
3100
                monitor
3101
                        .beginTask(
3102
                                WorkbenchMessages.FilteredItemsSelectionDialog_cacheRefreshJob_getFilteredElements,
3103
                                ticks);
3104
                if (filters != null) {
3105
                    ticks /= (filters.size() + 2);
3106
                } else {
3107
                    ticks /= 2;
3108
                }
3109

    
3110
                // get already sorted array
3111
                Object[] filteredElements = getSortedItems();
3112

    
3113
                monitor.worked(ticks);
3114

    
3115
                // filter the elements using provided ViewerFilters
3116
                if (filters != null && filteredElements != null) {
3117
                    for (Iterator iter = filters.iterator(); iter.hasNext();) {
3118
                        ViewerFilter f = (ViewerFilter) iter.next();
3119
                        filteredElements = f.filter(list, parent, filteredElements);
3120
                        monitor.worked(ticks);
3121
                    }
3122
                }
3123

    
3124
                if (filteredElements == null || monitor.isCanceled()) {
3125
                    monitor.done();
3126
                    return new Object[0];
3127
                }
3128

    
3129
                ArrayList preparedElements = new ArrayList();
3130
                boolean hasHistory = false;
3131

    
3132
                if (filteredElements.length > 0) {
3133
                    if (isHistoryElement(filteredElements[0])) {
3134
                        hasHistory = true;
3135
                    }
3136
                }
3137

    
3138
                int reportEvery = filteredElements.length / ticks;
3139

    
3140
                // add separator
3141
                for (int i = 0; i < filteredElements.length; i++) {
3142
                    Object item = filteredElements[i];
3143

    
3144
                    if (hasHistory && !isHistoryElement(item)) {
3145
                        preparedElements.add(itemsListSeparator);
3146
                        hasHistory = false;
3147
                    }
3148

    
3149
                    preparedElements.add(item);
3150

    
3151
                    if (reportEvery != 0 && ((i + 1) % reportEvery == 0)) {
3152
                        monitor.worked(1);
3153
                    }
3154
                }
3155

    
3156
                monitor.done();
3157

    
3158
                return preparedElements.toArray();
3159
            }
3160

    
3161
            /**
3162
             * Adds a filter to this content provider. For an example usage of such
3163
             * filters look at the project <code>org.eclipse.ui.ide</code>, class
3164
             * <code>org.eclipse.ui.dialogs.FilteredResourcesSelectionDialog.CustomWorkingSetFilter</code>.
3165
             *
3166
             *
3167
             * @param filter
3168
             *            the filter to be added
3169
             */
3170
            public void addFilter(ViewerFilter filter) {
3171
                if (filters == null) {
3172
                    filters = new ArrayList();
3173
                }
3174
                filters.add(filter);
3175
                // currently filters are only added when dialog is restored
3176
                // if it is changed, refreshing the whole TableViewer should be
3177
                // added
3178
            }
3179

    
3180
        }
3181

    
3182
        /**
3183
         * A content provider that does nothing.
3184
         */
3185
        private class NullContentProvider implements IContentProvider {
3186

    
3187
            /*
3188
             * (non-Javadoc)
3189
             *
3190
             * @see org.eclipse.jface.viewers.IContentProvider#dispose()
3191
             */
3192
            @Override
3193
            public void dispose() {
3194
            }
3195

    
3196
            /*
3197
             * (non-Javadoc)
3198
             *
3199
             * @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer,
3200
             *      java.lang.Object, java.lang.Object)
3201
             */
3202
            @Override
3203
            public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
3204
            }
3205

    
3206
        }
3207

    
3208
        /**
3209
         * DetailsContentViewer objects are wrappers for labels.
3210
         * DetailsContentViewer provides means to change label's image and text when
3211
         * the attached LabelProvider is updated.
3212
         */
3213
        private class DetailsContentViewer extends ContentViewer {
3214

    
3215
            private final CLabel label;
3216

    
3217
            /**
3218
             * Unfortunately, it was impossible to delegate displaying border to
3219
             * label. The <code>ViewForm</code> is used because
3220
             * <code>CLabel</code> displays shadow when border is present.
3221
             */
3222
            private final ViewForm viewForm;
3223

    
3224
            /**
3225
             * Constructs a new instance of this class given its parent and a style
3226
             * value describing its behavior and appearance.
3227
             *
3228
             * @param parent
3229
             *            the parent component
3230
             * @param style
3231
             *            SWT style bits
3232
             */
3233
            public DetailsContentViewer(Composite parent, int style) {
3234
                viewForm = new ViewForm(parent, style);
3235
                GridData gd = new GridData(GridData.FILL_HORIZONTAL);
3236
                gd.horizontalSpan = 2;
3237
                viewForm.setLayoutData(gd);
3238
                label = new CLabel(viewForm, SWT.FLAT);
3239
                label.setFont(parent.getFont());
3240
                viewForm.setContent(label);
3241
                hookControl(label);
3242
            }
3243

    
3244
            /**
3245
             * Shows/hides the content viewer.
3246
             *
3247
             * @param visible
3248
             *            if the content viewer should be visible.
3249
             */
3250
            public void setVisible(boolean visible) {
3251
                GridData gd = (GridData) viewForm.getLayoutData();
3252
                gd.exclude = !visible;
3253
                viewForm.getParent().layout();
3254
            }
3255

    
3256
            /*
3257
             * (non-Javadoc)
3258
             *
3259
             * @see org.eclipse.jface.viewers.Viewer#inputChanged(java.lang.Object,
3260
             *      java.lang.Object)
3261
             */
3262
            @Override
3263
            protected void inputChanged(Object input, Object oldInput) {
3264
                if (oldInput == null) {
3265
                    if (input == null) {
3266
                        return;
3267
                    }
3268
                    refresh();
3269
                    return;
3270
                }
3271

    
3272
                refresh();
3273

    
3274
            }
3275

    
3276
            /*
3277
             * (non-Javadoc)
3278
             *
3279
             * @see org.eclipse.jface.viewers.ContentViewer#handleLabelProviderChanged(org.eclipse.jface.viewers.LabelProviderChangedEvent)
3280
             */
3281
            @Override
3282
            protected void handleLabelProviderChanged(
3283
                    LabelProviderChangedEvent event) {
3284
                if (event != null) {
3285
                    refresh(event.getElements());
3286
                }
3287
            }
3288

    
3289
            /*
3290
             * (non-Javadoc)
3291
             *
3292
             * @see org.eclipse.jface.viewers.Viewer#getControl()
3293
             */
3294
            @Override
3295
            public Control getControl() {
3296
                return label;
3297
            }
3298

    
3299
            /*
3300
             * (non-Javadoc)
3301
             *
3302
             * @see org.eclipse.jface.viewers.Viewer#getSelection()
3303
             */
3304
            @Override
3305
            public ISelection getSelection() {
3306
                // not supported
3307
                return null;
3308
            }
3309

    
3310
            /*
3311
             * (non-Javadoc)
3312
             *
3313
             * @see org.eclipse.jface.viewers.Viewer#refresh()
3314
             */
3315
            @Override
3316
            public void refresh() {
3317
                Object input = this.getInput();
3318
                if (input != null) {
3319
                    ILabelProvider labelProvider = (ILabelProvider) getLabelProvider();
3320
                    doRefresh(labelProvider.getText(input), labelProvider
3321
                            .getImage(input));
3322
                } else {
3323
                    doRefresh(null, null);
3324
                }
3325
            }
3326

    
3327
            /**
3328
             * Sets the given text and image to the label.
3329
             *
3330
             * @param text
3331
             *            the new text or null
3332
             * @param image
3333
             *            the new image
3334
             */
3335
            private void doRefresh(String text, Image image) {
3336
                if ( text != null ) {
3337
                    text = LegacyActionTools.escapeMnemonics(text);
3338
                }
3339
                label.setText(text);
3340
                label.setImage(image);
3341
            }
3342

    
3343
            /*
3344
             * (non-Javadoc)
3345
             *
3346
             * @see org.eclipse.jface.viewers.Viewer#setSelection(org.eclipse.jface.viewers.ISelection,
3347
             *      boolean)
3348
             */
3349
            @Override
3350
            public void setSelection(ISelection selection, boolean reveal) {
3351
                // not supported
3352
            }
3353

    
3354
            /**
3355
             * Refreshes the label if currently chosen element is on the list.
3356
             *
3357
             * @param objs
3358
             *            list of changed object
3359
             */
3360
            private void refresh(Object[] objs) {
3361
                if (objs == null || getInput() == null) {
3362
                    return;
3363
                }
3364
                Object input = getInput();
3365
                for (int i = 0; i < objs.length; i++) {
3366
                    if (objs[i].equals(input)) {
3367
                        refresh();
3368
                        break;
3369
                    }
3370
                }
3371
            }
3372
        }
3373

    
3374
        /**
3375
         * Compares items according to the history.
3376
         */
3377
        private class HistoryComparator implements Comparator {
3378

    
3379
            /*
3380
             * (non-Javadoc)
3381
             *
3382
             * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
3383
             */
3384
            @Override
3385
            public int compare(Object o1, Object o2) {
3386
                boolean h1 = isHistoryElement(o1);
3387
                boolean h2 = isHistoryElement(o2);
3388
                if (h1 == h2) {
3389
                    return getItemsComparator().compare(o1, o2);
3390
                }
3391

    
3392
                if (h1) {
3393
                    return -2;
3394
                }
3395
                if (h2) {
3396
                    return +2;
3397
                }
3398

    
3399
                return 0;
3400
            }
3401

    
3402
        }
3403

    
3404

    
3405
        /**
3406
         * Get the control where the search pattern is entered. Any filtering should
3407
         * be done using an {@link ItemsFilter}. This control should only be
3408
         * accessed for listeners that wish to handle events that do not affect
3409
         * filtering such as custom traversal.
3410
         *
3411
         * @return Control or <code>null</code> if the pattern control has not
3412
         *         been created.
3413
         */
3414
        public Control getPatternControl() {
3415
            return pattern;
3416
        }
3417

    
3418
        /**
3419
         * we have to call the initModel() method in the applyFilter method, so we had to add this abstract method (moved from AbstractFilteredCdmResourceSelectionDialog)
3420
         * <p>initModel</p>
3421
         */
3422
        abstract protected void initModel();
3423

    
3424
    }
3425

    
3426

    
(5-5/32)