From 7602050a681085ca888eb1024fb733863109bb78 Mon Sep 17 00:00:00 2001 From: Katja Luther Date: Fri, 10 Jun 2016 14:15:44 +0200 Subject: [PATCH] add new class CdmFilteredSelectionDialog --- .../CdmFilteredItemsSelectionDialog.java | 3406 +++++++++++++++++ 1 file changed, 3406 insertions(+) create mode 100644 eu.etaxonomy.taxeditor.store/src/main/java/eu/etaxonomy/taxeditor/ui/dialog/selection/CdmFilteredItemsSelectionDialog.java diff --git a/eu.etaxonomy.taxeditor.store/src/main/java/eu/etaxonomy/taxeditor/ui/dialog/selection/CdmFilteredItemsSelectionDialog.java b/eu.etaxonomy.taxeditor.store/src/main/java/eu/etaxonomy/taxeditor/ui/dialog/selection/CdmFilteredItemsSelectionDialog.java new file mode 100644 index 000000000..975673b48 --- /dev/null +++ b/eu.etaxonomy.taxeditor.store/src/main/java/eu/etaxonomy/taxeditor/ui/dialog/selection/CdmFilteredItemsSelectionDialog.java @@ -0,0 +1,3406 @@ +// $Id$ +/** +* Copyright (C) 2016 EDIT +* European Distributed Institute of Taxonomy +* http://www.e-taxonomy.eu +* +* The contents of this file are subject to the Mozilla Public License Version 1.1 +* See LICENSE.TXT at the top of this package for the full license terms. +*/ +package eu.etaxonomy.taxeditor.ui.dialog.selection; + +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.IHandler; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.ListenerList; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.ProgressMonitorWrapper; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubProgressMonitor; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.ActionContributionItem; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.action.IMenuListener; +import org.eclipse.jface.action.IMenuManager; +import org.eclipse.jface.action.LegacyActionTools; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.jface.viewers.ContentViewer; +import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IColorProvider; +import org.eclipse.jface.viewers.IContentProvider; +import org.eclipse.jface.viewers.IDoubleClickListener; +import org.eclipse.jface.viewers.IFontProvider; +import org.eclipse.jface.viewers.ILabelDecorator; +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.jface.viewers.ILabelProviderListener; +import org.eclipse.jface.viewers.ILazyContentProvider; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.LabelProviderChangedEvent; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.StyledCellLabelProvider; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerCell; +import org.eclipse.jface.viewers.ViewerFilter; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.SWT; +import org.eclipse.swt.accessibility.ACC; +import org.eclipse.swt.accessibility.AccessibleAdapter; +import org.eclipse.swt.accessibility.AccessibleEvent; +import org.eclipse.swt.custom.CLabel; +import org.eclipse.swt.custom.ViewForm; +import org.eclipse.swt.events.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.TraverseEvent; +import org.eclipse.swt.events.TraverseListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.Text; +import org.eclipse.swt.widgets.ToolBar; +import org.eclipse.swt.widgets.ToolItem; +import org.eclipse.ui.ActiveShellExpression; +import org.eclipse.ui.IMemento; +import org.eclipse.ui.IWorkbenchCommandConstants; +import org.eclipse.ui.IWorkbenchPreferenceConstants; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.WorkbenchException; +import org.eclipse.ui.XMLMemento; +import org.eclipse.ui.dialogs.FilteredItemsSelectionDialog; +import org.eclipse.ui.dialogs.SearchPattern; +import org.eclipse.ui.dialogs.SelectionStatusDialog; +import org.eclipse.ui.handlers.IHandlerActivation; +import org.eclipse.ui.handlers.IHandlerService; +import org.eclipse.ui.internal.IWorkbenchGraphicConstants; +import org.eclipse.ui.internal.WorkbenchImages; +import org.eclipse.ui.internal.WorkbenchMessages; +import org.eclipse.ui.internal.WorkbenchPlugin; +import org.eclipse.ui.progress.UIJob; +import org.eclipse.ui.statushandlers.StatusManager; + +/** + * @author k.luther + * @date 10.06.2016 + * + */ +public abstract class CdmFilteredItemsSelectionDialog extends SelectionStatusDialog { + + + + private static final String DIALOG_BOUNDS_SETTINGS = "DialogBoundsSettings"; //$NON-NLS-1$ + + private static final String SHOW_STATUS_LINE = "ShowStatusLine"; //$NON-NLS-1$ + + private static final String HISTORY_SETTINGS = "History"; //$NON-NLS-1$ + + private static final String DIALOG_HEIGHT = "DIALOG_HEIGHT"; //$NON-NLS-1$ + + private static final String DIALOG_WIDTH = "DIALOG_WIDTH"; //$NON-NLS-1$ + + /** + * Represents an empty selection in the pattern input field (used only for + * initial pattern). + */ + public static final int NONE = 0; + + /** + * Pattern input field selection where caret is at the beginning (used only + * for initial pattern). + */ + public static final int CARET_BEGINNING = 1; + + /** + * Represents a full selection in the pattern input field (used only for + * initial pattern). + */ + public static final int FULL_SELECTION = 2; + + private Text pattern; + + private TableViewer list; + + private DetailsContentViewer details; + + /** + * It is a duplicate of a field in the CLabel class in DetailsContentViewer. + * It is maintained, because the setDetailsLabelProvider() + * could be called before content area is created. + */ + private ILabelProvider detailsLabelProvider; + + private ItemsListLabelProvider itemsListLabelProvider; + + private MenuManager menuManager; + + private MenuManager contextMenuManager; + + private final boolean multi; + + private ToolBar toolBar; + + private ToolItem toolItem; + + private Label progressLabel; + + private ToggleStatusLineAction toggleStatusLineAction; + + private RemoveHistoryItemAction removeHistoryItemAction; + + private ActionContributionItem removeHistoryActionContributionItem; + + private IStatus status; + + private final RefreshCacheJob refreshCacheJob; + + private final RefreshProgressMessageJob refreshProgressMessageJob = new RefreshProgressMessageJob(); + + private Object[] currentSelection; + + private final ContentProvider contentProvider; + + private final FilterHistoryJob filterHistoryJob; + + private final FilterJob filterJob; + + private ItemsFilter filter; + + private List lastCompletedResult; + + private ItemsFilter lastCompletedFilter; + + private String initialPatternText; + + private int selectionMode; + + private ItemsListSeparator itemsListSeparator; + + private static final String EMPTY_STRING = ""; //$NON-NLS-1$ + + private boolean refreshWithLastSelection = false; + + private IHandlerActivation showViewHandler; + + /** + * Creates a new instance of the class. + * + * @param shell + * shell to parent the dialog on + * @param multi + * indicates whether dialog allows to select more than one + * position in its list of items + */ + public CdmFilteredItemsSelectionDialog(Shell shell, boolean multi) { + super(shell); + this.multi = multi; + filterHistoryJob = new FilterHistoryJob(); + filterJob = new FilterJob(); + contentProvider = new ContentProvider(); + refreshCacheJob = new RefreshCacheJob(); + itemsListSeparator = new ItemsListSeparator( + WorkbenchMessages.FilteredItemsSelectionDialog_separatorLabel); + selectionMode = NONE; + } + + /** + * Creates a new instance of the class. Created dialog won't allow to select + * more than one item. + * + * @param shell + * shell to parent the dialog on + */ + public CdmFilteredItemsSelectionDialog(Shell shell) { + this(shell, false); + } + + /** + * Adds viewer filter to the dialog items list. + * + * @param filter + * the new filter + */ + protected void addListFilter(ViewerFilter filter) { + contentProvider.addFilter(filter); + } + + /** + * Sets a new label provider for items in the list. If the label provider + * also implements {@link + * org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider + * .IStyledLabelProvider}, the style text labels provided by it will be used + * provided that the corresponding preference is set. + * + * @see IWorkbenchPreferenceConstants#USE_COLORED_LABELS + * + * @param listLabelProvider + * the label provider for items in the list + */ + public void setListLabelProvider(ILabelProvider listLabelProvider) { + getItemsListLabelProvider().setProvider(listLabelProvider); + } + + /** + * Returns the label decorator for selected items in the list. + * + * @return the label decorator for selected items in the list + */ + private ILabelDecorator getListSelectionLabelDecorator() { + return getItemsListLabelProvider().getSelectionDecorator(); + } + + /** + * Sets the label decorator for selected items in the list. + * + * @param listSelectionLabelDecorator + * the label decorator for selected items in the list + */ + public void setListSelectionLabelDecorator( + ILabelDecorator listSelectionLabelDecorator) { + getItemsListLabelProvider().setSelectionDecorator( + listSelectionLabelDecorator); + } + + /** + * Returns the item list label provider. + * + * @return the item list label provider + */ + private ItemsListLabelProvider getItemsListLabelProvider() { + if (itemsListLabelProvider == null) { + itemsListLabelProvider = new ItemsListLabelProvider( + new LabelProvider(), null); + } + return itemsListLabelProvider; + } + + /** + * Sets label provider for the details field. + * + * For a single selection, the element sent to + * {@link ILabelProvider#getImage(Object)} and + * {@link ILabelProvider#getText(Object)} is the selected object, for + * multiple selection a {@link String} with amount of selected items is the + * element. + * + * @see #getSelectedItems() getSelectedItems() can be used to retrieve + * selected items and get the items count. + * + * @param detailsLabelProvider + * the label provider for the details field + */ + public void setDetailsLabelProvider(ILabelProvider detailsLabelProvider) { + this.detailsLabelProvider = detailsLabelProvider; + if (details != null) { + details.setLabelProvider(detailsLabelProvider); + } + } + + private ILabelProvider getDetailsLabelProvider() { + if (detailsLabelProvider == null) { + detailsLabelProvider = new LabelProvider(); + } + return detailsLabelProvider; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.window.Window#create() + */ + @Override + public void create() { + super.create(); + pattern.setFocus(); + } + + /** + * Restores dialog using persisted settings. The default implementation + * restores the status of the details line and the selection history. + * + * @param settings + * settings used to restore dialog + */ + protected void restoreDialog(IDialogSettings settings) { + boolean toggleStatusLine = true; + + if (settings.get(SHOW_STATUS_LINE) != null) { + toggleStatusLine = settings.getBoolean(SHOW_STATUS_LINE); + } + + toggleStatusLineAction.setChecked(toggleStatusLine); + + details.setVisible(toggleStatusLine); + + String setting = settings.get(HISTORY_SETTINGS); + if (setting != null) { + try { + IMemento memento = XMLMemento.createReadRoot(new StringReader( + setting)); + this.contentProvider.loadHistory(memento); + } catch (WorkbenchException e) { + // Simply don't restore the settings + StatusManager + .getManager() + .handle( + new Status( + IStatus.ERROR, + PlatformUI.PLUGIN_ID, + IStatus.ERROR, + WorkbenchMessages.FilteredItemsSelectionDialog_restoreError, + e)); + } + } + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.window.Window#close() + */ + @Override + public boolean close() { + this.filterJob.cancel(); + this.refreshCacheJob.cancel(); + this.refreshProgressMessageJob.cancel(); + if (showViewHandler != null) { + IHandlerService service = (IHandlerService) PlatformUI + .getWorkbench().getService(IHandlerService.class); + service.deactivateHandler(showViewHandler); + showViewHandler.getHandler().dispose(); + showViewHandler = null; + } + if (menuManager != null) { + menuManager.dispose(); + } + if (contextMenuManager != null) { + contextMenuManager.dispose(); + } + storeDialog(getDialogSettings()); + return super.close(); + } + + /** + * Stores dialog settings. + * + * @param settings + * settings used to store dialog + */ + protected void storeDialog(IDialogSettings settings) { + settings.put(SHOW_STATUS_LINE, toggleStatusLineAction.isChecked()); + + XMLMemento memento = XMLMemento.createWriteRoot(HISTORY_SETTINGS); + this.contentProvider.saveHistory(memento); + StringWriter writer = new StringWriter(); + try { + memento.save(writer); + settings.put(HISTORY_SETTINGS, writer.getBuffer().toString()); + } catch (IOException e) { + // Simply don't store the settings + StatusManager + .getManager() + .handle( + new Status( + IStatus.ERROR, + PlatformUI.PLUGIN_ID, + IStatus.ERROR, + WorkbenchMessages.FilteredItemsSelectionDialog_storeError, + e)); + } + } + + /** + * Create a new header which is labelled by headerLabel. + * + * @param parent + * @return Label the label of the header + */ + private Label createHeader(Composite parent) { + Composite header = new Composite(parent, SWT.NONE); + + GridLayout layout = new GridLayout(); + layout.numColumns = 2; + layout.marginWidth = 0; + layout.marginHeight = 0; + header.setLayout(layout); + + Label headerLabel = new Label(header, SWT.NONE); + headerLabel.setText((getMessage() != null && getMessage().trim() + .length() > 0) ? getMessage() + : WorkbenchMessages.FilteredItemsSelectionDialog_patternLabel); + headerLabel.addTraverseListener(new TraverseListener() { + @Override + public void keyTraversed(TraverseEvent e) { + if (e.detail == SWT.TRAVERSE_MNEMONIC && e.doit) { + e.detail = SWT.TRAVERSE_NONE; + pattern.setFocus(); + } + } + }); + + GridData gd = new GridData(GridData.FILL_HORIZONTAL); + headerLabel.setLayoutData(gd); + + createViewMenu(header); + header.setLayoutData(gd); + return headerLabel; + } + + /** + * Create the labels for the list and the progress. Return the list label. + * + * @param parent + * @return Label + */ + private Label createLabels(Composite parent) { + Composite labels = new Composite(parent, SWT.NONE); + + GridLayout layout = new GridLayout(); + layout.numColumns = 2; + layout.marginWidth = 0; + layout.marginHeight = 0; + labels.setLayout(layout); + + Label listLabel = new Label(labels, SWT.NONE); + listLabel + .setText(WorkbenchMessages.FilteredItemsSelectionDialog_listLabel); + + listLabel.addTraverseListener(new TraverseListener() { + @Override + public void keyTraversed(TraverseEvent e) { + if (e.detail == SWT.TRAVERSE_MNEMONIC && e.doit) { + e.detail = SWT.TRAVERSE_NONE; + list.getTable().setFocus(); + } + } + }); + + GridData gd = new GridData(GridData.FILL_HORIZONTAL); + listLabel.setLayoutData(gd); + + progressLabel = new Label(labels, SWT.RIGHT); + progressLabel.setLayoutData(gd); + + labels.setLayoutData(gd); + return listLabel; + } + + private void createViewMenu(Composite parent) { + toolBar = new ToolBar(parent, SWT.FLAT); + toolItem = new ToolItem(toolBar, SWT.PUSH, 0); + + GridData data = new GridData(); + data.horizontalAlignment = GridData.END; + toolBar.setLayoutData(data); + + toolBar.addMouseListener(new MouseAdapter() { + @Override + public void mouseDown(MouseEvent e) { + showViewMenu(); + } + }); + + toolItem.setImage(WorkbenchImages + .getImage(IWorkbenchGraphicConstants.IMG_LCL_VIEW_MENU)); + toolItem + .setToolTipText(WorkbenchMessages.FilteredItemsSelectionDialog_menu); + toolItem.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + showViewMenu(); + } + }); + + menuManager = new MenuManager(); + + fillViewMenu(menuManager); + + IHandlerService service = (IHandlerService) PlatformUI.getWorkbench() + .getService(IHandlerService.class); + IHandler handler = new AbstractHandler() { + @Override + public Object execute(ExecutionEvent event) { + showViewMenu(); + return null; + } + }; + showViewHandler = service.activateHandler( + IWorkbenchCommandConstants.WINDOW_SHOW_VIEW_MENU, handler, + new ActiveShellExpression(getShell())); + } + + /** + * Fills the menu of the dialog. + * + * @param menuManager + * the menu manager + */ + protected void fillViewMenu(IMenuManager menuManager) { + toggleStatusLineAction = new ToggleStatusLineAction(); + menuManager.add(toggleStatusLineAction); + } + + private void showViewMenu() { + Menu menu = menuManager.createContextMenu(getShell()); + Rectangle bounds = toolItem.getBounds(); + Point topLeft = new Point(bounds.x, bounds.y + bounds.height); + topLeft = toolBar.toDisplay(topLeft); + menu.setLocation(topLeft.x, topLeft.y); + menu.setVisible(true); + } + + /** + * Hook that allows to add actions to the context menu. + *

