| /******************************************************************************* |
| * Copyright (c) 2000, 2018 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Robert Roth (robert.roth.off@gmail.com) - Bug 477471 |
| *******************************************************************************/ |
| package org.eclipse.search.ui.text; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.Set; |
| |
| import org.osgi.framework.FrameworkUtil; |
| |
| 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.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.core.resources.IFile; |
| |
| import org.eclipse.jface.action.Action; |
| import org.eclipse.jface.action.IAction; |
| 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.IEditorInput; |
| import org.eclipse.ui.IEditorPart; |
| import org.eclipse.ui.IMemento; |
| import org.eclipse.ui.IWorkbenchCommandConstants; |
| import org.eclipse.ui.IWorkbenchPage; |
| import org.eclipse.ui.OpenAndLinkWithEditorHelper; |
| 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.search.internal.ui.CopyToClipboardAction; |
| import org.eclipse.search.internal.ui.SearchPluginImages; |
| import org.eclipse.search.internal.ui.SelectAllAction; |
| import org.eclipse.search.internal.ui.text.EditorOpener; |
| 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.MatchFilterAction; |
| import org.eclipse.search2.internal.ui.MatchFilterSelectionAction; |
| import org.eclipse.search2.internal.ui.SearchMessages; |
| import org.eclipse.search2.internal.ui.SearchView; |
| 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.AnnotationManagers; |
| import org.eclipse.search2.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); |
| } |
| |
| @Override |
| 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. |
| */ |
| @Override |
| public boolean belongsTo(Object family) { |
| return family == AbstractTextSearchViewPage.this; |
| } |
| |
| } |
| |
| private class SelectionProviderAdapter implements ISelectionProvider, ISelectionChangedListener { |
| private ArrayList<ISelectionChangedListener> fListeners= new ArrayList<>(5); |
| |
| @Override |
| public void addSelectionChangedListener(ISelectionChangedListener listener) { |
| fListeners.add(listener); |
| } |
| |
| @Override |
| public ISelection getSelection() { |
| return fViewer.getSelection(); |
| } |
| |
| @Override |
| public void removeSelectionChangedListener(ISelectionChangedListener listener) { |
| fListeners.remove(listener); |
| } |
| |
| @Override |
| public void setSelection(ISelection selection) { |
| fViewer.setSelection(selection); |
| } |
| |
| @Override |
| public void selectionChanged(SelectionChangedEvent event) { |
| // forward to my listeners |
| SelectionChangedEvent wrappedEvent= new SelectionChangedEvent(this, event.getSelection()); |
| for (ISelectionChangedListener listener : fListeners) { |
| listener.selectionChanged(wrappedEvent); |
| } |
| } |
| |
| } |
| |
| private volatile boolean fIsUIUpdateScheduled= false; |
| private volatile boolean fScheduleEnsureSelection= false; |
| private static final String KEY_LAYOUT = "org.eclipse.search.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<Object> 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; |
| |
| /** |
| * The editor opener. |
| * @since 3.6 |
| */ |
| private EditorOpener fEditorOpener= new EditorOpener(); |
| |
| /** |
| * 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 = this::handleSearchResultChanged; |
| 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 | FLAG_LAYOUT_TREE); |
| } |
| |
| |
| 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 = PlatformUI |
| .getDialogSettingsProvider(FrameworkUtil.getBundle(AbstractTextSearchViewPage.class)) |
| .getDialogSettings(); |
| IDialogSettings settings = parent.getSection(getID()); |
| if (settings == null) |
| settings = parent.addNewSection(getID()); |
| return settings; |
| } |
| |
| @Override |
| public void setID(String id) { |
| fId = id; |
| } |
| |
| @Override |
| public String getID() { |
| return fId; |
| } |
| |
| @Override |
| 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 Use {@link #showMatch(Match, int, int, boolean)} instead |
| */ |
| @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); |
| } |
| |
| /** |
| * Opens an editor on the given file resource and tries to select the given offset and length. |
| * <p> |
| * If the page already has an editor open on the target object then that editor is brought to |
| * front; otherwise, a new editor is opened. If <code>activate == true</code> the editor will be |
| * activated. |
| * <p> |
| * |
| * @param page the workbench page in which the editor will be opened |
| * @param file the file to open |
| * @param offset the offset to select in the editor |
| * @param length the length to select in the editor |
| * @param activate if <code>true</code> the editor will be activated |
| * @return an open editor or <code>null</code> if an external editor was opened |
| * @throws PartInitException if the editor could not be initialized |
| * @see org.eclipse.ui.IWorkbenchPage#openEditor(IEditorInput, String, boolean) |
| * @since 3.6 |
| */ |
| protected final IEditorPart openAndSelect(IWorkbenchPage page, IFile file, int offset, int length, boolean activate) throws PartInitException { |
| return fEditorOpener.openAndSelect(page, file, offset, length, activate); |
| } |
| |
| /** |
| * Opens an editor on the given file resource. |
| * <p> |
| * If the page already has an editor open on the target object then that editor is brought to |
| * front; otherwise, a new editor is opened. If <code>activate == true</code> the editor will be |
| * activated. |
| * <p> |
| * |
| * @param page the workbench page in which the editor will be opened |
| * @param file the file to open |
| * @param activate if <code>true</code> the editor will be activated |
| * @return an open editor or <code>null</code> if an external editor was opened |
| * @throws PartInitException if the editor could not be initialized |
| * @see org.eclipse.ui.IWorkbenchPage#openEditor(IEditorInput, String, boolean) |
| * @since 3.6 |
| */ |
| protected final IEditorPart open(IWorkbenchPage page, IFile file, boolean activate) throws PartInitException { |
| return fEditorOpener.open(page, file, activate); |
| } |
| |
| /** |
| * 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); |
| } |
| |
| /** |
| * 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(); |
| } |
| |
| @Override |
| public void createControl(Composite parent) { |
| fQueryListener = createQueryListener(); |
| fMenu = new MenuManager("#PopUp"); //$NON-NLS-1$ |
| fMenu.setRemoveAllWhenShown(true); |
| fMenu.setParent(getSite().getActionBars().getMenuManager()); |
| fMenu.addMenuListener(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() { |
| @Override |
| public void queryAdded(ISearchQuery query) { |
| // ignore |
| } |
| |
| @Override |
| public void queryRemoved(ISearchQuery query) { |
| // ignore |
| } |
| |
| @Override |
| public void queryStarting(final ISearchQuery query) { |
| final Runnable runnable1 = () -> { |
| updateBusyLabel(); |
| AbstractTextSearchResult result = getInput(); |
| |
| if (result == null || !result.getQuery().equals(query)) { |
| return; |
| } |
| turnOffDecoration(); |
| scheduleUIUpdate(); |
| }; |
| asyncExec(runnable1); |
| } |
| |
| @Override |
| 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); |
| |
| new OpenAndLinkWithEditorHelper(fViewer) { |
| |
| @Override |
| protected void activate(ISelection selection) { |
| final int currentMode= OpenStrategy.getOpenMethod(); |
| try { |
| OpenStrategy.setOpenMethod(OpenStrategy.DOUBLE_CLICK); |
| handleOpen(new OpenEvent(fViewer, selection)); |
| } finally { |
| OpenStrategy.setOpenMethod(currentMode); |
| } |
| } |
| |
| @Override |
| protected void linkToEditor(ISelection selection) { |
| // not supported by this part |
| } |
| |
| @Override |
| protected void open(ISelection selection, boolean activate) { |
| handleOpen(new OpenEvent(fViewer, selection)); |
| } |
| |
| }; |
| |
| fViewer.addSelectionChangedListener(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); |
| } |
| |
| @Override |
| public void setFocus() { |
| Control control = fViewer.getControl(); |
| if (control != null && !control.isDisposed()) |
| control.setFocus(); |
| } |
| |
| @Override |
| public Control getControl() { |
| return fPagebook; |
| } |
| |
| @Override |
| 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 (IAction filterAction : filterActions) { |
| menu.remove(filterAction.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 (IAction curr : filterActions) { |
| if (curr instanceof IUpdate) { |
| ((IUpdate) curr).update(); |
| } |
| } |
| } |
| } |
| |
| @Override |
| 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() { |
| @Override |
| 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()); |
| } |
| } |
| |
| @Override |
| 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 (Match match : matches) { |
| if (!match.isFiltered()) { |
| count++; |
| } |
| } |
| return count; |
| } |
| |
| private Object getFirstSelectedElement() { |
| IStructuredSelection selection = fViewer.getStructuredSelection(); |
| if (selection.size() > 0) |
| return selection.getFirstElement(); |
| return null; |
| } |
| |
| @Override |
| public void dispose() { |
| AbstractTextSearchResult oldSearch = getInput(); |
| if (oldSearch != null) |
| AnnotationManagers.removeSearchResult(getSite().getWorkbenchWindow(), oldSearch); |
| super.dispose(); |
| NewSearchUI.removeQueryListener(fQueryListener); |
| } |
| |
| @Override |
| 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(IWorkbenchCommandConstants.EDIT_COPY); |
| fRemoveSelectedMatches.setActionDefinitionId(IWorkbenchCommandConstants.EDIT_DELETE); |
| fShowNextAction.setActionDefinitionId(IWorkbenchCommandConstants.NAVIGATE_NEXT); |
| fShowPreviousAction.setActionDefinitionId(IWorkbenchCommandConstants.NAVIGATE_PREVIOUS); |
| fSelectAllAction.setActionDefinitionId(IWorkbenchCommandConstants.EDIT_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 |
| */ |
| @Override |
| 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<Object> changedElements) { |
| for (Match match : matches) { |
| changedElements.add(match.getElement()); |
| } |
| } |
| |
| private synchronized void postUpdate(Match[] matches) { |
| evaluateChangedElements(matches, fBatchedUpdates); |
| scheduleUIUpdate(); |
| } |
| |
| private synchronized void runBatchedUpdates() { |
| elementsChanged(fBatchedUpdates.toArray()); |
| fBatchedUpdates.clear(); |
| updateBusyLabel(); |
| } |
| |
| private synchronized void postClear() { |
| fBatchedClearAll= true; |
| fBatchedUpdates.clear(); |
| scheduleUIUpdate(); |
| } |
| |
| private synchronized boolean hasMoreUpdates() { |
| return fBatchedClearAll || !fBatchedUpdates.isEmpty(); |
| } |
| |
| 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(() -> { |
| if (!control.isDisposed()) |
| runnable.run(); |
| }); |
| else |
| runnable.run(); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| * Subclasses may extend this method. |
| */ |
| @Override |
| 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(); |
| } |
| } |
| } |
| } |
| |
| @Override |
| 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 = viewer.getStructuredSelection(); |
| |
| HashSet<Match> 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<Match> set, Object[] elements) { |
| for (Object element : elements) { |
| Match[] matches = getDisplayedMatches(element); |
| Collections.addAll(set, matches); |
| } |
| } |
| |
| private void collectAllMatchesBelow(AbstractTextSearchResult result, Set<Match> set, ITreeContentProvider cp, Object[] elements) { |
| for (Object element : elements) { |
| Match[] matches = getDisplayedMatches(element); |
| Collections.addAll(set, matches); |
| Object[] children = cp.getChildren(element); |
| 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' event from its |
| * 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: |
| * <ul> |
| * <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> |
| * </ul> |
| * |
| * @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: |
| * <ul> |
| * <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> |
| * </ul> |
| * |
| * @since 3.3 |
| */ |
| public Integer getElementLimit() { |
| return fElementLimit; |
| } |
| |
| |
| } |