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