From dfde88b139d345b628415dda0769aa87dd7733c6 Mon Sep 17 00:00:00 2001 From: Christian W. Damus Date: Wed, 18 Jun 2014 11:57:18 -0400 Subject: 437217: [Editors] In-place reloading of model resources in the editors https://bugs.eclipse.org/bugs/show_bug.cgi?id=437217 In situ editor reloading. Introduces an IReloadableEditor adapter protocol with an implementation in the CoreMultiDiagramEditor that implements internal destruction of the ServicesRegistry and nested editors. Some refactoring of the initialization and disposal code in the editor class hierarchy and dependencies facilitates reuse of init/dispose code in the reload scenario. The re-loading of an editor is deferred until it is next activated, unless it is already the active editor (can happen when "Save All" is invoked). Editor re-load notifications to dependent views like Model Explorer and Outline. A new listener protocol informs dependents before and after reload so that they may properly dispose of obsolete state and re-initialize when the editor is reloaded. Also ensure that an editor is only reloaded once when some resource that it depends on has changed, not once for each resource. State restoration tokens. Re-load listeners can insert tokens into the re-load event that capture state to be restored after the re-load. Listeners retrieve and apply these tokens after the editor re-loads itself. Current state restoration includes: - tree node expansion and selection state in the Model Explorer view - diagram outline view: which presentation (tree or overview thumbnail) is active - which workbench part is active, such that the correct selection is reflected in views such as Model Explorer, Outline, and Properties - current active diagram in the re-loaded editor - edit-part selections in all diagrams - selection (columns and rows, not individual cells) in table editors - palettes in each diagram (or palette pages when the Palette View is open): * active tool * pinnable stack tool selection * drawer expansion state * drawer scroll position The Palette View support incidentally fixes loss of palette state when switching between Papyrus editors, caused by the PapyrusPaletteSynchronizer. JUnit regression tests for various aspects of editor re-load. Includes a fix for an NPE in the Validation View's content provider that occurs in several tests when an editor is closed or re-loaded. Also support for tests that need to load more than one test-fixture model and/or open more than one editor. Change-Id: Ic0f654ab138d3e091f81f1e9159bcca80d6bb0a5 --- .../ModelExplorerTreeViewerContext.java | 141 +++++++++++++++++ .../views/modelexplorer/ModelExplorerView.java | 173 ++++++++++++--------- .../providers/ProblemsContentProvider.java | 95 ++++++----- 3 files changed, 287 insertions(+), 122 deletions(-) create mode 100644 plugins/views/modelexplorer/org.eclipse.papyrus.views.modelexplorer/src/org/eclipse/papyrus/views/modelexplorer/ModelExplorerTreeViewerContext.java (limited to 'plugins/views') diff --git a/plugins/views/modelexplorer/org.eclipse.papyrus.views.modelexplorer/src/org/eclipse/papyrus/views/modelexplorer/ModelExplorerTreeViewerContext.java b/plugins/views/modelexplorer/org.eclipse.papyrus.views.modelexplorer/src/org/eclipse/papyrus/views/modelexplorer/ModelExplorerTreeViewerContext.java new file mode 100644 index 00000000000..e81398f494a --- /dev/null +++ b/plugins/views/modelexplorer/org.eclipse.papyrus.views.modelexplorer/src/org/eclipse/papyrus/views/modelexplorer/ModelExplorerTreeViewerContext.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2010, 2014 CEA 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: + * CEA LIST - Initial API and implementation + * Christian W. Damus (CEA) - adapted from ModelExplorerView::reveal(...) API + * + */ +package org.eclipse.papyrus.views.modelexplorer; + +import java.util.ArrayList; +import java.util.Collection; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.jface.viewers.AbstractTreeViewer; +import org.eclipse.papyrus.infra.core.editor.reload.EMFTreeViewerContext; +import org.eclipse.papyrus.infra.core.resource.ModelSet; +import org.eclipse.papyrus.infra.core.resource.additional.AdditionalResourcesModel; +import org.eclipse.papyrus.infra.emf.utils.EMFHelper; +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 com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + + +/** + * A specialization of the editor re-load tree viewer context that knows how to expand and select nodes + * in the EMF Facet-based Model Explorer view. + */ +class ModelExplorerTreeViewerContext extends EMFTreeViewerContext { + + public ModelExplorerTreeViewerContext(AbstractTreeViewer viewer) { + super(viewer); + } + + public Object deresolveSelectableElement(Object selectableElement) { + return EMFHelper.getEObject(selectableElement); + } + + public Object resolveSelectableElement(Object object) { + return new ModelElementItemMatchingItemWithElement((EObject)object); + } + + @Override + protected void setExpandedElements(AbstractTreeViewer viewer, Collection toExpand) { + // EMF Facet makes expanding tree elements very complicated + if(viewer.getContentProvider() != null) { + for(ModelElementItemMatchingItemWithElement next : Iterables.filter(toExpand, ModelElementItemMatchingItemWithElement.class)) { + + // retrieve the ancestors to reveal them + // and allow the selection of the object + EObject currentEObject = next.element(); + ArrayList parents = new ArrayList(); + EObject tmp = currentEObject.eContainer(); + while(tmp != null) { + parents.add(tmp); + tmp = tmp.eContainer(); + } + + Iterable reverseParents = Lists.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) { + final ResourceSet rs = r.getResourceSet(); + final Resource resource = r; + if(rs instanceof ModelSet && AdditionalResourcesModel.isAdditionalResource((ModelSet)rs, r.getURI())) { + viewer.expandToLevel(new ReferencableMatchingItem(rs), 1); + viewer.expandToLevel(new ReferencableMatchingItem(resource), 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) { + viewer.expandToLevel(new LinkItemMatchingItem(previousParent, parent.eContainmentFeature()), 1); + } + + final IMatchingItem itemToExpand = new ModelElementItemMatchingItem(parent); + viewer.expandToLevel(itemToExpand, 1); + + previousParent = parent; + } + + // expand a reference-link item, if necessary + final IMatchingItem linkItem = new LinkItemMatchingItem(currentEObject.eContainer(), currentEObject.eContainmentFeature()); + viewer.expandToLevel(linkItem, 1); + + // and the actual element + viewer.expandToLevel(next, 1); + } + } + } + + // + // Nested types + // + + static class ModelElementItemMatchingItemWithElement extends ModelElementItemMatchingItem { + + private EObject element; + + ModelElementItemMatchingItemWithElement(EObject element) { + super(element); + + this.element = element; + } + + EObject element() { + return element; + } + } +} diff --git a/plugins/views/modelexplorer/org.eclipse.papyrus.views.modelexplorer/src/org/eclipse/papyrus/views/modelexplorer/ModelExplorerView.java b/plugins/views/modelexplorer/org.eclipse.papyrus.views.modelexplorer/src/org/eclipse/papyrus/views/modelexplorer/ModelExplorerView.java index 7f2a6262cf1..b7f71045b88 100644 --- a/plugins/views/modelexplorer/org.eclipse.papyrus.views.modelexplorer/src/org/eclipse/papyrus/views/modelexplorer/ModelExplorerView.java +++ b/plugins/views/modelexplorer/org.eclipse.papyrus.views.modelexplorer/src/org/eclipse/papyrus/views/modelexplorer/ModelExplorerView.java @@ -12,6 +12,7 @@ * Christian W. Damus (CEA) - post refreshes for transaction commit asynchronously (CDO) * Christian W. Damus (CEA) - bug 429826 * Christian W. Damus (CEA) - bug 434635 + * Christian W. Damus (CEA) - bug 437217 * *****************************************************************************/ package org.eclipse.papyrus.views.modelexplorer; @@ -53,6 +54,10 @@ import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerColumn; import org.eclipse.jface.window.ToolTip; import org.eclipse.papyrus.infra.core.editor.IMultiDiagramEditor; +import org.eclipse.papyrus.infra.core.editor.IReloadableEditor; +import org.eclipse.papyrus.infra.core.editor.reload.EditorReloadAdapter; +import org.eclipse.papyrus.infra.core.editor.reload.EditorReloadEvent; +import org.eclipse.papyrus.infra.core.editor.reload.TreeViewerContext; import org.eclipse.papyrus.infra.core.lifecycleevents.IEditorInputChangedListener; import org.eclipse.papyrus.infra.core.lifecycleevents.ISaveAndDirtyService; import org.eclipse.papyrus.infra.core.resource.IReadOnlyHandler2; @@ -127,7 +132,7 @@ import com.google.common.collect.Lists; public class ModelExplorerView extends CommonNavigator implements IRevealSemanticElement, IEditingDomainProvider, IPageLifeCycleEventsListener { private SharedModelExplorerState sharedState; - + private SharedModelExplorerState.StateChangedListener sharedStateListener; /** @@ -137,17 +142,11 @@ public class ModelExplorerView extends CommonNavigator implements IRevealSemanti */ public static final String LABEL_PROVIDER_SERVICE_CONTEXT = "org.eclipse.papyrus.views.modelexplorer.labelProvider.context"; - /** - * 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 final ServicesRegistry serviceRegistry; + private ServicesRegistry serviceRegistry; /** The save aservice associated to the editor. */ private ISaveAndDirtyService saveAndDirtyService; @@ -217,10 +216,37 @@ public class ModelExplorerView extends CommonNavigator implements IRevealSemanti throw new IllegalArgumentException("A part should be provided."); } + init(part); + + IReloadableEditor.Adapter.getAdapter(part).addEditorReloadListener(new EditorReloadAdapter() { + + @Override + public void editorAboutToReload(EditorReloadEvent event) { + // Stash expansion and selection state of the common viewer + event.putContext(new ModelExplorerTreeViewerContext(getCommonViewer())); + + deactivate(); + } + + @Override + public void editorReloaded(EditorReloadEvent event) { + init(event.getEditor()); + + activate(); + + initCommonViewer(getCommonViewer()); + + // Restore expansion and selection state of the common viewer + ((TreeViewerContext)event.getContext()).restore(getCommonViewer()); + } + }); + } + + private void init(IMultiDiagramEditor editor) { // Try to get the ServicesRegistry - serviceRegistry = part.getServicesRegistry(); + serviceRegistry = editor.getServicesRegistry(); if(serviceRegistry == null) { - throw new IllegalArgumentException("The part should have a ServiceRegistry."); + throw new IllegalArgumentException("The editor should have a ServiceRegistry."); } // Get required services from ServicesRegistry @@ -228,7 +254,7 @@ public class ModelExplorerView extends CommonNavigator implements IRevealSemanti saveAndDirtyService = serviceRegistry.getService(ISaveAndDirtyService.class); undoContext = serviceRegistry.getService(IUndoContext.class); } catch (ServiceException e) { - e.printStackTrace(); + Activator.log.error(e); } } @@ -341,6 +367,41 @@ public class ModelExplorerView extends CommonNavigator implements IRevealSemanti @Override protected CommonViewer createCommonViewerObject(Composite aParent) { CommonViewer viewer = new CustomCommonViewer(getViewSite().getId(), aParent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); + + initCommonViewer(viewer); + + viewer.getNavigatorContentService().getActivationService().addExtensionActivationListener(new IExtensionActivationListener() { + + public void onExtensionActivation(String aViewerId, String[] theNavigatorExtensionIds, boolean isActive) { + sharedState.updateNavigatorContentExtensions(theNavigatorExtensionIds, isActive); + } + }); + + ColumnViewerToolTipSupport.enableFor(viewer, ToolTip.NO_RECREATE); + + return viewer; + } + + private void installEMFFacetTreePainter(Tree tree) { + // Install the EMFFacet Custom Tree Painter + //org.eclipse.papyrus.infra.emf.Activator.getDefault().getCustomizationManager().installCustomPainter(tree); + + // The EMF Facet MeasureItem Listener is incompatible with the NavigatorDecoratingLabelProvider. Remove it. + // Symptoms: ModelElementItems with an EMF Facet Overlay have a small selection size + // Removal also fixes bug 400012: no scrollbar although tree is larger than visible area + Collection listenersToRemove = new LinkedList(); + for(Listener listener : tree.getListeners(SWT.MeasureItem)) { + if(listener.getClass().getName().contains("org.eclipse.papyrus.emf.facet.infra.browser.uicore.internal.CustomTreePainter")) { + listenersToRemove.add(listener); + } + } + + for(Listener listener : listenersToRemove) { + tree.removeListener(SWT.MeasureItem, listener); + } + } + + private void initCommonViewer(CommonViewer viewer) { // 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 @@ -375,36 +436,6 @@ public class ModelExplorerView extends CommonNavigator implements IRevealSemanti } } contentService.dispose(); // No longer need this - - viewer.getNavigatorContentService().getActivationService().addExtensionActivationListener(new IExtensionActivationListener() { - - public void onExtensionActivation(String aViewerId, String[] theNavigatorExtensionIds, boolean isActive) { - sharedState.updateNavigatorContentExtensions(theNavigatorExtensionIds, isActive); - } - }); - - ColumnViewerToolTipSupport.enableFor(viewer, ToolTip.NO_RECREATE); - - return viewer; - } - - private void installEMFFacetTreePainter(Tree tree) { - // Install the EMFFacet Custom Tree Painter - //org.eclipse.papyrus.infra.emf.Activator.getDefault().getCustomizationManager().installCustomPainter(tree); - - // The EMF Facet MeasureItem Listener is incompatible with the NavigatorDecoratingLabelProvider. Remove it. - // Symptoms: ModelElementItems with an EMF Facet Overlay have a small selection size - // Removal also fixes bug 400012: no scrollbar although tree is larger than visible area - Collection listenersToRemove = new LinkedList(); - for(Listener listener : tree.getListeners(SWT.MeasureItem)) { - if(listener.getClass().getName().contains("org.eclipse.papyrus.emf.facet.infra.browser.uicore.internal.CustomTreePainter")) { - listenersToRemove.add(listener); - } - } - - for(Listener listener : listenersToRemove) { - tree.removeListener(SWT.MeasureItem, listener); - } } @Override @@ -625,6 +656,19 @@ public class ModelExplorerView extends CommonNavigator implements IRevealSemanti super.init(site, aMemento); activate(); + + // Self-listen for property changes + addPropertyListener(new IPropertyListener() { + + public void propertyChanged(Object source, int propId) { + switch(propId) { + case IS_LINKING_ENABLED_PROPERTY: + // Propagate to other instances + sharedState.setLinkingEnabled(isLinkingEnabled()); + break; + } + } + }); } /** @@ -753,8 +797,6 @@ public class ModelExplorerView extends CommonNavigator implements IRevealSemanti * Activate specified Part. */ private void activate() { - - try { this.editingDomain = ServiceUtils.getInstance().getTransactionalEditingDomain(serviceRegistry); @@ -777,19 +819,6 @@ public class ModelExplorerView extends CommonNavigator implements IRevealSemanti if(this.getCommonViewer() != null) { syncRefresh(); } - - // Self-listen for property changes - addPropertyListener(new IPropertyListener() { - - public void propertyChanged(Object source, int propId) { - switch(propId) { - case IS_LINKING_ENABLED_PROPERTY: - // Propagate to other instances - sharedState.setLinkingEnabled(isLinkingEnabled()); - break; - } - } - }); } /** @@ -801,6 +830,15 @@ public class ModelExplorerView extends CommonNavigator implements IRevealSemanti Activator.log.debug("deactivate ModelExplorerView"); //$NON-NLS-1$ } + try { + ISashWindowsContainer sashWindowsContainer = serviceRegistry.getService(ISashWindowsContainer.class); + if(sashWindowsContainer != null) { + sashWindowsContainer.removePageLifeCycleListener(this); + } + } catch (ServiceException ex) { + //Ignore + } + // Stop listening on change events getSite().getPage().removeSelectionListener(pageSelectionListener); // Stop Listening to isDirty flag @@ -811,6 +849,11 @@ public class ModelExplorerView extends CommonNavigator implements IRevealSemanti editingDomain = null; } + saveAndDirtyService = null; + undoContext = null; + editingDomain = null; + editingDomain = null; + lastTrans = null; } /** @@ -828,31 +871,19 @@ public class ModelExplorerView extends CommonNavigator implements IRevealSemanti sharedState.removeListener(sharedStateListener); } - try { - ISashWindowsContainer sashWindowsContainer = serviceRegistry.getService(ISashWindowsContainer.class); - if(sashWindowsContainer != null) { - sashWindowsContainer.removePageLifeCycleListener(this); - } - } catch (ServiceException ex) { - //Ignore + if(getSite() != null) { + getSite().getPage().removeSelectionListener(pageSelectionListener); } deactivate(); - saveAndDirtyService = null; - undoContext = null; - editingDomain = null; - pageSelectionListener = null; - editingDomain = null; - lastTrans = null; - for(IPropertySheetPage propertySheetPage : this.propertySheetPages) { propertySheetPage.dispose(); } propertySheetPages.clear(); - + pageSelectionListener = null; super.dispose(); diff --git a/plugins/views/validation/org.eclipse.papyrus.views.validation/src/org/eclipse/papyrus/views/validation/internal/providers/ProblemsContentProvider.java b/plugins/views/validation/org.eclipse.papyrus.views.validation/src/org/eclipse/papyrus/views/validation/internal/providers/ProblemsContentProvider.java index 23b5f5c347c..c47c5d83a33 100644 --- a/plugins/views/validation/org.eclipse.papyrus.views.validation/src/org/eclipse/papyrus/views/validation/internal/providers/ProblemsContentProvider.java +++ b/plugins/views/validation/org.eclipse.papyrus.views.validation/src/org/eclipse/papyrus/views/validation/internal/providers/ProblemsContentProvider.java @@ -1,5 +1,5 @@ /***************************************************************************** - * Copyright (c) 2013 CEA LIST. + * Copyright (c) 2013, 2014 CEA LIST and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -8,6 +8,8 @@ * * Contributors: * CEA LIST - Initial API and implementation + * Christian W. Damus (CEA) - bug 437217 + * *****************************************************************************/ package org.eclipse.papyrus.views.validation.internal.providers; @@ -31,8 +33,7 @@ import com.google.common.collect.Iterables; /** * This is the ProblemsContentProvider type. Enjoy. */ -public class ProblemsContentProvider - implements IStructuredContentProvider { +public class ProblemsContentProvider implements IStructuredContentProvider { private static final Object[] NONE = {}; @@ -53,31 +54,30 @@ public class ProblemsContentProvider } public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - this.viewer = (AbstractTableViewer) viewer; + this.viewer = (AbstractTableViewer)viewer; - if (oldInput instanceof ValidationMarkersService) { - ValidationMarkersService service = (ValidationMarkersService) oldInput; + if(oldInput instanceof ValidationMarkersService) { + ValidationMarkersService service = (ValidationMarkersService)oldInput; unhookMarkers(service); - unhookResourceSet(service.getModelSet() - .getTransactionalEditingDomain()); + + // The old service may have been disposed if its editor was closed + if(service.getModelSet() != null) { + unhookResourceSet(service.getModelSet().getTransactionalEditingDomain()); + } + this.service = null; } - if (newInput instanceof ValidationMarkersService) { - ValidationMarkersService service = (ValidationMarkersService) newInput; + if(newInput instanceof ValidationMarkersService) { + ValidationMarkersService service = (ValidationMarkersService)newInput; this.service = service; hookMarkers(service); - hookResourceSet(service.getModelSet() - .getTransactionalEditingDomain()); + hookResourceSet(service.getModelSet().getTransactionalEditingDomain()); } } public Object[] getElements(Object inputElement) { - return (inputElement instanceof ValidationMarkersService) - ? Iterables.toArray( - ((ValidationMarkersService) inputElement).getMarkers(), - IPapyrusMarker.class) - : NONE; + return (inputElement instanceof ValidationMarkersService) ? Iterables.toArray(((ValidationMarkersService)inputElement).getMarkers(), IPapyrusMarker.class) : NONE; } protected void hookMarkers(ValidationMarkersService service) { @@ -89,19 +89,18 @@ public class ProblemsContentProvider } private IValidationMarkerListener getValidationMarkerListener() { - if (listener == null) { + if(listener == null) { listener = new IValidationMarkerListener() { - public void notifyMarkerChange(IPapyrusMarker marker, - MarkerChangeKind kind) { - if (viewer != null) { - switch (kind) { - case ADDED : - viewer.add(marker); - break; - case REMOVED : - viewer.remove(marker); - break; + public void notifyMarkerChange(IPapyrusMarker marker, MarkerChangeKind kind) { + if(viewer != null) { + switch(kind) { + case ADDED: + viewer.add(marker); + break; + case REMOVED: + viewer.remove(marker); + break; } } } @@ -120,42 +119,36 @@ public class ProblemsContentProvider } private ResourceSetListener getResourceSetListener() { - if (resourceSetListener == null) { + if(resourceSetListener == null) { resourceSetListener = new DemultiplexingListener() { @Override - protected void handleNotification( - TransactionalEditingDomain domain, - Notification notification) { + protected void handleNotification(TransactionalEditingDomain domain, Notification notification) { // handle containment changes of problem elements to update // labels Object feature = notification.getFeature(); - if ((feature instanceof EReference) - && ((EReference) feature).isContainment()) { - - switch (notification.getEventType()) { - case Notification.ADD : - handleContainment((EObject) notification - .getNewValue()); - break; - case Notification.ADD_MANY : - for (Object next : (Collection) notification - .getNewValue()) { - handleContainment((EObject) next); - } - break; - case Notification.SET : - handleContainment((EObject) notification - .getNewValue()); - break; + if((feature instanceof EReference) && ((EReference)feature).isContainment()) { + + switch(notification.getEventType()) { + case Notification.ADD: + handleContainment((EObject)notification.getNewValue()); + break; + case Notification.ADD_MANY: + for(Object next : (Collection)notification.getNewValue()) { + handleContainment((EObject)next); + } + break; + case Notification.SET: + handleContainment((EObject)notification.getNewValue()); + break; } } } private void handleContainment(EObject object) { Object[] markers = service.getMarkers(object).toArray(); - if (markers.length > 0) { + if(markers.length > 0) { viewer.update(markers, null); } } -- cgit v1.2.3