+ * Subclasses may extend in order to add other actions.

+ * + * @param menuManager the context menu manager + * @since 3.5 + */ + protected void fillContextMenu(IMenuManager menuManager) { + List selectedElements= ((StructuredSelection)list.getSelection()).toList(); + + Object item= null; + + for (Iterator it= selectedElements.iterator(); it.hasNext();) { + item= it.next(); + if (item instanceof ItemsListSeparator || !isHistoryElement(item)) { + return; + } + } + + if (selectedElements.size() > 0) { + removeHistoryItemAction.setText(WorkbenchMessages.FilteredItemsSelectionDialog_removeItemsFromHistoryAction); + + menuManager.add(removeHistoryActionContributionItem); + + } + } + + private void createPopupMenu() { + removeHistoryItemAction = new RemoveHistoryItemAction(); + removeHistoryActionContributionItem = new ActionContributionItem( + removeHistoryItemAction); + + contextMenuManager = new MenuManager(); + contextMenuManager.setRemoveAllWhenShown(true); + contextMenuManager.addMenuListener(new IMenuListener() { + @Override + public void menuAboutToShow(IMenuManager manager) { + fillContextMenu(manager); + } + }); + + final Table table = list.getTable(); + Menu menu= contextMenuManager.createContextMenu(table); + table.setMenu(menu); + } + + /** + * Creates an extra content area, which will be located above the details. + * + * @param parent + * parent to create the dialog widgets in + * @return an extra content area + */ + protected abstract Control createExtendedContentArea(Composite parent); + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite) + */ + @Override + protected Control createDialogArea(Composite parent) { + Composite dialogArea = (Composite) super.createDialogArea(parent); + + Composite content = new Composite(dialogArea, SWT.NONE); + GridData gd = new GridData(GridData.FILL_BOTH); + content.setLayoutData(gd); + + GridLayout layout = new GridLayout(); + layout.numColumns = 1; + layout.marginWidth = 0; + layout.marginHeight = 0; + content.setLayout(layout); + + final Label headerLabel = createHeader(content); + + pattern = new Text(content, SWT.SINGLE | SWT.BORDER | SWT.SEARCH | SWT.ICON_CANCEL); + pattern.getAccessible().addAccessibleListener(new AccessibleAdapter() { + @Override + public void getName(AccessibleEvent e) { + e.result = LegacyActionTools.removeMnemonics(headerLabel + .getText()); + } + }); + gd = new GridData(GridData.FILL_HORIZONTAL); + pattern.setLayoutData(gd); + + final Label listLabel = createLabels(content); + + list = new TableViewer(content, (multi ? SWT.MULTI : SWT.SINGLE) + | SWT.BORDER | SWT.V_SCROLL | SWT.VIRTUAL); + list.getTable().getAccessible().addAccessibleListener( + new AccessibleAdapter() { + @Override + public void getName(AccessibleEvent e) { + if (e.childID == ACC.CHILDID_SELF) { + e.result = LegacyActionTools + .removeMnemonics(listLabel.getText()); + } + } + }); + list.setContentProvider(contentProvider); + list.setLabelProvider(getItemsListLabelProvider()); + list.setInput(new Object[0]); + list.setItemCount(contentProvider.getNumberOfElements()); + gd = new GridData(GridData.FILL_BOTH); + applyDialogFont(list.getTable()); + gd.heightHint= list.getTable().getItemHeight() * 15; + list.getTable().setLayoutData(gd); + + createPopupMenu(); + + pattern.addModifyListener(new ModifyListener() { + @Override + public void modifyText(ModifyEvent e) { + applyFilter(); + } + }); + + pattern.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.keyCode == SWT.ARROW_DOWN) { + if (list.getTable().getItemCount() > 0) { + list.getTable().setFocus(); + } + } + } + }); + + list.addSelectionChangedListener(new ISelectionChangedListener() { + @Override + public void selectionChanged(SelectionChangedEvent event) { + StructuredSelection selection = (StructuredSelection) event + .getSelection(); + handleSelected(selection); + } + }); + + list.addDoubleClickListener(new IDoubleClickListener() { + @Override + public void doubleClick(DoubleClickEvent event) { + handleDoubleClick(); + } + }); + + list.getTable().addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + + if (e.keyCode == SWT.DEL) { + + List selectedElements = ((StructuredSelection) list + .getSelection()).toList(); + + Object item = null; + boolean isSelectedHistory = true; + + for (Iterator it = selectedElements.iterator(); it + .hasNext();) { + item = it.next(); + if (item instanceof ItemsListSeparator + || !isHistoryElement(item)) { + isSelectedHistory = false; + break; + } + } + if (isSelectedHistory) { + removeSelectedItems(selectedElements); + } + + } + + if (e.keyCode == SWT.ARROW_UP && (e.stateMask & SWT.SHIFT) != 0 + && (e.stateMask & SWT.CTRL) != 0) { + StructuredSelection selection = (StructuredSelection) list + .getSelection(); + + if (selection.size() == 1) { + Object element = selection.getFirstElement(); + if (element.equals(list.getElementAt(0))) { + pattern.setFocus(); + } + if (list.getElementAt(list.getTable() + .getSelectionIndex() - 1) instanceof ItemsListSeparator) { + list.getTable().setSelection( + list.getTable().getSelectionIndex() - 1); + } + list.getTable().notifyListeners(SWT.Selection, + new Event()); + + } + } + + if (e.keyCode == SWT.ARROW_DOWN + && (e.stateMask & SWT.SHIFT) != 0 + && (e.stateMask & SWT.CTRL) != 0) { + + if (list + .getElementAt(list.getTable().getSelectionIndex() + 1) instanceof ItemsListSeparator) { + list.getTable().setSelection( + list.getTable().getSelectionIndex() + 1); + } + list.getTable().notifyListeners(SWT.Selection, new Event()); + } + + } + }); + + createExtendedContentArea(content); + + details = new DetailsContentViewer(content, SWT.BORDER | SWT.FLAT); + details.setVisible(toggleStatusLineAction.isChecked()); + details.setContentProvider(new NullContentProvider()); + details.setLabelProvider(getDetailsLabelProvider()); + + applyDialogFont(content); + + restoreDialog(getDialogSettings()); + + if (initialPatternText != null) { + pattern.setText(initialPatternText); + } + + switch (selectionMode) { + case CARET_BEGINNING: + pattern.setSelection(0, 0); + break; + case FULL_SELECTION: + pattern.setSelection(0, initialPatternText.length()); + break; + } + + // apply filter even if pattern is empty (display history) + applyFilter(); + + return dialogArea; + } + + /** + * This method is a hook for subclasses to override default dialog behavior. + * The handleDoubleClick() method handles double clicks on + * the list of filtered elements. + *

