blob: 81434ec5591d66a5000fc9b47978c3dc27b08dfc [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2004 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.search.ui.text;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.GroupMarker;
import org.eclipse.jface.action.IContributionManager;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.util.Assert;
import org.eclipse.jface.viewers.DecoratingLabelProvider;
import org.eclipse.jface.viewers.IBaseLabelProvider;
import org.eclipse.jface.viewers.IOpenListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProviderChangedEvent;
import org.eclipse.jface.viewers.OpenEvent;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.search.internal.ui.CopyToClipboardAction;
import org.eclipse.search.internal.ui.SearchPlugin;
import org.eclipse.search.internal.ui.SearchPluginImages;
import org.eclipse.search.ui.IContextMenuConstants;
import org.eclipse.search.ui.IQueryListener;
import org.eclipse.search.ui.ISearchQuery;
import org.eclipse.search.ui.ISearchResult;
import org.eclipse.search.ui.ISearchResultListener;
import org.eclipse.search.ui.ISearchResultPage;
import org.eclipse.search.ui.ISearchResultViewPart;
import org.eclipse.search.ui.NewSearchUI;
import org.eclipse.search.ui.SearchResultEvent;
import org.eclipse.search2.internal.ui.InternalSearchUI;
import org.eclipse.search2.internal.ui.SearchMessages;
import org.eclipse.search2.internal.ui.basic.views.CollapseAllAction;
import org.eclipse.search2.internal.ui.basic.views.ExpandAllAction;
import org.eclipse.search2.internal.ui.basic.views.INavigate;
import org.eclipse.search2.internal.ui.basic.views.RemoveAllMatchesAction;
import org.eclipse.search2.internal.ui.basic.views.RemoveMatchAction;
import org.eclipse.search2.internal.ui.basic.views.RemoveSelectedMatchesAction;
import org.eclipse.search2.internal.ui.basic.views.SetLayoutAction;
import org.eclipse.search2.internal.ui.basic.views.ShowNextResultAction;
import org.eclipse.search2.internal.ui.basic.views.ShowPreviousResultAction;
import org.eclipse.search2.internal.ui.basic.views.TableViewerNavigator;
import org.eclipse.search2.internal.ui.basic.views.TreeViewerNavigator;
import org.eclipse.search2.internal.ui.text.AnnotationManager;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.actions.ActionFactory.IWorkbenchAction;
import org.eclipse.ui.part.IPageSite;
import org.eclipse.ui.part.Page;
import org.eclipse.ui.part.PageBook;
import org.eclipse.ui.progress.UIJob;
/**
* An abstract base implementation for classes showing
* <code>AbstractTextSearchResult</code> instances. This class assumes that
* the input element set via {@link AbstractTextSearchViewPage#setInput(ISearchResult,Object)}
* is a subclass of {@link AbstractTextSearchResult}.
* This result page supports a tree and/or a table presentation of search
* results. Subclasses can determine which presentations they want to support at
* construction time by passing the appropriate flags.
* Subclasses must customize the viewers for each presentation with a label
* provider and a content provider. <br>
* Changes in the search result are handled by updating the viewer in the
* <code>elementsChanged()</code> and <code>clear()</code> methods.
*
* @since 3.0
*/
public abstract class AbstractTextSearchViewPage extends Page implements ISearchResultPage {
private class UpdateUIJob extends UIJob {
public UpdateUIJob() {
super(SearchMessages.getString("AbstractTextSearchViewPage.update_job.name")); //$NON-NLS-1$
setSystem(true);
}
public IStatus runInUIThread(IProgressMonitor monitor) {
fViewPart.updateLabel();
runBatchedUpdates();
if (hasMoreUpdates() || isQueryRunning()) {
schedule(500);
} else {
fIsUIUpdateScheduled= false;
turnOnDecoration();
}
return Status.OK_STATUS;
}
/*
* Undocumented for testing only. Used to find UpdateUIJobs.
*/
public boolean belongsTo(Object family) {
return family == AbstractTextSearchViewPage.this;
}
}
private transient boolean fIsUIUpdateScheduled= false;
private static final String KEY_LAYOUT = "org.eclipse.search.resultpage.layout"; //$NON-NLS-1$
protected static final Match[] EMPTY_MATCH_ARRAY= new Match[0];
private StructuredViewer fViewer;
private Composite fViewerContainer;
private Control fBusyLabel;
private PageBook fPagebook;
private boolean fIsBusyShown;
private ISearchResultViewPart fViewPart;
private Set fBatchedUpdates;
private ISearchResultListener fListener;
private IQueryListener fQueryListener;
private MenuManager fMenu;
private ISearchResult fInput;
// Actions
private Action fCopyToClipboardAction;
private Action fRemoveSelectedMatches;
private Action fRemoveCurrentMatch;
private Action fRemoveAllResultsAction;
private Action fShowNextAction;
private Action fShowPreviousAction;
private SetLayoutAction fFlatAction;
private SetLayoutAction fHierarchicalAction;
private int fCurrentLayout;
private int fCurrentMatchIndex = 0;
private String fId;
private int fSupportedLayouts;
/**
* Flag (<code>value 1</code>) denoting tree layout.
*/
public static final int FLAG_LAYOUT_FLAT = 1;
/**
* Flag (<code>value 2</code>) denoting flat list layout.
*/
public static final int FLAG_LAYOUT_TREE = 2;
/**
* This constructor must be passed a combination of layout flags combined
* with bitwise or. At least one flag must be passed in (i.e. 0 is not a
* permitted value).
*
* @param supportedLayouts
* flags determining which layout options this page supports.
* Must not be 0
* @see #FLAG_LAYOUT_FLAT
* @see #FLAG_LAYOUT_TREE
*/
protected AbstractTextSearchViewPage(int supportedLayouts) {
fSupportedLayouts = supportedLayouts;
initLayout();
fRemoveAllResultsAction = new RemoveAllMatchesAction(this);
fRemoveSelectedMatches = new RemoveSelectedMatchesAction(this);
fRemoveCurrentMatch = new RemoveMatchAction(this);
fShowNextAction = new ShowNextResultAction(this);
fShowPreviousAction = new ShowPreviousResultAction(this);
createLayoutActions();
fBatchedUpdates = new HashSet();
fListener = new ISearchResultListener() {
public void searchResultChanged(SearchResultEvent e) {
handleSearchResultsChanged(e);
}
};
}
private void initLayout() {
if (supportsFlatLayout())
fCurrentLayout = FLAG_LAYOUT_FLAT;
else
fCurrentLayout = FLAG_LAYOUT_TREE;
}
/**
* Constructs this page with the default layout flags.
*
* @see AbstractTextSearchViewPage#AbstractTextSearchViewPage(int)
*/
protected AbstractTextSearchViewPage() {
this(FLAG_LAYOUT_FLAT | FLAG_LAYOUT_TREE);
}
private void createLayoutActions() {
if (countBits(fSupportedLayouts) > 1) {
fFlatAction = new SetLayoutAction(this, SearchMessages.getString("AbstractTextSearchViewPage.flat_layout.label"), SearchMessages.getString("AbstractTextSearchViewPage.flat_layout.tooltip"), FLAG_LAYOUT_FLAT); //$NON-NLS-1$ //$NON-NLS-2$
fHierarchicalAction = new SetLayoutAction(this, SearchMessages.getString("AbstractTextSearchViewPage.hierarchical_layout.label"), SearchMessages.getString("AbstractTextSearchViewPage.hierarchical_layout.tooltip"), FLAG_LAYOUT_TREE); //$NON-NLS-1$ //$NON-NLS-2$
SearchPluginImages.setImageDescriptors(fFlatAction, SearchPluginImages.T_LCL, SearchPluginImages.IMG_LCL_SEARCH_FLAT_LAYOUT);
SearchPluginImages.setImageDescriptors(fHierarchicalAction, SearchPluginImages.T_LCL, SearchPluginImages.IMG_LCL_SEARCH_HIERARCHICAL_LAYOUT);
}
}
private int countBits(int layoutFlags) {
int bitCount = 0;
for (int i = 0; i < 32; i++) {
if (layoutFlags % 2 == 1)
bitCount++;
layoutFlags >>= 1;
}
return bitCount;
}
private boolean supportsFlatLayout() {
return isLayoutSupported(FLAG_LAYOUT_FLAT);
}
/**
* Returns a dialog settings object for this search result page. There will be
* one dialog settings object per search result page id.
*
* @return the dialog settings for this search result page
* @see AbstractTextSearchViewPage#getID()
*/
protected IDialogSettings getSettings() {
IDialogSettings parent = SearchPlugin.getDefault().getDialogSettings();
IDialogSettings settings = parent.getSection(getID());
if (settings == null)
settings = parent.addNewSection(getID());
return settings;
}
/**
* {@inheritDoc}
*/
public void setID(String id) {
fId = id;
}
/**
* {@inheritDoc}
*/
public String getID() {
return fId;
}
/**
* {@inheritDoc}
*/
public String getLabel() {
AbstractTextSearchResult result= getInput();
if (result == null)
return ""; //$NON-NLS-1$
return result.getLabel();
}
/**
* Opens an editor on the given element and selects the given range of text.
* If a search results implements a <code>IFileMatchAdapter</code>, match
* locations will be tracked and the current match range will be passed into
* this method.
*
* @param match
* the match to show
* @param currentOffset
* the current start offset of the match
* @param currentLength
* the current length of the selection
* @throws PartInitException
* if an editor can't be opened
*
* @see org.eclipse.core.filebuffers.ITextFileBufferManager
* @see IFileMatchAdapter
* @deprecated
*/
protected void showMatch(Match match, int currentOffset, int currentLength) throws PartInitException {
}
/**
* Opens an editor on the given element and selects the given range of text.
* If a search results implements a <code>IFileMatchAdapter</code>, match
* locations will be tracked and the current match range will be passed into
* this method.
* If the <code>activate</code> parameter is <code>true</code> the opened editor
* shoud have be activated. Otherwise the focus should not be changed.
*
* @param match
* the match to show
* @param currentOffset
* the current start offset of the match
* @param currentLength
* the current length of the selection
* @param activate
* whether to activate the editor.
* @throws PartInitException
* if an editor can't be opened
*
* @see org.eclipse.core.filebuffers.ITextFileBufferManager
* @see IFileMatchAdapter
*/
protected void showMatch(Match match, int currentOffset, int currentLength, boolean activate) throws PartInitException {
showMatch(match, currentOffset, currentLength);
}
/**
* This method is called whenever the set of matches for the given elements
* changes. This method is guaranteed to be called in the UI thread. Note
* that this notification is asynchronous. i.e. further changes may have
* occurred by the time this method is called. They will be described in a
* future call.
*
* @param objects
* array of objects that has to be refreshed
*/
protected abstract void elementsChanged(Object[] objects);
/**
* This method is called whenever all elements have been removed from the
* shown <code>AbstractSearchResult</code>. This method is guaranteed to
* be called in the UI thread. Note that this notification is asynchronous.
* i.e. further changes may have occurred by the time this method is called.
* They will be described in a future call.
*/
protected abstract void clear();
/**
* Configures the given viewer. Implementers have to set at least a content
* provider and a label provider. This method may be called if the page was
* constructed with the flag <code>FLAG_LAYOUT_TREE</code>.
*
* @param viewer the viewer to be configured
*/
protected abstract void configureTreeViewer(TreeViewer viewer);
/**
* Configures the given viewer. Implementers have to set at least a content
* provider and a label provider. This method may be called if the page was
* constructed with the flag <code>FLAG_LAYOUT_FLAT</code>.
*
* @param viewer the viewer to be configured
*/
protected abstract void configureTableViewer(TableViewer viewer);
private static void createStandardGroups(IContributionManager menu) {
menu.add(new Separator(IContextMenuConstants.GROUP_NEW));
menu.add(new GroupMarker(IContextMenuConstants.GROUP_GOTO));
menu.add(new GroupMarker(IContextMenuConstants.GROUP_OPEN));
menu.add(new Separator(IContextMenuConstants.GROUP_SHOW));
menu.add(new Separator(IContextMenuConstants.GROUP_BUILD));
menu.add(new Separator(IContextMenuConstants.GROUP_REORGANIZE));
menu.add(new Separator(IContextMenuConstants.GROUP_REMOVE_MATCHES));
menu.add(new GroupMarker(IContextMenuConstants.GROUP_GENERATE));
menu.add(new Separator(IContextMenuConstants.GROUP_SEARCH));
menu.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
menu.add(new Separator(IContextMenuConstants.GROUP_VIEWER_SETUP));
menu.add(new Separator(IContextMenuConstants.GROUP_PROPERTIES));
}
/**
* Fills the context menu for this page. Subclasses may override this
* method.
*
* @param mgr the menu manager representing the context menu
*/
protected void fillContextMenu(IMenuManager mgr) {
mgr.appendToGroup(IContextMenuConstants.GROUP_ADDITIONS, fCopyToClipboardAction);
mgr.appendToGroup(IContextMenuConstants.GROUP_SHOW, fShowNextAction);
mgr.appendToGroup(IContextMenuConstants.GROUP_SHOW, fShowPreviousAction);
if (getCurrentMatch() != null)
mgr.appendToGroup(IContextMenuConstants.GROUP_REMOVE_MATCHES, fRemoveCurrentMatch);
if (!getViewer().getSelection().isEmpty())
mgr.appendToGroup(IContextMenuConstants.GROUP_REMOVE_MATCHES, fRemoveSelectedMatches);
mgr.appendToGroup(IContextMenuConstants.GROUP_REMOVE_MATCHES, fRemoveAllResultsAction);
}
/**
* {@inheritDoc}
*/
public void createControl(Composite parent) {
fQueryListener = createQueryListener();
fMenu = new MenuManager("#PopUp"); //$NON-NLS-1$
fMenu.setRemoveAllWhenShown(true);
fMenu.setParent(getSite().getActionBars().getMenuManager());
fMenu.addMenuListener(new IMenuListener() {
public void menuAboutToShow(IMenuManager mgr) {
createStandardGroups(mgr);
fillContextMenu(mgr);
fViewPart.fillContextMenu(mgr);
}
});
fPagebook = new PageBook(parent, SWT.NULL);
fPagebook.setLayoutData(new GridData(GridData.FILL_BOTH));
fBusyLabel = createBusyControl();
fViewerContainer = new Composite(fPagebook, SWT.NULL);
fViewerContainer.setLayoutData(new GridData(GridData.FILL_BOTH));
fViewerContainer.setSize(100, 100);
fViewerContainer.setLayout(new FillLayout());
createViewer(fViewerContainer, fCurrentLayout);
showBusyLabel(fIsBusyShown);
NewSearchUI.addQueryListener(fQueryListener);
}
private Control createBusyControl() {
Table busyLabel = new Table(fPagebook, SWT.NONE);
TableItem item = new TableItem(busyLabel, SWT.NONE);
item.setText(SearchMessages.getString("AbstractTextSearchViewPage.searching.label")); //$NON-NLS-1$
busyLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
return busyLabel;
}
private synchronized void scheduleUIUpdate() {
if (!fIsUIUpdateScheduled) {
fIsUIUpdateScheduled= true;
new UpdateUIJob().schedule();
}
}
private IQueryListener createQueryListener() {
return new IQueryListener() {
public void queryAdded(ISearchQuery query) {
// ignore
}
public void queryRemoved(ISearchQuery query) {
// ignore
}
public void queryStarting(final ISearchQuery query) {
final Runnable runnable1 = new Runnable() {
public void run() {
updateBusyLabel();
AbstractTextSearchResult result = getInput();
if (result == null || !result.getQuery().equals(query)) {
return;
}
turnOffDecoration();
scheduleUIUpdate();
}
};
asyncExec(runnable1);
}
public void queryFinished(final ISearchQuery query) {
final Runnable runnable2 = new Runnable() {
public void run() {
updateBusyLabel();
AbstractTextSearchResult result = getInput();
if (result == null || !result.getQuery().equals(query)) {
return;
}
if (fViewer.getSelection().isEmpty()) {
navigateNext(true);
}
}
};
asyncExec(runnable2);
}
};
}
private void updateBusyLabel() {
AbstractTextSearchResult result = getInput();
boolean shouldShowBusy = result != null && NewSearchUI.isQueryRunning(result.getQuery()) && result.getMatchCount() == 0;
if (shouldShowBusy == fIsBusyShown)
return;
fIsBusyShown = shouldShowBusy;
showBusyLabel(fIsBusyShown);
}
private void showBusyLabel(boolean shouldShowBusy) {
if (shouldShowBusy)
fPagebook.showPage(fBusyLabel);
else
fPagebook.showPage(fViewerContainer);
}
/**
* Determines whether a certain layout is supported by this search result
* page.
*
* @param layout the layout to test for
* @return whether the given layout is supported or not
*
* @see AbstractTextSearchViewPage#AbstractTextSearchViewPage(int)
*/
public boolean isLayoutSupported(int layout) {
return (layout & fSupportedLayouts) == layout;
}
/**
* Sets the layout of this search result page. The layout must be on of
* <code>FLAG_LAYOUT_FLAT</code> or <code>FLAG_LAYOUT_TREE</code> and
* it must be one of the values passed during construction of this search
* result page.
*
* @see AbstractTextSearchViewPage#isLayoutSupported(int)
*/
public void setLayout(int layout) {
Assert.isTrue(countBits(layout) == 1);
Assert.isTrue(isLayoutSupported(layout));
if (countBits(fSupportedLayouts) < 2)
return;
if (fCurrentLayout == layout)
return;
fCurrentLayout = layout;
ISelection selection = fViewer.getSelection();
ISearchResult result = disconnectViewer();
fViewer.getControl().dispose();
fViewer = null;
createViewer(fViewerContainer, layout);
fViewerContainer.layout(true);
connectViewer(result);
fViewer.setSelection(selection, true);
getSettings().put(KEY_LAYOUT, layout);
}
private void updateLayoutActions() {
if (fFlatAction != null)
fFlatAction.setChecked(fCurrentLayout == fFlatAction.getLayout());
if (fHierarchicalAction != null)
fHierarchicalAction.setChecked(fCurrentLayout == fHierarchicalAction.getLayout());
}
/**
* Return the layout this page is currently using.
*
* @return the layout this page is currently using
*
* @see #FLAG_LAYOUT_FLAT
* @see #FLAG_LAYOUT_TREE
*/
public int getLayout() {
return fCurrentLayout;
}
private void createViewer(Composite parent, int layout) {
if ((layout & FLAG_LAYOUT_FLAT) != 0) {
TableViewer viewer = createTableViewer(parent);
fViewer = viewer;
configureTableViewer(viewer);
} else if ((layout & FLAG_LAYOUT_TREE) != 0) {
TreeViewer viewer = createTreeViewer(parent);
fViewer = viewer;
configureTreeViewer(viewer);
}
IToolBarManager tbm = getSite().getActionBars().getToolBarManager();
tbm.removeAll();
createStandardGroups(tbm);
fillToolbar(tbm);
tbm.update(false);
fViewer.addOpenListener(new IOpenListener() {
public void open(OpenEvent event) {
boolean hasCurrentMatch = showCurrentMatch(true);
if (event.getViewer() instanceof TreeViewer && event.getSelection() instanceof IStructuredSelection) {
TreeViewer tv = (TreeViewer) event.getViewer();
Object element = ((IStructuredSelection) event.getSelection()).getFirstElement();
if (element != null) {
tv.setExpandedState(element, !tv.getExpandedState(element));
if (!hasCurrentMatch && getDisplayedMatchCount(element) > 0)
gotoNextMatch(true);
}
return;
} else if (!hasCurrentMatch) {
gotoNextMatch(true);
}
}
});
fViewer.addSelectionChangedListener(new ISelectionChangedListener() {
public void selectionChanged(SelectionChangedEvent event) {
fCurrentMatchIndex = -1;
fRemoveSelectedMatches.setEnabled(!event.getSelection().isEmpty());
}
});
Menu menu = fMenu.createContextMenu(fViewer.getControl());
fViewer.getControl().setMenu(menu);
getSite().setSelectionProvider(fViewer);
// Register menu
getSite().registerContextMenu(fViewPart.getViewSite().getId(), fMenu, fViewer);
updateLayoutActions();
}
/**
* Creates the tree viewer to be shown on this page. Clients may override
* this method.
*
* @param parent the parent widget
* @return returns a newly created <code>TreeViewer</code>.
*/
protected TreeViewer createTreeViewer(Composite parent) {
return new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
}
/**
* Creates the table viewer to be shown on this page. Clients may override
* this method.
*
* @param parent the parent widget
* @return returns a newly created <code>TableViewer</code>
*/
protected TableViewer createTableViewer(Composite parent) {
return new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION) {
protected void handleLabelProviderChanged(LabelProviderChangedEvent event) {
getTable().setRedraw(false);
try {
super.handleLabelProviderChanged(event);
} finally {
getTable().setRedraw(true);
}
}
};
}
/**
* {@inheritDoc}
*/
public void setFocus() {
Control control = fViewer.getControl();
if (control != null && !control.isDisposed())
control.setFocus();
}
/**
* {@inheritDoc}
*/
public Control getControl() {
return fPagebook;
}
/**
* {@inheritDoc}
*/
public void setInput(ISearchResult search, Object viewState) {
ISearchResult oldSearch = disconnectViewer();
if (oldSearch != null)
oldSearch.removeListener(fListener);
AnnotationManager.searchResultActivated(getSite().getWorkbenchWindow(), (AbstractTextSearchResult) search);
fInput= search;
if (search != null) {
search.addListener(fListener);
connectViewer(search);
if (viewState instanceof ISelection)
fViewer.setSelection((ISelection) viewState, true);
else
gotoNextMatch();
}
updateBusyLabel();
turnOffDecoration();
scheduleUIUpdate();
}
/**
* {@inheritDoc}
*/
public Object getUIState() {
return fViewer.getSelection();
}
private void connectViewer(ISearchResult search) {
fCopyToClipboardAction = new CopyToClipboardAction(fViewer);
fViewer.setInput(search);
}
private ISearchResult disconnectViewer() {
ISearchResult result = (ISearchResult) fViewer.getInput();
fViewer.setInput(null);
return result;
}
/**
* Returns the viewer currently used in this page.
*
* @return the currently used viewer or <code>null</code> if none has been
* created yet.
*/
protected StructuredViewer getViewer() {
return fViewer;
}
private void showMatch(final Match match, final boolean activateEditor) {
ISafeRunnable runnable = new ISafeRunnable() {
public void handleException(Throwable exception) {
if (exception instanceof PartInitException) {
PartInitException pie = (PartInitException) exception;
ErrorDialog.openError(getSite().getShell(), SearchMessages.getString("DefaultSearchViewPage.show_match"), SearchMessages.getString("DefaultSearchViewPage.error.no_editor"), pie.getStatus()); //$NON-NLS-1$ //$NON-NLS-2$
}
}
public void run() throws Exception {
Position currentPosition = InternalSearchUI.getInstance().getPositionTracker().getCurrentPosition(match);
if (currentPosition != null) {
showMatch(match, currentPosition.getOffset(), currentPosition.getLength(), activateEditor);
} else {
showMatch(match, match.getOffset(), match.getLength(), activateEditor);
}
}
};
Platform.run(runnable);
}
/**
* Returns the currently shown result.
*
* @return the previously set result or <code>null</code>
*
* @see AbstractTextSearchViewPage#setInput(ISearchResult, Object)
*/
public AbstractTextSearchResult getInput() {
return (AbstractTextSearchResult) fInput;
}
/**
* Selects the element corresponding to the next match and shows the match
* in an editor. Note that this will cycle back to the first match after the
* last match.
*/
public void gotoNextMatch() {
gotoNextMatch(false);
}
private void gotoNextMatch(boolean activateEditor) {
fCurrentMatchIndex++;
Match nextMatch = getCurrentMatch();
if (nextMatch == null) {
navigateNext(true);
fCurrentMatchIndex = 0;
}
showCurrentMatch(activateEditor);
}
/**
* Selects the element corresponding to the previous match and shows the
* match in an editor. Note that this will cycle back to the last match
* after the first match.
*/
public void gotoPreviousMatch() {
gotoPreviousMatch(false);
}
private void gotoPreviousMatch(boolean activateEditor) {
fCurrentMatchIndex--;
Match nextMatch = getCurrentMatch();
if (nextMatch == null) {
navigateNext(false);
fCurrentMatchIndex = getInput().getMatchCount(getFirstSelectedElement()) - 1;
}
showCurrentMatch(activateEditor);
}
private void navigateNext(boolean forward) {
INavigate navigator = null;
if (fViewer instanceof TableViewer) {
navigator = new TableViewerNavigator((TableViewer) fViewer);
} else {
navigator = new TreeViewerNavigator(this, (TreeViewer) fViewer);
}
navigator.navigateNext(forward);
}
private boolean showCurrentMatch(boolean activateEditor) {
Match currentMatch = getCurrentMatch();
if (currentMatch != null) {
showMatch(currentMatch, activateEditor);
return true;
} else {
return false;
}
}
/**
* Returns the currently selected match.
*
* @return the selected match or <code>null</code> if none are selected
*/
public Match getCurrentMatch() {
Object element = getFirstSelectedElement();
if (element != null) {
Match[] matches = getDisplayedMatches(element);
if (fCurrentMatchIndex >= 0 && fCurrentMatchIndex < matches.length)
return matches[fCurrentMatchIndex];
}
return null;
}
/**
* Returns the matches that are currently displayed for the given element.
* While the default implementation just forwards to the current input
* search result of the page, subclasses may override this method to do
* filtering, etc. Any action operating on the visible matches in the search
* result page should use this method to get the matches for a search
* result (instead of asking the search result directly).
*
* @param element
* The element to get the matches for
* @return The matches displayed for the given element. If the current input
* of this page is <code>null</code>, an empty array is returned
* @see AbstractTextSearchResult#getMatches(Object)
*/
public Match[] getDisplayedMatches(Object element) {
AbstractTextSearchResult result= getInput();
if (result == null)
return EMPTY_MATCH_ARRAY;
return result.getMatches(element);
}
/**
* Returns the number of matches that are currently displayed for the given
* element. While the default implementation just forwards to the current
* input search result of the page, subclasses may override this method to
* do filtering, etc. Any action operating on the visible matches in the
* search result page should use this method to get the match count for a
* search result (instead of asking the search result directly).
*
* @param element
* The element to get the matches for
* @return The number of matches displayed for the given element. If the
* current input of this page is <code>null</code>, 0 is
* returned
* @see AbstractTextSearchResult#getMatchCount(Object)
*/
public int getDisplayedMatchCount(Object element) {
AbstractTextSearchResult result= getInput();
if (result == null)
return 0;
return result.getMatchCount(element);
}
private Object getFirstSelectedElement() {
IStructuredSelection selection = (IStructuredSelection) fViewer.getSelection();
if (selection.size() > 0)
return selection.getFirstElement();
return null;
}
/**
* {@inheritDoc}
*/
public void dispose() {
//disconnectViewer();
super.dispose();
NewSearchUI.removeQueryListener(fQueryListener);
}
/**
* {@inheritDoc}
*/
public void init(IPageSite pageSite) {
super.init(pageSite);
addLayoutActions(pageSite.getActionBars().getMenuManager());
initActionDefinitionIDs(pageSite.getWorkbenchWindow());
}
private void initActionDefinitionIDs(IWorkbenchWindow window) {
fRemoveSelectedMatches.setActionDefinitionId(getActionDefinitionId(window, ActionFactory.DELETE));
fShowNextAction.setActionDefinitionId(getActionDefinitionId(window, ActionFactory.NEXT));
fShowPreviousAction.setActionDefinitionId(getActionDefinitionId(window, ActionFactory.PREVIOUS));
}
private String getActionDefinitionId(IWorkbenchWindow window, ActionFactory factory) {
IWorkbenchAction action= factory.create(window);
String id= action.getActionDefinitionId();
action.dispose();
return id;
}
/**
* Fills the toolbar contribution for this page. Subclasses may override
* this method.
*
* @param tbm the tool bar manager representing the view's toolbar
*/
protected void fillToolbar(IToolBarManager tbm) {
tbm.appendToGroup(IContextMenuConstants.GROUP_SHOW, fShowNextAction); //$NON-NLS-1$
tbm.appendToGroup(IContextMenuConstants.GROUP_SHOW, fShowPreviousAction); //$NON-NLS-1$
tbm.appendToGroup(IContextMenuConstants.GROUP_REMOVE_MATCHES, fRemoveSelectedMatches); //$NON-NLS-1$
tbm.appendToGroup(IContextMenuConstants.GROUP_REMOVE_MATCHES, fRemoveAllResultsAction); //$NON-NLS-1$
IActionBars actionBars = getSite().getActionBars();
getSite().getWorkbenchWindow();
if (actionBars != null) {
actionBars.setGlobalActionHandler(ActionFactory.NEXT.getId(), fShowNextAction);
actionBars.setGlobalActionHandler(ActionFactory.PREVIOUS.getId(), fShowPreviousAction);
actionBars.setGlobalActionHandler(ActionFactory.DELETE.getId(), fRemoveSelectedMatches);
}
if (getLayout() == FLAG_LAYOUT_TREE) {
addTreeActions(tbm);
}
}
private void addTreeActions(IToolBarManager tbm) {
// create new actions, new viewer created
tbm.appendToGroup(IContextMenuConstants.GROUP_VIEWER_SETUP, new ExpandAllAction((TreeViewer)getViewer()));
tbm.appendToGroup(IContextMenuConstants.GROUP_VIEWER_SETUP, new CollapseAllAction((TreeViewer)getViewer()));
}
private void addLayoutActions(IMenuManager menuManager) {
if (fFlatAction != null)
menuManager.add(fFlatAction);
if (fHierarchicalAction != null)
menuManager.add(fHierarchicalAction);
}
/**
* { @inheritDoc }
*/
public void setViewPart(ISearchResultViewPart part) {
fViewPart = part;
}
/**
* Returns the view part set with
* <code>setViewPart(ISearchResultViewPart)</code>.
*
* @return The view part or <code>null</code> if the view part hasn't been
* set yet (or set to null).
*/
protected ISearchResultViewPart getViewPart() {
return fViewPart;
}
// multithreaded update handling.
private synchronized void handleSearchResultsChanged(final SearchResultEvent e) {
if (e instanceof MatchEvent) {
MatchEvent me = (MatchEvent) e;
postUpdate(me.getMatches());
} else if (e instanceof RemoveAllEvent) {
postClear();
}
}
private synchronized void postUpdate(Match[] matches) {
for (int i = 0; i < matches.length; i++) {
fBatchedUpdates.add(matches[i].getElement());
}
scheduleUIUpdate();
}
private synchronized void runBatchedUpdates() {
if (false /*fBatchedUpdates.size() > 50*/) {
Object[] hundredUpdates= new Object[50];
Iterator elements= fBatchedUpdates.iterator();
for (int i= 0; i < hundredUpdates.length; i++) {
hundredUpdates[i]= elements.next();
elements.remove();
}
elementsChanged(hundredUpdates);
} else {
elementsChanged(fBatchedUpdates.toArray());
fBatchedUpdates.clear();
}
updateBusyLabel();
}
private void postClear() {
asyncExec(new Runnable() {
public void run() {
runClear();
}
});
}
private synchronized boolean hasMoreUpdates() {
return fBatchedUpdates.size() > 0;
}
private boolean isQueryRunning() {
AbstractTextSearchResult result= getInput();
if (result != null) {
return NewSearchUI.isQueryRunning(result.getQuery());
}
return false;
}
private void runClear() {
synchronized (this) {
fBatchedUpdates.clear();
updateBusyLabel();
}
clear();
}
private void asyncExec(Runnable runnable) {
Control control = getControl();
if (control != null && !control.isDisposed()) {
Display currentDisplay = Display.getCurrent();
if (currentDisplay == null || !currentDisplay.equals(control.getDisplay()))
// meaning we're not executing on the display thread of the
// control
control.getDisplay().asyncExec(runnable);
else
runnable.run();
}
}
/**
* {@inheritDoc}Subclasses may exend this method.
*/
public void restoreState(IMemento memento) {
if (countBits(fSupportedLayouts) > 1) {
try {
fCurrentLayout = getSettings().getInt(KEY_LAYOUT);
// workaround because the saved value may be 0
if (fCurrentLayout == 0)
initLayout();
} catch (NumberFormatException e) {
// ignore, signals no value stored.
}
if (memento != null) {
Integer layout = memento.getInteger(KEY_LAYOUT);
if (layout != null) {
fCurrentLayout = layout.intValue();
// workaround because the saved value may be 0
if (fCurrentLayout == 0)
initLayout();
}
}
}
}
/**
* { @inheritDoc }
* Subclasses my extend this method.
*/
public void saveState(IMemento memento) {
if (countBits(fSupportedLayouts) > 1) {
memento.putInteger(KEY_LAYOUT, fCurrentLayout);
}
}
/**
* Note: this is internal API and should not be called from clients outside
* of the search plug-in.
* <p>
* Removes the currently selected match. Does nothing if no match is
* selected.
* </p>
*/
public void internalRemoveSelected() {
AbstractTextSearchResult result = getInput();
if (result == null)
return;
StructuredViewer viewer = getViewer();
IStructuredSelection selection = (IStructuredSelection) viewer.getSelection();
HashSet set = new HashSet();
if (viewer instanceof TreeViewer) {
ITreeContentProvider cp = (ITreeContentProvider) viewer.getContentProvider();
collectAllMatchesBelow(result, set, cp, selection.toArray());
} else {
collectAllMatches(set, selection.toArray());
}
Match[] matches = new Match[set.size()];
set.toArray(matches);
result.removeMatches(matches);
}
private void collectAllMatches(HashSet set, Object[] elements) {
for (int j = 0; j < elements.length; j++) {
Match[] matches = getDisplayedMatches(elements[j]);
for (int i = 0; i < matches.length; i++) {
set.add(matches[i]);
}
}
}
private void collectAllMatchesBelow(AbstractTextSearchResult result, Set set, ITreeContentProvider cp, Object[] elements) {
for (int j = 0; j < elements.length; j++) {
Match[] matches = getDisplayedMatches(elements[j]);
for (int i = 0; i < matches.length; i++) {
set.add(matches[i]);
}
Object[] children = cp.getChildren(elements[j]);
collectAllMatchesBelow(result, set, cp, children);
}
}
private void turnOffDecoration() {
IBaseLabelProvider lp= fViewer.getLabelProvider();
if (lp instanceof DecoratingLabelProvider) {
((DecoratingLabelProvider)lp).setLabelDecorator(null);
}
}
private void turnOnDecoration() {
IBaseLabelProvider lp= fViewer.getLabelProvider();
if (lp instanceof DecoratingLabelProvider) {
((DecoratingLabelProvider)lp).setLabelDecorator(PlatformUI.getWorkbench().getDecoratorManager().getLabelDecorator());
}
}
}