Java tutorial
/******************************************************************************* * Copyright (c) 2000, 2008 IBM Corporation, Nokia 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 *******************************************************************************/ package com.nokia.carbide.search.system.ui.text; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.ISafeRunnable; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.SafeRunner; import org.eclipse.core.runtime.Status; 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.jface.action.Action; import org.eclipse.jface.action.IAction; 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.dialogs.ErrorDialog; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.util.OpenStrategy; 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.ISelectionProvider; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ITreeContentProvider; 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.jface.viewers.Viewer; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.Region; import org.eclipse.ui.IActionBars; import org.eclipse.ui.IMemento; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.actions.ActionFactory; import org.eclipse.ui.part.IPageSite; import org.eclipse.ui.part.Page; import org.eclipse.ui.part.PageBook; import org.eclipse.ui.progress.UIJob; import org.eclipse.ui.texteditor.IUpdate; import org.eclipse.ui.texteditor.IWorkbenchActionDefinitionIds; import com.nokia.carbide.search.system.internal.ui.CopyToClipboardAction; import com.nokia.carbide.search.system.internal.ui.SearchPlugin; import com.nokia.carbide.search.system.internal.ui.SearchPluginImages; import com.nokia.carbide.search.system.internal.ui.SelectAllAction; import com.nokia.carbide.search.system.ui.IContextMenuConstants; import com.nokia.carbide.search.system.ui.IQueryListener; import com.nokia.carbide.search.system.ui.ISearchQuery; import com.nokia.carbide.search.system.ui.ISearchResult; import com.nokia.carbide.search.system.ui.ISearchResultListener; import com.nokia.carbide.search.system.ui.ISearchResultPage; import com.nokia.carbide.search.system.ui.ISearchResultViewPart; import com.nokia.carbide.search.system.ui.NewSearchUI; import com.nokia.carbide.search.system.ui.SearchResultEvent; import com.nokia.carbide.search.system2.internal.ui.InternalSearchUI; import com.nokia.carbide.search.system2.internal.ui.MatchFilterAction; import com.nokia.carbide.search.system2.internal.ui.MatchFilterSelectionAction; import com.nokia.carbide.search.system2.internal.ui.SearchMessages; import com.nokia.carbide.search.system2.internal.ui.SearchView; import com.nokia.carbide.search.system2.internal.ui.basic.views.CollapseAllAction; import com.nokia.carbide.search.system2.internal.ui.basic.views.ExpandAllAction; import com.nokia.carbide.search.system2.internal.ui.basic.views.INavigate; import com.nokia.carbide.search.system2.internal.ui.basic.views.RemoveAllMatchesAction; import com.nokia.carbide.search.system2.internal.ui.basic.views.RemoveMatchAction; import com.nokia.carbide.search.system2.internal.ui.basic.views.RemoveSelectedMatchesAction; import com.nokia.carbide.search.system2.internal.ui.basic.views.SetLayoutAction; import com.nokia.carbide.search.system2.internal.ui.basic.views.ShowNextResultAction; import com.nokia.carbide.search.system2.internal.ui.basic.views.ShowPreviousResultAction; import com.nokia.carbide.search.system2.internal.ui.basic.views.TableViewerNavigator; import com.nokia.carbide.search.system2.internal.ui.basic.views.TreeViewerNavigator; import com.nokia.carbide.search.system2.internal.ui.text.AnnotationManagers; import com.nokia.carbide.search.system2.internal.ui.text.PositionTracker; /** * 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.AbstractTextSearchViewPage_update_job_name); setSystem(true); } public IStatus runInUIThread(IProgressMonitor monitor) { Control control = getControl(); if (control == null || control.isDisposed()) { // disposed the control while the UI was posted. return Status.OK_STATUS; } runBatchedClear(); runBatchedUpdates(); if (hasMoreUpdates() || isQueryRunning()) { schedule(500); } else { fIsUIUpdateScheduled = false; turnOnDecoration(); updateBusyLabel(); if (fScheduleEnsureSelection) { fScheduleEnsureSelection = false; AbstractTextSearchResult result = getInput(); if (result != null && fViewer.getSelection().isEmpty()) { navigateNext(true); } } } fViewPart.updateLabel(); return Status.OK_STATUS; } /* * Undocumented for testing only. Used to find UpdateUIJobs. */ public boolean belongsTo(Object family) { return family == AbstractTextSearchViewPage.this; } } private class SelectionProviderAdapter implements ISelectionProvider, ISelectionChangedListener { private ArrayList fListeners = new ArrayList(5); public void addSelectionChangedListener(ISelectionChangedListener listener) { fListeners.add(listener); } public ISelection getSelection() { return fViewer.getSelection(); } public void removeSelectionChangedListener(ISelectionChangedListener listener) { fListeners.remove(listener); } public void setSelection(ISelection selection) { fViewer.setSelection(selection); } public void selectionChanged(SelectionChangedEvent event) { // forward to my listeners SelectionChangedEvent wrappedEvent = new SelectionChangedEvent(this, event.getSelection()); for (Iterator listeners = fListeners.iterator(); listeners.hasNext();) { ISelectionChangedListener listener = (ISelectionChangedListener) listeners.next(); listener.selectionChanged(wrappedEvent); } } } private volatile boolean fIsUIUpdateScheduled = false; private volatile boolean fScheduleEnsureSelection = false; private static final String KEY_LAYOUT = "com.nokia.carbide.search.system.resultpage.layout"; //$NON-NLS-1$ /** * An empty array. */ 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 boolean fBatchedClearAll; private ISearchResultListener fListener; private IQueryListener fQueryListener; private MenuManager fMenu; private AbstractTextSearchResult fInput; // Actions private CopyToClipboardAction fCopyToClipboardAction; private Action fRemoveSelectedMatches; private Action fRemoveCurrentMatch; private Action fRemoveAllResultsAction; private Action fShowNextAction; private Action fShowPreviousAction; private ExpandAllAction fExpandAllAction; private CollapseAllAction fCollapseAllAction; private SetLayoutAction fFlatAction; private SetLayoutAction fHierarchicalAction; private int fCurrentLayout; private int fCurrentMatchIndex = 0; private String fId; private final int fSupportedLayouts; private SelectionProviderAdapter fViewerAdapter; private SelectAllAction fSelectAllAction; private IAction[] fFilterActions; private Integer fElementLimit; /** * Flag (<code>value 1</code>) denoting flat list layout. */ public static final int FLAG_LAYOUT_FLAT = 1; /** * Flag (<code>value 2</code>) denoting tree 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); fCopyToClipboardAction = new CopyToClipboardAction(); if ((supportedLayouts & FLAG_LAYOUT_TREE) != 0) { fExpandAllAction = new ExpandAllAction(); fCollapseAllAction = new CollapseAllAction(); } fSelectAllAction = new SelectAllAction(); createLayoutActions(); fBatchedUpdates = new HashSet(); fBatchedClearAll = false; fListener = new ISearchResultListener() { public void searchResultChanged(SearchResultEvent e) { handleSearchResultChanged(e); } }; fFilterActions = null; fElementLimit = null; } private void initLayout() { if (supportsTreeLayout()) fCurrentLayout = FLAG_LAYOUT_TREE; else fCurrentLayout = FLAG_LAYOUT_FLAT; } /** * Constructs this page with the default layout flags. * * @see AbstractTextSearchViewPage#AbstractTextSearchViewPage(int) */ protected AbstractTextSearchViewPage() { this(FLAG_LAYOUT_FLAT); } private void createLayoutActions() { if (countBits(fSupportedLayouts) > 1) { fFlatAction = new SetLayoutAction(this, SearchMessages.AbstractTextSearchViewPage_flat_layout_label, SearchMessages.AbstractTextSearchViewPage_flat_layout_tooltip, FLAG_LAYOUT_FLAT); fHierarchicalAction = new SetLayoutAction(this, SearchMessages.AbstractTextSearchViewPage_hierarchical_layout_label, SearchMessages.AbstractTextSearchViewPage_hierarchical_layout_tooltip, FLAG_LAYOUT_TREE); 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 supportsTreeLayout() { return isLayoutSupported(FLAG_LAYOUT_TREE); } /** * 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 * should 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. * <p>The changed elements are evaluated by {@link #evaluateChangedElements(Match[], Set)}.</p> * * @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); /** * 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_SHOW, fShowNextAction); mgr.appendToGroup(IContextMenuConstants.GROUP_SHOW, fShowPreviousAction); mgr.appendToGroup(IContextMenuConstants.GROUP_EDIT, fCopyToClipboardAction); if (getCurrentMatch() != null) mgr.appendToGroup(IContextMenuConstants.GROUP_REMOVE_MATCHES, fRemoveCurrentMatch); if (canRemoveMatchesWith(getViewer().getSelection())) mgr.appendToGroup(IContextMenuConstants.GROUP_REMOVE_MATCHES, fRemoveSelectedMatches); mgr.appendToGroup(IContextMenuConstants.GROUP_REMOVE_MATCHES, fRemoveAllResultsAction); if (getLayout() == FLAG_LAYOUT_TREE) { mgr.appendToGroup(IContextMenuConstants.GROUP_SHOW, fExpandAllAction); } } /** * Determines whether the provided selection can be used to remove matches from the result. * @param selection the selection to test * @return returns <code>true</code> if the elements in the current selection can be removed. * @since 3.2 */ protected boolean canRemoveMatchesWith(ISelection selection) { return !selection.isEmpty(); } /** * {@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) { SearchView.createContextMenuGroups(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()); fViewerAdapter = new SelectionProviderAdapter(); getSite().setSelectionProvider(fViewerAdapter); // Register menu getSite().registerContextMenu(fViewPart.getViewSite().getId(), fMenu, fViewerAdapter); 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.AbstractTextSearchViewPage_searching_label); 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) { // handle the end of the query in the UIUpdateJob, as ui updates // may not be finished here. postEnsureSelection(); } }; } /** * Posts a UI update to make sure an element is selected. * @since 3.2 */ protected void postEnsureSelection() { fScheduleEnsureSelection = true; scheduleUIUpdate(); } 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. * @param layout the new layout * * @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(); disconnectViewer(); disposeViewer(); createViewer(fViewerContainer, layout); fViewerContainer.layout(true); connectViewer(fInput); fViewer.setSelection(selection, true); getSettings().put(KEY_LAYOUT, layout); getViewPart().updateLabel(); } private void disposeViewer() { fViewer.removeSelectionChangedListener(fViewerAdapter); fViewer.getControl().dispose(); fViewer = null; } 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); fCollapseAllAction.setViewer(viewer); fExpandAllAction.setViewer(viewer); } fCopyToClipboardAction.setViewer(fViewer); fSelectAllAction.setViewer(fViewer); IToolBarManager tbm = getSite().getActionBars().getToolBarManager(); tbm.removeAll(); SearchView.createToolBarGroups(tbm); fillToolbar(tbm); tbm.update(false); fViewer.addOpenListener(new IOpenListener() { public void open(OpenEvent event) { handleOpen(event); } }); fViewer.addSelectionChangedListener(new ISelectionChangedListener() { public void selectionChanged(SelectionChangedEvent event) { fCurrentMatchIndex = -1; fRemoveSelectedMatches.setEnabled(canRemoveMatchesWith(event.getSelection())); } }); fViewer.addSelectionChangedListener(fViewerAdapter); Menu menu = fMenu.createContextMenu(fViewer.getControl()); fViewer.getControl().setMenu(menu); updateLayoutActions(); getViewPart().updateLabel(); } /** * 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); } /** * {@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 newSearch, Object viewState) { if (newSearch != null && !(newSearch instanceof AbstractTextSearchResult)) return; // ignore AbstractTextSearchResult oldSearch = fInput; if (oldSearch != null) { disconnectViewer(); removeFilterActionsFromViewMenu(fFilterActions); oldSearch.removeListener(fListener); AnnotationManagers.removeSearchResult(getSite().getWorkbenchWindow(), oldSearch); } fInput = (AbstractTextSearchResult) newSearch; if (fInput != null) { AnnotationManagers.addSearchResult(getSite().getWorkbenchWindow(), fInput); fInput.addListener(fListener); connectViewer(fInput); if (viewState instanceof ISelection) fViewer.setSelection((ISelection) viewState, true); else navigateNext(true); updateBusyLabel(); turnOffDecoration(); scheduleUIUpdate(); fFilterActions = addFilterActionsToViewMenu(); } else { getViewPart().updateLabel(); } } private void removeFilterActionsFromViewMenu(IAction[] filterActions) { IActionBars bars = getSite().getActionBars(); IMenuManager menu = bars.getMenuManager(); if (filterActions != null) { for (int i = 0; i < filterActions.length; i++) { menu.remove(filterActions[i].getId()); } } menu.remove(MatchFilterSelectionAction.ACTION_ID); } private IAction[] addFilterActionsToViewMenu() { AbstractTextSearchResult input = getInput(); if (input == null) { return null; } MatchFilter[] allMatchFilters = input.getAllMatchFilters(); if (allMatchFilters == null && getElementLimit() == null) { return null; } IActionBars bars = getSite().getActionBars(); IMenuManager menu = bars.getMenuManager(); menu.prependToGroup(IContextMenuConstants.GROUP_FILTERING, new MatchFilterSelectionAction(this)); if (allMatchFilters != null) { MatchFilterAction[] actions = new MatchFilterAction[allMatchFilters.length]; for (int i = allMatchFilters.length - 1; i >= 0; i--) { MatchFilterAction filterAction = new MatchFilterAction(this, allMatchFilters[i]); actions[i] = filterAction; menu.prependToGroup(IContextMenuConstants.GROUP_FILTERING, filterAction); } return actions; } return null; } private void updateFilterActions(IAction[] filterActions) { if (filterActions != null) { for (int i = 0; i < filterActions.length; i++) { IAction curr = filterActions[i]; if (curr instanceof IUpdate) { ((IUpdate) curr).update(); } } } } /** * {@inheritDoc} */ public Object getUIState() { return fViewer.getSelection(); } private void connectViewer(AbstractTextSearchResult search) { fViewer.setInput(search); } private void disconnectViewer() { fViewer.setInput(null); } /** * 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.DefaultSearchViewPage_show_match, SearchMessages.DefaultSearchViewPage_error_no_editor, pie.getStatus()); } } public void run() throws Exception { IRegion location = getCurrentMatchLocation(match); showMatch(match, location.getOffset(), location.getLength(), activateEditor); } }; SafeRunner.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 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 = getDisplayedMatchCount(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; } 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. * If {@link AbstractTextSearchResult#getActiveMatchFilters()} is not null, only matches are returned * that are not filtered by the match filters. If {@link AbstractTextSearchResult#getActiveMatchFilters()} is * null all matches of the given element are returned. * 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; Match[] matches = result.getMatches(element); if (result.getActiveMatchFilters() == null) // default behaviour: filter state not used, all matches shown return matches; int count = 0; for (int i = 0; i < matches.length; i++) { if (matches[i].isFiltered()) matches[i] = null; else count++; } if (count == matches.length) return matches; Match[] filteredMatches = new Match[count]; for (int i = 0, k = 0; i < matches.length; i++) { if (matches[i] != null) filteredMatches[k++] = matches[i]; } return filteredMatches; } /** * Returns the current location of the match. This takes possible * modifications of the file into account. Therefore the result may * differ from the position information that can be obtained directly * off the match. * @param match the match to get the position for. * @return the current position of the match. * * @since 3.2 */ public IRegion getCurrentMatchLocation(Match match) { PositionTracker tracker = InternalSearchUI.getInstance().getPositionTracker(); int offset, length; Position pos = tracker.getCurrentPosition(match); if (pos == null) { offset = match.getOffset(); length = match.getLength(); } else { offset = pos.getOffset(); length = pos.getLength(); } return new Region(offset, length); } /** * Returns the number of matches that are currently displayed for the given * element. If {@link AbstractTextSearchResult#getActiveMatchFilters()} is not null, only matches * are returned that are not filtered by the match filters. * 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; if (result.getActiveMatchFilters() == null) // default behaviour: filter state not used, all matches shown return result.getMatchCount(element); int count = 0; Match[] matches = result.getMatches(element); for (int i = 0; i < matches.length; i++) { if (!matches[i].isFiltered()) count++; } return count; } private Object getFirstSelectedElement() { IStructuredSelection selection = (IStructuredSelection) fViewer.getSelection(); if (selection.size() > 0) return selection.getFirstElement(); return null; } /** * {@inheritDoc} */ public void dispose() { AbstractTextSearchResult oldSearch = getInput(); if (oldSearch != null) AnnotationManagers.removeSearchResult(getSite().getWorkbenchWindow(), oldSearch); super.dispose(); NewSearchUI.removeQueryListener(fQueryListener); } /** * {@inheritDoc} */ public void init(IPageSite pageSite) { super.init(pageSite); IMenuManager menuManager = pageSite.getActionBars().getMenuManager(); addLayoutActions(menuManager); initActionDefinitionIDs(); menuManager.updateAll(true); pageSite.getActionBars().updateActionBars(); } private void initActionDefinitionIDs() { fCopyToClipboardAction.setActionDefinitionId(IWorkbenchActionDefinitionIds.COPY); fRemoveSelectedMatches.setActionDefinitionId(IWorkbenchActionDefinitionIds.DELETE); //using manifest strings to work around https://bugs.eclipse.org/bugs/show_bug.cgi?id=54581 : fShowNextAction.setActionDefinitionId("org.eclipse.ui.navigate.next"); //$NON-NLS-1$ fShowPreviousAction.setActionDefinitionId("org.eclipse.ui.navigate.previous"); //$NON-NLS-1$ fSelectAllAction.setActionDefinitionId(IWorkbenchActionDefinitionIds.SELECT_ALL); } /** * 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); tbm.appendToGroup(IContextMenuConstants.GROUP_SHOW, fShowPreviousAction); tbm.appendToGroup(IContextMenuConstants.GROUP_REMOVE_MATCHES, fRemoveSelectedMatches); tbm.appendToGroup(IContextMenuConstants.GROUP_REMOVE_MATCHES, fRemoveAllResultsAction); IActionBars actionBars = getSite().getActionBars(); if (actionBars != null) { actionBars.setGlobalActionHandler(ActionFactory.NEXT.getId(), fShowNextAction); actionBars.setGlobalActionHandler(ActionFactory.PREVIOUS.getId(), fShowPreviousAction); actionBars.setGlobalActionHandler(ActionFactory.DELETE.getId(), fRemoveSelectedMatches); actionBars.setGlobalActionHandler(ActionFactory.COPY.getId(), fCopyToClipboardAction); actionBars.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(), fSelectAllAction); } if (getLayout() == FLAG_LAYOUT_TREE) { tbm.appendToGroup(IContextMenuConstants.GROUP_VIEWER_SETUP, fExpandAllAction); tbm.appendToGroup(IContextMenuConstants.GROUP_VIEWER_SETUP, fCollapseAllAction); } } private void addLayoutActions(IMenuManager menuManager) { if (fFlatAction != null) menuManager.appendToGroup(IContextMenuConstants.GROUP_VIEWER_SETUP, fFlatAction); if (fHierarchicalAction != null) menuManager.appendToGroup(IContextMenuConstants.GROUP_VIEWER_SETUP, fHierarchicalAction); } /** * Sets the view part * @param part View part to set */ 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; } // multi-threaded update handling. /** * Handles a search result event for the current search result. * @param e the event to handle * * @since 3.2 */ protected void handleSearchResultChanged(final SearchResultEvent e) { if (e instanceof MatchEvent) { postUpdate(((MatchEvent) e).getMatches()); } else if (e instanceof RemoveAllEvent) { postClear(); } else if (e instanceof FilterUpdateEvent) { postUpdate(((FilterUpdateEvent) e).getUpdatedMatches()); updateFilterActions(fFilterActions); } } /** * Evaluates the elements to that are later passed to {@link #elementsChanged(Object[])}. By default * the element to change are the elements received by ({@link Match#getElement()}). Client implementations * can modify this behavior. * * @param matches the matches that were added or removed * @param changedElements the set that collects the elements to change. Clients should only add elements to the set. * @since 3.4 */ protected void evaluateChangedElements(Match[] matches, Set changedElements) { for (int i = 0; i < matches.length; i++) { changedElements.add(matches[i].getElement()); } } private synchronized void postUpdate(Match[] matches) { evaluateChangedElements(matches, fBatchedUpdates); 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 synchronized void postClear() { fBatchedClearAll = true; fBatchedUpdates.clear(); scheduleUIUpdate(); } private synchronized boolean hasMoreUpdates() { return fBatchedClearAll || fBatchedUpdates.size() > 0; } private boolean isQueryRunning() { AbstractTextSearchResult result = getInput(); if (result != null) { return NewSearchUI.isQueryRunning(result.getQuery()); } return false; } private void runBatchedClear() { synchronized (this) { if (!fBatchedClearAll) { return; } fBatchedClearAll = false; updateBusyLabel(); } getViewPart().updateLabel(); clear(); } private void asyncExec(final Runnable runnable) { final 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(new Runnable() { public void run() { if (!control.isDisposed()) runnable.run(); } }); else runnable.run(); } } /** * {@inheritDoc} * Subclasses may extend 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(); } } } } /* (non-Javadoc) * @see com.nokia.carbide.search.system.ui.ISearchResultPage#saveState(org.eclipse.ui.IMemento) */ 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> * * @noreference This method is not intended to be referenced by clients. */ 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()); } navigateNext(true); 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()); } } /** * <p>This method is called when the search page gets an open even from it's * underlying viewer (for example on double click). The default * implementation will open the first match on any element that has matches. * If the element to be opened is an inner node in the tree layout, the node * will be expanded if it's collapsed and vice versa. Subclasses are allowed * to override this method. * </p> * @param event * the event sent for the currently shown viewer * * @see IOpenListener */ protected void handleOpen(OpenEvent event) { Viewer viewer = event.getViewer(); boolean hasCurrentMatch = showCurrentMatch(OpenStrategy.activateOnOpen()); ISelection sel = event.getSelection(); if (viewer instanceof TreeViewer && sel instanceof IStructuredSelection) { IStructuredSelection selection = (IStructuredSelection) sel; TreeViewer tv = (TreeViewer) getViewer(); Object element = selection.getFirstElement(); if (element != null) { if (!hasCurrentMatch && getDisplayedMatchCount(element) > 0) gotoNextMatch(OpenStrategy.activateOnOpen()); else tv.setExpandedState(element, !tv.getExpandedState(element)); } return; } else if (!hasCurrentMatch) { gotoNextMatch(OpenStrategy.activateOnOpen()); } } /** * Sets the maximal number of top level elements to be shown in a viewer. * If <code>null</code> is set, the view page does not support to limit the elements and will not provide * UI to configure it. If a non-null value is set, configuration UI will be provided. The limit value must be a positive * number or <code>-1</code> to not limit top level element. * If enabled, the element limit has to be enforced by the content provider that is implemented by the client. The view * page just manages the value and configuration. * * @param limit the element limit. Valid values are: * <dl> * <li><code>null</code> to not limit and not provide configuration UI</li> * <li><code>-1</code> to not limit and provide configuration UI</li> * <li><code>positive integer</code> to limit by the given value and provide configuration UI</li> * </dl> * * @since 3.3 */ public void setElementLimit(Integer limit) { fElementLimit = limit; if (fViewer != null) { fViewer.refresh(); } if (fViewPart != null) { fViewPart.updateLabel(); } } /** * Gets the maximal number of top level elements to be shown in a viewer. * <code>null</code> means the view page does not limit the elements and will not provide * UI to configure it. If a non-null value is set, configuration UI will be provided. The limit value must be a positive * number or <code>-1</code> to not limit top level element. * * @return returns the element limit. Valid values are: * <dl> * <li><code>null</code> to not limit and not provide configuration UI (default value)</li> * <li><code>-1</code> to not limit and provide configuration UI</li> * <li><code>positive integer</code> to limit by the given value and provide configuration UI</li> * </dl> * * @since 3.3 */ public Integer getElementLimit() { return fElementLimit; } }