--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2000, 2010 IBM Corporation and others.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * IBM Corporation - initial API and implementation\r
+ * Willian Mitsuda <wmitsuda@gmail.com>\r
+ * - Fix for bug 196553 - [Dialogs] Support IColorProvider/IFontProvider in FilteredItemsSelectionDialog\r
+ * Peter Friese <peter.friese@gentleware.com>\r
+ * - Fix for bug 208602 - [Dialogs] Open Type dialog needs accessible labels\r
+ * Simon Muschel <smuschel@gmx.de> - bug 258493\r
+ *******************************************************************************/\r
+package org.simantics.utils.ui.dialogs;\r
+\r
+import java.io.IOException;\r
+import java.io.StringReader;\r
+import java.io.StringWriter;\r
+import java.util.ArrayList;\r
+import java.util.Arrays;\r
+import java.util.Collections;\r
+import java.util.Comparator;\r
+import java.util.HashMap;\r
+import java.util.HashSet;\r
+import java.util.Iterator;\r
+import java.util.LinkedHashSet;\r
+import java.util.List;\r
+import java.util.Set;\r
+\r
+import org.eclipse.core.commands.AbstractHandler;\r
+import org.eclipse.core.commands.ExecutionEvent;\r
+import org.eclipse.core.commands.IHandler;\r
+import org.eclipse.core.runtime.Assert;\r
+import org.eclipse.core.runtime.CoreException;\r
+import org.eclipse.core.runtime.IProgressMonitor;\r
+import org.eclipse.core.runtime.IStatus;\r
+import org.eclipse.core.runtime.ListenerList;\r
+import org.eclipse.core.runtime.NullProgressMonitor;\r
+import org.eclipse.core.runtime.ProgressMonitorWrapper;\r
+import org.eclipse.core.runtime.Status;\r
+import org.eclipse.core.runtime.SubProgressMonitor;\r
+import org.eclipse.core.runtime.jobs.Job;\r
+import org.eclipse.jface.action.Action;\r
+import org.eclipse.jface.action.ActionContributionItem;\r
+import org.eclipse.jface.action.IAction;\r
+import org.eclipse.jface.action.IMenuListener;\r
+import org.eclipse.jface.action.IMenuManager;\r
+import org.eclipse.jface.action.LegacyActionTools;\r
+import org.eclipse.jface.action.MenuManager;\r
+import org.eclipse.jface.dialogs.IDialogSettings;\r
+import org.eclipse.jface.viewers.ColumnLabelProvider;\r
+import org.eclipse.jface.viewers.ContentViewer;\r
+import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider;\r
+import org.eclipse.jface.viewers.DoubleClickEvent;\r
+import org.eclipse.jface.viewers.IColorProvider;\r
+import org.eclipse.jface.viewers.IContentProvider;\r
+import org.eclipse.jface.viewers.IDoubleClickListener;\r
+import org.eclipse.jface.viewers.IFontProvider;\r
+import org.eclipse.jface.viewers.ILabelDecorator;\r
+import org.eclipse.jface.viewers.ILabelProvider;\r
+import org.eclipse.jface.viewers.ILabelProviderListener;\r
+import org.eclipse.jface.viewers.ILazyContentProvider;\r
+import org.eclipse.jface.viewers.ISelection;\r
+import org.eclipse.jface.viewers.ISelectionChangedListener;\r
+import org.eclipse.jface.viewers.IStructuredContentProvider;\r
+import org.eclipse.jface.viewers.LabelProvider;\r
+import org.eclipse.jface.viewers.LabelProviderChangedEvent;\r
+import org.eclipse.jface.viewers.SelectionChangedEvent;\r
+import org.eclipse.jface.viewers.StructuredSelection;\r
+import org.eclipse.jface.viewers.StyledCellLabelProvider;\r
+import org.eclipse.jface.viewers.StyledString;\r
+import org.eclipse.jface.viewers.TableViewer;\r
+import org.eclipse.jface.viewers.TableViewerColumn;\r
+import org.eclipse.jface.viewers.Viewer;\r
+import org.eclipse.jface.viewers.ViewerCell;\r
+import org.eclipse.jface.viewers.ViewerFilter;\r
+import org.eclipse.osgi.util.NLS;\r
+import org.eclipse.swt.SWT;\r
+import org.eclipse.swt.accessibility.ACC;\r
+import org.eclipse.swt.accessibility.AccessibleAdapter;\r
+import org.eclipse.swt.accessibility.AccessibleEvent;\r
+import org.eclipse.swt.custom.CLabel;\r
+import org.eclipse.swt.custom.ViewForm;\r
+import org.eclipse.swt.events.KeyAdapter;\r
+import org.eclipse.swt.events.KeyEvent;\r
+import org.eclipse.swt.events.ModifyEvent;\r
+import org.eclipse.swt.events.ModifyListener;\r
+import org.eclipse.swt.events.MouseAdapter;\r
+import org.eclipse.swt.events.MouseEvent;\r
+import org.eclipse.swt.events.SelectionAdapter;\r
+import org.eclipse.swt.events.SelectionEvent;\r
+import org.eclipse.swt.events.TraverseEvent;\r
+import org.eclipse.swt.events.TraverseListener;\r
+import org.eclipse.swt.graphics.Color;\r
+import org.eclipse.swt.graphics.Font;\r
+import org.eclipse.swt.graphics.GC;\r
+import org.eclipse.swt.graphics.Image;\r
+import org.eclipse.swt.graphics.Point;\r
+import org.eclipse.swt.graphics.Rectangle;\r
+import org.eclipse.swt.layout.GridData;\r
+import org.eclipse.swt.layout.GridLayout;\r
+import org.eclipse.swt.widgets.Composite;\r
+import org.eclipse.swt.widgets.Control;\r
+import org.eclipse.swt.widgets.Display;\r
+import org.eclipse.swt.widgets.Event;\r
+import org.eclipse.swt.widgets.Label;\r
+import org.eclipse.swt.widgets.Menu;\r
+import org.eclipse.swt.widgets.Shell;\r
+import org.eclipse.swt.widgets.Table;\r
+import org.eclipse.swt.widgets.TableColumn;\r
+import org.eclipse.swt.widgets.Text;\r
+import org.eclipse.swt.widgets.ToolBar;\r
+import org.eclipse.swt.widgets.ToolItem;\r
+import org.eclipse.ui.ActiveShellExpression;\r
+import org.eclipse.ui.IMemento;\r
+import org.eclipse.ui.IWorkbenchCommandConstants;\r
+import org.eclipse.ui.IWorkbenchPreferenceConstants;\r
+import org.eclipse.ui.PlatformUI;\r
+import org.eclipse.ui.WorkbenchException;\r
+import org.eclipse.ui.XMLMemento;\r
+import org.eclipse.ui.dialogs.SearchPattern;\r
+import org.eclipse.ui.dialogs.SelectionStatusDialog;\r
+import org.eclipse.ui.handlers.IHandlerActivation;\r
+import org.eclipse.ui.handlers.IHandlerService;\r
+import org.eclipse.ui.internal.IWorkbenchGraphicConstants;\r
+import org.eclipse.ui.internal.WorkbenchImages;\r
+import org.eclipse.ui.internal.WorkbenchMessages;\r
+import org.eclipse.ui.internal.WorkbenchPlugin;\r
+import org.eclipse.ui.progress.UIJob;\r
+import org.eclipse.ui.statushandlers.StatusManager;\r
+\r
+/**\r
+ * Shows a list of items to the user with a text entry field for a string\r
+ * pattern used to filter the list of items.\r
+ * \r
+ * This is a copy from org.eclipse.ui.dialogs.FilteredItemsSelectionDialog\r
+ * with a hook for column creation and a fix to a (possible) bug that \r
+ * prevented the empty pattern to be handled correctly. This version of the\r
+ * dialog also has the possibility to force an update of the contents if new\r
+ * elements are added.\r
+ * \r
+ * TODO: Clean up warnings.\r
+ * \r
+ * @author Janne Kauttio (modifications only)\r
+ * \r
+ * @since 3.3\r
+ */\r
+@SuppressWarnings({ "restriction", "rawtypes", "unchecked" })\r
+public abstract class ColumnFilteredItemsSelectionDialog extends\r
+ SelectionStatusDialog {\r
+\r
+ private static final String DIALOG_BOUNDS_SETTINGS = "DialogBoundsSettings"; //$NON-NLS-1$\r
+\r
+ private static final String SHOW_STATUS_LINE = "ShowStatusLine"; //$NON-NLS-1$\r
+\r
+ private static final String HISTORY_SETTINGS = "History"; //$NON-NLS-1$\r
+\r
+ private static final String DIALOG_HEIGHT = "DIALOG_HEIGHT"; //$NON-NLS-1$\r
+\r
+ private static final String DIALOG_WIDTH = "DIALOG_WIDTH"; //$NON-NLS-1$\r
+\r
+ /**\r
+ * Represents an empty selection in the pattern input field (used only for\r
+ * initial pattern).\r
+ */\r
+ public static final int NONE = 0;\r
+\r
+ /**\r
+ * Pattern input field selection where caret is at the beginning (used only\r
+ * for initial pattern).\r
+ */\r
+ public static final int CARET_BEGINNING = 1;\r
+\r
+ /**\r
+ * Represents a full selection in the pattern input field (used only for\r
+ * initial pattern).\r
+ */\r
+ public static final int FULL_SELECTION = 2;\r
+\r
+ private Text pattern;\r
+\r
+ private TableViewer viewer;\r
+\r
+ private DetailsContentViewer details;\r
+\r
+ /**\r
+ * It is a duplicate of a field in the CLabel class in DetailsContentViewer.\r
+ * It is maintained, because the <code>setDetailsLabelProvider()</code>\r
+ * could be called before content area is created.\r
+ */\r
+ private ILabelProvider detailsLabelProvider;\r
+\r
+ private ItemsListLabelProvider itemsListLabelProvider;\r
+\r
+ private MenuManager menuManager;\r
+\r
+ private MenuManager contextMenuManager;\r
+\r
+ private boolean multi;\r
+\r
+ private ToolBar toolBar;\r
+\r
+ private ToolItem toolItem;\r
+\r
+ private Label progressLabel;\r
+\r
+ private ToggleStatusLineAction toggleStatusLineAction;\r
+\r
+ private RemoveHistoryItemAction removeHistoryItemAction;\r
+\r
+ private ActionContributionItem removeHistoryActionContributionItem;\r
+\r
+ private IStatus status;\r
+\r
+ private RefreshCacheJob refreshCacheJob;\r
+\r
+ private RefreshProgressMessageJob refreshProgressMessageJob = new RefreshProgressMessageJob();\r
+\r
+ private Object[] currentSelection;\r
+\r
+ private ContentProvider contentProvider;\r
+\r
+ private FilterHistoryJob filterHistoryJob;\r
+\r
+ private FilterJob filterJob;\r
+\r
+ private ItemsFilter filter;\r
+\r
+ private List lastCompletedResult;\r
+\r
+ private ItemsFilter lastCompletedFilter;\r
+\r
+ private String initialPatternText;\r
+\r
+ private int selectionMode;\r
+\r
+ private ItemsListSeparator itemsListSeparator;\r
+\r
+ private static final String EMPTY_STRING = ""; //$NON-NLS-1$\r
+\r
+ private boolean refreshWithLastSelection = false;\r
+\r
+ private IHandlerActivation showViewHandler;\r
+\r
+ /**\r
+ * Creates a new instance of the class.\r
+ * \r
+ * @param shell\r
+ * shell to parent the dialog on\r
+ * @param multi\r
+ * indicates whether dialog allows to select more than one\r
+ * position in its list of items\r
+ */\r
+ public ColumnFilteredItemsSelectionDialog(Shell shell, boolean multi) {\r
+ super(shell);\r
+ this.multi = multi;\r
+ filterHistoryJob = new FilterHistoryJob();\r
+ filterJob = new FilterJob();\r
+ contentProvider = new ContentProvider();\r
+ refreshCacheJob = new RefreshCacheJob();\r
+ itemsListSeparator = new ItemsListSeparator(WorkbenchMessages.FilteredItemsSelectionDialog_separatorLabel);\r
+ selectionMode = NONE;\r
+ }\r
+\r
+ /**\r
+ * Creates a new instance of the class. Created dialog won't allow to select\r
+ * more than one item.\r
+ * \r
+ * @param shell\r
+ * shell to parent the dialog on\r
+ */\r
+ public ColumnFilteredItemsSelectionDialog(Shell shell) {\r
+ this(shell, false);\r
+ }\r
+\r
+ /**\r
+ * Adds viewer filter to the dialog items list.\r
+ * \r
+ * @param filter\r
+ * the new filter\r
+ */\r
+ protected void addListFilter(ViewerFilter filter) {\r
+ contentProvider.addFilter(filter);\r
+ }\r
+\r
+ /**\r
+ * Sets a new label provider for items in the list. If the label provider\r
+ * also implements {@link\r
+ * org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider\r
+ * .IStyledLabelProvider}, the style text labels provided by it will be used\r
+ * provided that the corresponding preference is set.\r
+ * \r
+ * @see IWorkbenchPreferenceConstants#USE_COLORED_LABELS\r
+ * \r
+ * @param listLabelProvider\r
+ * the label provider for items in the list\r
+ */\r
+ public void setListLabelProvider(ILabelProvider listLabelProvider) {\r
+ getItemsListLabelProvider().setProvider(listLabelProvider);\r
+ }\r
+\r
+ /**\r
+ * Returns the label decorator for selected items in the list.\r
+ * \r
+ * @return the label decorator for selected items in the list\r
+ */\r
+ private ILabelDecorator getListSelectionLabelDecorator() {\r
+ return getItemsListLabelProvider().getSelectionDecorator();\r
+ }\r
+\r
+ /**\r
+ * Sets the label decorator for selected items in the list.\r
+ * \r
+ * @param listSelectionLabelDecorator\r
+ * the label decorator for selected items in the list\r
+ */\r
+ public void setListSelectionLabelDecorator(\r
+ ILabelDecorator listSelectionLabelDecorator) {\r
+ getItemsListLabelProvider().setSelectionDecorator(\r
+ listSelectionLabelDecorator);\r
+ }\r
+\r
+ /**\r
+ * Returns the item list label provider.\r
+ * \r
+ * @return the item list label provider\r
+ */\r
+ private ItemsListLabelProvider getItemsListLabelProvider() {\r
+ if (itemsListLabelProvider == null) {\r
+ itemsListLabelProvider = new ItemsListLabelProvider(\r
+ new LabelProvider(), null);\r
+ }\r
+ return itemsListLabelProvider;\r
+ }\r
+\r
+ /**\r
+ * Sets label provider for the details field.\r
+ * \r
+ * For a single selection, the element sent to\r
+ * {@link ILabelProvider#getImage(Object)} and\r
+ * {@link ILabelProvider#getText(Object)} is the selected object, for\r
+ * multiple selection a {@link String} with amount of selected items is the\r
+ * element.\r
+ * \r
+ * @see #getSelectedItems() getSelectedItems() can be used to retrieve\r
+ * selected items and get the items count.\r
+ * \r
+ * @param detailsLabelProvider\r
+ * the label provider for the details field\r
+ */\r
+ public void setDetailsLabelProvider(ILabelProvider detailsLabelProvider) {\r
+ this.detailsLabelProvider = detailsLabelProvider;\r
+ if (details != null) {\r
+ details.setLabelProvider(detailsLabelProvider);\r
+ }\r
+ }\r
+\r
+ private ILabelProvider getDetailsLabelProvider() {\r
+ if (detailsLabelProvider == null) {\r
+ detailsLabelProvider = new LabelProvider();\r
+ }\r
+ return detailsLabelProvider;\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.jface.window.Window#create()\r
+ */\r
+ public void create() {\r
+ super.create();\r
+ pattern.setFocus();\r
+ }\r
+\r
+ /**\r
+ * Restores dialog using persisted settings. The default implementation\r
+ * restores the status of the details line and the selection history.\r
+ * \r
+ * @param settings\r
+ * settings used to restore dialog\r
+ */\r
+ protected void restoreDialog(IDialogSettings settings) {\r
+ boolean toggleStatusLine = true;\r
+\r
+ if (settings.get(SHOW_STATUS_LINE) != null) {\r
+ toggleStatusLine = settings.getBoolean(SHOW_STATUS_LINE);\r
+ }\r
+\r
+ toggleStatusLineAction.setChecked(toggleStatusLine);\r
+\r
+ details.setVisible(toggleStatusLine);\r
+\r
+ String setting = settings.get(HISTORY_SETTINGS);\r
+ if (setting != null) {\r
+ try {\r
+ IMemento memento = XMLMemento.createReadRoot(new StringReader(setting));\r
+ this.contentProvider.loadHistory(memento);\r
+ } catch (WorkbenchException e) {\r
+ // Simply don't restore the settings\r
+ StatusManager.getManager().handle(new Status(\r
+ IStatus.ERROR,\r
+ PlatformUI.PLUGIN_ID,\r
+ IStatus.ERROR,\r
+ WorkbenchMessages.FilteredItemsSelectionDialog_restoreError,\r
+ e));\r
+ }\r
+ }\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.jface.window.Window#close()\r
+ */\r
+ public boolean close() {\r
+ this.filterJob.cancel();\r
+ this.refreshCacheJob.cancel();\r
+ this.refreshProgressMessageJob.cancel();\r
+ if (showViewHandler != null) {\r
+ IHandlerService service = (IHandlerService) PlatformUI.getWorkbench().getService(IHandlerService.class);\r
+ service.deactivateHandler(showViewHandler);\r
+ showViewHandler.getHandler().dispose();\r
+ showViewHandler = null;\r
+ }\r
+ if (menuManager != null)\r
+ menuManager.dispose();\r
+ if (contextMenuManager != null)\r
+ contextMenuManager.dispose();\r
+ storeDialog(getDialogSettings());\r
+ return super.close();\r
+ }\r
+\r
+ /**\r
+ * Stores dialog settings.\r
+ * \r
+ * @param settings\r
+ * settings used to store dialog\r
+ */\r
+ protected void storeDialog(IDialogSettings settings) {\r
+ settings.put(SHOW_STATUS_LINE, toggleStatusLineAction.isChecked());\r
+\r
+ XMLMemento memento = XMLMemento.createWriteRoot(HISTORY_SETTINGS);\r
+ this.contentProvider.saveHistory(memento);\r
+ StringWriter writer = new StringWriter();\r
+ try {\r
+ memento.save(writer);\r
+ settings.put(HISTORY_SETTINGS, writer.getBuffer().toString());\r
+ } catch (IOException e) {\r
+ // Simply don't store the settings\r
+ StatusManager.getManager().handle(new Status(\r
+ IStatus.ERROR,\r
+ PlatformUI.PLUGIN_ID,\r
+ IStatus.ERROR,\r
+ WorkbenchMessages.FilteredItemsSelectionDialog_storeError,\r
+ e));\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Create a new header which is labelled by headerLabel.\r
+ * \r
+ * @param parent\r
+ * @return Label the label of the header\r
+ */\r
+ private Label createHeader(Composite parent) {\r
+ Composite header = new Composite(parent, SWT.NONE);\r
+\r
+ GridLayout layout = new GridLayout();\r
+ layout.numColumns = 2;\r
+ layout.marginWidth = 0;\r
+ layout.marginHeight = 0;\r
+ header.setLayout(layout);\r
+\r
+ Label headerLabel = new Label(header, SWT.NONE);\r
+ headerLabel.setText((getMessage() != null && getMessage().trim().length() > 0) ? getMessage()\r
+ : WorkbenchMessages.FilteredItemsSelectionDialog_patternLabel);\r
+ headerLabel.addTraverseListener(new TraverseListener() {\r
+ public void keyTraversed(TraverseEvent e) {\r
+ if (e.detail == SWT.TRAVERSE_MNEMONIC && e.doit) {\r
+ e.detail = SWT.TRAVERSE_NONE;\r
+ pattern.setFocus();\r
+ }\r
+ }\r
+ });\r
+\r
+ GridData gd = new GridData(GridData.FILL_HORIZONTAL);\r
+ headerLabel.setLayoutData(gd);\r
+\r
+ createViewMenu(header);\r
+ header.setLayoutData(gd);\r
+ return headerLabel;\r
+ }\r
+\r
+ /**\r
+ * Create the labels for the list and the progress. Return the list label.\r
+ * \r
+ * @param parent\r
+ * @return Label\r
+ */\r
+ private Label createLabels(Composite parent) {\r
+ Composite labels = new Composite(parent, SWT.NONE);\r
+\r
+ GridLayout layout = new GridLayout();\r
+ layout.numColumns = 2;\r
+ layout.marginWidth = 0;\r
+ layout.marginHeight = 0;\r
+ labels.setLayout(layout);\r
+\r
+ Label listLabel = new Label(labels, SWT.NONE);\r
+ listLabel.setText(WorkbenchMessages.FilteredItemsSelectionDialog_listLabel);\r
+\r
+ listLabel.addTraverseListener(new TraverseListener() {\r
+ public void keyTraversed(TraverseEvent e) {\r
+ if (e.detail == SWT.TRAVERSE_MNEMONIC && e.doit) {\r
+ e.detail = SWT.TRAVERSE_NONE;\r
+ viewer.getTable().setFocus();\r
+ }\r
+ }\r
+ });\r
+\r
+ GridData gd = new GridData(GridData.FILL_HORIZONTAL);\r
+ listLabel.setLayoutData(gd);\r
+\r
+ progressLabel = new Label(labels, SWT.RIGHT);\r
+ progressLabel.setLayoutData(gd);\r
+\r
+ labels.setLayoutData(gd);\r
+ return listLabel;\r
+ }\r
+\r
+ private void createViewMenu(Composite parent) {\r
+ toolBar = new ToolBar(parent, SWT.FLAT);\r
+ toolItem = new ToolItem(toolBar, SWT.PUSH, 0);\r
+\r
+ GridData data = new GridData();\r
+ data.horizontalAlignment = GridData.END;\r
+ toolBar.setLayoutData(data);\r
+\r
+ toolBar.addMouseListener(new MouseAdapter() {\r
+ public void mouseDown(MouseEvent e) {\r
+ showViewMenu();\r
+ }\r
+ });\r
+\r
+ toolItem.setImage(WorkbenchImages.getImage(IWorkbenchGraphicConstants.IMG_LCL_VIEW_MENU));\r
+ toolItem.setToolTipText(WorkbenchMessages.FilteredItemsSelectionDialog_menu);\r
+ toolItem.addSelectionListener(new SelectionAdapter() {\r
+ public void widgetSelected(SelectionEvent e) {\r
+ showViewMenu();\r
+ }\r
+ });\r
+\r
+ menuManager = new MenuManager();\r
+\r
+ fillViewMenu(menuManager);\r
+\r
+ IHandlerService service = (IHandlerService) PlatformUI.getWorkbench().getService(IHandlerService.class);\r
+ IHandler handler = new AbstractHandler() {\r
+ public Object execute(ExecutionEvent event) {\r
+ showViewMenu();\r
+ return null;\r
+ }\r
+ };\r
+ showViewHandler = service.activateHandler(\r
+ IWorkbenchCommandConstants.WINDOW_SHOW_VIEW_MENU, handler,\r
+ new ActiveShellExpression(getShell()));\r
+ }\r
+\r
+ /**\r
+ * Fills the menu of the dialog.\r
+ * \r
+ * @param menuManager\r
+ * the menu manager\r
+ */\r
+ protected void fillViewMenu(IMenuManager menuManager) {\r
+ toggleStatusLineAction = new ToggleStatusLineAction();\r
+ menuManager.add(toggleStatusLineAction);\r
+ }\r
+\r
+ private void showViewMenu() {\r
+ Menu menu = menuManager.createContextMenu(getShell());\r
+ Rectangle bounds = toolItem.getBounds();\r
+ Point topLeft = new Point(bounds.x, bounds.y + bounds.height);\r
+ topLeft = toolBar.toDisplay(topLeft);\r
+ menu.setLocation(topLeft.x, topLeft.y);\r
+ menu.setVisible(true);\r
+ }\r
+\r
+ /**\r
+ * Hook that allows to add actions to the context menu.\r
+ * <p>\r
+ * Subclasses may extend in order to add other actions.</p>\r
+ * \r
+ * @param menuManager the context menu manager\r
+ * @since 3.5\r
+ */\r
+ protected void fillContextMenu(IMenuManager menuManager) {\r
+ List selectedElements= ((StructuredSelection)viewer.getSelection()).toList();\r
+\r
+ Object item= null;\r
+\r
+ for (Iterator it= selectedElements.iterator(); it.hasNext();) {\r
+ item= it.next();\r
+ if (item instanceof ItemsListSeparator || !isHistoryElement(item)) {\r
+ return;\r
+ }\r
+ }\r
+\r
+ if (selectedElements.size() > 0) {\r
+ removeHistoryItemAction.setText(WorkbenchMessages.FilteredItemsSelectionDialog_removeItemsFromHistoryAction);\r
+\r
+ menuManager.add(removeHistoryActionContributionItem);\r
+\r
+ }\r
+ }\r
+\r
+ private void createPopupMenu() {\r
+ removeHistoryItemAction = new RemoveHistoryItemAction();\r
+ removeHistoryActionContributionItem = new ActionContributionItem(removeHistoryItemAction);\r
+\r
+ contextMenuManager = new MenuManager();\r
+ contextMenuManager.setRemoveAllWhenShown(true);\r
+ contextMenuManager.addMenuListener(new IMenuListener() {\r
+ public void menuAboutToShow(IMenuManager manager) {\r
+ fillContextMenu(manager);\r
+ }\r
+ });\r
+\r
+ final Table table = viewer.getTable();\r
+ Menu menu= contextMenuManager.createContextMenu(table);\r
+ table.setMenu(menu);\r
+ }\r
+\r
+ /**\r
+ * Creates an extra content area, which will be located above the details.\r
+ * \r
+ * @param parent\r
+ * parent to create the dialog widgets in\r
+ * @return an extra content area\r
+ */\r
+ protected abstract Control createExtendedContentArea(Composite parent);\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite)\r
+ */\r
+ protected Control createDialogArea(Composite parent) {\r
+ Composite dialogArea = (Composite) super.createDialogArea(parent);\r
+\r
+ Composite content = new Composite(dialogArea, SWT.NONE);\r
+ GridData gd = new GridData(GridData.FILL_BOTH);\r
+ content.setLayoutData(gd);\r
+\r
+ GridLayout layout = new GridLayout();\r
+ layout.numColumns = 1;\r
+ layout.marginWidth = 0;\r
+ layout.marginHeight = 0;\r
+ content.setLayout(layout);\r
+\r
+ final Label headerLabel = createHeader(content);\r
+\r
+ pattern = new Text(content, SWT.SINGLE | SWT.BORDER | SWT.SEARCH | SWT.ICON_CANCEL);\r
+ pattern.getAccessible().addAccessibleListener(new AccessibleAdapter() {\r
+ public void getName(AccessibleEvent e) {\r
+ e.result = LegacyActionTools.removeMnemonics(headerLabel.getText());\r
+ }\r
+ });\r
+ gd = new GridData(GridData.FILL_HORIZONTAL);\r
+ pattern.setLayoutData(gd);\r
+\r
+ final Label listLabel = createLabels(content);\r
+\r
+ viewer = new TableViewer(content, (multi ? SWT.MULTI : SWT.SINGLE)\r
+ | SWT.BORDER | SWT.V_SCROLL | SWT.VIRTUAL | SWT.FULL_SELECTION);\r
+ viewer.getTable().getAccessible().addAccessibleListener(\r
+ new AccessibleAdapter() {\r
+ public void getName(AccessibleEvent e) {\r
+ if (e.childID == ACC.CHILDID_SELF) {\r
+ e.result = LegacyActionTools.removeMnemonics(listLabel.getText());\r
+ }\r
+ }\r
+ });\r
+ viewer.setContentProvider(contentProvider);\r
+ \r
+ // added column creation hook\r
+ createColumns(viewer);\r
+ \r
+ // show headers etc. if columns were added by the subclass\r
+ if (viewer.getTable().getColumnCount() > 0) {\r
+ viewer.getTable().setLinesVisible(true);\r
+ viewer.getTable().setHeaderVisible(true);\r
+ }\r
+ \r
+ viewer.setInput(new Object[0]);\r
+ viewer.setItemCount(contentProvider.getNumberOfElements());\r
+ gd = new GridData(GridData.FILL_BOTH);\r
+ applyDialogFont(viewer.getTable());\r
+ gd.heightHint= viewer.getTable().getItemHeight() * 15;\r
+ viewer.getTable().setLayoutData(gd);\r
+\r
+ createPopupMenu();\r
+\r
+ pattern.addModifyListener(new ModifyListener() {\r
+ public void modifyText(ModifyEvent e) {\r
+ applyFilter();\r
+ }\r
+ });\r
+\r
+ pattern.addKeyListener(new KeyAdapter() {\r
+ public void keyPressed(KeyEvent e) {\r
+ if (e.keyCode == SWT.ARROW_DOWN) {\r
+ if (viewer.getTable().getItemCount() > 0) {\r
+ viewer.getTable().setFocus();\r
+ }\r
+ }\r
+ }\r
+ });\r
+\r
+ viewer.addSelectionChangedListener(new ISelectionChangedListener() {\r
+ public void selectionChanged(SelectionChangedEvent event) {\r
+ StructuredSelection selection = (StructuredSelection) event.getSelection();\r
+ handleSelected(selection);\r
+ }\r
+ });\r
+\r
+ viewer.addDoubleClickListener(new IDoubleClickListener() {\r
+ public void doubleClick(DoubleClickEvent event) {\r
+ handleDoubleClick();\r
+ }\r
+ });\r
+\r
+ viewer.getTable().addKeyListener(new KeyAdapter() {\r
+ public void keyPressed(KeyEvent e) {\r
+\r
+ if (e.keyCode == SWT.DEL) {\r
+\r
+ List selectedElements = ((StructuredSelection) viewer.getSelection()).toList();\r
+\r
+ Object item = null;\r
+ boolean isSelectedHistory = true;\r
+\r
+ for (Iterator it = selectedElements.iterator(); it.hasNext();) {\r
+ item = it.next();\r
+ if (item instanceof ItemsListSeparator || !isHistoryElement(item)) {\r
+ isSelectedHistory = false;\r
+ break;\r
+ }\r
+ }\r
+ if (isSelectedHistory)\r
+ removeSelectedItems(selectedElements);\r
+\r
+ }\r
+\r
+ if (e.keyCode == SWT.ARROW_UP && (e.stateMask & SWT.SHIFT) != 0\r
+ && (e.stateMask & SWT.CTRL) != 0) {\r
+ StructuredSelection selection = (StructuredSelection) viewer.getSelection();\r
+\r
+ if (selection.size() == 1) {\r
+ Object element = selection.getFirstElement();\r
+ if (element.equals(viewer.getElementAt(0))) {\r
+ pattern.setFocus();\r
+ }\r
+ if (viewer.getElementAt(viewer.getTable().getSelectionIndex() - 1) instanceof ItemsListSeparator)\r
+ viewer.getTable().setSelection(viewer.getTable().getSelectionIndex() - 1);\r
+ viewer.getTable().notifyListeners(SWT.Selection, new Event());\r
+\r
+ }\r
+ }\r
+\r
+ if (e.keyCode == SWT.ARROW_DOWN\r
+ && (e.stateMask & SWT.SHIFT) != 0\r
+ && (e.stateMask & SWT.CTRL) != 0) {\r
+\r
+ if (viewer.getElementAt(viewer.getTable().getSelectionIndex() + 1) instanceof ItemsListSeparator)\r
+ viewer.getTable().setSelection(viewer.getTable().getSelectionIndex() + 1);\r
+ viewer.getTable().notifyListeners(SWT.Selection, new Event());\r
+ }\r
+ }\r
+ });\r
+\r
+ createExtendedContentArea(content);\r
+\r
+ details = new DetailsContentViewer(content, SWT.BORDER | SWT.FLAT);\r
+ details.setVisible(toggleStatusLineAction.isChecked());\r
+ details.setContentProvider(new NullContentProvider());\r
+ details.setLabelProvider(getDetailsLabelProvider());\r
+\r
+ applyDialogFont(content);\r
+\r
+ restoreDialog(getDialogSettings());\r
+\r
+ if (initialPatternText != null) {\r
+ pattern.setText(initialPatternText);\r
+ }\r
+\r
+ switch (selectionMode) {\r
+ case CARET_BEGINNING:\r
+ pattern.setSelection(0, 0);\r
+ break;\r
+ case FULL_SELECTION:\r
+ pattern.setSelection(0, initialPatternText.length());\r
+ break;\r
+ }\r
+\r
+ // apply filter even if pattern is empty (display history)\r
+ applyFilter();\r
+\r
+ return dialogArea;\r
+ }\r
+ \r
+ /**\r
+ * Override this method to add columns to the content area of this dialog.\r
+ * \r
+ * Subclass implementation of this method should NOT call the super \r
+ * implementation as this will break the individual column label providers\r
+ * \r
+ * @param viewer\r
+ */\r
+ protected void createColumns(TableViewer viewer) {\r
+ // no columns are added by default, just set the label provider for the viewer\r
+ viewer.setLabelProvider(getItemsListLabelProvider());\r
+ }\r
+ \r
+ /**\r
+ * An utility method for adding a column to the TableViewer, should be called\r
+ * from inside createColumns.\r
+ * \r
+ * @param viewer\r
+ * @param label\r
+ * @param width\r
+ * @param labelProvider\r
+ */\r
+ protected void createColumn(TableViewer viewer, String label, int width, ColumnLabelProvider labelProvider) {\r
+ TableViewerColumn viewercol = new TableViewerColumn(viewer, SWT.LEFT);\r
+ \r
+ TableColumn col = viewercol.getColumn();\r
+ col.setText(label);\r
+ col.setWidth(width);\r
+ col.setResizable(true);\r
+ col.setMoveable(true);\r
+ \r
+ // TODO: should use the local label provider class instead but it \r
+ // should be made compatible with multiple columns first\r
+ viewercol.setLabelProvider(labelProvider);\r
+ }\r
+\r
+ /**\r
+ * This method is a hook for subclasses to override default dialog behavior.\r
+ * The <code>handleDoubleClick()</code> method handles double clicks on\r
+ * the list of filtered elements.\r
+ * <p>\r
+ * Current implementation makes double-clicking on the list do the same as\r
+ * pressing <code>OK</code> button on the dialog.\r
+ */\r
+ protected void handleDoubleClick() {\r
+ okPressed();\r
+ }\r
+\r
+ /**\r
+ * Refreshes the details field according to the current selection in the\r
+ * items list.\r
+ */\r
+ private void refreshDetails() {\r
+ StructuredSelection selection = getSelectedItems();\r
+\r
+ switch (selection.size()) {\r
+ case 0:\r
+ details.setInput(null);\r
+ break;\r
+ case 1:\r
+ details.setInput(selection.getFirstElement());\r
+ break;\r
+ default:\r
+ details.setInput(NLS.bind(\r
+ WorkbenchMessages.FilteredItemsSelectionDialog_nItemsSelected,\r
+ new Integer(selection.size())));\r
+ break;\r
+ }\r
+\r
+ }\r
+\r
+ /**\r
+ * Handle selection in the items list by updating labels of selected and\r
+ * unselected items and refresh the details field using the selection.\r
+ * \r
+ * @param selection\r
+ * the new selection\r
+ */\r
+ protected void handleSelected(StructuredSelection selection) {\r
+ IStatus status = new Status(IStatus.OK, PlatformUI.PLUGIN_ID,\r
+ IStatus.OK, EMPTY_STRING, null);\r
+\r
+ Object[] lastSelection = currentSelection;\r
+\r
+ currentSelection = selection.toArray();\r
+\r
+ if (selection.size() == 0) {\r
+ status = new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID,\r
+ IStatus.ERROR, EMPTY_STRING, null);\r
+\r
+ if (lastSelection != null && getListSelectionLabelDecorator() != null) {\r
+ viewer.update(lastSelection, null);\r
+ }\r
+\r
+ currentSelection = null;\r
+\r
+ } else {\r
+ status = new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID,\r
+ IStatus.ERROR, EMPTY_STRING, null);\r
+\r
+ List items = selection.toList();\r
+\r
+ Object item = null;\r
+ IStatus tempStatus = null;\r
+\r
+ for (Iterator it = items.iterator(); it.hasNext();) {\r
+ Object o = it.next();\r
+\r
+ if (o instanceof ItemsListSeparator) {\r
+ continue;\r
+ }\r
+\r
+ item = o;\r
+ tempStatus = validateItem(item);\r
+\r
+ if (tempStatus.isOK()) {\r
+ status = new Status(IStatus.OK, PlatformUI.PLUGIN_ID,\r
+ IStatus.OK, EMPTY_STRING, null);\r
+ } else {\r
+ status = tempStatus;\r
+ // if any selected element is not valid status is set to\r
+ // ERROR\r
+ break;\r
+ }\r
+ }\r
+\r
+ if (lastSelection != null && getListSelectionLabelDecorator() != null) {\r
+ viewer.update(lastSelection, null);\r
+ }\r
+\r
+ if (getListSelectionLabelDecorator() != null) {\r
+ viewer.update(currentSelection, null);\r
+ }\r
+ }\r
+\r
+ refreshDetails();\r
+ updateStatus(status);\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.jface.window.Dialog#getDialogBoundsSettings()\r
+ */\r
+ protected IDialogSettings getDialogBoundsSettings() {\r
+ IDialogSettings settings = getDialogSettings();\r
+ IDialogSettings section = settings.getSection(DIALOG_BOUNDS_SETTINGS);\r
+ if (section == null) {\r
+ section = settings.addNewSection(DIALOG_BOUNDS_SETTINGS);\r
+ section.put(DIALOG_HEIGHT, 500);\r
+ section.put(DIALOG_WIDTH, 600);\r
+ }\r
+ return section;\r
+ }\r
+\r
+ /**\r
+ * Returns the dialog settings. Returned object can't be null.\r
+ * \r
+ * @return return dialog settings for this dialog\r
+ */\r
+ protected abstract IDialogSettings getDialogSettings();\r
+\r
+ /**\r
+ * Refreshes the dialog - has to be called in UI thread.\r
+ */\r
+ public void refresh() {\r
+ if (viewer != null && !viewer.getTable().isDisposed()) {\r
+\r
+ List lastRefreshSelection = ((StructuredSelection) viewer.getSelection()).toList();\r
+ viewer.getTable().deselectAll();\r
+\r
+ viewer.setItemCount(contentProvider.getNumberOfElements());\r
+ viewer.refresh();\r
+\r
+ if (viewer.getTable().getItemCount() > 0) {\r
+ // preserve previous selection\r
+ if (refreshWithLastSelection && lastRefreshSelection != null\r
+ && lastRefreshSelection.size() > 0) {\r
+ viewer.setSelection(new StructuredSelection(\r
+ lastRefreshSelection));\r
+ } else {\r
+ refreshWithLastSelection = true;\r
+ viewer.getTable().setSelection(0);\r
+ viewer.getTable().notifyListeners(SWT.Selection, new Event());\r
+ }\r
+ } else {\r
+ viewer.setSelection(StructuredSelection.EMPTY);\r
+ }\r
+\r
+ }\r
+\r
+ scheduleProgressMessageRefresh();\r
+ }\r
+\r
+ /**\r
+ * Updates the progress label.\r
+ * \r
+ * @deprecated\r
+ */\r
+ public void updateProgressLabel() {\r
+ scheduleProgressMessageRefresh();\r
+ }\r
+\r
+ /**\r
+ * Notifies the content provider - fires filtering of content provider\r
+ * elements. During the filtering, a separator between history and workspace\r
+ * matches is added.\r
+ * <p>\r
+ * This is a long running operation and should be called in a job.\r
+ * \r
+ * @param checkDuplicates\r
+ * <code>true</code> if data concerning elements duplication\r
+ * should be computed - it takes much more time than the standard\r
+ * filtering\r
+ * @param monitor\r
+ * a progress monitor or <code>null</code> if no monitor is\r
+ * available\r
+ */\r
+ public void reloadCache(boolean checkDuplicates, IProgressMonitor monitor) {\r
+ if (viewer != null && !viewer.getTable().isDisposed() && contentProvider != null) {\r
+ contentProvider.reloadCache(checkDuplicates, monitor);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Schedule refresh job.\r
+ */\r
+ public void scheduleRefresh() {\r
+ refreshCacheJob.cancelAll();\r
+ refreshCacheJob.schedule();\r
+ }\r
+\r
+ /**\r
+ * Schedules progress message refresh.\r
+ */\r
+ public void scheduleProgressMessageRefresh() {\r
+ if (filterJob.getState() != Job.RUNNING && refreshProgressMessageJob.getState() != Job.RUNNING)\r
+ refreshProgressMessageJob.scheduleProgressRefresh(null);\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.ui.dialogs.SelectionStatusDialog#computeResult()\r
+ */\r
+ protected void computeResult() {\r
+\r
+ List selectedElements = ((StructuredSelection) viewer.getSelection())\r
+ .toList();\r
+\r
+ List objectsToReturn = new ArrayList();\r
+\r
+ Object item = null;\r
+\r
+ for (Iterator it = selectedElements.iterator(); it.hasNext();) {\r
+ item = it.next();\r
+\r
+ if (!(item instanceof ItemsListSeparator)) {\r
+ accessedHistoryItem(item);\r
+ objectsToReturn.add(item);\r
+ }\r
+ }\r
+\r
+ setResult(objectsToReturn);\r
+ }\r
+\r
+ /*\r
+ * @see org.eclipse.ui.dialogs.SelectionStatusDialog#updateStatus(org.eclipse.core.runtime.IStatus)\r
+ */\r
+ protected void updateStatus(IStatus status) {\r
+ this.status = status;\r
+ super.updateStatus(status);\r
+ }\r
+\r
+ /*\r
+ * @see Dialog#okPressed()\r
+ */\r
+ protected void okPressed() {\r
+ if (status != null && (status.isOK() || status.getCode() == IStatus.INFO)) {\r
+ super.okPressed();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Sets the initial pattern used by the filter. This text is copied into the\r
+ * selection input on the dialog. A full selection is used in the pattern\r
+ * input field.\r
+ * \r
+ * @param text\r
+ * initial pattern for the filter\r
+ * @see ColumnFilteredItemsSelectionDialog#FULL_SELECTION\r
+ */\r
+ public void setInitialPattern(String text) {\r
+ setInitialPattern(text, FULL_SELECTION);\r
+ }\r
+\r
+ /**\r
+ * Sets the initial pattern used by the filter. This text is copied into the\r
+ * selection input on the dialog. The <code>selectionMode</code> is used\r
+ * to choose selection type for the input field.\r
+ * \r
+ * @param text\r
+ * initial pattern for the filter\r
+ * @param selectionMode\r
+ * one of: {@link ColumnFilteredItemsSelectionDialog#NONE},\r
+ * {@link ColumnFilteredItemsSelectionDialog#CARET_BEGINNING},\r
+ * {@link ColumnFilteredItemsSelectionDialog#FULL_SELECTION}\r
+ */\r
+ public void setInitialPattern(String text, int selectionMode) {\r
+ this.initialPatternText = text;\r
+ this.selectionMode = selectionMode;\r
+ }\r
+\r
+ /**\r
+ * Gets initial pattern.\r
+ * \r
+ * @return initial pattern, or <code>null</code> if initial pattern is not\r
+ * set\r
+ */\r
+ protected String getInitialPattern() {\r
+ return this.initialPatternText;\r
+ }\r
+\r
+ /**\r
+ * Returns the current selection.\r
+ * \r
+ * @return the current selection\r
+ */\r
+ protected StructuredSelection getSelectedItems() {\r
+\r
+ StructuredSelection selection = (StructuredSelection) viewer.getSelection();\r
+\r
+ List selectedItems = selection.toList();\r
+ Object itemToRemove = null;\r
+\r
+ for (Iterator it = selection.iterator(); it.hasNext();) {\r
+ Object item = it.next();\r
+ if (item instanceof ItemsListSeparator) {\r
+ itemToRemove = item;\r
+ break;\r
+ }\r
+ }\r
+\r
+ if (itemToRemove == null)\r
+ return new StructuredSelection(selectedItems);\r
+ // Create a new selection without the collision\r
+ List newItems = new ArrayList(selectedItems);\r
+ newItems.remove(itemToRemove);\r
+ return new StructuredSelection(newItems);\r
+\r
+ }\r
+\r
+ /**\r
+ * Validates the item. When items on the items list are selected or\r
+ * deselected, it validates each item in the selection and the dialog status\r
+ * depends on all validations.\r
+ * \r
+ * @param item\r
+ * an item to be checked\r
+ * @return status of the dialog to be set\r
+ */\r
+ protected abstract IStatus validateItem(Object item);\r
+\r
+ /**\r
+ * Creates an instance of a filter.\r
+ * \r
+ * @return a filter for items on the items list. Can be <code>null</code>,\r
+ * no filtering will be applied then, causing no item to be shown in\r
+ * the list.\r
+ */\r
+ protected abstract ItemsFilter createFilter();\r
+\r
+ /**\r
+ * Applies the filter created by <code>createFilter()</code> method to the\r
+ * items list. When new filter is different than previous one it will cause\r
+ * refiltering.\r
+ */\r
+ protected void applyFilter() {\r
+ ItemsFilter newFilter = createFilter();\r
+\r
+ // don't apply filtering for patterns which mean the same, for example:\r
+ // *a**b and ***a*b\r
+ if (filter != null && filter.equalsFilter(newFilter)) {\r
+ return;\r
+ }\r
+ \r
+ filterHistoryJob.cancel();\r
+ filterJob.cancel();\r
+\r
+ this.filter = newFilter;\r
+ \r
+ if (this.filter != null) {\r
+ filterHistoryJob.schedule();\r
+ }\r
+ \r
+ }\r
+\r
+ /**\r
+ * Returns comparator to sort items inside content provider. Returned object\r
+ * will be probably created as an anonymous class. Parameters passed to the\r
+ * <code>compare(java.lang.Object, java.lang.Object)</code> are going to\r
+ * be the same type as the one used in the content provider.\r
+ * \r
+ * @return comparator to sort items content provider\r
+ */\r
+ protected abstract Comparator getItemsComparator();\r
+\r
+ /**\r
+ * Fills the content provider with matching items.\r
+ * \r
+ * @param contentProvider\r
+ * collector to add items to.\r
+ * {@link ColumnFilteredItemsSelectionDialog.AbstractContentProvider#add(Object, ColumnFilteredItemsSelectionDialog.ItemsFilter)}\r
+ * only adds items that pass the given <code>itemsFilter</code>.\r
+ * @param itemsFilter\r
+ * the items filter\r
+ * @param progressMonitor\r
+ * must be used to report search progress. The state of this\r
+ * progress monitor reflects the state of the filtering process.\r
+ * @throws CoreException\r
+ */\r
+ protected abstract void fillContentProvider(\r
+ AbstractContentProvider contentProvider, ItemsFilter itemsFilter,\r
+ IProgressMonitor progressMonitor) throws CoreException;\r
+ \r
+ /**\r
+ * Force a refresh of the content provider.\r
+ */\r
+ protected void forceRefresh() {\r
+ lastCompletedFilter = null;\r
+ lastCompletedResult = null;\r
+ filterHistoryJob.schedule();\r
+ }\r
+\r
+ /**\r
+ * Removes selected items from history.\r
+ * \r
+ * @param items\r
+ * items to be removed\r
+ */\r
+ private void removeSelectedItems(List items) {\r
+ for (Iterator iter = items.iterator(); iter.hasNext();) {\r
+ Object item = iter.next();\r
+ removeHistoryItem(item);\r
+ }\r
+ refreshWithLastSelection = false;\r
+ contentProvider.refresh();\r
+ }\r
+\r
+ /**\r
+ * Removes an item from history.\r
+ * \r
+ * @param item\r
+ * an item to remove\r
+ * @return removed item\r
+ */\r
+ protected Object removeHistoryItem(Object item) {\r
+ return contentProvider.removeHistoryElement(item);\r
+ }\r
+\r
+ /**\r
+ * Adds item to history.\r
+ * \r
+ * @param item\r
+ * the item to be added\r
+ */\r
+ protected void accessedHistoryItem(Object item) {\r
+ contentProvider.addHistoryElement(item);\r
+ }\r
+\r
+ /**\r
+ * Returns a history comparator.\r
+ * \r
+ * @return decorated comparator\r
+ */\r
+ private Comparator getHistoryComparator() {\r
+ return new HistoryComparator();\r
+ }\r
+\r
+ /**\r
+ * Returns the history of selected elements.\r
+ * \r
+ * @return history of selected elements, or <code>null</code> if it is not\r
+ * set\r
+ */\r
+ protected SelectionHistory getSelectionHistory() {\r
+ return this.contentProvider.getSelectionHistory();\r
+ }\r
+\r
+ /**\r
+ * Sets new history.\r
+ * \r
+ * @param selectionHistory\r
+ * the history\r
+ */\r
+ protected void setSelectionHistory(SelectionHistory selectionHistory) {\r
+ if (this.contentProvider != null)\r
+ this.contentProvider.setSelectionHistory(selectionHistory);\r
+ }\r
+\r
+ /**\r
+ * Indicates whether the given item is a history item.\r
+ * \r
+ * @param item\r
+ * the item to be investigated\r
+ * @return <code>true</code> if the given item exists in history,\r
+ * <code>false</code> otherwise\r
+ */\r
+ public boolean isHistoryElement(Object item) {\r
+ return this.contentProvider.isHistoryElement(item);\r
+ }\r
+\r
+ /**\r
+ * Indicates whether the given item is a duplicate.\r
+ * \r
+ * @param item\r
+ * the item to be investigated\r
+ * @return <code>true</code> if the item is duplicate, <code>false</code>\r
+ * otherwise\r
+ */\r
+ public boolean isDuplicateElement(Object item) {\r
+ return this.contentProvider.isDuplicateElement(item);\r
+ }\r
+\r
+ /**\r
+ * Sets separator label\r
+ * \r
+ * @param separatorLabel\r
+ * the label showed on separator\r
+ */\r
+ public void setSeparatorLabel(String separatorLabel) {\r
+ this.itemsListSeparator = new ItemsListSeparator(separatorLabel);\r
+ }\r
+\r
+ /**\r
+ * Returns name for then given object.\r
+ * \r
+ * @param item\r
+ * an object from the content provider. Subclasses should pay\r
+ * attention to the passed argument. They should either only pass\r
+ * objects of a known type (one used in content provider) or make\r
+ * sure that passed parameter is the expected one (by type\r
+ * checking like <code>instanceof</code> inside the method).\r
+ * @return name of the given item\r
+ */\r
+ public abstract String getElementName(Object item);\r
+\r
+ private class ToggleStatusLineAction extends Action {\r
+\r
+ /**\r
+ * Creates a new instance of the class.\r
+ */\r
+ public ToggleStatusLineAction() {\r
+ super(WorkbenchMessages.FilteredItemsSelectionDialog_toggleStatusAction, IAction.AS_CHECK_BOX);\r
+ }\r
+\r
+ public void run() {\r
+ details.setVisible(isChecked());\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Only refreshes UI on the basis of an already sorted and filtered set of\r
+ * items.\r
+ * <p>\r
+ * Standard invocation scenario:\r
+ * <ol>\r
+ * <li>filtering job (<code>FilterJob</code> class extending\r
+ * <code>Job</code> class)</li>\r
+ * <li>cache refresh without checking for duplicates (<code>RefreshCacheJob</code>\r
+ * class extending <code>Job</code> class)</li>\r
+ * <li>UI refresh (<code>RefreshJob</code> class extending\r
+ * <code>UIJob</code> class)</li>\r
+ * <li>cache refresh with checking for duplicates (<cod>CacheRefreshJob</code>\r
+ * class extending <code>Job</code> class)</li>\r
+ * <li>UI refresh (<code>RefreshJob</code> class extending <code>UIJob</code>\r
+ * class)</li>\r
+ * </ol>\r
+ * The scenario is rather complicated, but it had to be applied, because:\r
+ * <ul>\r
+ * <li> refreshing cache is rather a long action and cannot be run in the UI -\r
+ * cannot be run in a UIJob</li>\r
+ * <li> refreshing cache checking for duplicates is twice as long as\r
+ * refreshing cache without checking for duplicates; results of the search\r
+ * could be displayed earlier</li>\r
+ * <li> refreshing the UI have to be run in a UIJob</li>\r
+ * </ul>\r
+ * \r
+ * @see org.eclipse.ui.dialogs.FilteredItemsSelectionDialog.FilterJob\r
+ * @see org.eclipse.ui.dialogs.FilteredItemsSelectionDialog.RefreshJob\r
+ * @see org.eclipse.ui.dialogs.FilteredItemsSelectionDialog.RefreshCacheJob\r
+ */\r
+ private class RefreshJob extends UIJob {\r
+\r
+ /**\r
+ * Creates a new instance of the class.\r
+ */\r
+ public RefreshJob() {\r
+ super(ColumnFilteredItemsSelectionDialog.this.getParentShell().getDisplay(),\r
+ WorkbenchMessages.FilteredItemsSelectionDialog_refreshJob);\r
+ setSystem(true);\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.ui.progress.UIJob#runInUIThread(org.eclipse.core.runtime.IProgressMonitor)\r
+ */\r
+ public IStatus runInUIThread(IProgressMonitor monitor) {\r
+ if (monitor.isCanceled())\r
+ return new Status(IStatus.OK, WorkbenchPlugin.PI_WORKBENCH,\r
+ IStatus.OK, EMPTY_STRING, null);\r
+\r
+ if (ColumnFilteredItemsSelectionDialog.this != null) {\r
+ ColumnFilteredItemsSelectionDialog.this.refresh();\r
+ }\r
+\r
+ return new Status(IStatus.OK, PlatformUI.PLUGIN_ID, IStatus.OK,\r
+ EMPTY_STRING, null);\r
+ }\r
+\r
+ }\r
+\r
+ /**\r
+ * Refreshes the progress message cyclically with 500 milliseconds delay.\r
+ * <code>RefreshProgressMessageJob</code> is strictly connected with\r
+ * <code>GranualProgressMonitor</code> and use it to to get progress\r
+ * message and to decide about break of cyclical refresh.\r
+ */\r
+ private class RefreshProgressMessageJob extends UIJob {\r
+\r
+ private GranualProgressMonitor progressMonitor;\r
+\r
+ /**\r
+ * Creates a new instance of the class.\r
+ */\r
+ public RefreshProgressMessageJob() {\r
+ super(ColumnFilteredItemsSelectionDialog.this.getParentShell().getDisplay(),\r
+ WorkbenchMessages.FilteredItemsSelectionDialog_progressRefreshJob);\r
+ setSystem(true);\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.ui.progress.UIJob#runInUIThread(org.eclipse.core.runtime.IProgressMonitor)\r
+ */\r
+ public IStatus runInUIThread(IProgressMonitor monitor) {\r
+\r
+ if (!progressLabel.isDisposed())\r
+ progressLabel.setText(progressMonitor != null ? progressMonitor.getMessage() : EMPTY_STRING);\r
+\r
+ if (progressMonitor == null || progressMonitor.isDone()) {\r
+ return new Status(IStatus.CANCEL, PlatformUI.PLUGIN_ID,\r
+ IStatus.CANCEL, EMPTY_STRING, null);\r
+ }\r
+\r
+ // Schedule cyclical with 500 milliseconds delay\r
+ schedule(500);\r
+\r
+ return new Status(IStatus.OK, PlatformUI.PLUGIN_ID, IStatus.OK,\r
+ EMPTY_STRING, null);\r
+ }\r
+\r
+ /**\r
+ * Schedule progress refresh job.\r
+ * \r
+ * @param progressMonitor\r
+ * used during refresh progress label\r
+ */\r
+ public void scheduleProgressRefresh(\r
+ GranualProgressMonitor progressMonitor) {\r
+ this.progressMonitor = progressMonitor;\r
+ // Schedule with initial delay to avoid flickering when the user\r
+ // types quickly\r
+ schedule(200);\r
+ }\r
+\r
+ }\r
+\r
+ /**\r
+ * A job responsible for computing filtered items list presented using\r
+ * <code>RefreshJob</code>.\r
+ * \r
+ * @see ColumnFilteredItemsSelectionDialog.RefreshJob\r
+ * \r
+ */\r
+ private class RefreshCacheJob extends Job {\r
+\r
+ private RefreshJob refreshJob = new RefreshJob();\r
+\r
+ /**\r
+ * Creates a new instance of the class.\r
+ */\r
+ public RefreshCacheJob() {\r
+ super(WorkbenchMessages.FilteredItemsSelectionDialog_cacheRefreshJob);\r
+ setSystem(true);\r
+ }\r
+\r
+ /**\r
+ * Stops the job and all sub-jobs.\r
+ */\r
+ public void cancelAll() {\r
+ cancel();\r
+ refreshJob.cancel();\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)\r
+ */\r
+ protected IStatus run(IProgressMonitor monitor) {\r
+ if (monitor.isCanceled()) {\r
+ return new Status(IStatus.CANCEL, WorkbenchPlugin.PI_WORKBENCH,\r
+ IStatus.CANCEL, EMPTY_STRING, null);\r
+ }\r
+\r
+ if (ColumnFilteredItemsSelectionDialog.this != null) {\r
+ GranualProgressMonitor wrappedMonitor = new GranualProgressMonitor(monitor);\r
+ ColumnFilteredItemsSelectionDialog.this.reloadCache(true, wrappedMonitor);\r
+ }\r
+\r
+ if (!monitor.isCanceled()) {\r
+ refreshJob.schedule();\r
+ }\r
+\r
+ return new Status(IStatus.OK, PlatformUI.PLUGIN_ID, IStatus.OK,\r
+ EMPTY_STRING, null);\r
+\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.core.runtime.jobs.Job#canceling()\r
+ */\r
+ protected void canceling() {\r
+ super.canceling();\r
+ contentProvider.stopReloadingCache();\r
+ }\r
+\r
+ }\r
+\r
+ private class RemoveHistoryItemAction extends Action {\r
+\r
+ /**\r
+ * Creates a new instance of the class.\r
+ */\r
+ public RemoveHistoryItemAction() {\r
+ super(WorkbenchMessages.FilteredItemsSelectionDialog_removeItemsFromHistoryAction);\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.jface.action.Action#run()\r
+ */\r
+ public void run() {\r
+ List selectedElements = ((StructuredSelection) viewer.getSelection()).toList();\r
+ removeSelectedItems(selectedElements);\r
+ }\r
+ }\r
+\r
+ private static boolean showColoredLabels() {\r
+ return PlatformUI.getPreferenceStore().getBoolean(IWorkbenchPreferenceConstants.USE_COLORED_LABELS);\r
+ }\r
+\r
+ private class ItemsListLabelProvider extends StyledCellLabelProvider\r
+ implements ILabelProviderListener {\r
+ private ILabelProvider provider;\r
+\r
+ private ILabelDecorator selectionDecorator;\r
+\r
+ // Need to keep our own list of listeners\r
+ private ListenerList listeners = new ListenerList();\r
+\r
+ /**\r
+ * Creates a new instance of the class.\r
+ * \r
+ * @param provider\r
+ * the label provider for all items, not <code>null</code>\r
+ * @param selectionDecorator\r
+ * the decorator for selected items, can be <code>null</code>\r
+ */\r
+ public ItemsListLabelProvider(ILabelProvider provider,\r
+ ILabelDecorator selectionDecorator) {\r
+ Assert.isNotNull(provider);\r
+ this.provider = provider;\r
+ this.selectionDecorator = selectionDecorator;\r
+\r
+ setOwnerDrawEnabled(showColoredLabels() && provider instanceof IStyledLabelProvider);\r
+\r
+ provider.addListener(this);\r
+\r
+ if (selectionDecorator != null) {\r
+ selectionDecorator.addListener(this);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Sets new selection decorator.\r
+ * \r
+ * @param newSelectionDecorator\r
+ * new label decorator for selected items in the list\r
+ */\r
+ public void setSelectionDecorator(ILabelDecorator newSelectionDecorator) {\r
+ if (selectionDecorator != null) {\r
+ selectionDecorator.removeListener(this);\r
+ selectionDecorator.dispose();\r
+ }\r
+\r
+ selectionDecorator = newSelectionDecorator;\r
+\r
+ if (selectionDecorator != null) {\r
+ selectionDecorator.addListener(this);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Gets selection decorator.\r
+ * \r
+ * @return the label decorator for selected items in the list\r
+ */\r
+ public ILabelDecorator getSelectionDecorator() {\r
+ return selectionDecorator;\r
+ }\r
+\r
+ /**\r
+ * Sets new label provider.\r
+ * \r
+ * @param newProvider\r
+ * new label provider for items in the list, not\r
+ * <code>null</code>\r
+ */\r
+ public void setProvider(ILabelProvider newProvider) {\r
+ Assert.isNotNull(newProvider);\r
+ provider.removeListener(this);\r
+ provider.dispose();\r
+\r
+ provider = newProvider;\r
+\r
+ if (provider != null) {\r
+ provider.addListener(this);\r
+ }\r
+\r
+ setOwnerDrawEnabled(showColoredLabels() && provider instanceof IStyledLabelProvider);\r
+ }\r
+\r
+ private Image getImage(Object element) {\r
+ if (element instanceof ItemsListSeparator) {\r
+ return WorkbenchImages.getImage(IWorkbenchGraphicConstants.IMG_OBJ_SEPARATOR);\r
+ }\r
+\r
+ return provider.getImage(element);\r
+ }\r
+\r
+ private boolean isSelected(Object element) {\r
+ if (element != null && currentSelection != null) {\r
+ for (int i = 0; i < currentSelection.length; i++) {\r
+ if (element.equals(currentSelection[i]))\r
+ return true;\r
+ }\r
+ }\r
+ return false;\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.jface.viewers.ILabelProvider#getText(java.lang.Object)\r
+ */\r
+ private String getText(Object element) {\r
+ if (element instanceof ItemsListSeparator) {\r
+ return getSeparatorLabel(((ItemsListSeparator) element).getName());\r
+ }\r
+\r
+ String str = provider.getText(element);\r
+ if (selectionDecorator != null && isSelected(element)) {\r
+ return selectionDecorator.decorateText(str.toString(), element);\r
+ }\r
+\r
+ return str;\r
+ }\r
+\r
+ private StyledString getStyledText(Object element,\r
+ IStyledLabelProvider provider) {\r
+ StyledString string = provider.getStyledText(element);\r
+\r
+ if (selectionDecorator != null && isSelected(element)) {\r
+ String decorated = selectionDecorator.decorateText(string.getString(), element);\r
+ return StyledCellLabelProvider.styleDecoratedString(decorated, null, string);\r
+ // no need to add colors when element is selected\r
+ }\r
+ return string;\r
+ }\r
+\r
+ public void update(ViewerCell cell) {\r
+ Object element = cell.getElement();\r
+\r
+ if (!(element instanceof ItemsListSeparator) && provider instanceof IStyledLabelProvider) {\r
+ IStyledLabelProvider styledLabelProvider = (IStyledLabelProvider) provider;\r
+ StyledString styledString = getStyledText(element, styledLabelProvider);\r
+\r
+ cell.setText(styledString.getString());\r
+ cell.setStyleRanges(styledString.getStyleRanges());\r
+ cell.setImage(styledLabelProvider.getImage(element));\r
+ } else {\r
+ cell.setText(getText(element));\r
+ cell.setImage(getImage(element));\r
+ }\r
+ cell.setFont(getFont(element));\r
+ cell.setForeground(getForeground(element));\r
+ cell.setBackground(getBackground(element));\r
+\r
+ super.update(cell);\r
+ }\r
+\r
+ private String getSeparatorLabel(String separatorLabel) {\r
+ Rectangle rect = viewer.getTable().getBounds();\r
+\r
+ int borderWidth = viewer.getTable().computeTrim(0, 0, 0, 0).width;\r
+\r
+ int imageWidth = WorkbenchImages.getImage(\r
+ IWorkbenchGraphicConstants.IMG_OBJ_SEPARATOR).getBounds().width;\r
+\r
+ int width = rect.width - borderWidth - imageWidth;\r
+\r
+ GC gc = new GC(viewer.getTable());\r
+ gc.setFont(viewer.getTable().getFont());\r
+\r
+ int fSeparatorWidth = gc.getAdvanceWidth('-');\r
+ int fMessageLength = gc.textExtent(separatorLabel).x;\r
+\r
+ gc.dispose();\r
+\r
+ StringBuffer dashes = new StringBuffer();\r
+ int chars = (((width - fMessageLength) / fSeparatorWidth) / 2) - 2;\r
+ for (int i = 0; i < chars; i++) {\r
+ dashes.append('-');\r
+ }\r
+\r
+ StringBuffer result = new StringBuffer();\r
+ result.append(dashes);\r
+ result.append(" " + separatorLabel + " "); //$NON-NLS-1$//$NON-NLS-2$\r
+ result.append(dashes);\r
+ return result.toString().trim();\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.jface.viewers.IBaseLabelProvider#addListener(org.eclipse.jface.viewers.ILabelProviderListener)\r
+ */\r
+ public void addListener(ILabelProviderListener listener) {\r
+ listeners.add(listener);\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.jface.viewers.IBaseLabelProvider#dispose()\r
+ */\r
+ public void dispose() {\r
+ provider.removeListener(this);\r
+ provider.dispose();\r
+\r
+ if (selectionDecorator != null) {\r
+ selectionDecorator.removeListener(this);\r
+ selectionDecorator.dispose();\r
+ }\r
+\r
+ super.dispose();\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.jface.viewers.IBaseLabelProvider#isLabelProperty(java.lang.Object,\r
+ * java.lang.String)\r
+ */\r
+ public boolean isLabelProperty(Object element, String property) {\r
+ if (provider.isLabelProperty(element, property)) {\r
+ return true;\r
+ }\r
+ if (selectionDecorator != null\r
+ && selectionDecorator.isLabelProperty(element, property)) {\r
+ return true;\r
+ }\r
+ return false;\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.jface.viewers.IBaseLabelProvider#removeListener(org.eclipse.jface.viewers.ILabelProviderListener)\r
+ */\r
+ public void removeListener(ILabelProviderListener listener) {\r
+ listeners.remove(listener);\r
+ }\r
+\r
+ private Color getBackground(Object element) {\r
+ if (element instanceof ItemsListSeparator) {\r
+ return null;\r
+ }\r
+ if (provider instanceof IColorProvider) {\r
+ return ((IColorProvider) provider).getBackground(element);\r
+ }\r
+ return null;\r
+ }\r
+\r
+ private Color getForeground(Object element) {\r
+ if (element instanceof ItemsListSeparator) {\r
+ return Display.getCurrent().getSystemColor(\r
+ SWT.COLOR_WIDGET_NORMAL_SHADOW);\r
+ }\r
+ if (provider instanceof IColorProvider) {\r
+ return ((IColorProvider) provider).getForeground(element);\r
+ }\r
+ return null;\r
+ }\r
+\r
+ private Font getFont(Object element) {\r
+ if (element instanceof ItemsListSeparator) {\r
+ return null;\r
+ }\r
+ if (provider instanceof IFontProvider) {\r
+ return ((IFontProvider) provider).getFont(element);\r
+ }\r
+ return null;\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.jface.viewers.ILabelProviderListener#labelProviderChanged(org.eclipse.jface.viewers.LabelProviderChangedEvent)\r
+ */\r
+ public void labelProviderChanged(LabelProviderChangedEvent event) {\r
+ Object[] l = listeners.getListeners();\r
+ for (int i = 0; i < listeners.size(); i++) {\r
+ ((ILabelProviderListener) l[i]).labelProviderChanged(event);\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Used in ItemsListContentProvider, separates history and non-history\r
+ * items.\r
+ */\r
+ private class ItemsListSeparator {\r
+\r
+ private String name;\r
+\r
+ /**\r
+ * Creates a new instance of the class.\r
+ * \r
+ * @param name\r
+ * the name of the separator\r
+ */\r
+ public ItemsListSeparator(String name) {\r
+ this.name = name;\r
+ }\r
+\r
+ /**\r
+ * Returns the name of this separator.\r
+ * \r
+ * @return the name of the separator\r
+ */\r
+ public String getName() {\r
+ return name;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * GranualProgressMonitor is used for monitoring progress of filtering\r
+ * process. It is used by <code>RefreshProgressMessageJob</code> to\r
+ * refresh progress message. State of this monitor illustrates state of\r
+ * filtering or cache refreshing process.\r
+ * \r
+ */\r
+ private class GranualProgressMonitor extends ProgressMonitorWrapper {\r
+\r
+ private String name;\r
+\r
+ private String subName;\r
+\r
+ private int totalWork;\r
+\r
+ private double worked;\r
+\r
+ private boolean done;\r
+\r
+ /**\r
+ * Creates instance of <code>GranualProgressMonitor</code>.\r
+ * \r
+ * @param monitor\r
+ * progress to be wrapped\r
+ */\r
+ public GranualProgressMonitor(IProgressMonitor monitor) {\r
+ super(monitor);\r
+ }\r
+\r
+ /**\r
+ * Checks if filtering has been done\r
+ * \r
+ * @return true if filtering work has been done false in other way\r
+ */\r
+ public boolean isDone() {\r
+ return done;\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.core.runtime.ProgressMonitorWrapper#setTaskName(java.lang.String)\r
+ */\r
+ public void setTaskName(String name) {\r
+ super.setTaskName(name);\r
+ this.name = name;\r
+ this.subName = null;\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.core.runtime.ProgressMonitorWrapper#subTask(java.lang.String)\r
+ */\r
+ public void subTask(String name) {\r
+ super.subTask(name);\r
+ this.subName = name;\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.core.runtime.ProgressMonitorWrapper#beginTask(java.lang.String,\r
+ * int)\r
+ */\r
+ public void beginTask(String name, int totalWork) {\r
+ super.beginTask(name, totalWork);\r
+ if (this.name == null)\r
+ this.name = name;\r
+ this.totalWork = totalWork;\r
+ refreshProgressMessageJob.scheduleProgressRefresh(this);\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.core.runtime.ProgressMonitorWrapper#worked(int)\r
+ */\r
+ public void worked(int work) {\r
+ super.worked(work);\r
+ internalWorked(work);\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.core.runtime.ProgressMonitorWrapper#done()\r
+ */\r
+ public void done() {\r
+ done = true;\r
+ super.done();\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.core.runtime.ProgressMonitorWrapper#setCanceled(boolean)\r
+ */\r
+ public void setCanceled(boolean b) {\r
+ done = b;\r
+ super.setCanceled(b);\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.core.runtime.ProgressMonitorWrapper#internalWorked(double)\r
+ */\r
+ public void internalWorked(double work) {\r
+ worked = worked + work;\r
+ }\r
+\r
+ private String getMessage() {\r
+ if (done)\r
+ return ""; //$NON-NLS-1$\r
+\r
+ String message;\r
+\r
+ if (name == null) {\r
+ message = subName == null ? "" : subName; //$NON-NLS-1$\r
+ } else {\r
+ message = subName == null ? name\r
+ : NLS.bind(WorkbenchMessages.FilteredItemsSelectionDialog_subtaskProgressMessage,\r
+ new Object[] { name, subName });\r
+ }\r
+ if (totalWork == 0)\r
+ return message;\r
+\r
+ return NLS.bind(WorkbenchMessages.FilteredItemsSelectionDialog_taskProgressMessage,\r
+ new Object[] { message, new Integer((int) ((worked * 100) / totalWork)) });\r
+\r
+ }\r
+\r
+ }\r
+\r
+ /**\r
+ * Filters items history and schedule filter job.\r
+ */\r
+ private class FilterHistoryJob extends Job {\r
+\r
+ /**\r
+ * Filter used during the filtering process.\r
+ */\r
+ private ItemsFilter itemsFilter;\r
+\r
+ /**\r
+ * Creates new instance of receiver.\r
+ */\r
+ public FilterHistoryJob() {\r
+ super(WorkbenchMessages.FilteredItemsSelectionDialog_jobLabel);\r
+ setSystem(true);\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)\r
+ */\r
+ protected IStatus run(IProgressMonitor monitor) {\r
+ this.itemsFilter = filter;\r
+\r
+ contentProvider.reset();\r
+\r
+ refreshWithLastSelection = false;\r
+\r
+ contentProvider.addHistoryItems(itemsFilter);\r
+\r
+ if (!(lastCompletedFilter != null && lastCompletedFilter.isSubFilter(this.itemsFilter)))\r
+ contentProvider.refresh();\r
+\r
+ filterJob.schedule();\r
+\r
+ return Status.OK_STATUS;\r
+ }\r
+\r
+ }\r
+\r
+ /**\r
+ * Filters items in indicated set and history. During filtering, it\r
+ * refreshes the dialog (progress monitor and elements list).\r
+ * \r
+ * Depending on the filter, <code>FilterJob</code> decides which kind of\r
+ * search will be run inside <code>filterContent</code>. If the last\r
+ * filtering is done (last completed filter), is not null, and the new\r
+ * filter is a sub-filter ({@link ColumnFilteredItemsSelectionDialog.ItemsFilter#isSubFilter(ColumnFilteredItemsSelectionDialog.ItemsFilter)})\r
+ * of the last, then <code>FilterJob</code> only filters in the cache. If\r
+ * it is the first filtering or the new filter isn't a sub-filter of the\r
+ * last one, a full search is run.\r
+ */\r
+ private class FilterJob extends Job {\r
+\r
+ /**\r
+ * Filter used during the filtering process.\r
+ */\r
+ protected ItemsFilter itemsFilter;\r
+\r
+ /**\r
+ * Creates new instance of FilterJob\r
+ */\r
+ public FilterJob() {\r
+ super(WorkbenchMessages.FilteredItemsSelectionDialog_jobLabel);\r
+ setSystem(true);\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)\r
+ */\r
+ protected final IStatus run(IProgressMonitor parent) {\r
+ GranualProgressMonitor monitor = new GranualProgressMonitor(parent);\r
+ return doRun(monitor);\r
+ }\r
+\r
+ /**\r
+ * Executes job using the given filtering progress monitor. A hook for\r
+ * subclasses.\r
+ * \r
+ * @param monitor\r
+ * progress monitor\r
+ * @return result of the execution\r
+ */\r
+ protected IStatus doRun(GranualProgressMonitor monitor) {\r
+ try {\r
+ internalRun(monitor);\r
+ } catch (CoreException e) {\r
+ cancel();\r
+ return new Status(\r
+ IStatus.ERROR,\r
+ PlatformUI.PLUGIN_ID,\r
+ IStatus.ERROR,\r
+ WorkbenchMessages.FilteredItemsSelectionDialog_jobError,\r
+ e);\r
+ }\r
+ return Status.OK_STATUS;\r
+ }\r
+\r
+ /**\r
+ * Main method for the job.\r
+ * \r
+ * @param monitor\r
+ * @throws CoreException\r
+ */\r
+ private void internalRun(GranualProgressMonitor monitor)\r
+ throws CoreException {\r
+ try {\r
+ if (monitor.isCanceled())\r
+ return;\r
+\r
+ this.itemsFilter = filter;\r
+\r
+ // why is the content not filtered if the patter is empty? \r
+ // this makes no sense since the search pattern is able to\r
+ // handle the empty pattern just fine, and even has settings\r
+ // for what to do in this case\r
+ //if (filter.getPattern().length() != 0) {\r
+ // filterContent(monitor);\r
+ //}\r
+ \r
+ filterContent(monitor);\r
+\r
+ if (monitor.isCanceled())\r
+ return;\r
+\r
+ contentProvider.refresh();\r
+ } finally {\r
+ monitor.done();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Filters items.\r
+ * \r
+ * @param monitor\r
+ * for monitoring progress\r
+ * @throws CoreException\r
+ */\r
+ protected void filterContent(GranualProgressMonitor monitor)\r
+ throws CoreException {\r
+\r
+ if (lastCompletedFilter != null\r
+ && lastCompletedFilter.isSubFilter(this.itemsFilter)) {\r
+\r
+ int length = lastCompletedResult.size() / 500;\r
+ monitor.beginTask(WorkbenchMessages.FilteredItemsSelectionDialog_cacheSearchJob_taskName, length);\r
+\r
+ for (int pos = 0; pos < lastCompletedResult.size(); pos++) {\r
+\r
+ Object item = lastCompletedResult.get(pos);\r
+ if (monitor.isCanceled())\r
+ break;\r
+ contentProvider.add(item, itemsFilter);\r
+\r
+ if ((pos % 500) == 0) {\r
+ monitor.worked(1);\r
+ }\r
+ }\r
+\r
+ } else {\r
+\r
+ lastCompletedFilter = null;\r
+ lastCompletedResult = null;\r
+\r
+ SubProgressMonitor subMonitor = null;\r
+ if (monitor != null) {\r
+ monitor.beginTask(WorkbenchMessages.FilteredItemsSelectionDialog_searchJob_taskName, 100);\r
+ subMonitor = new SubProgressMonitor(monitor, 95);\r
+\r
+ }\r
+\r
+ fillContentProvider(contentProvider, itemsFilter, subMonitor);\r
+\r
+ if (monitor != null && !monitor.isCanceled()) {\r
+ monitor.worked(2);\r
+ contentProvider.rememberResult(itemsFilter);\r
+ monitor.worked(3);\r
+ }\r
+ }\r
+\r
+ }\r
+\r
+ }\r
+\r
+ /**\r
+ * History stores a list of key, object pairs. The list is bounded at a\r
+ * certain size. If the list exceeds this size the oldest element is removed\r
+ * from the list. An element can be added/renewed with a call to\r
+ * <code>accessed(Object)</code>.\r
+ * <p>\r
+ * The history can be stored to/loaded from an XML file.\r
+ */\r
+ protected static abstract class SelectionHistory {\r
+\r
+ private static final String DEFAULT_ROOT_NODE_NAME = "historyRootNode"; //$NON-NLS-1$\r
+\r
+ private static final String DEFAULT_INFO_NODE_NAME = "infoNode"; //$NON-NLS-1$\r
+\r
+ private static final int MAX_HISTORY_SIZE = 60;\r
+\r
+ private final Set historyList;\r
+\r
+ private final String rootNodeName;\r
+\r
+ private final String infoNodeName;\r
+\r
+ private SelectionHistory(String rootNodeName, String infoNodeName) {\r
+\r
+ historyList = Collections.synchronizedSet(new LinkedHashSet() {\r
+\r
+ private static final long serialVersionUID = 0L;\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see java.util.LinkedList#add(java.lang.Object)\r
+ */\r
+ public boolean add(Object arg0) {\r
+ if (this.size() >= MAX_HISTORY_SIZE) {\r
+ Iterator iterator = this.iterator();\r
+ iterator.next();\r
+ iterator.remove();\r
+ }\r
+ return super.add(arg0);\r
+ }\r
+\r
+ });\r
+\r
+ this.rootNodeName = rootNodeName;\r
+ this.infoNodeName = infoNodeName;\r
+ }\r
+\r
+ /**\r
+ * Creates new instance of <code>SelectionHistory</code>.\r
+ */\r
+ public SelectionHistory() {\r
+ this(DEFAULT_ROOT_NODE_NAME, DEFAULT_INFO_NODE_NAME);\r
+ }\r
+\r
+ /**\r
+ * Adds object to history.\r
+ * \r
+ * @param object\r
+ * the item to be added to the history\r
+ */\r
+ public synchronized void accessed(Object object) {\r
+ historyList.remove(object);\r
+ historyList.add(object);\r
+ }\r
+\r
+ /**\r
+ * Returns <code>true</code> if history contains object.\r
+ * \r
+ * @param object\r
+ * the item for which check will be executed\r
+ * @return <code>true</code> if history contains object\r
+ * <code>false</code> in other way\r
+ */\r
+ public synchronized boolean contains(Object object) {\r
+ return historyList.contains(object);\r
+ }\r
+\r
+ /**\r
+ * Returns <code>true</code> if history is empty.\r
+ * \r
+ * @return <code>true</code> if history is empty\r
+ */\r
+ public synchronized boolean isEmpty() {\r
+ return historyList.isEmpty();\r
+ }\r
+\r
+ /**\r
+ * Remove element from history.\r
+ * \r
+ * @param element\r
+ * to remove form the history\r
+ * @return <code>true</code> if this list contained the specified\r
+ * element\r
+ */\r
+ public synchronized boolean remove(Object element) {\r
+ return historyList.remove(element);\r
+ }\r
+\r
+ /**\r
+ * Load history elements from memento.\r
+ * \r
+ * @param memento\r
+ * memento from which the history will be retrieved\r
+ */\r
+ public void load(IMemento memento) {\r
+\r
+ XMLMemento historyMemento = (XMLMemento) memento\r
+ .getChild(rootNodeName);\r
+\r
+ if (historyMemento == null) {\r
+ return;\r
+ }\r
+\r
+ IMemento[] mementoElements = historyMemento\r
+ .getChildren(infoNodeName);\r
+ for (int i = 0; i < mementoElements.length; ++i) {\r
+ IMemento mementoElement = mementoElements[i];\r
+ Object object = restoreItemFromMemento(mementoElement);\r
+ if (object != null) {\r
+ historyList.add(object);\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Save history elements to memento.\r
+ * \r
+ * @param memento\r
+ * memento to which the history will be added\r
+ */\r
+ public void save(IMemento memento) {\r
+\r
+ IMemento historyMemento = memento.createChild(rootNodeName);\r
+\r
+ Object[] items = getHistoryItems();\r
+ for (int i = 0; i < items.length; i++) {\r
+ Object item = items[i];\r
+ IMemento elementMemento = historyMemento\r
+ .createChild(infoNodeName);\r
+ storeItemToMemento(item, elementMemento);\r
+ }\r
+\r
+ }\r
+\r
+ /**\r
+ * Gets array of history items.\r
+ * \r
+ * @return array of history elements\r
+ */\r
+ public synchronized Object[] getHistoryItems() {\r
+ return historyList.toArray();\r
+ }\r
+\r
+ /**\r
+ * Creates an object using given memento.\r
+ * \r
+ * @param memento\r
+ * memento used for creating new object\r
+ * \r
+ * @return the restored object\r
+ */\r
+ protected abstract Object restoreItemFromMemento(IMemento memento);\r
+\r
+ /**\r
+ * Store object in <code>IMemento</code>.\r
+ * \r
+ * @param item\r
+ * the item to store\r
+ * @param memento\r
+ * the memento to store to\r
+ */\r
+ protected abstract void storeItemToMemento(Object item, IMemento memento);\r
+\r
+ }\r
+\r
+ /**\r
+ * Filters elements using SearchPattern by comparing the names of items with\r
+ * the filter pattern.\r
+ */\r
+ protected abstract class ItemsFilter {\r
+\r
+ protected SearchPattern patternMatcher;\r
+\r
+ /**\r
+ * Creates new instance of ItemsFilter.\r
+ */\r
+ public ItemsFilter() {\r
+ this(new SearchPattern());\r
+ }\r
+\r
+ /**\r
+ * Creates new instance of ItemsFilter.\r
+ * \r
+ * @param searchPattern\r
+ * the pattern to be used when filtering\r
+ */\r
+ public ItemsFilter(SearchPattern searchPattern) {\r
+ patternMatcher = searchPattern;\r
+ String stringPattern = ""; //$NON-NLS-1$\r
+ if (pattern != null && !pattern.getText().equals("*")) { //$NON-NLS-1$\r
+ stringPattern = pattern.getText();\r
+ }\r
+ patternMatcher.setPattern(stringPattern);\r
+ }\r
+\r
+ /**\r
+ * Check if the given filter is a sub-filter of this filter. The default\r
+ * implementation checks if the <code>SearchPattern</code> from the\r
+ * given filter is a sub-pattern of the one from this filter.\r
+ * <p>\r
+ * <i>WARNING: This method is <b>not</b> defined in reading order, i.e.\r
+ * <code>a.isSubFilter(b)</code> is <code>true</code> iff\r
+ * <code>b</code> is a sub-filter of <code>a</code>, and not\r
+ * vice-versa. </i>\r
+ * </p>\r
+ * \r
+ * @param filter\r
+ * the filter to be checked, or <code>null</code>\r
+ * @return <code>true</code> if the given filter is sub-filter of this\r
+ * filter, <code>false</code> if the given filter isn't a\r
+ * sub-filter or is <code>null</code>\r
+ * \r
+ * @see org.eclipse.ui.dialogs.SearchPattern#isSubPattern(org.eclipse.ui.dialogs.SearchPattern)\r
+ */\r
+ public boolean isSubFilter(ItemsFilter filter) {\r
+ if (filter != null) {\r
+ return this.patternMatcher.isSubPattern(filter.patternMatcher);\r
+ }\r
+ return false;\r
+ }\r
+\r
+ /**\r
+ * Checks whether the provided filter is equal to the current filter.\r
+ * The default implementation checks if <code>SearchPattern</code>\r
+ * from current filter is equal to the one from provided filter.\r
+ * \r
+ * @param filter\r
+ * filter to be checked, or <code>null</code>\r
+ * @return <code>true</code> if the given filter is equal to current\r
+ * filter, <code>false</code> if given filter isn't equal to\r
+ * current one or if it is <code>null</code>\r
+ * \r
+ * @see org.eclipse.ui.dialogs.SearchPattern#equalsPattern(org.eclipse.ui.dialogs.SearchPattern)\r
+ */\r
+ public boolean equalsFilter(ItemsFilter filter) {\r
+ if (filter != null\r
+ && filter.patternMatcher.equalsPattern(this.patternMatcher)) {\r
+ return true;\r
+ }\r
+ return false;\r
+ }\r
+\r
+ /**\r
+ * Checks whether the pattern's match rule is camel case.\r
+ * \r
+ * @return <code>true</code> if pattern's match rule is camel case,\r
+ * <code>false</code> otherwise\r
+ */\r
+ public boolean isCamelCasePattern() {\r
+ return patternMatcher.getMatchRule() == SearchPattern.RULE_CAMELCASE_MATCH;\r
+ }\r
+\r
+ /**\r
+ * Returns the pattern string.\r
+ * \r
+ * @return pattern for this filter\r
+ * \r
+ * @see SearchPattern#getPattern()\r
+ */\r
+ public String getPattern() {\r
+ return patternMatcher.getPattern();\r
+ }\r
+\r
+ /**\r
+ * Returns the rule to apply for matching keys.\r
+ * \r
+ * @return an implementation-specific match rule\r
+ * \r
+ * @see SearchPattern#getMatchRule() for match rules returned by the\r
+ * default implementation\r
+ */\r
+ public int getMatchRule() {\r
+ return patternMatcher.getMatchRule();\r
+ }\r
+\r
+ /**\r
+ * Matches text with filter.\r
+ * \r
+ * @param text\r
+ * the text to match with the filter\r
+ * @return <code>true</code> if text matches with filter pattern,\r
+ * <code>false</code> otherwise\r
+ */\r
+ protected boolean matches(String text) {\r
+ return patternMatcher.matches(text);\r
+ }\r
+\r
+ /**\r
+ * General method for matching raw name pattern. Checks whether current\r
+ * pattern is prefix of name provided item.\r
+ * \r
+ * @param item\r
+ * item to check\r
+ * @return <code>true</code> if current pattern is a prefix of name\r
+ * provided item, <code>false</code> if item's name is shorter\r
+ * than prefix or sequences of characters don't match.\r
+ */\r
+ public boolean matchesRawNamePattern(Object item) {\r
+ String prefix = patternMatcher.getPattern();\r
+ String text = getElementName(item);\r
+\r
+ if (text == null)\r
+ return false;\r
+\r
+ int textLength = text.length();\r
+ int prefixLength = prefix.length();\r
+ if (textLength < prefixLength) {\r
+ return false;\r
+ }\r
+ for (int i = prefixLength - 1; i >= 0; i--) {\r
+ if (Character.toLowerCase(prefix.charAt(i)) != Character\r
+ .toLowerCase(text.charAt(i)))\r
+ return false;\r
+ }\r
+ return true;\r
+ }\r
+\r
+ /**\r
+ * Matches an item against filter conditions.\r
+ * \r
+ * @param item\r
+ * @return <code>true<code> if item matches against filter conditions, <code>false</code>\r
+ * otherwise\r
+ */\r
+ public abstract boolean matchItem(Object item);\r
+\r
+ /**\r
+ * Checks consistency of an item. Item is inconsistent if was changed or\r
+ * removed.\r
+ * \r
+ * @param item\r
+ * @return <code>true</code> if item is consistent, <code>false</code>\r
+ * if item is inconsistent\r
+ */\r
+ public abstract boolean isConsistentItem(Object item);\r
+\r
+ }\r
+\r
+ /**\r
+ * An interface to content providers for\r
+ * <code>FilterItemsSelectionDialog</code>.\r
+ */\r
+ protected abstract class AbstractContentProvider {\r
+ /**\r
+ * Adds the item to the content provider iff the filter matches the\r
+ * item. Otherwise does nothing.\r
+ * \r
+ * @param item\r
+ * the item to add\r
+ * @param itemsFilter\r
+ * the filter\r
+ * \r
+ * @see ColumnFilteredItemsSelectionDialog.ItemsFilter#matchItem(Object)\r
+ */\r
+ public abstract void add(Object item, ItemsFilter itemsFilter);\r
+ }\r
+\r
+ /**\r
+ * Collects filtered elements. Contains one synchronized, sorted set for\r
+ * collecting filtered elements. All collected elements are sorted using\r
+ * comparator. Comparator is returned by getElementComparator() method.\r
+ * Implementation of <code>ItemsFilter</code> is used to filter elements.\r
+ * The key function of filter used in to filtering is\r
+ * <code>matchElement(Object item)</code>.\r
+ * <p>\r
+ * The <code>ContentProvider</code> class also provides item filtering\r
+ * methods. The filtering has been moved from the standard TableView\r
+ * <code>getFilteredItems()</code> method to content provider, because\r
+ * <code>ILazyContentProvider</code> and virtual tables are used. This\r
+ * class is responsible for adding a separator below history items and\r
+ * marking each items as duplicate if its name repeats more than once on the\r
+ * filtered list.\r
+ */\r
+ private class ContentProvider extends AbstractContentProvider implements\r
+ IStructuredContentProvider, ILazyContentProvider {\r
+\r
+ private SelectionHistory selectionHistory;\r
+\r
+ /**\r
+ * Raw result of the searching (unsorted, unfiltered).\r
+ * <p>\r
+ * Standard object flow:\r
+ * <code>items -> lastSortedItems -> lastFilteredItems</code>\r
+ */\r
+ private Set items;\r
+\r
+ /**\r
+ * Items that are duplicates.\r
+ */\r
+ private Set duplicates;\r
+\r
+ /**\r
+ * List of <code>ViewerFilter</code>s to be used during filtering\r
+ */\r
+ private List filters;\r
+\r
+ /**\r
+ * Result of the last filtering.\r
+ * <p>\r
+ * Standard object flow:\r
+ * <code>items -> lastSortedItems -> lastFilteredItems</code>\r
+ */\r
+ private List lastFilteredItems;\r
+\r
+ /**\r
+ * Result of the last sorting.\r
+ * <p>\r
+ * Standard object flow:\r
+ * <code>items -> lastSortedItems -> lastFilteredItems</code>\r
+ */\r
+ private List lastSortedItems;\r
+\r
+ /**\r
+ * Used for <code>getFilteredItems()</code> method canceling (when the\r
+ * job that invoked the method was canceled).\r
+ * <p>\r
+ * Method canceling could be based (only) on monitor canceling\r
+ * unfortunately sometimes the method <code>getFilteredElements()</code>\r
+ * could be run with a null monitor, the <code>reset</code> flag have\r
+ * to be left intact.\r
+ */\r
+ private boolean reset;\r
+\r
+ /**\r
+ * Creates new instance of <code>ContentProvider</code>.\r
+ */\r
+ public ContentProvider() {\r
+ this.items = Collections.synchronizedSet(new HashSet(2048));\r
+ this.duplicates = Collections.synchronizedSet(new HashSet(256));\r
+ this.lastFilteredItems = new ArrayList();\r
+ this.lastSortedItems = Collections.synchronizedList(new ArrayList(2048));\r
+ }\r
+\r
+ /**\r
+ * Sets selection history.\r
+ * \r
+ * @param selectionHistory\r
+ * The selectionHistory to set.\r
+ */\r
+ public void setSelectionHistory(SelectionHistory selectionHistory) {\r
+ this.selectionHistory = selectionHistory;\r
+ }\r
+\r
+ /**\r
+ * @return Returns the selectionHistory.\r
+ */\r
+ public SelectionHistory getSelectionHistory() {\r
+ return selectionHistory;\r
+ }\r
+\r
+ /**\r
+ * Removes all content items and resets progress message.\r
+ */\r
+ public void reset() {\r
+ reset = true;\r
+ this.items.clear();\r
+ this.duplicates.clear();\r
+ this.lastSortedItems.clear();\r
+ }\r
+\r
+ /**\r
+ * Stops reloading cache - <code>getFilteredItems()</code> method.\r
+ */\r
+ public void stopReloadingCache() {\r
+ reset = true;\r
+ }\r
+\r
+ /**\r
+ * Adds filtered item.\r
+ * \r
+ * @param item\r
+ * @param itemsFilter\r
+ */\r
+ public void add(Object item, ItemsFilter itemsFilter) {\r
+ if (itemsFilter == filter) {\r
+ if (itemsFilter != null) {\r
+ if (itemsFilter.matchItem(item)) {\r
+ this.items.add(item);\r
+ }\r
+ } else {\r
+ this.items.add(item);\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Add all history items to <code>contentProvider</code>.\r
+ * \r
+ * @param itemsFilter\r
+ */\r
+ public void addHistoryItems(ItemsFilter itemsFilter) {\r
+ if (this.selectionHistory != null) {\r
+ Object[] items = this.selectionHistory.getHistoryItems();\r
+ for (int i = 0; i < items.length; i++) {\r
+ Object item = items[i];\r
+ if (itemsFilter == filter) {\r
+ if (itemsFilter != null) {\r
+ if (itemsFilter.matchItem(item)) {\r
+ if (itemsFilter.isConsistentItem(item)) {\r
+ this.items.add(item);\r
+ } else {\r
+ this.selectionHistory.remove(item);\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Refresh dialog.\r
+ */\r
+ public void refresh() {\r
+ scheduleRefresh();\r
+ }\r
+\r
+ /**\r
+ * Removes items from history and refreshes the view.\r
+ * \r
+ * @param item\r
+ * to remove\r
+ * \r
+ * @return removed item\r
+ */\r
+ public Object removeHistoryElement(Object item) {\r
+ if (this.selectionHistory != null)\r
+ this.selectionHistory.remove(item);\r
+ if (filter == null || filter.getPattern().length() == 0) {\r
+ items.remove(item);\r
+ duplicates.remove(item);\r
+ this.lastSortedItems.remove(item);\r
+ }\r
+\r
+ synchronized (lastSortedItems) {\r
+ Collections.sort(lastSortedItems, getHistoryComparator());\r
+ }\r
+ return item;\r
+ }\r
+\r
+ /**\r
+ * Adds item to history and refresh view.\r
+ * \r
+ * @param item\r
+ * to add\r
+ */\r
+ public void addHistoryElement(Object item) {\r
+ if (this.selectionHistory != null)\r
+ this.selectionHistory.accessed(item);\r
+ if (filter == null || !filter.matchItem(item)) {\r
+ this.items.remove(item);\r
+ this.duplicates.remove(item);\r
+ this.lastSortedItems.remove(item);\r
+ }\r
+ synchronized (lastSortedItems) {\r
+ Collections.sort(lastSortedItems, getHistoryComparator());\r
+ }\r
+ this.refresh();\r
+ }\r
+\r
+ /**\r
+ * @param item\r
+ * @return <code>true</code> if given item is part of the history\r
+ */\r
+ public boolean isHistoryElement(Object item) {\r
+ if (this.selectionHistory != null) {\r
+ return this.selectionHistory.contains(item);\r
+ }\r
+ return false;\r
+ }\r
+\r
+ /**\r
+ * Sets/unsets given item as duplicate.\r
+ * \r
+ * @param item\r
+ * item to change\r
+ * \r
+ * @param isDuplicate\r
+ * duplicate flag\r
+ */\r
+ public void setDuplicateElement(Object item, boolean isDuplicate) {\r
+ if (this.items.contains(item)) {\r
+ if (isDuplicate)\r
+ this.duplicates.add(item);\r
+ else\r
+ this.duplicates.remove(item);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Indicates whether given item is a duplicate.\r
+ * \r
+ * @param item\r
+ * item to check\r
+ * @return <code>true</code> if item is duplicate\r
+ */\r
+ public boolean isDuplicateElement(Object item) {\r
+ return duplicates.contains(item);\r
+ }\r
+\r
+ /**\r
+ * Load history from memento.\r
+ * \r
+ * @param memento\r
+ * memento from which the history will be retrieved\r
+ */\r
+ public void loadHistory(IMemento memento) {\r
+ if (this.selectionHistory != null)\r
+ this.selectionHistory.load(memento);\r
+ }\r
+\r
+ /**\r
+ * Save history to memento.\r
+ * \r
+ * @param memento\r
+ * memento to which the history will be added\r
+ */\r
+ public void saveHistory(IMemento memento) {\r
+ if (this.selectionHistory != null)\r
+ this.selectionHistory.save(memento);\r
+ }\r
+\r
+ /**\r
+ * Gets sorted items.\r
+ * \r
+ * @return sorted items\r
+ */\r
+ private Object[] getSortedItems() {\r
+ if (lastSortedItems.size() != items.size()) {\r
+ synchronized (lastSortedItems) {\r
+ lastSortedItems.clear();\r
+ lastSortedItems.addAll(items);\r
+ Collections.sort(lastSortedItems, getHistoryComparator());\r
+ }\r
+ }\r
+ return lastSortedItems.toArray();\r
+ }\r
+\r
+ /**\r
+ * Remember result of filtering.\r
+ * \r
+ * @param itemsFilter\r
+ */\r
+ public void rememberResult(ItemsFilter itemsFilter) {\r
+ List itemsList = Collections.synchronizedList(Arrays\r
+ .asList(getSortedItems()));\r
+ // synchronization\r
+ if (itemsFilter == filter) {\r
+ lastCompletedFilter = itemsFilter;\r
+ lastCompletedResult = itemsList;\r
+ }\r
+\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object)\r
+ */\r
+ public Object[] getElements(Object inputElement) {\r
+ return lastFilteredItems.toArray();\r
+ }\r
+\r
+ public int getNumberOfElements() {\r
+ return lastFilteredItems.size();\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.jface.viewers.IContentProvider#dispose()\r
+ */\r
+ public void dispose() {\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer,\r
+ * java.lang.Object, java.lang.Object)\r
+ */\r
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.jface.viewers.ILazyContentProvider#updateElement(int)\r
+ */\r
+ public void updateElement(int index) {\r
+\r
+ ColumnFilteredItemsSelectionDialog.this.viewer.replace((lastFilteredItems\r
+ .size() > index) ? lastFilteredItems.get(index) : null,\r
+ index);\r
+\r
+ }\r
+\r
+ /**\r
+ * Main method responsible for getting the filtered items and checking\r
+ * for duplicates. It is based on the\r
+ * {@link ColumnFilteredItemsSelectionDialog.ContentProvider#getFilteredItems(Object, IProgressMonitor)}.\r
+ * \r
+ * @param checkDuplicates\r
+ * <code>true</code> if data concerning elements\r
+ * duplication should be computed - it takes much more time\r
+ * than standard filtering\r
+ * \r
+ * @param monitor\r
+ * progress monitor\r
+ */\r
+ public void reloadCache(boolean checkDuplicates,\r
+ IProgressMonitor monitor) {\r
+\r
+ reset = false;\r
+\r
+ if (monitor != null) {\r
+ // the work is divided into two actions of the same length\r
+ int totalWork = checkDuplicates ? 200 : 100;\r
+\r
+ monitor.beginTask(WorkbenchMessages.FilteredItemsSelectionDialog_cacheRefreshJob, totalWork);\r
+ }\r
+\r
+ // the TableViewer's root (the input) is treated as parent\r
+\r
+ lastFilteredItems = Arrays.asList(getFilteredItems(viewer.getInput(),\r
+ monitor != null ? new SubProgressMonitor(monitor, 100) : null));\r
+\r
+ if (reset || (monitor != null && monitor.isCanceled())) {\r
+ if (monitor != null)\r
+ monitor.done();\r
+ return;\r
+ }\r
+\r
+ if (checkDuplicates) {\r
+ checkDuplicates(monitor);\r
+ }\r
+ if (monitor != null)\r
+ monitor.done();\r
+ }\r
+\r
+ private void checkDuplicates(IProgressMonitor monitor) {\r
+ synchronized (lastFilteredItems) {\r
+ IProgressMonitor subMonitor = null;\r
+ int reportEvery = lastFilteredItems.size() / 20;\r
+ if (monitor != null) {\r
+ subMonitor = new SubProgressMonitor(monitor, 100);\r
+ subMonitor.beginTask(WorkbenchMessages.FilteredItemsSelectionDialog_cacheRefreshJob_checkDuplicates, 5);\r
+ }\r
+ HashMap helperMap = new HashMap();\r
+ for (int i = 0; i < lastFilteredItems.size(); i++) {\r
+ if (reset || (subMonitor != null && subMonitor.isCanceled()))\r
+ return;\r
+ Object item = lastFilteredItems.get(i);\r
+\r
+ if (!(item instanceof ItemsListSeparator)) {\r
+ Object previousItem = helperMap.put(getElementName(item), item);\r
+ if (previousItem != null) {\r
+ setDuplicateElement(previousItem, true);\r
+ setDuplicateElement(item, true);\r
+ } else {\r
+ setDuplicateElement(item, false);\r
+ }\r
+ }\r
+\r
+ if (subMonitor != null && reportEvery != 0\r
+ && (i + 1) % reportEvery == 0)\r
+ subMonitor.worked(1);\r
+ }\r
+ helperMap.clear();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Returns an array of items filtered using the provided\r
+ * <code>ViewerFilter</code>s with a separator added.\r
+ * \r
+ * @param parent\r
+ * the parent\r
+ * @param monitor\r
+ * progress monitor, can be <code>null</code>\r
+ * @return an array of filtered items\r
+ */\r
+ protected Object[] getFilteredItems(Object parent,\r
+ IProgressMonitor monitor) {\r
+ int ticks = 100;\r
+ if (monitor == null) {\r
+ monitor = new NullProgressMonitor();\r
+ }\r
+\r
+ monitor.beginTask(WorkbenchMessages.FilteredItemsSelectionDialog_cacheRefreshJob_getFilteredElements, ticks);\r
+ if (filters != null) {\r
+ ticks /= (filters.size() + 2);\r
+ } else {\r
+ ticks /= 2;\r
+ }\r
+\r
+ // get already sorted array\r
+ Object[] filteredElements = getSortedItems();\r
+\r
+ monitor.worked(ticks);\r
+\r
+ // filter the elements using provided ViewerFilters\r
+ if (filters != null && filteredElements != null) {\r
+ for (Iterator iter = filters.iterator(); iter.hasNext();) {\r
+ ViewerFilter f = (ViewerFilter) iter.next();\r
+ filteredElements = f.filter(viewer, parent, filteredElements);\r
+ monitor.worked(ticks);\r
+ }\r
+ }\r
+\r
+ if (filteredElements == null || monitor.isCanceled()) {\r
+ monitor.done();\r
+ return new Object[0];\r
+ }\r
+\r
+ ArrayList preparedElements = new ArrayList();\r
+ boolean hasHistory = false;\r
+\r
+ if (filteredElements.length > 0) {\r
+ if (isHistoryElement(filteredElements[0])) {\r
+ hasHistory = true;\r
+ }\r
+ }\r
+\r
+ int reportEvery = filteredElements.length / ticks;\r
+\r
+ // add separator\r
+ for (int i = 0; i < filteredElements.length; i++) {\r
+ Object item = filteredElements[i];\r
+\r
+ if (hasHistory && !isHistoryElement(item)) {\r
+ preparedElements.add(itemsListSeparator);\r
+ hasHistory = false;\r
+ }\r
+\r
+ preparedElements.add(item);\r
+\r
+ if (reportEvery != 0 && ((i + 1) % reportEvery == 0)) {\r
+ monitor.worked(1);\r
+ }\r
+ }\r
+\r
+ monitor.done();\r
+\r
+ return preparedElements.toArray();\r
+ }\r
+\r
+ /**\r
+ * Adds a filter to this content provider. For an example usage of such\r
+ * filters look at the project <code>org.eclipse.ui.ide</code>, class\r
+ * <code>org.eclipse.ui.dialogs.FilteredResourcesSelectionDialog.CustomWorkingSetFilter</code>.\r
+ * \r
+ * \r
+ * @param filter\r
+ * the filter to be added\r
+ */\r
+ public void addFilter(ViewerFilter filter) {\r
+ if (filters == null) {\r
+ filters = new ArrayList();\r
+ }\r
+ filters.add(filter);\r
+ // currently filters are only added when dialog is restored\r
+ // if it is changed, refreshing the whole TableViewer should be\r
+ // added\r
+ }\r
+\r
+ }\r
+\r
+ /**\r
+ * A content provider that does nothing.\r
+ */\r
+ private class NullContentProvider implements IContentProvider {\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.jface.viewers.IContentProvider#dispose()\r
+ */\r
+ public void dispose() {\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer,\r
+ * java.lang.Object, java.lang.Object)\r
+ */\r
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {\r
+ }\r
+\r
+ }\r
+\r
+ /**\r
+ * DetailsContentViewer objects are wrappers for labels.\r
+ * DetailsContentViewer provides means to change label's image and text when\r
+ * the attached LabelProvider is updated.\r
+ */\r
+ private class DetailsContentViewer extends ContentViewer {\r
+\r
+ private CLabel label;\r
+\r
+ /**\r
+ * Unfortunately, it was impossible to delegate displaying border to\r
+ * label. The <code>ViewForm</code> is used because\r
+ * <code>CLabel</code> displays shadow when border is present.\r
+ */\r
+ private ViewForm viewForm;\r
+\r
+ /**\r
+ * Constructs a new instance of this class given its parent and a style\r
+ * value describing its behavior and appearance.\r
+ * \r
+ * @param parent\r
+ * the parent component\r
+ * @param style\r
+ * SWT style bits\r
+ */\r
+ public DetailsContentViewer(Composite parent, int style) {\r
+ viewForm = new ViewForm(parent, style);\r
+ GridData gd = new GridData(GridData.FILL_HORIZONTAL);\r
+ gd.horizontalSpan = 2;\r
+ viewForm.setLayoutData(gd);\r
+ label = new CLabel(viewForm, SWT.FLAT);\r
+ label.setFont(parent.getFont());\r
+ viewForm.setContent(label);\r
+ hookControl(label);\r
+ }\r
+\r
+ /**\r
+ * Shows/hides the content viewer.\r
+ * \r
+ * @param visible\r
+ * if the content viewer should be visible.\r
+ */\r
+ public void setVisible(boolean visible) {\r
+ GridData gd = (GridData) viewForm.getLayoutData();\r
+ gd.exclude = !visible;\r
+ viewForm.getParent().layout();\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.jface.viewers.Viewer#inputChanged(java.lang.Object,\r
+ * java.lang.Object)\r
+ */\r
+ protected void inputChanged(Object input, Object oldInput) {\r
+ if (oldInput == null) {\r
+ if (input == null) {\r
+ return;\r
+ }\r
+ refresh();\r
+ return;\r
+ }\r
+\r
+ refresh();\r
+\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.jface.viewers.ContentViewer#handleLabelProviderChanged(org.eclipse.jface.viewers.LabelProviderChangedEvent)\r
+ */\r
+ protected void handleLabelProviderChanged(\r
+ LabelProviderChangedEvent event) {\r
+ if (event != null) {\r
+ refresh(event.getElements());\r
+ }\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.jface.viewers.Viewer#getControl()\r
+ */\r
+ public Control getControl() {\r
+ return label;\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.jface.viewers.Viewer#getSelection()\r
+ */\r
+ public ISelection getSelection() {\r
+ // not supported\r
+ return null;\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.jface.viewers.Viewer#refresh()\r
+ */\r
+ public void refresh() {\r
+ Object input = this.getInput();\r
+ if (input != null) {\r
+ ILabelProvider labelProvider = (ILabelProvider) getLabelProvider();\r
+ doRefresh(labelProvider.getText(input), labelProvider\r
+ .getImage(input));\r
+ } else {\r
+ doRefresh(null, null);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Sets the given text and image to the label.\r
+ * \r
+ * @param text\r
+ * the new text or null\r
+ * @param image\r
+ * the new image\r
+ */\r
+ private void doRefresh(String text, Image image) {\r
+ if ( text != null ) {\r
+ text = LegacyActionTools.escapeMnemonics(text);\r
+ }\r
+ label.setText(text);\r
+ label.setImage(image);\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see org.eclipse.jface.viewers.Viewer#setSelection(org.eclipse.jface.viewers.ISelection,\r
+ * boolean)\r
+ */\r
+ public void setSelection(ISelection selection, boolean reveal) {\r
+ // not supported\r
+ }\r
+\r
+ /**\r
+ * Refreshes the label if currently chosen element is on the list.\r
+ * \r
+ * @param objs\r
+ * list of changed object\r
+ */\r
+ private void refresh(Object[] objs) {\r
+ if (objs == null || getInput() == null) {\r
+ return;\r
+ }\r
+ Object input = getInput();\r
+ for (int i = 0; i < objs.length; i++) {\r
+ if (objs[i].equals(input)) {\r
+ refresh();\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Compares items according to the history.\r
+ */\r
+ private class HistoryComparator implements Comparator {\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * \r
+ * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)\r
+ */\r
+ public int compare(Object o1, Object o2) {\r
+ boolean h1 = isHistoryElement(o1);\r
+ boolean h2 = isHistoryElement(o2);\r
+ if (h1 == h2)\r
+ return getItemsComparator().compare(o1, o2);\r
+\r
+ if (h1)\r
+ return -2;\r
+ if (h2)\r
+ return +2;\r
+\r
+ return 0;\r
+ }\r
+\r
+ }\r
+ \r
+\r
+ /**\r
+ * Get the control where the search pattern is entered. Any filtering should\r
+ * be done using an {@link ItemsFilter}. This control should only be\r
+ * accessed for listeners that wish to handle events that do not affect\r
+ * filtering such as custom traversal.\r
+ * \r
+ * @return Control or <code>null</code> if the pattern control has not\r
+ * been created.\r
+ */\r
+ public Control getPatternControl() {\r
+ return pattern;\r
+ }\r
+\r
+}\r
+\r