/***************************************************************************** = * Copyright (c) 2010 CEA LIST. * * * 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: * Patrick Tessier (CEA LIST) Patrick.tessier@cea.fr - Initial API and implementation * *****************************************************************************/ package org.eclipse.papyrus.views.modelexplorer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.core.commands.operations.IUndoContext; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.edit.domain.EditingDomain; import org.eclipse.emf.edit.domain.IEditingDomainProvider; import org.eclipse.emf.transaction.ResourceSetChangeEvent; import org.eclipse.emf.transaction.ResourceSetListener; import org.eclipse.emf.transaction.ResourceSetListenerImpl; import org.eclipse.emf.transaction.Transaction; import org.eclipse.emf.transaction.TransactionalEditingDomain; import org.eclipse.jface.viewers.ColumnViewerToolTipSupport; import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.window.ToolTip; import org.eclipse.papyrus.infra.core.editor.IMultiDiagramEditor; import org.eclipse.papyrus.infra.core.lifecycleevents.IEditorInputChangedListener; import org.eclipse.papyrus.infra.core.lifecycleevents.ISaveAndDirtyService; import org.eclipse.papyrus.infra.core.resource.ModelSet; import org.eclipse.papyrus.infra.core.resource.additional.AdditionalResourcesModel; import org.eclipse.papyrus.infra.core.sasheditor.contentprovider.IPageMngr; import org.eclipse.papyrus.infra.core.services.ServiceException; import org.eclipse.papyrus.infra.core.services.ServicesRegistry; import org.eclipse.papyrus.infra.core.ui.IRevealSemanticElement; import org.eclipse.papyrus.infra.core.utils.EditorUtils; import org.eclipse.papyrus.infra.core.utils.ServiceUtils; import org.eclipse.papyrus.infra.emf.providers.SemanticFromModelExplorer; import org.eclipse.papyrus.views.modelexplorer.listener.DoubleClickListener; import org.eclipse.papyrus.views.modelexplorer.matching.IMatchingItem; import org.eclipse.papyrus.views.modelexplorer.matching.LinkItemMatchingItem; import org.eclipse.papyrus.views.modelexplorer.matching.ModelElementItemMatchingItem; import org.eclipse.papyrus.views.modelexplorer.matching.ReferencableMatchingItem; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IMemento; import org.eclipse.ui.ISaveablePart; import org.eclipse.ui.ISelectionListener; import org.eclipse.ui.IViewSite; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.internal.navigator.NavigatorContentService; import org.eclipse.ui.internal.navigator.extensions.NavigatorContentDescriptor; import org.eclipse.ui.navigator.CommonNavigator; import org.eclipse.ui.navigator.CommonViewer; import org.eclipse.ui.operations.RedoActionHandler; import org.eclipse.ui.operations.UndoActionHandler; import org.eclipse.ui.part.FileEditorInput; import org.eclipse.ui.views.properties.IPropertySheetPage; import org.eclipse.ui.views.properties.tabbed.ITabbedPropertySheetPageContributor; import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetPage; import com.google.common.base.Function; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; /** * Papyrus Model Explorer associated to one {@link IMultiDiagramEditor}. * This ModelExplorer is linked to one single {@link IMultiDiagramEditor}. It doesn't change its * source when the current Editor change. To allow to explore different Model, use a {@link ModelExplorerPageBookView}. * */ public class ModelExplorerView extends CommonNavigator implements IRevealSemanticElement, IEditingDomainProvider { /** * The associated EditorPart * The View is associated to the ServicesRegistry rather than to an editor. * */ // private IMultiDiagramEditor editorPart; /** * The {@link ServicesRegistry} associated to the Editor. This view is associated to the * ServicesRegistry rather than to the EditorPart. */ private ServicesRegistry serviceRegistry; /** The save aservice associated to the editor. */ private ISaveAndDirtyService saveAndDirtyService; /** {@link IUndoContext} used to tag command in the commandStack. */ private IUndoContext undoContext; /** editing domain used to read/write the model */ private TransactionalEditingDomain editingDomain; /** Flag to avoid reentrant call to refresh. */ private AtomicBoolean isRefreshing = new AtomicBoolean(false); /** * A listener on page (all editors) selection change. This listener is set * in {@link ModelExplorerView#init(IViewSite)}. It should be dispose to remove * hook to the Eclipse page. */ private ISelectionListener pageSelectionListener = new ISelectionListener() { public void selectionChanged(IWorkbenchPart part, ISelection selection) { handleSelectionChangedFromDiagramEditor(part, selection); } }; /** * Listener on {@link ISaveAndDirtyService#addInputChangedListener(IEditorInputChangedListener)} */ protected IEditorInputChangedListener editorInputChangedListener = new IEditorInputChangedListener() { /** * This method is called when the editor input is changed from the ISaveAndDirtyService. * * @see org.eclipse.papyrus.infra.core.lifecycleevents.IEditorInputChangedListener#editorInputChanged(org.eclipse.ui.part.FileEditorInput) * * @param fileEditorInput */ public void editorInputChanged(FileEditorInput fileEditorInput) { // Change the editor input. setPartName(fileEditorInput.getName()); } /** * The isDirty flag has changed, reflect its new value * * @see org.eclipse.papyrus.infra.core.lifecycleevents.IEditorInputChangedListener#isDirtyChanged() * */ public void isDirtyChanged() { firePropertyChange(IEditorPart.PROP_DIRTY); } }; /** Undo action handler */ UndoActionHandler undoHandler; /** Redo action handler */ RedoActionHandler redoHandler; /** The {@link IPropertySheetPage} this model explorer will use. */ private IPropertySheetPage propertySheetPage = null; /** * * Constructor. * * @param part * The part associated to this ModelExplorer */ public ModelExplorerView(IMultiDiagramEditor part) { if(part == null) { throw new IllegalArgumentException("A part should be provided."); } // Try to get the ServicesRegistry serviceRegistry = part.getServicesRegistry(); if(serviceRegistry == null) { throw new IllegalArgumentException("The part should have a ServiceRegistry."); } setLinkingEnabled(true); // Get required services from ServicesRegistry try { saveAndDirtyService = serviceRegistry.getService(ISaveAndDirtyService.class); undoContext = serviceRegistry.getService(IUndoContext.class); } catch (ServiceException e) { e.printStackTrace(); } } /** * Handle a selection change in the editor. * * @param part * @param selection */ private void handleSelectionChangedFromDiagramEditor(IWorkbenchPart part, ISelection selection) { // Handle selection from diagram editor if(isLinkingEnabled()) { if(part instanceof IEditorPart) { if(selection instanceof IStructuredSelection) { Iterator<?> selectionIterator = ((IStructuredSelection)selection).iterator(); ArrayList<Object> semanticElementList = new ArrayList<Object>(); while(selectionIterator.hasNext()) { Object currentSelection = selectionIterator.next(); if(currentSelection instanceof IAdaptable) { Object semanticElement = ((IAdaptable)currentSelection).getAdapter(EObject.class); if(semanticElement != null) { semanticElementList.add(semanticElement); } } } revealSemanticElement(semanticElementList); } } } } /** * look for the path the list of element (comes from the content provider) to go the eObject * * @param eobject * that we look for. * @param objects * a list of elements where eobject can be wrapped. * @return the list of modelElementItem ( from the root to the element that wrap the eobject) */ protected List<Object> searchPath(EObject eobject, List<Object> objects) { SemanticFromModelExplorer semanticGetter = new SemanticFromModelExplorer(); List<Object> path = new ArrayList<Object>(); ITreeContentProvider contentProvider = (ITreeContentProvider)getCommonViewer().getContentProvider(); // IPageMngr iPageMngr = EditorUtils.getIPageMngr(); IPageMngr iPageMngr; try { iPageMngr = ServiceUtils.getInstance().getIPageMngr(serviceRegistry); } catch (ServiceException e) { // This shouldn't happen. return Collections.emptyList(); } Object[] result = iPageMngr.allPages().toArray(); List<Object> editors = Arrays.asList(result); for(Object o : objects) { // Search matches in this level // if(!(o instanceof Diagram) && o instanceof IAdaptable) { if(!editors.contains(o) && o instanceof IAdaptable) { if(eobject.equals(((IAdaptable)o).getAdapter(EObject.class))) { path.add(o); return path; } } // Find childs only for feature container for(int i = 0; i < contentProvider.getChildren(o).length; i++) { Object treeItem = contentProvider.getChildren(o)[i]; List<Object> tmppath = new ArrayList<Object>(); Object element = semanticGetter.getSemanticElement(treeItem); if(element != null) { if(element instanceof EReference) { if(((EReference)element).isContainment() && (!((EReference)element).isDerived())) { List<Object> childs = new ArrayList<Object>(); childs.add(treeItem); tmppath = searchPath(eobject, childs); } } else { if(element instanceof EObject) { List<Object> childs = new ArrayList<Object>(); childs.add(treeItem); tmppath = searchPath(eobject, childs); } } } // if tmppath contains the wrapped eobject we have find the good path if(tmppath.size() > 0) { if(tmppath.get(tmppath.size() - 1) instanceof IAdaptable) { if(eobject.equals(((IAdaptable)(tmppath.get(tmppath.size() - 1))).getAdapter(EObject.class))) { path.add(o); path.addAll(tmppath); return path; } } } } } return new ArrayList<Object>(); } /** * {@inheritDoc} */ // FIXME Use of internal class (NavigatorContentService) - in the hope that the bug gets fixed soon. @Override protected CommonViewer createCommonViewerObject(Composite aParent) { CommonViewer viewer = new CustomCommonViewer(getViewSite().getId(), aParent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); // enable tool-tips // workaround for bug 311827: the Common Viewer always uses NavigatorDecoratingLabelProvider // as a wrapper for the LabelProvider provided by the application. The NavigatorDecoratingLabelProvider // does not delegate tooltip related functions but defines them as empty. NavigatorContentService contentService = new NavigatorContentService(getViewSite().getId()); @SuppressWarnings("unchecked") // get label provider from content service (which in turn evaluates extension points in // function of the input) Set<Object> descriptors = contentService.findDescriptorsByTriggerPoint(getInitialInput(), false); for(Object descriptor : descriptors) { if(descriptor instanceof NavigatorContentDescriptor) { try { ILabelProvider labelProvider = ((NavigatorContentDescriptor)descriptor).createLabelProvider(); viewer.setLabelProvider(new DecoratingLabelProviderWTooltips(labelProvider)); // add for decorator and tooltip support } catch (CoreException e) { Activator.log.error(e); } break; } } ColumnViewerToolTipSupport.enableFor(viewer, ToolTip.NO_RECREATE); return viewer; } @Override public void createPartControl(Composite aParent) { super.createPartControl(aParent); getCommonViewer().setSorter(null); ((CustomCommonViewer)getCommonViewer()).getDropAdapter().setFeedbackEnabled(true); getCommonViewer().addDoubleClickListener(new DoubleClickListener()); Tree tree = getCommonViewer().getTree(); Activator.getDefault().getCustomizationManager().installCustomPainter(tree); } /** * Return the control used to render this View * * @see org.eclipse.papyrus.views.modelexplorer.core.ui.pagebookview.IPageBookNestableViewPart#getControl() * * @return the main control of the navigator viewer */ public Control getControl() { return getCommonViewer().getControl(); } /** * {@inheritDoc} */ @Override public void init(IViewSite site, IMemento aMemento) throws PartInitException { super.init(site, aMemento); // Hook undo/redo action // hookGlobalHistoryHandler(site); // page.addPartListener(partListener); activate(); } /** * {@inheritDoc} */ @Override public void init(IViewSite site) throws PartInitException { super.init(site); IWorkbenchPage page = site.getPage(); // an ISelectionListener to react to workbench selection changes. page.addSelectionListener(pageSelectionListener); } /** * Hook the global undo/redi actions. */ // private void hookGlobalHistoryHandler(IViewSite site) { // undoHandler = new UndoActionHandler(site, null); // redoHandler = new RedoActionHandler(site, null); // // IActionBars actionBars = site.getActionBars(); // // actionBars.setGlobalActionHandler(ActionFactory.UNDO.getId(), undoHandler); // actionBars.setGlobalActionHandler(ActionFactory.REDO.getId(), redoHandler); // } /** * {@link ResourceSetListener} to listen and react to changes in the * resource set. */ private final ResourceSetListener resourceSetListener = new ResourceSetListenerImpl() { /** * {@inheritDoc} */ @Override public void resourceSetChanged(ResourceSetChangeEvent event) { super.resourceSetChanged(event); handleResourceSetChanged(event); } }; /** cache variable with last transaction which triggered a refresh */ private Transaction lastTrans = null; /** * Run in a UI thread to avoid non UI thread exception. * * @param event */ private void handleResourceSetChanged(ResourceSetChangeEvent event) { // avoid refreshing N times for the same transaction (called for each object in resource) Transaction curTrans = event.getTransaction(); if(lastTrans != null && lastTrans.equals(curTrans)) { return; } lastTrans = curTrans; PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() { /** * {@inheritDoc} */ public void run() { refresh(); } }); } /** * refresh the view. */ public void refresh() { // Need to refresh, even if (temporarily) invisible // (Better alternative?: store refresh event and execute once visible again) if(getControl().isDisposed()) { return; } // avoid reentrant call // Refresh only of we are not already refreshing. if(isRefreshing.compareAndSet(false, true)) { if(!getCommonViewer().isBusy()) { getCommonViewer().refresh(); } isRefreshing.set(false); } } /** * {@inheritDoc} */ @Override protected Object getInitialInput() { if(serviceRegistry != null) { return serviceRegistry; } else { return super.getInitialInput(); } } /** * Activate specified Part. */ private void activate() { try { this.editingDomain = ServiceUtils.getInstance().getTransactionalEditingDomain(serviceRegistry); // this.editingDomain = EditorUtils.getTransactionalEditingDomain(editorPart.getServicesRegistry()); // Set Viewer input if it already exist if(getCommonViewer() != null) { getCommonViewer().setInput(serviceRegistry); } editingDomain.addResourceSetListener(resourceSetListener); } catch (ServiceException e) { // Can't get EditingDomain, skip } // Listen to isDirty flag saveAndDirtyService.addInputChangedListener(editorInputChangedListener); // Hook // if(undoHandler != null){ // IUndoContext undoContext = getUndoContext(part); // undoHandler.setContext(undoContext); // undoHandler.update(); // redoHandler.setContext(undoContext); // redoHandler.update(); // } if(this.getCommonViewer() != null) { refresh(); } } /** * Deactivate the Model Explorer. */ private void deactivate() { // deactivate global handler if(Activator.log.isDebugEnabled()) { Activator.log.debug("deactivate ModelExplorerView"); //$NON-NLS-1$ } // Stop listening on change events getSite().getPage().removeSelectionListener(pageSelectionListener); // Stop Listening to isDirty flag saveAndDirtyService.removeInputChangedListener(editorInputChangedListener); // unhook // IUndoContext undoContext = getUndoContext(editorPart); // undoHandler.setContext(undoContext); // undoHandler.update(); // redoHandler.setContext(undoContext); // redoHandler.update(); if(editingDomain != null) { editingDomain.removeResourceSetListener(resourceSetListener); editingDomain = null; } } /** * {@inheritDoc} */ @Override public void dispose() { // Stop if we are already disposed if(isDisposed()) { return; } deactivate(); saveAndDirtyService = null; undoContext = null; editingDomain = null; serviceRegistry = null; super.dispose(); // Clean up properties to help GC } /** * Return true if the component is already disposed. * * @return */ public boolean isDisposed() { // use editorPart as flag return saveAndDirtyService == null; } /** * Retrieves the {@link IPropertySheetPage} that his Model Explorer uses. * * @return */ private IPropertySheetPage getPropertySheetPage() { final IMultiDiagramEditor multiDiagramEditor = EditorUtils.getMultiDiagramEditor(); if(multiDiagramEditor != null) { if(propertySheetPage == null) { if(multiDiagramEditor instanceof ITabbedPropertySheetPageContributor) { ITabbedPropertySheetPageContributor contributor = (ITabbedPropertySheetPageContributor)multiDiagramEditor; this.propertySheetPage = new TabbedPropertySheetPage(contributor); } } return propertySheetPage; } return null; } /** * in order to see element if the property view */ @Override @SuppressWarnings("rawtypes") public Object getAdapter(Class adapter) { if(IPropertySheetPage.class.equals(adapter)) { return getPropertySheetPage(); } if(IUndoContext.class == adapter) { // Return the IUndoContext of associated model. return undoContext; } if(ISaveablePart.class.equals(adapter)) { return saveAndDirtyService; } if(ServicesRegistry.class == adapter) { return serviceRegistry; } return super.getAdapter(adapter); } /** * {@inheritDoc} * * @return the EditingDomain used by the properties view */ public EditingDomain getEditingDomain() { return editingDomain; } /** * {@inheritDoc} */ @Override public void selectReveal(ISelection selection) { if(getCommonViewer() != null) { getCommonViewer().setSelection(selection, true); } } public void revealSemanticElement(List<?> elementList) { reveal(elementList, getCommonViewer()); } /** * Expands the given CommonViewer to reveal the given elements * @param elementList The elements to reveal * @param commonViewer The CommonViewer they are to be revealed in */ public static void reveal(Iterable<?> elementList, CommonViewer commonViewer) { ArrayList<IMatchingItem> matchingItemsToSelect = new ArrayList<IMatchingItem>(); // filter out non EMF objects Iterable<EObject> list = Iterables.transform(Iterables.filter(elementList, EObject.class), new Function<Object, EObject>() { public EObject apply(Object from) { return (EObject)from; } }); for(EObject currentEObject : list) { matchingItemsToSelect.add(new ModelElementItemMatchingItem(currentEObject)); // the content provider exist? if(commonViewer.getContentProvider() != null) { // retrieve the ancestors to reveal them // and allow the selection of the object ArrayList<EObject> parents = new ArrayList<EObject>(); EObject tmp = currentEObject.eContainer(); while(tmp != null) { parents.add(tmp); tmp = tmp.eContainer(); } Iterable<EObject> reverseParents = Iterables.reverse(parents); // reveal the resource if necessary Resource r = null; if (!parents.isEmpty()) { r = parents.get(parents.size() - 1).eResource(); } else { r = currentEObject.eResource(); } if (r != null) { ResourceSet rs = r.getResourceSet(); if (rs instanceof ModelSet && AdditionalResourcesModel.isAdditionalResource((ModelSet)rs, r.getURI())) { commonViewer.expandToLevel(new ReferencableMatchingItem(rs), 1); commonViewer.expandToLevel(new ReferencableMatchingItem(r), 1); } } /* * reveal the ancestors tree using expandToLevel on each of them * in the good order. This is a lot faster than going through the whole tree * using getChildren of the ContentProvider since our Viewer uses a Hashtable * to keep track of the revealed elements. * * However we need to use a dedicated MatchingItem to do the matching, * and a specific comparer in our viewer so than the equals of MatchingItem is * used in priority. * * Please refer to MatchingItem for more infos. */ EObject previousParent = null; for(EObject parent : reverseParents) { if(parent.eContainingFeature() != null && previousParent != null) { commonViewer.expandToLevel(new LinkItemMatchingItem(previousParent, parent.eContainmentFeature()), 1); } commonViewer.expandToLevel(new ModelElementItemMatchingItem(parent), 1); previousParent = parent; } commonViewer.expandToLevel(new LinkItemMatchingItem(currentEObject.eContainer(), currentEObject.eContainmentFeature()), 1); } } selectReveal(new StructuredSelection(matchingItemsToSelect), commonViewer); } /** * Selects the given ISelection in the given CommonViwer * @param structuredSelection The ISelection to select * @param commonViewer The ComonViewer to select it in */ public static void selectReveal(ISelection structuredSelection, Viewer commonViewer) { commonViewer.setSelection(structuredSelection, true); } /** * Selects and, if possible, reveals the given ISelection in the given CommonViwer * @param selection The ISelection to select * @param viewer The ComonViewer to select it in */ public static void reveal(ISelection selection, CommonViewer viewer) { if(selection instanceof IStructuredSelection) { IStructuredSelection structured = (IStructuredSelection)selection; reveal(Lists.newArrayList(structured.iterator()), viewer); } else { viewer.setSelection(selection); } } }