/*******************************************************************************
* Copyright (c) 2005, 2016 QNX Software Systems 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:
* QNX - Initial API and implementation
* Andrew Ferguson (Symbian)
* Markus Schorn (Wind River Systems)
*******************************************************************************/
package org.eclipse.cdt.internal.ui.indexview;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.cdt.core.dom.IPDOMNode;
import org.eclipse.cdt.core.dom.IPDOMVisitor;
import org.eclipse.cdt.core.index.IIndex;
import org.eclipse.cdt.core.index.IIndexName;
import org.eclipse.cdt.core.model.CModelException;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.model.ElementChangedEvent;
import org.eclipse.cdt.core.model.ICElement;
import org.eclipse.cdt.core.model.ICElementDelta;
import org.eclipse.cdt.core.model.ICModel;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.model.IElementChangedListener;
import org.eclipse.cdt.ui.CUIPlugin;
import org.eclipse.cdt.internal.core.CCoreInternals;
import org.eclipse.cdt.internal.core.pdom.IPDOM;
import org.eclipse.cdt.internal.core.pdom.PDOM;
import org.eclipse.cdt.internal.core.pdom.dom.PDOMBinding;
import org.eclipse.cdt.internal.core.pdom.dom.PDOMLinkage;
import org.eclipse.cdt.internal.core.pdom.dom.PDOMNode;
import org.eclipse.cdt.internal.ui.viewsupport.AsyncTreeContentProvider;
import org.eclipse.cdt.internal.ui.viewsupport.ExtendedTreeViewer;
/**
* @author Doug Schaefer
*
*/
public class IndexView extends ViewPart implements PDOM.IListener, IElementChangedListener {
private TreeViewer viewer;
private ToggleLinkingAction toggleLinkingAction;
private IndexAction countSymbolsAction;
private IndexAction discardExternalDefsAction;
private IndexAction openDefinitionAction;
private IndexAction findDeclarationsAction;
private IndexAction findReferencesAction;
Filter filter = new Filter();
public boolean isLinking = false;
private volatile boolean fUpdateRequested= false;
private Map<String, Long> fTimestampPerProject= new HashMap<String, Long>();
private IndexContentProvider contentProvider;
public void toggleExternalDefs() {
filter.showExternalDefs = ! filter.showExternalDefs;
if (!filter.showExternalDefs) {
viewer.addFilter(filter);
} else {
viewer.removeFilter(filter);
}
}
public void toggleLinking() {
isLinking = ! isLinking;
if (isLinking) {
openDefinitionAction.run();
}
}
/**
* Handles selection changed in viewer. Updates global actions. Links to
* editor (if option enabled)
*/
void handleSelectionChanged(SelectionChangedEvent event) {
if (isLinking) {
openDefinitionAction.run();
}
}
private static class Filter extends ViewerFilter {
public boolean showExternalDefs = false;
@Override
public boolean select(Viewer viewer, Object parentElement, Object element) {
if (element instanceof IndexNode) {
IndexNode node= (IndexNode)element;
return node.fHasDeclarationInProject;
}
return true;
}
public static boolean hasDeclarationInProject(IPDOMNode element) {
if (element instanceof PDOMBinding) {
try {
PDOMBinding binding = (PDOMBinding)element;
final PDOM pdom= binding.getPDOM();
IIndexName[] names= pdom.findNames(binding, IIndex.FIND_DECLARATIONS);
for (int i = 0; i < names.length; i++) {
IIndexName name = names[i];
if (name.getFile().getLocation().getFullPath() != null) {
return true;
}
}
names= pdom.findNames(binding, IIndex.FIND_DEFINITIONS);
for (int i = 0; i < names.length; i++) {
IIndexName name = names[i];
if (name.getFile().getLocation().getFullPath() != null) {
return true;
}
}
} catch (CoreException e) {
CUIPlugin.log(e);
}
}
else if (element instanceof PDOMLinkage) {
return true;
}
return false;
}
}
private static class Children implements IPDOMVisitor {
private ArrayList<IPDOMNode> fNodes;
public Children() {
fNodes= new ArrayList<IPDOMNode>();
}
@Override
public boolean visit(IPDOMNode node) throws CoreException {
fNodes.add(node);
return false;
}
@Override
public void leave(IPDOMNode node) throws CoreException {
}
public IPDOMNode[] getNodes() {
return fNodes.toArray(new IPDOMNode[fNodes.size()]);
}
}
private class IndexContentProvider extends AsyncTreeContentProvider {
public IndexContentProvider(Display disp) {
super(disp);
}
@Override
public Object getParent(Object element) {
if (element instanceof IndexNode) {
return ((IndexNode) element).fParent;
}
if (element instanceof ICElement) {
return ((ICElement) element).getParent();
}
return null;
}
@Override
protected Object[] syncronouslyComputeChildren(Object parentElement) {
if (parentElement instanceof ICModel) {
ICModel element = (ICModel) parentElement;
try {
return element.getCProjects();
} catch (CModelException e) {
CUIPlugin.log(e);
return new Object[0];
}
}
else if (parentElement instanceof IndexNode) {
final IndexNode node= (IndexNode) parentElement;
if (node.fObject instanceof PDOMBinding) {
final PDOMBinding binding= (PDOMBinding) node.fObject;
if (!binding.mayHaveChildren()) {
return new Object[0];
}
}
}
// allow for async computation
return null;
}
@Override
protected Object[] asyncronouslyComputeChildren(Object parentElement, IProgressMonitor monitor) {
try {
if (parentElement instanceof ICProject) {
ICProject cproject= (ICProject)parentElement;
if (!cproject.getProject().isOpen()) {
return new Object[0];
}
return computeChildren(cproject);
}
else if (parentElement instanceof IndexNode) {
IndexNode node= (IndexNode) parentElement;
ICProject cproject= node.getProject();
if (cproject != null && cproject.getProject().isOpen()) {
Long ts= fTimestampPerProject.get(cproject.getElementName());
IPDOM pdom= CCoreInternals.getPDOMManager().getPDOM(cproject);
pdom.acquireReadLock();
try {
if (ts == null || ts.longValue() == pdom.getLastWriteAccess()) {
return computeChildren(parentElement, node.fObject);
}
}
finally {
pdom.releaseReadLock();
}
}
}
} catch (CoreException e) {
CUIPlugin.log(e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return new Object[0];
}
private Object[] computeChildren(ICProject cproject) throws CoreException, InterruptedException {
IPDOM pdom = CCoreInternals.getPDOMManager().getPDOM(cproject);
pdom.acquireReadLock();
try {
fTimestampPerProject.put(cproject.getElementName(), Long.valueOf(pdom.getLastWriteAccess()));
IPDOMNode[] linkages= pdom.getLinkageImpls();
if (linkages.length == 1) {
// Skip linkages in hierarchy if there is only one
return computeChildren(cproject, linkages[0]);
}
return wrap(cproject, linkages);
}
finally {
pdom.releaseReadLock();
}
}
private Object[] computeChildren(Object parent, IPDOMNode node) throws CoreException {
Children collector = new Children();
node.accept(collector);
return wrap(parent, collector.getNodes());
}
private Object[] wrap(Object parent, IPDOMNode[] nodes) {
if (nodes.length == 0) {
return nodes;
}
IndexNode[] result= new IndexNode[nodes.length];
for (int i = 0; i < result.length; i++) {
final IndexNode indexNode = result[i]= new IndexNode();
final IPDOMNode node= nodes[i];
indexNode.fParent= parent;
indexNode.fObject= node;
indexNode.fText= IndexLabelProvider.getText(node);
indexNode.fImage= IndexLabelProvider.getImage(node);
indexNode.fHasDeclarationInProject= Filter.hasDeclarationInProject(node);
if (node instanceof PDOMNode) {
indexNode.fBindingKind= ((PDOMNode) node).getNodeType();
}
}
return result;
}
}
@Override
public void createPartControl(Composite parent) {
viewer = new ExtendedTreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
contentProvider= new IndexContentProvider(getSite().getShell().getDisplay());
viewer.setContentProvider(contentProvider);
viewer.setLabelProvider(new IndexLabelProvider());
viewer.setUseHashlookup(true);
ICModel model = CoreModel.getDefault().getCModel();
viewer.setInput(model);
viewer.addFilter(filter);
try {
ICProject[] projects = model.getCProjects();
for (int i = 0; i < projects.length; ++i) {
IPDOM pdom = CCoreInternals.getPDOMManager().getPDOM(projects[i]);
pdom.addListener(this);
}
} catch (CoreException e) {
CUIPlugin.log(e);
}
CoreModel.getDefault().addElementChangedListener(this);
makeActions();
hookContextMenu();
hookDoubleClickAction();
contributeToActionBars();
// Menu
MenuManager menuMgr = new MenuManager();
menuMgr.setRemoveAllWhenShown(true);
menuMgr.addMenuListener(new IMenuListener() {
private void hideMenuItems(IMenuManager manager) {
}
@Override
public void menuAboutToShow(IMenuManager manager) {
IndexView.this.fillContextMenu(manager);
hideMenuItems(manager);
}
});
Menu menu = menuMgr.createContextMenu(viewer.getControl());
viewer.getControl().setMenu(menu);
getSite().registerContextMenu(menuMgr, viewer);
getSite().setSelectionProvider(viewer);
viewer.addSelectionChangedListener(new ISelectionChangedListener() {
@Override
public void selectionChanged(SelectionChangedEvent event) {
handleSelectionChanged(event);
}
});
}
@Override
public void dispose() {
super.dispose();
ICModel model = CoreModel.getDefault().getCModel();
try {
ICProject[] projects = model.getCProjects();
for (int i = 0; i < projects.length; ++i) {
IPDOM pdom = CCoreInternals.getPDOMManager().getPDOM(projects[i]);
pdom.removeListener(this);
}
} catch (CoreException e) {
CUIPlugin.log(e);
}
CoreModel.getDefault().removeElementChangedListener(this);
}
private void makeActions() {
countSymbolsAction = new CountNodeAction(this, viewer);
discardExternalDefsAction = new DiscardExternalDefsAction(viewer, this);
toggleLinkingAction = new ToggleLinkingAction(this);
openDefinitionAction = new OpenDefinitionAction(this, viewer);
findDeclarationsAction = new FindDeclarationsAction(this, viewer);
findReferencesAction = new FindReferencesAction(this, viewer);
}
private void hookContextMenu() {
MenuManager menuMgr = new MenuManager("#PopupMenu"); //$NON-NLS-1$
menuMgr.setRemoveAllWhenShown(true);
menuMgr.addMenuListener(new IMenuListener() {
@Override
public void menuAboutToShow(IMenuManager manager) {
IndexView.this.fillContextMenu(manager);
}
});
Menu menu = menuMgr.createContextMenu(viewer.getControl());
viewer.getControl().setMenu(menu);
getSite().registerContextMenu(menuMgr, viewer);
}
private void fillContextMenu(IMenuManager manager) {
if (countSymbolsAction.valid())
manager.add(countSymbolsAction);
if (discardExternalDefsAction.valid())
manager.add(discardExternalDefsAction);
if (openDefinitionAction.valid())
manager.add(openDefinitionAction);
if (findDeclarationsAction.valid())
manager.add(findDeclarationsAction);
if (findReferencesAction.valid())
manager.add(findReferencesAction);
manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
}
private void hookDoubleClickAction() {
viewer.addDoubleClickListener(new IDoubleClickListener() {
@Override
public void doubleClick(DoubleClickEvent event) {
openDefinitionAction.run();
}
});
}
private void contributeToActionBars() {
IActionBars bars = getViewSite().getActionBars();
//fillLocalPullDown(bars.getMenuManager());
fillLocalToolBar(bars.getToolBarManager());
}
private void fillLocalToolBar(IToolBarManager manager) {
// drillDownAdapter.addNavigationActions(manager);
manager.add(toggleLinkingAction);
manager.add(discardExternalDefsAction);
}
@Override
public void setFocus() {
viewer.getControl().setFocus();
}
@Override
public void handleChange(PDOM pdom, PDOM.ChangeEvent e) {
requestUpdate();
}
private void requestUpdate() {
if (!fUpdateRequested) {
fUpdateRequested= true;
viewer.getControl().getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
fUpdateRequested= false;
if (!viewer.getControl().isDisposed()) {
contentProvider.recompute();
}
}
});
}
}
@Override
public void elementChanged(ElementChangedEvent event) {
// Only respond to post change events
if (event.getType() != ElementChangedEvent.POST_CHANGE)
return;
// TODO we'll get fancier when we do a virtual tree.
processDelta(event.getDelta());
}
private void processDelta(ICElementDelta delta) {
int type = delta.getElement().getElementType();
switch (type) {
case ICElement.C_MODEL:
// Loop through the children
ICElementDelta[] children = delta.getAffectedChildren();
for (int i = 0; i < children.length; ++i)
processDelta(children[i]);
break;
case ICElement.C_PROJECT:
switch (delta.getKind()) {
case ICElementDelta.ADDED:
try {
IPDOM pdom = CCoreInternals.getPDOMManager().getPDOM((ICProject)delta.getElement());
pdom.addListener(this);
handleChange(null, null);
} catch (CoreException e) {
}
break;
case ICElementDelta.REMOVED:
handleChange(null, null);
break;
}
}
}
public long getLastWriteAccess(ICProject cproject) {
Long result= fTimestampPerProject.get(cproject.getElementName());
return result == null ? -1 : result.longValue();
}
}