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