+ * Current implementation makes double-clicking on the list do the same as + * pressing OK button on the dialog. + */ + protected void handleDoubleClick() { + okPressed(); + } + + /** + * Refreshes the details field according to the current selection in the + * items list. + */ + private void refreshDetails() { + StructuredSelection selection = getSelectedItems(); + + switch (selection.size()) { + case 0: + details.setInput(null); + break; + case 1: + details.setInput(selection.getFirstElement()); + break; + default: + details + .setInput(NLS + .bind( + WorkbenchMessages.FilteredItemsSelectionDialog_nItemsSelected, + new Integer(selection.size()))); + break; + } + + } + + /** + * Handle selection in the items list by updating labels of selected and + * unselected items and refresh the details field using the selection. + * + * @param selection + * the new selection + */ + protected void handleSelected(StructuredSelection selection) { + IStatus status = new Status(IStatus.OK, PlatformUI.PLUGIN_ID, + IStatus.OK, EMPTY_STRING, null); + + Object[] lastSelection = currentSelection; + + currentSelection = selection.toArray(); + + if (selection.size() == 0) { + status = new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID, + IStatus.ERROR, EMPTY_STRING, null); + + if (lastSelection != null + && getListSelectionLabelDecorator() != null) { + list.update(lastSelection, null); + } + + currentSelection = null; + + } else { + status = new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID, + IStatus.ERROR, EMPTY_STRING, null); + + List items = selection.toList(); + + Object item = null; + IStatus tempStatus = null; + + for (Iterator it = items.iterator(); it.hasNext();) { + Object o = it.next(); + + if (o instanceof ItemsListSeparator) { + continue; + } + + item = o; + tempStatus = validateItem(item); + + if (tempStatus.isOK()) { + status = new Status(IStatus.OK, PlatformUI.PLUGIN_ID, + IStatus.OK, EMPTY_STRING, null); + } else { + status = tempStatus; + // if any selected element is not valid status is set to + // ERROR + break; + } + } + + if (lastSelection != null + && getListSelectionLabelDecorator() != null) { + list.update(lastSelection, null); + } + + if (getListSelectionLabelDecorator() != null) { + list.update(currentSelection, null); + } + } + + refreshDetails(); + updateStatus(status); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.window.Dialog#getDialogBoundsSettings() + */ + @Override + protected IDialogSettings getDialogBoundsSettings() { + IDialogSettings settings = getDialogSettings(); + IDialogSettings section = settings.getSection(DIALOG_BOUNDS_SETTINGS); + if (section == null) { + section = settings.addNewSection(DIALOG_BOUNDS_SETTINGS); + section.put(DIALOG_HEIGHT, 500); + section.put(DIALOG_WIDTH, 600); + } + return section; + } + + /** + * Returns the dialog settings. Returned object can't be null. + * + * @return return dialog settings for this dialog + */ + protected abstract IDialogSettings getDialogSettings(); + + /** + * Refreshes the dialog - has to be called in UI thread. + */ + public void refresh() { + if (list != null && !list.getTable().isDisposed()) { + + List lastRefreshSelection = ((StructuredSelection) list + .getSelection()).toList(); + list.getTable().deselectAll(); + + list.setItemCount(contentProvider.getNumberOfElements()); + list.refresh(); + + if (list.getTable().getItemCount() > 0) { + // preserve previous selection + if (refreshWithLastSelection && lastRefreshSelection != null + && lastRefreshSelection.size() > 0) { + list.setSelection(new StructuredSelection( + lastRefreshSelection)); + } else { + refreshWithLastSelection = true; + list.getTable().setSelection(0); + list.getTable().notifyListeners(SWT.Selection, new Event()); + } + } else { + list.setSelection(StructuredSelection.EMPTY); + } + + } + + scheduleProgressMessageRefresh(); + } + + /** + * Updates the progress label. + * + * @deprecated + */ + @Deprecated + public void updateProgressLabel() { + scheduleProgressMessageRefresh(); + } + + /** + * Notifies the content provider - fires filtering of content provider + * elements. During the filtering, a separator between history and workspace + * matches is added. + *

+ * This is a long running operation and should be called in a job. + * + * @param checkDuplicates + * true if data concerning elements duplication + * should be computed - it takes much more time than the standard + * filtering + * @param monitor + * a progress monitor or null if no monitor is + * available + */ + public void reloadCache(boolean checkDuplicates, IProgressMonitor monitor) { + if (list != null && !list.getTable().isDisposed() + && contentProvider != null) { + contentProvider.reloadCache(checkDuplicates, monitor); + } + } + + /** + * Schedule refresh job. + */ + public void scheduleRefresh() { + refreshCacheJob.cancelAll(); + refreshCacheJob.schedule(); + } + + /** + * Schedules progress message refresh. + */ + public void scheduleProgressMessageRefresh() { + if (filterJob.getState() != Job.RUNNING + && refreshProgressMessageJob.getState() != Job.RUNNING) { + refreshProgressMessageJob.scheduleProgressRefresh(null); + } + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.ui.dialogs.SelectionStatusDialog#computeResult() + */ + @Override + protected void computeResult() { + + List selectedElements = ((StructuredSelection) list.getSelection()) + .toList(); + + List objectsToReturn = new ArrayList(); + + Object item = null; + + for (Iterator it = selectedElements.iterator(); it.hasNext();) { + item = it.next(); + + if (!(item instanceof ItemsListSeparator)) { + accessedHistoryItem(item); + objectsToReturn.add(item); + } + } + + setResult(objectsToReturn); + } + + /* + * @see org.eclipse.ui.dialogs.SelectionStatusDialog#updateStatus(org.eclipse.core.runtime.IStatus) + */ + @Override + protected void updateStatus(IStatus status) { + this.status = status; + super.updateStatus(status); + } + + /* + * @see Dialog#okPressed() + */ + @Override + protected void okPressed() { + if (status != null + && (status.isOK() || status.getCode() == IStatus.INFO)) { + super.okPressed(); + } + } + + /** + * Sets the initial pattern used by the filter. This text is copied into the + * selection input on the dialog. A full selection is used in the pattern + * input field. + * + * @param text + * initial pattern for the filter + * @see FilteredItemsSelectionDialog#FULL_SELECTION + */ + public void setInitialPattern(String text) { + setInitialPattern(text, FULL_SELECTION); + } + + /** + * Sets the initial pattern used by the filter. This text is copied into the + * selection input on the dialog. The selectionMode is used + * to choose selection type for the input field. + * + * @param text + * initial pattern for the filter + * @param selectionMode + * one of: {@link FilteredItemsSelectionDialog#NONE}, + * {@link FilteredItemsSelectionDialog#CARET_BEGINNING}, + * {@link FilteredItemsSelectionDialog#FULL_SELECTION} + */ + public void setInitialPattern(String text, int selectionMode) { + this.initialPatternText = text; + this.selectionMode = selectionMode; + } + + /** + * Gets initial pattern. + * + * @return initial pattern, or null if initial pattern is not + * set + */ + protected String getInitialPattern() { + return this.initialPatternText; + } + + /** + * Returns the current selection. + * + * @return the current selection + */ + protected StructuredSelection getSelectedItems() { + + StructuredSelection selection = (StructuredSelection) list + .getSelection(); + + List selectedItems = selection.toList(); + Object itemToRemove = null; + + for (Iterator it = selection.iterator(); it.hasNext();) { + Object item = it.next(); + if (item instanceof ItemsListSeparator) { + itemToRemove = item; + break; + } + } + + if (itemToRemove == null) { + return new StructuredSelection(selectedItems); + } + // Create a new selection without the collision + List newItems = new ArrayList(selectedItems); + newItems.remove(itemToRemove); + return new StructuredSelection(newItems); + + } + + /** + * Validates the item. When items on the items list are selected or + * deselected, it validates each item in the selection and the dialog status + * depends on all validations. + * + * @param item + * an item to be checked + * @return status of the dialog to be set + */ + protected abstract IStatus validateItem(Object item); + + /** + * Creates an instance of a filter. + * + * @return a filter for items on the items list. Can be null, + * no filtering will be applied then, causing no item to be shown in + * the list. + */ + protected abstract ItemsFilter createFilter(); + + /** + * Applies the filter created by createFilter() method to the + * items list. When new filter is different than previous one it will cause + * refiltering. + */ + protected void applyFilter() { + + ItemsFilter newFilter = createFilter(); + + // don't apply filtering for patterns which mean the same, for example: + // *a**b and ***a*b + if (filter != null && filter.equalsFilter(newFilter)) { + return; + } + + filterHistoryJob.cancel(); + filterJob.cancel(); + + this.filter = newFilter; + + if (this.filter != null) { + filterHistoryJob.schedule(); + } + } + + /** + * Returns comparator to sort items inside content provider. Returned object + * will be probably created as an anonymous class. Parameters passed to the + * compare(java.lang.Object, java.lang.Object) are going to + * be the same type as the one used in the content provider. + * + * @return comparator to sort items content provider + */ + protected abstract Comparator getItemsComparator(); + + /** + * Fills the content provider with matching items. + * + * @param contentProvider + * collector to add items to. + * {@link FilteredItemsSelectionDialog.AbstractContentProvider#add(Object, FilteredItemsSelectionDialog.ItemsFilter)} + * only adds items that pass the given itemsFilter. + * @param itemsFilter + * the items filter + * @param progressMonitor + * must be used to report search progress. The state of this + * progress monitor reflects the state of the filtering process. + * @throws CoreException + */ + protected abstract void fillContentProvider( + AbstractContentProvider contentProvider, ItemsFilter itemsFilter, + IProgressMonitor progressMonitor) throws CoreException; + + /** + * Removes selected items from history. + * + * @param items + * items to be removed + */ + private void removeSelectedItems(List items) { + for (Iterator iter = items.iterator(); iter.hasNext();) { + Object item = iter.next(); + removeHistoryItem(item); + } + refreshWithLastSelection = false; + contentProvider.refresh(); + } + + /** + * Removes an item from history. + * + * @param item + * an item to remove + * @return removed item + */ + protected Object removeHistoryItem(Object item) { + return contentProvider.removeHistoryElement(item); + } + + /** + * Adds item to history. + * + * @param item + * the item to be added + */ + protected void accessedHistoryItem(Object item) { + contentProvider.addHistoryElement(item); + } + + /** + * Returns a history comparator. + * + * @return decorated comparator + */ + private Comparator getHistoryComparator() { + return new HistoryComparator(); + } + + /** + * Returns the history of selected elements. + * + * @return history of selected elements, or null if it is not + * set + */ + protected SelectionHistory getSelectionHistory() { + return this.contentProvider.getSelectionHistory(); + } + + /** + * Sets new history. + * + * @param selectionHistory + * the history + */ + protected void setSelectionHistory(SelectionHistory selectionHistory) { + if (this.contentProvider != null) { + this.contentProvider.setSelectionHistory(selectionHistory); + } + } + + /** + * Indicates whether the given item is a history item. + * + * @param item + * the item to be investigated + * @return true if the given item exists in history, + * false otherwise + */ + public boolean isHistoryElement(Object item) { + return this.contentProvider.isHistoryElement(item); + } + + /** + * Indicates whether the given item is a duplicate. + * + * @param item + * the item to be investigated + * @return true if the item is duplicate, false + * otherwise + */ + public boolean isDuplicateElement(Object item) { + return this.contentProvider.isDuplicateElement(item); + } + + /** + * Sets separator label + * + * @param separatorLabel + * the label showed on separator + */ + public void setSeparatorLabel(String separatorLabel) { + this.itemsListSeparator = new ItemsListSeparator(separatorLabel); + } + + /** + * Returns name for then given object. + * + * @param item + * an object from the content provider. Subclasses should pay + * attention to the passed argument. They should either only pass + * objects of a known type (one used in content provider) or make + * sure that passed parameter is the expected one (by type + * checking like instanceof inside the method). + * @return name of the given item + */ + public abstract String getElementName(Object item); + + private class ToggleStatusLineAction extends Action { + + /** + * Creates a new instance of the class. + */ + public ToggleStatusLineAction() { + super( + WorkbenchMessages.FilteredItemsSelectionDialog_toggleStatusAction, + IAction.AS_CHECK_BOX); + } + + @Override + public void run() { + details.setVisible(isChecked()); + } + } + + /** + * Only refreshes UI on the basis of an already sorted and filtered set of + * items. + *

+ * Standard invocation scenario: + *

    + *
  1. filtering job (FilterJob class extending + * Job class)
  2. + *
  3. cache refresh without checking for duplicates (RefreshCacheJob + * class extending Job class)
  4. + *
  5. UI refresh (RefreshJob class extending + * UIJob class)
  6. + *
  7. cache refresh with checking for duplicates (CacheRefreshJob + * class extending Job class)
  8. + *
  9. UI refresh (RefreshJob class extending UIJob + * class)
  10. + *
+ * The scenario is rather complicated, but it had to be applied, because: + * + * + * @see org.eclipse.ui.dialogs.FilteredItemsSelectionDialog.FilterJob + * @see org.eclipse.ui.dialogs.FilteredItemsSelectionDialog.RefreshJob + * @see org.eclipse.ui.dialogs.FilteredItemsSelectionDialog.RefreshCacheJob + */ + private class RefreshJob extends UIJob { + + /** + * Creates a new instance of the class. + */ + public RefreshJob() { + super(CdmFilteredItemsSelectionDialog.this.getParentShell() + .getDisplay(), + WorkbenchMessages.FilteredItemsSelectionDialog_refreshJob); + setSystem(true); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.ui.progress.UIJob#runInUIThread(org.eclipse.core.runtime.IProgressMonitor) + */ + @Override + public IStatus runInUIThread(IProgressMonitor monitor) { + if (monitor.isCanceled()) { + return new Status(IStatus.OK, WorkbenchPlugin.PI_WORKBENCH, + IStatus.OK, EMPTY_STRING, null); + } + + if (CdmFilteredItemsSelectionDialog.this != null) { + CdmFilteredItemsSelectionDialog.this.refresh(); + } + + return new Status(IStatus.OK, PlatformUI.PLUGIN_ID, IStatus.OK, + EMPTY_STRING, null); + } + + } + + /** + * Refreshes the progress message cyclically with 500 milliseconds delay. + * RefreshProgressMessageJob is strictly connected with + * GranualProgressMonitor and use it to to get progress + * message and to decide about break of cyclical refresh. + */ + private class RefreshProgressMessageJob extends UIJob { + + private GranualProgressMonitor progressMonitor; + + /** + * Creates a new instance of the class. + */ + public RefreshProgressMessageJob() { + super( + CdmFilteredItemsSelectionDialog.this.getParentShell() + .getDisplay(), + WorkbenchMessages.FilteredItemsSelectionDialog_progressRefreshJob); + setSystem(true); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.ui.progress.UIJob#runInUIThread(org.eclipse.core.runtime.IProgressMonitor) + */ + @Override + public IStatus runInUIThread(IProgressMonitor monitor) { + + if (!progressLabel.isDisposed()) { + progressLabel.setText(progressMonitor != null ? progressMonitor + .getMessage() : EMPTY_STRING); + } + + if (progressMonitor == null || progressMonitor.isDone()) { + return new Status(IStatus.CANCEL, PlatformUI.PLUGIN_ID, + IStatus.CANCEL, EMPTY_STRING, null); + } + + // Schedule cyclical with 500 milliseconds delay + schedule(500); + + return new Status(IStatus.OK, PlatformUI.PLUGIN_ID, IStatus.OK, + EMPTY_STRING, null); + } + + /** + * Schedule progress refresh job. + * + * @param progressMonitor + * used during refresh progress label + */ + public void scheduleProgressRefresh( + GranualProgressMonitor progressMonitor) { + this.progressMonitor = progressMonitor; + // Schedule with initial delay to avoid flickering when the user + // types quickly + schedule(200); + } + + } + + /** + * A job responsible for computing filtered items list presented using + * RefreshJob. + * + * @see FilteredItemsSelectionDialog.RefreshJob + * + */ + private class RefreshCacheJob extends Job { + + private final RefreshJob refreshJob = new RefreshJob(); + + /** + * Creates a new instance of the class. + */ + public RefreshCacheJob() { + super( + WorkbenchMessages.FilteredItemsSelectionDialog_cacheRefreshJob); + setSystem(true); + } + + /** + * Stops the job and all sub-jobs. + */ + public void cancelAll() { + cancel(); + refreshJob.cancel(); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor) + */ + @Override + protected IStatus run(IProgressMonitor monitor) { + if (monitor.isCanceled()) { + return new Status(IStatus.CANCEL, WorkbenchPlugin.PI_WORKBENCH, + IStatus.CANCEL, EMPTY_STRING, null); + } + + if (CdmFilteredItemsSelectionDialog.this != null) { + GranualProgressMonitor wrappedMonitor = new GranualProgressMonitor( + monitor); + CdmFilteredItemsSelectionDialog.this.reloadCache(true, + wrappedMonitor); + } + + if (!monitor.isCanceled()) { + refreshJob.schedule(); + } + + return new Status(IStatus.OK, PlatformUI.PLUGIN_ID, IStatus.OK, + EMPTY_STRING, null); + + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.core.runtime.jobs.Job#canceling() + */ + @Override + protected void canceling() { + super.canceling(); + contentProvider.stopReloadingCache(); + } + + } + + private class RemoveHistoryItemAction extends Action { + + /** + * Creates a new instance of the class. + */ + public RemoveHistoryItemAction() { + super( + WorkbenchMessages.FilteredItemsSelectionDialog_removeItemsFromHistoryAction); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.action.Action#run() + */ + @Override + public void run() { + List selectedElements = ((StructuredSelection) list.getSelection()) + .toList(); + removeSelectedItems(selectedElements); + } + } + + private static boolean showColoredLabels() { + return PlatformUI.getPreferenceStore().getBoolean(IWorkbenchPreferenceConstants.USE_COLORED_LABELS); + } + + private class ItemsListLabelProvider extends StyledCellLabelProvider + implements ILabelProviderListener { + private ILabelProvider provider; + + private ILabelDecorator selectionDecorator; + + // Need to keep our own list of listeners + private final ListenerList listeners = new ListenerList(); + + /** + * Creates a new instance of the class. + * + * @param provider + * the label provider for all items, not null + * @param selectionDecorator + * the decorator for selected items, can be null + */ + public ItemsListLabelProvider(ILabelProvider provider, + ILabelDecorator selectionDecorator) { + Assert.isNotNull(provider); + this.provider = provider; + this.selectionDecorator = selectionDecorator; + + setOwnerDrawEnabled(showColoredLabels() && provider instanceof IStyledLabelProvider); + + provider.addListener(this); + + if (selectionDecorator != null) { + selectionDecorator.addListener(this); + } + } + + /** + * Sets new selection decorator. + * + * @param newSelectionDecorator + * new label decorator for selected items in the list + */ + public void setSelectionDecorator(ILabelDecorator newSelectionDecorator) { + if (selectionDecorator != null) { + selectionDecorator.removeListener(this); + selectionDecorator.dispose(); + } + + selectionDecorator = newSelectionDecorator; + + if (selectionDecorator != null) { + selectionDecorator.addListener(this); + } + } + + /** + * Gets selection decorator. + * + * @return the label decorator for selected items in the list + */ + public ILabelDecorator getSelectionDecorator() { + return selectionDecorator; + } + + /** + * Sets new label provider. + * + * @param newProvider + * new label provider for items in the list, not + * null + */ + public void setProvider(ILabelProvider newProvider) { + Assert.isNotNull(newProvider); + provider.removeListener(this); + provider.dispose(); + + provider = newProvider; + + if (provider != null) { + provider.addListener(this); + } + + setOwnerDrawEnabled(showColoredLabels() && provider instanceof IStyledLabelProvider); + } + + private Image getImage(Object element) { + if (element instanceof ItemsListSeparator) { + return WorkbenchImages + .getImage(IWorkbenchGraphicConstants.IMG_OBJ_SEPARATOR); + } + + return provider.getImage(element); + } + + private boolean isSelected(Object element) { + if (element != null && currentSelection != null) { + for (int i = 0; i < currentSelection.length; i++) { + if (element.equals(currentSelection[i])) { + return true; + } + } + } + return false; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.viewers.ILabelProvider#getText(java.lang.Object) + */ + private String getText(Object element) { + if (element instanceof ItemsListSeparator) { + return getSeparatorLabel(((ItemsListSeparator) element) + .getName()); + } + + String str = provider.getText(element); + if (selectionDecorator != null && isSelected(element)) { + return selectionDecorator.decorateText(str.toString(), element); + } + + return str; + } + + private StyledString getStyledText(Object element, + IStyledLabelProvider provider) { + StyledString string = provider.getStyledText(element); + + if (selectionDecorator != null && isSelected(element)) { + String decorated = selectionDecorator.decorateText(string + .getString(), element); + return StyledCellLabelProvider.styleDecoratedString(decorated, null, string); + // no need to add colors when element is selected + } + return string; + } + + @Override + public void update(ViewerCell cell) { + Object element = cell.getElement(); + + if (!(element instanceof ItemsListSeparator) + && provider instanceof IStyledLabelProvider) { + IStyledLabelProvider styledLabelProvider = (IStyledLabelProvider) provider; + StyledString styledString = getStyledText(element, + styledLabelProvider); + + cell.setText(styledString.getString()); + cell.setStyleRanges(styledString.getStyleRanges()); + cell.setImage(styledLabelProvider.getImage(element)); + } else { + cell.setText(getText(element)); + cell.setImage(getImage(element)); + } + cell.setFont(getFont(element)); + cell.setForeground(getForeground(element)); + cell.setBackground(getBackground(element)); + + super.update(cell); + } + + private String getSeparatorLabel(String separatorLabel) { + Rectangle rect = list.getTable().getBounds(); + + int borderWidth = list.getTable().computeTrim(0, 0, 0, 0).width; + + int imageWidth = WorkbenchImages.getImage( + IWorkbenchGraphicConstants.IMG_OBJ_SEPARATOR).getBounds().width; + + int width = rect.width - borderWidth - imageWidth; + + GC gc = new GC(list.getTable()); + gc.setFont(list.getTable().getFont()); + + int fSeparatorWidth = gc.getAdvanceWidth('-'); + int fMessageLength = gc.textExtent(separatorLabel).x; + + gc.dispose(); + + StringBuffer dashes = new StringBuffer(); + int chars = (((width - fMessageLength) / fSeparatorWidth) / 2) - 2; + for (int i = 0; i < chars; i++) { + dashes.append('-'); + } + + StringBuffer result = new StringBuffer(); + result.append(dashes); + result.append(" " + separatorLabel + " "); //$NON-NLS-1$//$NON-NLS-2$ + result.append(dashes); + return result.toString().trim(); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.viewers.IBaseLabelProvider#addListener(org.eclipse.jface.viewers.ILabelProviderListener) + */ + @Override + public void addListener(ILabelProviderListener listener) { + listeners.add(listener); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.viewers.IBaseLabelProvider#dispose() + */ + @Override + public void dispose() { + provider.removeListener(this); + provider.dispose(); + + if (selectionDecorator != null) { + selectionDecorator.removeListener(this); + selectionDecorator.dispose(); + } + + super.dispose(); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.viewers.IBaseLabelProvider#isLabelProperty(java.lang.Object, + * java.lang.String) + */ + @Override + public boolean isLabelProperty(Object element, String property) { + if (provider.isLabelProperty(element, property)) { + return true; + } + if (selectionDecorator != null + && selectionDecorator.isLabelProperty(element, property)) { + return true; + } + return false; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.viewers.IBaseLabelProvider#removeListener(org.eclipse.jface.viewers.ILabelProviderListener) + */ + @Override + public void removeListener(ILabelProviderListener listener) { + listeners.remove(listener); + } + + private Color getBackground(Object element) { + if (element instanceof ItemsListSeparator) { + return null; + } + if (provider instanceof IColorProvider) { + return ((IColorProvider) provider).getBackground(element); + } + return null; + } + + private Color getForeground(Object element) { + if (element instanceof ItemsListSeparator) { + return Display.getCurrent().getSystemColor( + SWT.COLOR_WIDGET_NORMAL_SHADOW); + } + if (provider instanceof IColorProvider) { + return ((IColorProvider) provider).getForeground(element); + } + return null; + } + + private Font getFont(Object element) { + if (element instanceof ItemsListSeparator) { + return null; + } + if (provider instanceof IFontProvider) { + return ((IFontProvider) provider).getFont(element); + } + return null; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.viewers.ILabelProviderListener#labelProviderChanged(org.eclipse.jface.viewers.LabelProviderChangedEvent) + */ + @Override + public void labelProviderChanged(LabelProviderChangedEvent event) { + Object[] l = listeners.getListeners(); + for (int i = 0; i < listeners.size(); i++) { + ((ILabelProviderListener) l[i]).labelProviderChanged(event); + } + } + } + + /** + * Used in ItemsListContentProvider, separates history and non-history + * items. + */ + private class ItemsListSeparator { + + private final String name; + + /** + * Creates a new instance of the class. + * + * @param name + * the name of the separator + */ + public ItemsListSeparator(String name) { + this.name = name; + } + + /** + * Returns the name of this separator. + * + * @return the name of the separator + */ + public String getName() { + return name; + } + } + + /** + * GranualProgressMonitor is used for monitoring progress of filtering + * process. It is used by RefreshProgressMessageJob to + * refresh progress message. State of this monitor illustrates state of + * filtering or cache refreshing process. + * + */ + private class GranualProgressMonitor extends ProgressMonitorWrapper { + + private String name; + + private String subName; + + private int totalWork; + + private double worked; + + private boolean done; + + /** + * Creates instance of GranualProgressMonitor. + * + * @param monitor + * progress to be wrapped + */ + public GranualProgressMonitor(IProgressMonitor monitor) { + super(monitor); + } + + /** + * Checks if filtering has been done + * + * @return true if filtering work has been done false in other way + */ + public boolean isDone() { + return done; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.core.runtime.ProgressMonitorWrapper#setTaskName(java.lang.String) + */ + @Override + public void setTaskName(String name) { + super.setTaskName(name); + this.name = name; + this.subName = null; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.core.runtime.ProgressMonitorWrapper#subTask(java.lang.String) + */ + @Override + public void subTask(String name) { + super.subTask(name); + this.subName = name; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.core.runtime.ProgressMonitorWrapper#beginTask(java.lang.String, + * int) + */ + @Override + public void beginTask(String name, int totalWork) { + super.beginTask(name, totalWork); + if (this.name == null) { + this.name = name; + } + this.totalWork = totalWork; + refreshProgressMessageJob.scheduleProgressRefresh(this); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.core.runtime.ProgressMonitorWrapper#worked(int) + */ + @Override + public void worked(int work) { + super.worked(work); + internalWorked(work); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.core.runtime.ProgressMonitorWrapper#done() + */ + @Override + public void done() { + done = true; + super.done(); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.core.runtime.ProgressMonitorWrapper#setCanceled(boolean) + */ + @Override + public void setCanceled(boolean b) { + done = b; + super.setCanceled(b); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.core.runtime.ProgressMonitorWrapper#internalWorked(double) + */ + @Override + public void internalWorked(double work) { + worked = worked + work; + } + + private String getMessage() { + if (done) { + return ""; //$NON-NLS-1$ + } + + String message; + + if (name == null) { + message = subName == null ? "" : subName; //$NON-NLS-1$ + } else { + message = subName == null ? name + : NLS + .bind( + WorkbenchMessages.FilteredItemsSelectionDialog_subtaskProgressMessage, + new Object[] { name, subName }); + } + if (totalWork == 0) { + return message; + } + + return NLS + .bind( + WorkbenchMessages.FilteredItemsSelectionDialog_taskProgressMessage, + new Object[] { + message, + new Integer( + (int) ((worked * 100) / totalWork)) }); + + } + + } + + /** + * Filters items history and schedule filter job. + */ + private class FilterHistoryJob extends Job { + + /** + * Filter used during the filtering process. + */ + private ItemsFilter itemsFilter; + + /** + * Creates new instance of receiver. + */ + public FilterHistoryJob() { + super(WorkbenchMessages.FilteredItemsSelectionDialog_jobLabel); + setSystem(true); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor) + */ + @Override + protected IStatus run(IProgressMonitor monitor) { + + this.itemsFilter = filter; + + contentProvider.reset(); + + refreshWithLastSelection = false; + + contentProvider.addHistoryItems(itemsFilter); + + if (!(lastCompletedFilter != null && lastCompletedFilter + .isSubFilter(this.itemsFilter))) { + contentProvider.refresh(); + } + + filterJob.schedule(); + + return Status.OK_STATUS; + } + + } + + /** + * Filters items in indicated set and history. During filtering, it + * refreshes the dialog (progress monitor and elements list). + * + * Depending on the filter, FilterJob decides which kind of + * search will be run inside filterContent. If the last + * filtering is done (last completed filter), is not null, and the new + * filter is a sub-filter ({@link FilteredItemsSelectionDialog.ItemsFilter#isSubFilter(FilteredItemsSelectionDialog.ItemsFilter)}) + * of the last, then FilterJob only filters in the cache. If + * it is the first filtering or the new filter isn't a sub-filter of the + * last one, a full search is run. + */ + private class FilterJob extends Job { + + /** + * Filter used during the filtering process. + */ + protected ItemsFilter itemsFilter; + + /** + * Creates new instance of FilterJob + */ + public FilterJob() { + super(WorkbenchMessages.FilteredItemsSelectionDialog_jobLabel); + setSystem(true); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor) + */ + @Override + protected final IStatus run(IProgressMonitor parent) { + GranualProgressMonitor monitor = new GranualProgressMonitor(parent); + return doRun(monitor); + } + + /** + * Executes job using the given filtering progress monitor. A hook for + * subclasses. + * + * @param monitor + * progress monitor + * @return result of the execution + */ + protected IStatus doRun(GranualProgressMonitor monitor) { + try { + internalRun(monitor); + } catch (CoreException e) { + cancel(); + return new Status( + IStatus.ERROR, + PlatformUI.PLUGIN_ID, + IStatus.ERROR, + WorkbenchMessages.FilteredItemsSelectionDialog_jobError, + e); + } + return Status.OK_STATUS; + } + + /** + * Main method for the job. + * + * @param monitor + * @throws CoreException + */ + private void internalRun(GranualProgressMonitor monitor) + throws CoreException { + try { + if (monitor.isCanceled()) { + return; + } + + this.itemsFilter = filter; + + if (filter.getPattern().length() != 0) { + filterContent(monitor); + } + + if (monitor.isCanceled()) { + return; + } + + contentProvider.refresh(); + } finally { + monitor.done(); + } + } + + /** + * Filters items. + * + * @param monitor + * for monitoring progress + * @throws CoreException + */ + protected void filterContent(GranualProgressMonitor monitor) + throws CoreException { + +// if (lastCompletedFilter != null +// && lastCompletedFilter.isSubFilter(this.itemsFilter)) { +// +// int length = lastCompletedResult.size() / 500; +// monitor +// .beginTask( +// WorkbenchMessages.FilteredItemsSelectionDialog_cacheSearchJob_taskName, +// length); +// +// for (int pos = 0; pos < lastCompletedResult.size(); pos++) { +// +// Object item = lastCompletedResult.get(pos); +// if (monitor.isCanceled()) { +// break; +// } +// contentProvider.add(item, itemsFilter); +// +// if ((pos % 500) == 0) { +// monitor.worked(1); +// } +// } +// +// } else { + + lastCompletedFilter = null; + lastCompletedResult = null; + + SubProgressMonitor subMonitor = null; + if (monitor != null) { + monitor + .beginTask( + WorkbenchMessages.FilteredItemsSelectionDialog_searchJob_taskName, + 100); + subMonitor = new SubProgressMonitor(monitor, 95); + + } + + fillContentProvider(contentProvider, itemsFilter, subMonitor); + + if (monitor != null && !monitor.isCanceled()) { + monitor.worked(2); + contentProvider.rememberResult(itemsFilter); + monitor.worked(3); + } + //} + + } + + } + + /** + * History stores a list of key, object pairs. The list is bounded at a + * certain size. If the list exceeds this size the oldest element is removed + * from the list. An element can be added/renewed with a call to + * accessed(Object). + *

+ * The history can be stored to/loaded from an XML file. + */ + protected static abstract class SelectionHistory { + + private static final String DEFAULT_ROOT_NODE_NAME = "historyRootNode"; //$NON-NLS-1$ + + private static final String DEFAULT_INFO_NODE_NAME = "infoNode"; //$NON-NLS-1$ + + private static final int MAX_HISTORY_SIZE = 60; + + private final Set historyList; + + private final String rootNodeName; + + private final String infoNodeName; + + private SelectionHistory(String rootNodeName, String infoNodeName) { + + historyList = Collections.synchronizedSet(new LinkedHashSet() { + + private static final long serialVersionUID = 0L; + + /* + * (non-Javadoc) + * + * @see java.util.LinkedList#add(java.lang.Object) + */ + @Override + public boolean add(Object arg0) { + if (this.size() >= MAX_HISTORY_SIZE) { + Iterator iterator = this.iterator(); + iterator.next(); + iterator.remove(); + } + return super.add(arg0); + } + + }); + + this.rootNodeName = rootNodeName; + this.infoNodeName = infoNodeName; + } + + /** + * Creates new instance of SelectionHistory. + */ + public SelectionHistory() { + this(DEFAULT_ROOT_NODE_NAME, DEFAULT_INFO_NODE_NAME); + } + + /** + * Adds object to history. + * + * @param object + * the item to be added to the history + */ + public synchronized void accessed(Object object) { + historyList.remove(object); + historyList.add(object); + } + + /** + * Returns true if history contains object. + * + * @param object + * the item for which check will be executed + * @return true if history contains object + * false in other way + */ + public synchronized boolean contains(Object object) { + return historyList.contains(object); + } + + /** + * Returns true if history is empty. + * + * @return true if history is empty + */ + public synchronized boolean isEmpty() { + return historyList.isEmpty(); + } + + /** + * Remove element from history. + * + * @param element + * to remove form the history + * @return true if this list contained the specified + * element + */ + public synchronized boolean remove(Object element) { + return historyList.remove(element); + } + + /** + * Load history elements from memento. + * + * @param memento + * memento from which the history will be retrieved + */ + public void load(IMemento memento) { + + XMLMemento historyMemento = (XMLMemento) memento + .getChild(rootNodeName); + + if (historyMemento == null) { + return; + } + + IMemento[] mementoElements = historyMemento + .getChildren(infoNodeName); + for (int i = 0; i < mementoElements.length; ++i) { + IMemento mementoElement = mementoElements[i]; + Object object = restoreItemFromMemento(mementoElement); + if (object != null) { + historyList.add(object); + } + } + } + + /** + * Save history elements to memento. + * + * @param memento + * memento to which the history will be added + */ + public void save(IMemento memento) { + + IMemento historyMemento = memento.createChild(rootNodeName); + + Object[] items = getHistoryItems(); + for (int i = 0; i < items.length; i++) { + Object item = items[i]; + IMemento elementMemento = historyMemento + .createChild(infoNodeName); + storeItemToMemento(item, elementMemento); + } + + } + + /** + * Gets array of history items. + * + * @return array of history elements + */ + public synchronized Object[] getHistoryItems() { + return historyList.toArray(); + } + + /** + * Creates an object using given memento. + * + * @param memento + * memento used for creating new object + * + * @return the restored object + */ + protected abstract Object restoreItemFromMemento(IMemento memento); + + /** + * Store object in IMemento. + * + * @param item + * the item to store + * @param memento + * the memento to store to + */ + protected abstract void storeItemToMemento(Object item, IMemento memento); + + } + + /** + * Filters elements using SearchPattern by comparing the names of items with + * the filter pattern. + */ + protected abstract class ItemsFilter { + + protected SearchPattern patternMatcher; + + /** + * Creates new instance of ItemsFilter. + */ + public ItemsFilter() { + this(new SearchPattern()); + } + + /** + * Creates new instance of ItemsFilter. + * + * @param searchPattern + * the pattern to be used when filtering + */ + public ItemsFilter(SearchPattern searchPattern) { + patternMatcher = searchPattern; + String stringPattern = ""; //$NON-NLS-1$ + if (pattern != null && !pattern.getText().equals("*")) { //$NON-NLS-1$ + stringPattern = pattern.getText(); + } + patternMatcher.setPattern(stringPattern); + } + + /** + * Check if the given filter is a sub-filter of this filter. The default + * implementation checks if the SearchPattern from the + * given filter is a sub-pattern of the one from this filter. + *

+ * WARNING: This method is not defined in reading order, i.e. + * a.isSubFilter(b) is true iff + * b is a sub-filter of a, and not + * vice-versa. + *

+ * + * @param filter + * the filter to be checked, or null + * @return true if the given filter is sub-filter of this + * filter, false if the given filter isn't a + * sub-filter or is null + * + * @see org.eclipse.ui.dialogs.SearchPattern#isSubPattern(org.eclipse.ui.dialogs.SearchPattern) + */ + public boolean isSubFilter(ItemsFilter filter) { + if (filter != null) { + return this.patternMatcher.isSubPattern(filter.patternMatcher); + } + return false; + } + + /** + * Checks whether the provided filter is equal to the current filter. + * The default implementation checks if SearchPattern + * from current filter is equal to the one from provided filter. + * + * @param filter + * filter to be checked, or null + * @return true if the given filter is equal to current + * filter, false if given filter isn't equal to + * current one or if it is null + * + * @see org.eclipse.ui.dialogs.SearchPattern#equalsPattern(org.eclipse.ui.dialogs.SearchPattern) + */ + public boolean equalsFilter(ItemsFilter filter) { + if (filter != null + && filter.patternMatcher.equalsPattern(this.patternMatcher)) { + return true; + } + return false; + } + + /** + * Checks whether the pattern's match rule is camel case. + * + * @return true if pattern's match rule is camel case, + * false otherwise + */ + public boolean isCamelCasePattern() { + return patternMatcher.getMatchRule() == SearchPattern.RULE_CAMELCASE_MATCH; + } + + /** + * Returns the pattern string. + * + * @return pattern for this filter + * + * @see SearchPattern#getPattern() + */ + public String getPattern() { + return patternMatcher.getPattern(); + } + + /** + * Returns the rule to apply for matching keys. + * + * @return an implementation-specific match rule + * + * @see SearchPattern#getMatchRule() for match rules returned by the + * default implementation + */ + public int getMatchRule() { + return patternMatcher.getMatchRule(); + } + + /** + * Matches text with filter. + * + * @param text + * the text to match with the filter + * @return true if text matches with filter pattern, + * false otherwise + */ + protected boolean matches(String text) { + return patternMatcher.matches(text); + } + + /** + * General method for matching raw name pattern. Checks whether current + * pattern is prefix of name provided item. + * + * @param item + * item to check + * @return true if current pattern is a prefix of name + * provided item, false if item's name is shorter + * than prefix or sequences of characters don't match. + */ + public boolean matchesRawNamePattern(Object item) { + String prefix = patternMatcher.getPattern(); + String text = getElementName(item); + + if (text == null) { + return false; + } + + int textLength = text.length(); + int prefixLength = prefix.length(); + if (textLength < prefixLength) { + return false; + } + for (int i = prefixLength - 1; i >= 0; i--) { + if (Character.toLowerCase(prefix.charAt(i)) != Character + .toLowerCase(text.charAt(i))) { + return false; + } + } + return true; + } + + /** + * Matches an item against filter conditions. + * + * @param item + * @return true if item matches against filter conditions, false + * otherwise + */ + public abstract boolean matchItem(Object item); + + /** + * Checks consistency of an item. Item is inconsistent if was changed or + * removed. + * + * @param item + * @return true if item is consistent, false + * if item is inconsistent + */ + public abstract boolean isConsistentItem(Object item); + + } + + /** + * An interface to content providers for + * FilterItemsSelectionDialog. + */ + protected abstract class AbstractContentProvider { + /** + * Adds the item to the content provider iff the filter matches the + * item. Otherwise does nothing. + * + * @param item + * the item to add + * @param itemsFilter + * the filter + * + * @see FilteredItemsSelectionDialog.ItemsFilter#matchItem(Object) + */ + public abstract void add(Object item, ItemsFilter itemsFilter); + } + + /** + * Collects filtered elements. Contains one synchronized, sorted set for + * collecting filtered elements. All collected elements are sorted using + * comparator. Comparator is returned by getElementComparator() method. + * Implementation of ItemsFilter is used to filter elements. + * The key function of filter used in to filtering is + * matchElement(Object item). + *

+ * The ContentProvider class also provides item filtering + * methods. The filtering has been moved from the standard TableView + * getFilteredItems() method to content provider, because + * ILazyContentProvider and virtual tables are used. This + * class is responsible for adding a separator below history items and + * marking each items as duplicate if its name repeats more than once on the + * filtered list. + */ + private class ContentProvider extends AbstractContentProvider implements + IStructuredContentProvider, ILazyContentProvider { + + private SelectionHistory selectionHistory; + + /** + * Raw result of the searching (unsorted, unfiltered). + *

+ * Standard object flow: + * items -> lastSortedItems -> lastFilteredItems + */ + private final Set items; + + /** + * Items that are duplicates. + */ + private final Set duplicates; + + /** + * List of ViewerFilters to be used during filtering + */ + private List filters; + + /** + * Result of the last filtering. + *

+ * Standard object flow: + * items -> lastSortedItems -> lastFilteredItems + */ + private List lastFilteredItems; + + /** + * Result of the last sorting. + *

+ * Standard object flow: + * items -> lastSortedItems -> lastFilteredItems + */ + private final List lastSortedItems; + + /** + * Used for getFilteredItems() method canceling (when the + * job that invoked the method was canceled). + *

+ * Method canceling could be based (only) on monitor canceling + * unfortunately sometimes the method getFilteredElements() + * could be run with a null monitor, the reset flag have + * to be left intact. + */ + private boolean reset; + + /** + * Creates new instance of ContentProvider. + */ + public ContentProvider() { + this.items = Collections.synchronizedSet(new HashSet(2048)); + this.duplicates = Collections.synchronizedSet(new HashSet(256)); + this.lastFilteredItems = new ArrayList(); + this.lastSortedItems = Collections.synchronizedList(new ArrayList( + 2048)); + } + + /** + * Sets selection history. + * + * @param selectionHistory + * The selectionHistory to set. + */ + public void setSelectionHistory(SelectionHistory selectionHistory) { + this.selectionHistory = selectionHistory; + } + + /** + * @return Returns the selectionHistory. + */ + public SelectionHistory getSelectionHistory() { + return selectionHistory; + } + + /** + * Removes all content items and resets progress message. + */ + public void reset() { + reset = true; + this.items.clear(); + this.duplicates.clear(); + this.lastSortedItems.clear(); + } + + /** + * Stops reloading cache - getFilteredItems() method. + */ + public void stopReloadingCache() { + reset = true; + } + + /** + * Adds filtered item. + * + * @param item + * @param itemsFilter + */ + @Override + public void add(Object item, ItemsFilter itemsFilter) { + if (itemsFilter == filter) { + if (itemsFilter != null) { + if (itemsFilter.matchItem(item)) { + this.items.add(item); + } + } else { + this.items.add(item); + } + } + } + + /** + * Add all history items to contentProvider. + * + * @param itemsFilter + */ + public void addHistoryItems(ItemsFilter itemsFilter) { + if (this.selectionHistory != null) { + Object[] items = this.selectionHistory.getHistoryItems(); + for (int i = 0; i < items.length; i++) { + Object item = items[i]; + if (itemsFilter == filter) { + if (itemsFilter != null) { + if (itemsFilter.matchItem(item)) { + if (itemsFilter.isConsistentItem(item)) { + this.items.add(item); + } else { + this.selectionHistory.remove(item); + } + } + } + } + } + } + } + + /** + * Refresh dialog. + */ + public void refresh() { + scheduleRefresh(); + } + + /** + * Removes items from history and refreshes the view. + * + * @param item + * to remove + * + * @return removed item + */ + public Object removeHistoryElement(Object item) { + if (this.selectionHistory != null) { + this.selectionHistory.remove(item); + } + if (filter == null || filter.getPattern().length() == 0) { + items.remove(item); + duplicates.remove(item); + this.lastSortedItems.remove(item); + } + + synchronized (lastSortedItems) { + Collections.sort(lastSortedItems, getHistoryComparator()); + } + return item; + } + + /** + * Adds item to history and refresh view. + * + * @param item + * to add + */ + public void addHistoryElement(Object item) { + if (this.selectionHistory != null) { + this.selectionHistory.accessed(item); + } + if (filter == null || !filter.matchItem(item)) { + this.items.remove(item); + this.duplicates.remove(item); + this.lastSortedItems.remove(item); + } + synchronized (lastSortedItems) { + Collections.sort(lastSortedItems, getHistoryComparator()); + } + this.refresh(); + } + + /** + * @param item + * @return true if given item is part of the history + */ + public boolean isHistoryElement(Object item) { + if (this.selectionHistory != null) { + return this.selectionHistory.contains(item); + } + return false; + } + + /** + * Sets/unsets given item as duplicate. + * + * @param item + * item to change + * + * @param isDuplicate + * duplicate flag + */ + public void setDuplicateElement(Object item, boolean isDuplicate) { + if (this.items.contains(item)) { + if (isDuplicate) { + this.duplicates.add(item); + } else { + this.duplicates.remove(item); + } + } + } + + /** + * Indicates whether given item is a duplicate. + * + * @param item + * item to check + * @return true if item is duplicate + */ + public boolean isDuplicateElement(Object item) { + return duplicates.contains(item); + } + + /** + * Load history from memento. + * + * @param memento + * memento from which the history will be retrieved + */ + public void loadHistory(IMemento memento) { + if (this.selectionHistory != null) { + this.selectionHistory.load(memento); + } + } + + /** + * Save history to memento. + * + * @param memento + * memento to which the history will be added + */ + public void saveHistory(IMemento memento) { + if (this.selectionHistory != null) { + this.selectionHistory.save(memento); + } + } + + /** + * Gets sorted items. + * + * @return sorted items + */ + private Object[] getSortedItems() { + if (lastSortedItems.size() != items.size()) { + synchronized (lastSortedItems) { + lastSortedItems.clear(); + lastSortedItems.addAll(items); + Collections.sort(lastSortedItems, getHistoryComparator()); + } + } + return lastSortedItems.toArray(); + } + + /** + * Remember result of filtering. + * + * @param itemsFilter + */ + public void rememberResult(ItemsFilter itemsFilter) { + List itemsList = Collections.synchronizedList(Arrays + .asList(getSortedItems())); + // synchronization + if (itemsFilter == filter) { + lastCompletedFilter = itemsFilter; + lastCompletedResult = itemsList; + } + + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object) + */ + @Override + public Object[] getElements(Object inputElement) { + return lastFilteredItems.toArray(); + } + + public int getNumberOfElements() { + return lastFilteredItems.size(); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.viewers.IContentProvider#dispose() + */ + @Override + public void dispose() { + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer, + * java.lang.Object, java.lang.Object) + */ + @Override + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.viewers.ILazyContentProvider#updateElement(int) + */ + @Override + public void updateElement(int index) { + + CdmFilteredItemsSelectionDialog.this.list.replace((lastFilteredItems + .size() > index) ? lastFilteredItems.get(index) : null, + index); + + } + + /** + * Main method responsible for getting the filtered items and checking + * for duplicates. It is based on the + * {@link FilteredItemsSelectionDialog.ContentProvider#getFilteredItems(Object, IProgressMonitor)}. + * + * @param checkDuplicates + * true if data concerning elements + * duplication should be computed - it takes much more time + * than standard filtering + * + * @param monitor + * progress monitor + */ + public void reloadCache(boolean checkDuplicates, + IProgressMonitor monitor) { + + reset = false; + + if (monitor != null) { + // the work is divided into two actions of the same length + int totalWork = checkDuplicates ? 200 : 100; + + monitor + .beginTask( + WorkbenchMessages.FilteredItemsSelectionDialog_cacheRefreshJob, + totalWork); + } + + // the TableViewer's root (the input) is treated as parent + + lastFilteredItems = Arrays.asList(getFilteredItems(list.getInput(), + monitor != null ? new SubProgressMonitor(monitor, 100) + : null)); + + if (reset || (monitor != null && monitor.isCanceled())) { + if (monitor != null) { + monitor.done(); + } + return; + } + + if (checkDuplicates) { + checkDuplicates(monitor); + } + if (monitor != null) { + monitor.done(); + } + } + + private void checkDuplicates(IProgressMonitor monitor) { + synchronized (lastFilteredItems) { + IProgressMonitor subMonitor = null; + int reportEvery = lastFilteredItems.size() / 20; + if (monitor != null) { + subMonitor = new SubProgressMonitor(monitor, 100); + subMonitor + .beginTask( + WorkbenchMessages.FilteredItemsSelectionDialog_cacheRefreshJob_checkDuplicates, + 5); + } + HashMap helperMap = new HashMap(); + for (int i = 0; i < lastFilteredItems.size(); i++) { + if (reset + || (subMonitor != null && subMonitor.isCanceled())) { + return; + } + Object item = lastFilteredItems.get(i); + + if (!(item instanceof ItemsListSeparator)) { + Object previousItem = helperMap.put( + getElementName(item), item); + if (previousItem != null) { + setDuplicateElement(previousItem, true); + setDuplicateElement(item, true); + } else { + setDuplicateElement(item, false); + } + } + + if (subMonitor != null && reportEvery != 0 + && (i + 1) % reportEvery == 0) { + subMonitor.worked(1); + } + } + helperMap.clear(); + } + } + + /** + * Returns an array of items filtered using the provided + * ViewerFilters with a separator added. + * + * @param parent + * the parent + * @param monitor + * progress monitor, can be null + * @return an array of filtered items + */ + protected Object[] getFilteredItems(Object parent, + IProgressMonitor monitor) { + int ticks = 100; + if (monitor == null) { + monitor = new NullProgressMonitor(); + } + + monitor + .beginTask( + WorkbenchMessages.FilteredItemsSelectionDialog_cacheRefreshJob_getFilteredElements, + ticks); + if (filters != null) { + ticks /= (filters.size() + 2); + } else { + ticks /= 2; + } + + // get already sorted array + Object[] filteredElements = getSortedItems(); + + monitor.worked(ticks); + + // filter the elements using provided ViewerFilters + if (filters != null && filteredElements != null) { + for (Iterator iter = filters.iterator(); iter.hasNext();) { + ViewerFilter f = (ViewerFilter) iter.next(); + filteredElements = f.filter(list, parent, filteredElements); + monitor.worked(ticks); + } + } + + if (filteredElements == null || monitor.isCanceled()) { + monitor.done(); + return new Object[0]; + } + + ArrayList preparedElements = new ArrayList(); + boolean hasHistory = false; + + if (filteredElements.length > 0) { + if (isHistoryElement(filteredElements[0])) { + hasHistory = true; + } + } + + int reportEvery = filteredElements.length / ticks; + + // add separator + for (int i = 0; i < filteredElements.length; i++) { + Object item = filteredElements[i]; + + if (hasHistory && !isHistoryElement(item)) { + preparedElements.add(itemsListSeparator); + hasHistory = false; + } + + preparedElements.add(item); + + if (reportEvery != 0 && ((i + 1) % reportEvery == 0)) { + monitor.worked(1); + } + } + + monitor.done(); + + return preparedElements.toArray(); + } + + /** + * Adds a filter to this content provider. For an example usage of such + * filters look at the project org.eclipse.ui.ide, class + * org.eclipse.ui.dialogs.FilteredResourcesSelectionDialog.CustomWorkingSetFilter. + * + * + * @param filter + * the filter to be added + */ + public void addFilter(ViewerFilter filter) { + if (filters == null) { + filters = new ArrayList(); + } + filters.add(filter); + // currently filters are only added when dialog is restored + // if it is changed, refreshing the whole TableViewer should be + // added + } + + } + + /** + * A content provider that does nothing. + */ + private class NullContentProvider implements IContentProvider { + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.viewers.IContentProvider#dispose() + */ + @Override + public void dispose() { + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer, + * java.lang.Object, java.lang.Object) + */ + @Override + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + + } + + /** + * DetailsContentViewer objects are wrappers for labels. + * DetailsContentViewer provides means to change label's image and text when + * the attached LabelProvider is updated. + */ + private class DetailsContentViewer extends ContentViewer { + + private final CLabel label; + + /** + * Unfortunately, it was impossible to delegate displaying border to + * label. The ViewForm is used because + * CLabel displays shadow when border is present. + */ + private final ViewForm viewForm; + + /** + * Constructs a new instance of this class given its parent and a style + * value describing its behavior and appearance. + * + * @param parent + * the parent component + * @param style + * SWT style bits + */ + public DetailsContentViewer(Composite parent, int style) { + viewForm = new ViewForm(parent, style); + GridData gd = new GridData(GridData.FILL_HORIZONTAL); + gd.horizontalSpan = 2; + viewForm.setLayoutData(gd); + label = new CLabel(viewForm, SWT.FLAT); + label.setFont(parent.getFont()); + viewForm.setContent(label); + hookControl(label); + } + + /** + * Shows/hides the content viewer. + * + * @param visible + * if the content viewer should be visible. + */ + public void setVisible(boolean visible) { + GridData gd = (GridData) viewForm.getLayoutData(); + gd.exclude = !visible; + viewForm.getParent().layout(); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.viewers.Viewer#inputChanged(java.lang.Object, + * java.lang.Object) + */ + @Override + protected void inputChanged(Object input, Object oldInput) { + if (oldInput == null) { + if (input == null) { + return; + } + refresh(); + return; + } + + refresh(); + + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.viewers.ContentViewer#handleLabelProviderChanged(org.eclipse.jface.viewers.LabelProviderChangedEvent) + */ + @Override + protected void handleLabelProviderChanged( + LabelProviderChangedEvent event) { + if (event != null) { + refresh(event.getElements()); + } + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.viewers.Viewer#getControl() + */ + @Override + public Control getControl() { + return label; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.viewers.Viewer#getSelection() + */ + @Override + public ISelection getSelection() { + // not supported + return null; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.viewers.Viewer#refresh() + */ + @Override + public void refresh() { + Object input = this.getInput(); + if (input != null) { + ILabelProvider labelProvider = (ILabelProvider) getLabelProvider(); + doRefresh(labelProvider.getText(input), labelProvider + .getImage(input)); + } else { + doRefresh(null, null); + } + } + + /** + * Sets the given text and image to the label. + * + * @param text + * the new text or null + * @param image + * the new image + */ + private void doRefresh(String text, Image image) { + if ( text != null ) { + text = LegacyActionTools.escapeMnemonics(text); + } + label.setText(text); + label.setImage(image); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.viewers.Viewer#setSelection(org.eclipse.jface.viewers.ISelection, + * boolean) + */ + @Override + public void setSelection(ISelection selection, boolean reveal) { + // not supported + } + + /** + * Refreshes the label if currently chosen element is on the list. + * + * @param objs + * list of changed object + */ + private void refresh(Object[] objs) { + if (objs == null || getInput() == null) { + return; + } + Object input = getInput(); + for (int i = 0; i < objs.length; i++) { + if (objs[i].equals(input)) { + refresh(); + break; + } + } + } + } + + /** + * Compares items according to the history. + */ + private class HistoryComparator implements Comparator { + + /* + * (non-Javadoc) + * + * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) + */ + @Override + public int compare(Object o1, Object o2) { + boolean h1 = isHistoryElement(o1); + boolean h2 = isHistoryElement(o2); + if (h1 == h2) { + return getItemsComparator().compare(o1, o2); + } + + if (h1) { + return -2; + } + if (h2) { + return +2; + } + + return 0; + } + + } + + + /** + * Get the control where the search pattern is entered. Any filtering should + * be done using an {@link ItemsFilter}. This control should only be + * accessed for listeners that wish to handle events that do not affect + * filtering such as custom traversal. + * + * @return Control or null if the pattern control has not + * been created. + */ + public Control getPatternControl() { + return pattern; + } + + } + + -- 2.34.1