blob: ff7047c4fbf9937c08476ebbfe7a41db2c83f1d7 [file] [log] [blame]
/**
* <copyright>
*
* Copyright (c) 2011-2015 itemis and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
*
* Contributors:
* itemis - Initial API and implementatio
* itemis - [463119] References View navigation
*
* </copyright>
*/
package org.eclipse.sphinx.emf.workspace.ui.views;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.Assert;
import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EStructuralFeature.Setting;
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
import org.eclipse.emf.edit.provider.ComposedImage;
import org.eclipse.emf.edit.provider.ItemProviderAdapter;
import org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider;
import org.eclipse.emf.edit.ui.provider.ExtendedImageRegistry;
import org.eclipse.emf.transaction.RunnableWithResult;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.emf.transaction.ui.provider.TransactionalAdapterFactoryLabelProvider;
import org.eclipse.emf.transaction.util.TransactionUtil;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.util.DelegatingDropAdapter;
import org.eclipse.jface.util.LocalSelectionTransfer;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ILabelDecorator;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.sphinx.emf.ui.util.EcoreUIUtil;
import org.eclipse.sphinx.emf.ui.util.OpenStrategy;
import org.eclipse.sphinx.emf.util.EObjectUtil;
import org.eclipse.sphinx.emf.util.EcorePlatformUtil;
import org.eclipse.sphinx.emf.util.WorkspaceEditingDomainUtil;
import org.eclipse.sphinx.emf.workspace.ui.internal.Activator;
import org.eclipse.sphinx.emf.workspace.ui.internal.messages.Messages;
import org.eclipse.sphinx.emf.workspace.ui.internal.views.ReferencesHierarchyTransferDropAdapter;
import org.eclipse.sphinx.emf.workspace.ui.internal.views.ToggleReferencesModeAction;
import org.eclipse.sphinx.emf.workspace.ui.util.ICommonModelUIConstants;
import org.eclipse.sphinx.emf.workspace.ui.util.TransactionalDelegatingTreeContentProvider;
import org.eclipse.sphinx.platform.util.DirectedGraph;
import org.eclipse.sphinx.platform.util.PlatformLogUtil;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.part.IShowInSource;
import org.eclipse.ui.part.PageBook;
import org.eclipse.ui.part.ShowInContext;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.ui.views.properties.IPropertySheetPage;
import org.eclipse.ui.views.properties.tabbed.ITabbedPropertySheetPageContributor;
import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetPage;
/**
*
*/
public class ReferencesView extends ViewPart implements ITabbedPropertySheetPageContributor {
private static final String STORE_MODE = "MODE"; //$NON-NLS-1$
public static final int REFERENCED_OBJECTS_MODE = 0;
public static final int REFERENCING_OBJECTS_MODE = 1;
private static final int PAGE_EMPTY = 0;
private static final int PAGE_VIEWER = 1;
protected Set<IPropertySheetPage> propertySheetPages = new HashSet<IPropertySheetPage>();
private Object viewInput;
private int currentMode;
private Label noRefsHierarchyShownLabel;
private TreeViewer viewer;
private ILabelProvider labelProvider;
private IContentProvider contentProvider;
private IDialogSettings dialogSettings;
private ToggleReferencesModeAction[] toggleReferencesModeActions;
protected Map<TransactionalEditingDomain, IContentProvider> modelCrossReferenceContentProviders = new WeakHashMap<TransactionalEditingDomain, IContentProvider>();
protected Map<TransactionalEditingDomain, ILabelProvider> modelLabelProviders = new WeakHashMap<TransactionalEditingDomain, ILabelProvider>();
private Action doubleClickAction;
private PageBook pageBook;
private SashForm refsHierarchyInfosSplitter;
private DirectedGraph<EObject> graph;
public ReferencesView() {
dialogSettings = Activator.getDefault().getDialogSettings();
graph = new DirectedGraph<EObject>(false, true);
}
@Override
public void createPartControl(Composite parent) {
pageBook = new PageBook(parent, SWT.NONE);
// First page: viewers
refsHierarchyInfosSplitter = new SashForm(pageBook, SWT.NONE);
viewer = new TreeViewer(refsHierarchyInfosSplitter, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
if (contentProvider != null) {
viewer.setContentProvider(contentProvider);
}
if (labelProvider != null) {
viewer.setLabelProvider(labelProvider);
}
// Second page: when nothing selected
noRefsHierarchyShownLabel = new Label(pageBook, SWT.TOP + SWT.LEFT + SWT.WRAP);
noRefsHierarchyShownLabel.setText(Messages.label_ReferencesHierarchyEmpty); //
showPage(PAGE_EMPTY);
makeActions();
hookContextMenu();
hookDoubleClickAction();
contributeToActionBars();
// initDragAndDrop();
initMode();
}
private void showPage(int page) {
boolean isEmpty = page == PAGE_EMPTY;
Control control = isEmpty ? (Control) noRefsHierarchyShownLabel : refsHierarchyInfosSplitter;
if (isEmpty) {
setContentDescription(""); //$NON-NLS-1$
setTitleToolTip(getPartName());
getViewSite().getActionBars().getStatusLineManager().setMessage(""); //$NON-NLS-1$
}
pageBook.showPage(control);
}
protected TreeViewer getViewer() {
return viewer;
}
public void setViewInput(Object viewInput) {
this.viewInput = viewInput;
contentProvider = getModelContentProvider(viewInput);
if (contentProvider != null) {
viewer.setContentProvider(contentProvider);
}
labelProvider = getModelLabelProvider(viewInput);
if (labelProvider != null) {
viewer.setLabelProvider(labelProvider);
}
viewer.setInput(getViewerInput());
updateView();
}
protected Object getViewerInput() {
return new WrappedViewInput(viewInput);
}
protected boolean isRecursive(Object element) {
Set<DirectedGraph<EObject>.Edge> outgoingEdgesOfElement = graph.outgoingEdgesOf((EObject) element);
Set<DirectedGraph<EObject>.Edge> incomingEdgesOfElement = graph.incomingEdgesOf((EObject) element);
return !outgoingEdgesOfElement.isEmpty() && incomingEdgesOfElement.size() >= 1;
}
protected IContentProvider getModelContentProvider(Object element) {
// Retrieve editing domain behind specified object
TransactionalEditingDomain editingDomain = WorkspaceEditingDomainUtil.getEditingDomain(element);
if (editingDomain != null) {
// Retrieve model content provider for given editing domain; create new one if not existing yet
IContentProvider modelContentProvider = modelCrossReferenceContentProviders.get(editingDomain);
if (modelContentProvider == null) {
modelContentProvider = createModelCrossReferenceContentProvider(editingDomain);
modelCrossReferenceContentProviders.put(editingDomain, modelContentProvider);
}
return modelContentProvider;
}
return null;
}
protected ITreeContentProvider createModelCrossReferenceContentProvider(TransactionalEditingDomain editingDomain) {
return new TransactionalDelegatingTreeContentProvider(new ITreeContentProvider() {
@Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
// Do nothing
}
@Override
public Object[] getElements(Object inputElement) {
if (inputElement instanceof WrappedViewInput) {
EObject input = (EObject) ((WrappedViewInput) inputElement).getInput();
// As it's the first element to be displayed in the view, add it to the graph
graph.addVertex(input);
return new Object[] { input };
} else {
return getChildren(inputElement);
}
}
@Override
public boolean hasChildren(Object element) {
if (element instanceof EObject) {
return !isRecursive(element) && getChildren(element).length > 0;
}
return false;
}
// TODO Add defer and abort capability
@Override
public Object[] getChildren(Object parentElement) {
if (parentElement instanceof EObject) {
final EObject parentEObject = (EObject) parentElement;
// Add the parent to the graph
graph.addVertex(parentEObject);
TransactionalEditingDomain editingDomain = TransactionUtil.getEditingDomain(parentEObject);
if (editingDomain != null) {
try {
return TransactionUtil.runExclusive(editingDomain, new RunnableWithResult.Impl<Object[]>() {
@Override
public void run() {
Collection<EObject> eCrossReferences = getECrossReferences(parentEObject);
addEdges(parentEObject, eCrossReferences);
setResult(eCrossReferences.toArray(new EObject[eCrossReferences.size()]));
}
});
} catch (InterruptedException ex) {
PlatformLogUtil.logAsError(Activator.getPlugin(), ex);
}
} else {
Collection<EObject> eCrossReferences = getECrossReferences(parentEObject);
addEdges(parentEObject, eCrossReferences);
return eCrossReferences.toArray(new EObject[eCrossReferences.size()]);
}
}
return new Object[0];
}
protected void addEdges(EObject parentEObject, Collection<EObject> eCrossReferences) {
for (EObject crossRef : eCrossReferences) {
graph.addVertex(crossRef);
graph.addEdge(parentEObject, crossRef);
}
}
protected Collection<EObject> getECrossReferences(EObject eObject) {
if (currentMode == REFERENCING_OBJECTS_MODE) {
List<EObject> eInverseCrossReferences = new ArrayList<EObject>();
Collection<Setting> inverseReferences = EObjectUtil.getInverseReferences(eObject, true);
for (Setting inverseReference : inverseReferences) {
EStructuralFeature eStructuralFeature = inverseReference.getEStructuralFeature();
if (eStructuralFeature instanceof EReference) {
if (!((EReference) eStructuralFeature).isContainment()) {
eInverseCrossReferences.add(inverseReference.getEObject());
}
} else {
eInverseCrossReferences.add(inverseReference.getEObject());
}
}
return eInverseCrossReferences;
} else {
return eObject.eCrossReferences();
}
}
@Override
public Object getParent(Object element) {
if (element instanceof EObject) {
return ((EObject) element).eContainer();
}
return null;
}
@Override
public void dispose() {
// Do nothing
}
}, editingDomain);
}
protected ILabelProvider getModelLabelProvider(Object element) {
// Retrieve editing domain behind specified object
TransactionalEditingDomain editingDomain = WorkspaceEditingDomainUtil.getEditingDomain(element);
if (editingDomain != null) {
// Retrieve model label provider for given editing domain; create new one if not existing yet
ILabelProvider modelLabelProvider = modelLabelProviders.get(editingDomain);
if (modelLabelProvider == null) {
modelLabelProvider = createModelLabelProvider(editingDomain);
modelLabelProviders.put(editingDomain, modelLabelProvider);
}
return modelLabelProvider;
} else if (element instanceof EObject && ((EObject) element).eIsProxy()) {
// Use non-transactional adapter factory label provider to avoid that proxified EObjects end up being
// represented as empty tree nodes
return new AdapterFactoryLabelProvider(getAdapterFactory(editingDomain));
}
return null;
}
protected ILabelProvider createModelLabelProvider(final TransactionalEditingDomain editingDomain) {
Assert.isNotNull(editingDomain);
AdapterFactory adapterFactory = getAdapterFactory(editingDomain);
return new DecorationAwareTransactionalAdapterFactoryLabelProvider(editingDomain, adapterFactory) {
@Override
// Overridden to avoid the somewhat annoying logging of Eclipse exceptions resulting from event queue
// dispatching that is done before transaction is acquired and actually starts to run
protected <T> T run(RunnableWithResult<? extends T> run) {
try {
return TransactionUtil.runExclusive(editingDomain, run);
} catch (Exception e) {
// propagate interrupt status because we are not throwing
Thread.currentThread().interrupt();
return null;
}
}
};
}
/**
* Returns the {@link AdapterFactory adapter factory} to be used by this {@link BasicExplorerLabelProvider label
* provider} for creating {@link ItemProviderAdapter item provider}s which control the way how {@link EObject model
* element}s from given <code>editingDomain</code> are displayed and can be edited.
* <p>
* This implementation returns the {@link AdapterFactory adapter factory} which is embedded in the given
* <code>editingDomain</code> by default. Clients which want to use an alternative {@link AdapterFactory adapter
* factory} (e.g., an {@link AdapterFactory adapter factory} that creates {@link ItemProviderAdapter item provider}s
* which are specifically designed for the {@link IEditorPart editor} in which this
* {@link BasicExplorerLabelProvider label provider} is used) may override {@link #getCustomAdapterFactory()} and
* return any {@link AdapterFactory adapter factory} of their choice. This custom {@link AdapterFactory adapter
* factory} will then be returned as result by this method.
* </p>
*
* @param editingDomain
* The {@link TransactionalEditingDomain editing domain} whose embedded {@link AdapterFactory adapter
* factory} is to be returned as default. May be left <code>null</code> if
* {@link #getCustomAdapterFactory()} has been overridden and returns a non-<code>null</code> result.
* @return The {@link AdapterFactory adapter factory} that will be used by this {@link BasicExplorerLabelProvider
* label provider}. <code>null</code> if no custom {@link AdapterFactory adapter factory} is provided
* through {@link #getCustomAdapterFactory()} and no <code>editingDomain</code> has been specified.
* @see #getCustomAdapterFactory()
*/
protected AdapterFactory getAdapterFactory(TransactionalEditingDomain editingDomain) {
if (editingDomain != null) {
return ((AdapterFactoryEditingDomain) editingDomain).getAdapterFactory();
}
return null;
}
private void hookContextMenu() {
MenuManager menuMgr = new MenuManager("#PopupMenu"); //$NON-NLS-1$
menuMgr.setRemoveAllWhenShown(true);
menuMgr.addMenuListener(new IMenuListener() {
@Override
public void menuAboutToShow(IMenuManager manager) {
ReferencesView.this.fillContextMenu(manager);
}
});
Menu menu = menuMgr.createContextMenu(viewer.getControl());
viewer.getControl().setMenu(menu);
getSite().registerContextMenu(menuMgr, viewer);
}
private void contributeToActionBars() {
IActionBars bars = getViewSite().getActionBars();
fillLocalPullDown(bars.getMenuManager());
fillLocalToolBar(bars.getToolBarManager());
}
private void fillLocalPullDown(IMenuManager manager) {
for (ToggleReferencesModeAction toggleReferencesModeAction : toggleReferencesModeActions) {
manager.add(toggleReferencesModeAction);
if (toggleReferencesModeAction != toggleReferencesModeActions[toggleReferencesModeActions.length - 1]) {
manager.add(new Separator());
}
}
}
private void fillContextMenu(IMenuManager manager) {
for (ToggleReferencesModeAction toggleAction : toggleReferencesModeActions) {
manager.add(toggleAction);
}
manager.add(new Separator());
// Other plug-ins can contribute there actions here
manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
}
private void fillLocalToolBar(IToolBarManager manager) {
for (ToggleReferencesModeAction toggleAction : toggleReferencesModeActions) {
manager.add(toggleAction);
}
manager.add(new Separator());
}
private void makeActions() {
toggleReferencesModeActions = new ToggleReferencesModeAction[] { new ToggleReferencesModeAction(this, REFERENCED_OBJECTS_MODE),
new ToggleReferencesModeAction(this, REFERENCING_OBJECTS_MODE) };
doubleClickAction = new Action() {
@Override
public void run() {
ISelection selection = viewer.getSelection();
if (selection instanceof IStructuredSelection) {
Object obj = ((IStructuredSelection) selection).getFirstElement();
EcoreUIUtil.openEditor(getSite().getPage(), obj, OpenStrategy.OPEN_ON_RESOURCE);
}
}
};
}
private void hookDoubleClickAction() {
viewer.addDoubleClickListener(new IDoubleClickListener() {
@Override
public void doubleClick(DoubleClickEvent event) {
doubleClickAction.run();
}
});
}
private void initDragAndDrop() {
// TODO (aakar) Add Drag support if needed
// addDragAdapters(viewer);
addDropAdapters(viewer);
// DND on empty view
DropTarget dropTarget = new DropTarget(pageBook, DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_LINK | DND.DROP_DEFAULT);
dropTarget.setTransfer(new Transfer[] { LocalSelectionTransfer.getTransfer() });
dropTarget.addDropListener(new ReferencesHierarchyTransferDropAdapter(this, viewer));
}
private void addDropAdapters(StructuredViewer viewer) {
Transfer[] transfers = new Transfer[] { LocalSelectionTransfer.getTransfer() };
int ops = DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_LINK | DND.DROP_DEFAULT;
DelegatingDropAdapter delegatingDropAdapter = new DelegatingDropAdapter();
delegatingDropAdapter.addDropTargetListener(new ReferencesHierarchyTransferDropAdapter(this, viewer));
viewer.addDropSupport(ops, transfers, delegatingDropAdapter);
}
private void initMode() {
int mode;
try {
mode = dialogSettings.getInt(STORE_MODE);
if (mode < 0 || mode > 1) {
mode = REFERENCED_OBJECTS_MODE;
}
} catch (NumberFormatException e) {
mode = REFERENCED_OBJECTS_MODE;
}
// force update in setMode(int)
currentMode = -1;
setMode(mode);
}
@Override
public Object getAdapter(@SuppressWarnings("rawtypes") Class adapter) {
if (adapter.equals(IShowInSource.class)) {
return new IShowInSource() {
@Override
public ShowInContext getShowInContext() {
// Set the selection as input for the context so that the ExtendedCommonNavigator will show it and
// set the IFile behind the selection as the selection of the context so that the ProjectExplorer
// will be able to show the file.
return new ShowInContext(getViewer().getSelection(), getSelectedFiles(getViewer().getSelection()));
}
private ISelection getSelectedFiles(ISelection selection) {
if (selection instanceof IStructuredSelection) {
Set<IFile> selectedFiles = new HashSet<IFile>();
for (Object obj : ((IStructuredSelection) selection).toList()) {
IFile file = EcorePlatformUtil.getFile(obj);
if (file != null) {
selectedFiles.add(file);
}
}
return new StructuredSelection(selectedFiles.toArray());
}
return selection;
}
};
}
if (IPropertySheetPage.class == adapter) {
return getPropertySheetPage();
}
return super.getAdapter(adapter);
}
/**
* This creates a new property sheet page instance and manages it in the cache.
*/
protected IPropertySheetPage getPropertySheetPage() {
IPropertySheetPage propertySheetPage = new TabbedPropertySheetPage(this);
propertySheetPages.add(propertySheetPage);
return propertySheetPage;
}
/**
* Passing the focus request to the pageBook.
*/
@Override
public void setFocus() {
pageBook.setFocus();
}
/**
* Called from {@link ToggleReferencesModeAction}.
*
* @param mode
* {@link REFERENCED_OBJECTS_MODE } or {@link REFERENCING_OBJECTS_MODE}
*/
public void setMode(int mode) {
if (currentMode != mode) {
for (ToggleReferencesModeAction toggleReferencesModeAction : toggleReferencesModeActions) {
toggleReferencesModeAction.setChecked(mode == toggleReferencesModeAction.getMode());
}
currentMode = mode;
dialogSettings.put(STORE_MODE, mode);
updateView();
}
}
private void updateView() {
if (viewInput != null) {
showPage(PAGE_VIEWER);
// Clear the Graph so that when we switch between view modes, we will start from an empty graph.
graph.clear();
viewer.setInput(getViewerInput());
viewer.refresh();
} else {
showPage(PAGE_EMPTY);
}
}
@Override
public String getContributorId() {
return ICommonModelUIConstants.VIEW_REFERENCES_ID;
}
protected class WrappedViewInput {
private Object input;
protected WrappedViewInput(Object input) {
this.input = input;
}
public Object getInput() {
return input;
}
}
protected class DecorationAwareTransactionalAdapterFactoryLabelProvider extends TransactionalAdapterFactoryLabelProvider
implements ILabelDecorator {
private final ImageDescriptor RECURSIVE = Activator.getPlugin().getImageDescriptor("full/over16/recursive_ref.gif"); //$NON-NLS-1$
public DecorationAwareTransactionalAdapterFactoryLabelProvider(TransactionalEditingDomain domain, AdapterFactory adapterFactory) {
super(domain, adapterFactory);
}
@Override
public Image getImage(Object object) {
Image image = super.getImage(object);
return decorateImage(image, object);
}
@Override
public Image decorateImage(Image image, Object element) {
if (image != null && element instanceof EObject && isRecursive(element)) {
List<Image> images = new ArrayList<Image>();
images.add(image);
images.add(RECURSIVE.createImage());
ComposedImage composedImage = new DecoratedComposedImage(images);
return ExtendedImageRegistry.INSTANCE.getImage(composedImage);
}
return image;
}
@Override
public String decorateText(String text, Object element) {
return null;
}
}
private static final class DecoratedComposedImage extends ComposedImage {
private DecoratedComposedImage(Collection<?> images) {
super(images);
}
@Override
public List<Point> getDrawPoints(Size size) {
List<Point> result = new ArrayList<Point>();
result.add(new Point());
Point overlay = new Point();
overlay.y = 7;
result.add(overlay);
return result;
}
}
}