/**
* Copyright (c) 20015 by Brainwy Software Ltda. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
package org.python.pydev.shared_ui.search;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.OpenEvent;
import org.eclipse.jface.viewers.StructuredSelection;
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.viewers.ViewerComparator;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.search.ui.IContextMenuConstants;
import org.eclipse.search.ui.ISearchQuery;
import org.eclipse.search.ui.ISearchResult;
import org.eclipse.search.ui.ISearchResultViewPart;
import org.eclipse.search.ui.text.AbstractTextSearchResult;
import org.eclipse.search.ui.text.AbstractTextSearchViewPage;
import org.eclipse.search.ui.text.Match;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IPageLayout;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.actions.ActionContext;
import org.eclipse.ui.actions.ActionGroup;
import org.eclipse.ui.forms.events.HyperlinkAdapter;
import org.eclipse.ui.forms.events.HyperlinkEvent;
import org.eclipse.ui.part.IShowInSource;
import org.eclipse.ui.part.IShowInTargetList;
import org.eclipse.ui.part.ResourceTransfer;
import org.eclipse.ui.part.ShowInContext;
import org.eclipse.ui.progress.WorkbenchJob;
import org.eclipse.ui.views.navigator.NavigatorDragAdapter;
import org.python.pydev.shared_core.callbacks.ICallback;
import org.python.pydev.shared_core.log.Log;
import org.python.pydev.shared_core.structure.TreeNode;
import org.python.pydev.shared_core.structure.TreeNodeContentProvider;
import org.python.pydev.shared_core.structure.Tuple;
import org.python.pydev.shared_ui.dialogs.DialogHelpers;
import org.python.pydev.shared_ui.search.replace.ReplaceAction;
import org.python.pydev.shared_ui.swt.StyledLink.MultiStyledLink;
public abstract class AbstractSearchIndexResultPage extends AbstractTextSearchViewPage {
protected ISearchIndexContentProvider fContentProvider;
protected Text filterText;
protected WorkbenchJob refreshJob;
protected GroupByAction[] fGroupByActions;
protected ActionGroup fActionGroup;
public AbstractSearchIndexResultPage() {
super(FLAG_LAYOUT_TREE);
}
public static class DecoratorIgnoringViewerSorter extends ViewerComparator {
private final ILabelProvider fLabelProvider;
public DecoratorIgnoringViewerSorter(ILabelProvider labelProvider) {
fLabelProvider = labelProvider;
}
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.ViewerComparator#category(java.lang.Object)
*/
@Override
public int category(Object element) {
if (element instanceof IContainer) {
return 1;
}
return 2;
}
@Override
public int compare(Viewer viewer, Object e1, Object e2) {
int cat1 = category(e1);
int cat2 = category(e2);
if (cat1 != cat2) {
return cat1 - cat2;
}
if (e1 instanceof ICustomLineElement && e2 instanceof ICustomLineElement) {
ICustomLineElement m1 = (ICustomLineElement) e1;
ICustomLineElement m2 = (ICustomLineElement) e2;
return m1.getOffset() - m2.getOffset();
}
String name1 = fLabelProvider.getText(e1);
String name2 = fLabelProvider.getText(e2);
if (name1 == null) {
name1 = "";//$NON-NLS-1$
}
if (name2 == null) {
name2 = "";//$NON-NLS-1$
}
return getComparator().compare(name1, name2);
}
}
protected int groupWithConfiguration = ISearchIndexContentProvider.GROUP_WITH_PROJECT
| ISearchIndexContentProvider.GROUP_WITH_MODULES;
public int getGroupWithConfiguration() {
return groupWithConfiguration;
}
public void setGroupWithConfiguration(int groupWithConfiguration) {
this.groupWithConfiguration = groupWithConfiguration;
updateGroupWith(getViewer());
}
private static String STORE_GROUP_WITH = "group_with";
@Override
public void restoreState(IMemento memento) {
super.restoreState(memento);
if (memento != null) {
Integer value = memento.getInteger(STORE_GROUP_WITH);
if (value != null) {
groupWithConfiguration = value.intValue();
updateGroupWith(this.getViewer());
}
for (GroupByAction act : this.fGroupByActions) {
act.updateImage();
}
}
}
private void updateGroupWith(StructuredViewer viewer) {
if (viewer != null) {
IContentProvider contentProvider = viewer.getContentProvider();
if (contentProvider instanceof ISearchIndexContentProvider) {
ISearchIndexContentProvider searchIndexTreeContentProvider = (ISearchIndexContentProvider) contentProvider;
searchIndexTreeContentProvider.setGroupWith(groupWithConfiguration);
}
}
}
@Override
public void saveState(IMemento memento) {
super.saveState(memento);
memento.putInteger(STORE_GROUP_WITH, this.groupWithConfiguration);
}
protected void textChanged() {
if (refreshJob != null) {
refreshJob.cancel();
}
getRefreshJob().schedule(650);
}
protected WorkbenchJob getRefreshJob() {
if (refreshJob == null) {
refreshJob = new WorkbenchJob("Refresh Filter") {//$NON-NLS-1$
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
if (filterText != null && !filterText.isDisposed()) {
final String text = filterText.getText().trim();
AbstractTextSearchResult input = getInput();
if (input != null) {
if (!text.isEmpty()) {
ViewerFilter[] filters = new ViewerFilter[] {
createFilterFilter(text, false)
};
getViewer().setFilters(filters);
TreeViewer viewer = (TreeViewer) getViewer();
viewer.expandAll();
} else {
getViewer().setFilters(new ViewerFilter[0]);
}
}
}
getViewPart().updateLabel();
return Status.OK_STATUS;
}
};
refreshJob.setSystem(true);
}
return refreshJob;
}
protected abstract AbstractSearchResultsViewerFilter createFilterFilter(String text, boolean wholeWord);
protected static final String[] SHOW_IN_TARGETS = new String[] { IPageLayout.ID_RES_NAV };
protected static final IShowInTargetList SHOW_IN_TARGET_LIST = new IShowInTargetList() {
@Override
public String[] getShowInTargetIds() {
return SHOW_IN_TARGETS;
}
};
@Override
protected void configureTreeViewer(TreeViewer viewer) {
viewer.setUseHashlookup(true);
SearchIndexLabelProvider innerLabelProvider = createSearchIndexLabelProvider();
viewer.setLabelProvider(new DecoratingFileSearchLabelProvider(innerLabelProvider));
viewer.setContentProvider(createTreeContentProvider(viewer));
viewer.setComparator(new DecoratorIgnoringViewerSorter(innerLabelProvider));
fContentProvider = (ISearchIndexContentProvider) viewer.getContentProvider();
addDragAdapters(viewer);
updateGroupWith(viewer);
}
protected SearchIndexLabelProvider createSearchIndexLabelProvider() {
return new SearchIndexLabelProvider(this);
}
protected abstract TreeNodeContentProvider createTreeContentProvider(TreeViewer viewer);
@Override
protected void showMatch(Match match, int offset, int length, boolean activate) throws PartInitException {
IFile file = (IFile) match.getElement();
IWorkbenchPage page = getSite().getPage();
if (offset >= 0 && length != 0) {
openAndSelect(page, file, offset, length, activate);
} else {
open(page, file, activate);
}
}
@Override
protected void handleOpen(OpenEvent event) {
Object firstElement = ((IStructuredSelection) event.getSelection()).getFirstElement();
if (getDisplayedMatchCount(firstElement) == 0) {
try {
if (firstElement instanceof IAdaptable) {
IAdaptable iAdaptable = (IAdaptable) firstElement;
IFile file = iAdaptable.getAdapter(IFile.class);
if (file != null) {
open(getSite().getPage(), file, false);
}
}
} catch (PartInitException e) {
ErrorDialog.openError(getSite().getShell(),
"Open File",
"Opening the file failed.", e.getStatus());
}
return;
}
super.handleOpen(event);
}
@Override
protected void configureTableViewer(TableViewer viewer) {
throw new RuntimeException("Table layout is unsupported.");
}
@Override
public StructuredViewer getViewer() {
return super.getViewer();
}
@Override
public void setElementLimit(Integer elementLimit) {
super.setElementLimit(elementLimit);
}
private void addDragAdapters(StructuredViewer viewer) {
Transfer[] transfers = new Transfer[] { ResourceTransfer.getInstance() };
int ops = DND.DROP_COPY | DND.DROP_LINK;
viewer.addDragSupport(ops, transfers, new NavigatorDragAdapter(viewer));
}
@Override
protected void fillContextMenu(IMenuManager mgr) {
super.fillContextMenu(mgr);
fActionGroup.setContext(new ActionContext(getSite().getSelectionProvider().getSelection()));
fActionGroup.fillContextMenu(mgr);
AbstractSearchIndexQuery query = (AbstractSearchIndexQuery) getInput().getQuery();
if (query.getSearchString().length() > 0) {
IStructuredSelection selection = (IStructuredSelection) getViewer().getSelection();
if (!selection.isEmpty()) {
ReplaceAction replaceSelection = new ReplaceAction(getSite().getShell(), getInput(),
selection.toArray(), true);
replaceSelection.setText(SearchMessages.ReplaceAction_label_selected);
mgr.appendToGroup(IContextMenuConstants.GROUP_REORGANIZE, replaceSelection);
}
ICallback<Boolean, Match> skipMatch = new ICallback<Boolean, Match>() {
@Override
public Boolean call(Match match) {
StructuredViewer viewer = getViewer();
ViewerFilter[] filters = viewer.getFilters();
if (filters == null || filters.length == 0) {
return false;
}
for (ViewerFilter viewerFilter : filters) {
if (viewerFilter instanceof AbstractSearchResultsViewerFilter) {
AbstractSearchResultsViewerFilter searchResultsViewerFilter = (AbstractSearchResultsViewerFilter) viewerFilter;
if (searchResultsViewerFilter.isLeafMatch(viewer, match)) {
return false;
}
}
}
return true;
}
};
ReplaceAction replaceAll = new ReplaceAction(getSite().getShell(), getInput(), null, true, skipMatch);
replaceAll.setText(SearchMessages.ReplaceAction_label_all);
mgr.appendToGroup(IContextMenuConstants.GROUP_REORGANIZE, replaceAll);
}
}
@Override
public void setViewPart(ISearchResultViewPart part) {
super.setViewPart(part);
fActionGroup = new NewTextSearchActionGroup(part);
}
@Override
protected void fillToolbar(IToolBarManager tbm) {
super.fillToolbar(tbm);
for (Action a : fGroupByActions) {
String id = IContextMenuConstants.GROUP_PROPERTIES + "." + a.hashCode();
a.setId(id);
tbm.add(a);
}
}
@Override
protected void elementsChanged(Object[] objects) {
if (fContentProvider != null) {
ViewerFilter[] filters = getViewer().getFilters();
for (ViewerFilter viewerFilter : filters) {
if (viewerFilter instanceof AbstractSearchResultsViewerFilter) {
AbstractSearchResultsViewerFilter searchResultsViewerFilter = (AbstractSearchResultsViewerFilter) viewerFilter;
searchResultsViewerFilter.clearCache();
}
}
fContentProvider.elementsChanged(objects);
}
}
@Override
protected void clear() {
if (fContentProvider != null) {
fContentProvider.clear();
Job r = this.refreshJob;
if (r != null) {
r.cancel();
}
filterText.setText("");
getViewer().setFilters(new ViewerFilter[0]);
}
}
@Override
public Object getUIState() {
return new Tuple<>(super.getUIState(), filterText.getText());
}
@SuppressWarnings("unchecked")
@Override
public void setInput(ISearchResult newSearch, Object viewState) {
String filter = "";
if (viewState instanceof Tuple) {
Tuple<Object, Object> tuple = (Tuple<Object, Object>) viewState;
filter = (String) tuple.o2;
viewState = tuple.o1;
}
StructuredViewer viewer = getViewer();
Control control = viewer.getControl();
control.setRedraw(false);
try {
viewer.setFilters(new ViewerFilter[0]); //Reset the filter before setting the new selection
try {
super.setInput(newSearch, viewState);
} catch (Exception e) {
Log.log(e);
super.setInput(newSearch, null);
}
filterText.setText(filter);
textChanged();
} finally {
control.setRedraw(true);
}
}
@Override
public void dispose() {
fActionGroup.dispose();
if (this.filterText != null) {
this.filterText.dispose();
this.filterText = null;
}
if (refreshJob != null) {
refreshJob.cancel();
refreshJob = null;
}
super.dispose();
}
public Object getAdapter(Class<?> adapter) {
if (IShowInTargetList.class.equals(adapter)) {
return SHOW_IN_TARGET_LIST;
}
if (adapter == IShowInSource.class) {
ISelectionProvider selectionProvider = getSite().getSelectionProvider();
if (selectionProvider == null) {
return null;
}
ISelection selection = selectionProvider.getSelection();
if (selection instanceof IStructuredSelection) {
IStructuredSelection structuredSelection = ((StructuredSelection) selection);
final Set<Object> newSelection = new HashSet<>(structuredSelection.size());
Iterator<?> iter = structuredSelection.iterator();
while (iter.hasNext()) {
Object element = iter.next();
if (element instanceof ICustomLineElement) {
element = ((ICustomLineElement) element).getParent();
}
newSelection.add(element);
}
return new IShowInSource() {
@Override
public ShowInContext getShowInContext() {
return new ShowInContext(null, new StructuredSelection(new ArrayList<>(newSelection)));
}
};
}
return null;
}
return null;
}
@Override
public String getLabel() {
StructuredViewer viewer = getViewer();
if (viewer instanceof TreeViewer) {
int count = 0;
TreeViewer tv = (TreeViewer) viewer;
final AbstractTextSearchResult input = getInput();
if (input != null) {
ViewerFilter[] filters = tv.getFilters();
if (filters != null && filters.length > 0) {
Object[] elements = input.getElements();
for (int j = 0; j < elements.length; j++) {
Object element = elements[j];
Match[] matches = input.getMatches(element);
for (Match match : matches) {
for (int i = 0; i < filters.length; i++) {
ViewerFilter vf = filters[i];
if (vf instanceof AbstractSearchResultsViewerFilter) {
AbstractSearchResultsViewerFilter searchResultsViewerFilter = (AbstractSearchResultsViewerFilter) vf;
if (searchResultsViewerFilter.isLeafMatch(viewer, match)) {
count += 1;
}
}
}
}
}
} else {
// No active filters
count = input.getMatchCount();
}
}
AbstractTextSearchResult result = getInput();
if (result == null) {
return "";
}
ISearchQuery query = result.getQuery();
if (query instanceof AbstractSearchIndexQuery) {
AbstractSearchIndexQuery searchIndexQuery = (AbstractSearchIndexQuery) query;
return searchIndexQuery.getResultLabel(count);
}
}
return super.getLabel();
}
@Override
public int getDisplayedMatchCount(Object element) {
if (element instanceof TreeNode<?>) {
element = ((TreeNode<?>) element).data;
}
if (element instanceof ICustomLineElement) {
ICustomLineElement lineEntry = (ICustomLineElement) element;
return lineEntry.getNumberOfMatches(getInput());
}
return 0;
}
@Override
public Match[] getDisplayedMatches(Object element) {
if (element instanceof TreeNode<?>) {
element = ((TreeNode<?>) element).data;
}
if (element instanceof ICustomModule) {
ICustomModule customModule = (ICustomModule) element;
element = customModule.getModuleLineElement();
}
if (element instanceof ICustomLineElement) {
ICustomLineElement lineEntry = (ICustomLineElement) element;
return lineEntry.getMatches(getInput());
}
return new Match[0];
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
protected void evaluateChangedElements(Match[] matches, Set changedElements) {
for (int i = 0; i < matches.length; i++) {
changedElements.add(((ICustomMatch) matches[i]).getLineElement());
}
}
@Override
protected TreeViewer createTreeViewer(Composite parent) {
createFilterControl(parent);
TreeViewer ret = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL) {
long currentTimeMillis;
boolean inExpandAll = false;
@Override
public void expandAll() {
currentTimeMillis = System.currentTimeMillis();
Control control = this.getControl();
control.setRedraw(false);
try {
inExpandAll = true;
super.expandAll();
} catch (OperationCanceledException e) {
// Ignore
Log.log("Aborted expand operation because it took more than 5 seconds.");
} finally {
inExpandAll = false;
control.setRedraw(true);
}
}
@Override
protected void internalExpandToLevel(Widget widget, int level) {
if (inExpandAll) {
if (System.currentTimeMillis() - currentTimeMillis > 5000) {
throw new OperationCanceledException();
}
}
super.internalExpandToLevel(widget, level);
}
@Override
public void collapseAll() {
Control control = this.getControl();
control.setRedraw(false);
try {
super.collapseAll();
} finally {
control.setRedraw(true);
}
}
};
fixViewerLayout(ret.getControl());
return ret;
}
@Override
protected TableViewer createTableViewer(Composite parent) {
createFilterControl(parent);
TableViewer ret = super.createTableViewer(parent);
fixViewerLayout(ret.getControl());
return ret;
}
private void fixViewerLayout(Control control) {
GridData layoutData = new GridData(GridData.FILL_BOTH);
layoutData.grabExcessHorizontalSpace = true;
layoutData.grabExcessVerticalSpace = true;
layoutData.horizontalSpan = 3;
control.setLayoutData(layoutData);
}
private void createFilterControl(Composite parent) {
GridLayout layout = new GridLayout(3, false);
parent.setLayout(layout);
Label label = new Label(parent, SWT.NONE);
label.setText(getFilterText());
filterText = new Text(parent, SWT.BORDER | SWT.SINGLE);
GridData layoutData = new GridData(SWT.FILL, SWT.NONE, true, false);
filterText.setLayoutData(layoutData);
filterText.addModifyListener(new ModifyListener() {
@Override
public void modifyText(ModifyEvent e) {
textChanged();
}
});
MultiStyledLink link = new MultiStyledLink(parent, SWT.NONE);
link.setText("<a> ? </a>");
final String filterHelp = getFilterHelp();
link.getLink(0).addHyperlinkListener(new HyperlinkAdapter() {
@Override
public void linkActivated(HyperlinkEvent e) {
DialogHelpers.openInfo("", filterHelp);
}
});
link.getLink(0).setToolTipText(filterHelp);
}
protected abstract String getFilterHelp();
protected abstract String getFilterText();
}