| /******************************************************************************* |
| * Copyright (c) 2000, 2017 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.team.internal.ui.synchronize; |
| |
| import java.text.DateFormat; |
| import java.text.SimpleDateFormat; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.core.resources.IMarker; |
| import org.eclipse.core.resources.IMarkerDelta; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IResourceChangeEvent; |
| import org.eclipse.core.resources.IResourceChangeListener; |
| import org.eclipse.core.resources.IWorkspaceRoot; |
| import org.eclipse.core.resources.IWorkspaceRunnable; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.jface.util.IPropertyChangeListener; |
| import org.eclipse.jface.viewers.AbstractTreeViewer; |
| import org.eclipse.jface.viewers.StructuredViewer; |
| import org.eclipse.swt.custom.BusyIndicator; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.team.core.ITeamStatus; |
| import org.eclipse.team.core.TeamException; |
| import org.eclipse.team.core.synchronize.ISyncInfoSetChangeEvent; |
| import org.eclipse.team.core.synchronize.ISyncInfoSetChangeListener; |
| import org.eclipse.team.core.synchronize.ISyncInfoTreeChangeEvent; |
| import org.eclipse.team.core.synchronize.SyncInfoSet; |
| import org.eclipse.team.internal.core.BackgroundEventHandler; |
| import org.eclipse.team.internal.ui.Policy; |
| import org.eclipse.team.internal.ui.TeamUIMessages; |
| import org.eclipse.team.internal.ui.TeamUIPlugin; |
| import org.eclipse.team.internal.ui.Utils; |
| import org.eclipse.team.ui.synchronize.ISynchronizeModelElement; |
| |
| /** |
| * Handler that serializes the updating of a synchronize model provider. |
| * All modifications to the synchronize model are performed in this |
| * handler's thread. |
| */ |
| public class SynchronizeModelUpdateHandler extends BackgroundEventHandler implements IResourceChangeListener, ISyncInfoSetChangeListener { |
| private static final IWorkspaceRoot ROOT = ResourcesPlugin.getWorkspace().getRoot(); |
| |
| // Event that indicates that the markers for a set of elements has changed |
| private static final int MARKERS_CHANGED = 1; |
| private static final int BUSY_STATE_CHANGED = 2; |
| private static final int RESET = 3; |
| private static final int SYNC_INFO_SET_CHANGED = 4; |
| |
| private AbstractSynchronizeModelProvider provider; |
| |
| private Set<ISynchronizeModelElement> pendingLabelUpdates = Collections.synchronizedSet(new HashSet<>()); |
| |
| // Flag to indicate the need for an early dispath in order to show |
| // busy for elements involved in an operation |
| private boolean dispatchEarly = false; |
| |
| private static final int EARLY_DISPATCH_INCREMENT = 100; |
| |
| /** |
| * Custom event for posting marker changes |
| */ |
| static class MarkerChangeEvent extends Event { |
| private final ISynchronizeModelElement[] elements; |
| public MarkerChangeEvent(ISynchronizeModelElement[] elements) { |
| super(MARKERS_CHANGED); |
| this.elements = elements; |
| } |
| public ISynchronizeModelElement[] getElements() { |
| return elements; |
| } |
| } |
| |
| /** |
| * Custom event for posting busy state changes |
| */ |
| static class BusyStateChangeEvent extends Event { |
| |
| private final ISynchronizeModelElement element; |
| private final boolean isBusy; |
| public BusyStateChangeEvent(ISynchronizeModelElement element, boolean isBusy) { |
| super(BUSY_STATE_CHANGED); |
| this.element = element; |
| this.isBusy = isBusy; |
| } |
| public ISynchronizeModelElement getElement() { |
| return element; |
| } |
| public boolean isBusy() { |
| return isBusy; |
| } |
| } |
| |
| /** |
| * Custom event for posting sync info set changes |
| */ |
| static class SyncInfoSetChangeEvent extends Event { |
| private final ISyncInfoSetChangeEvent event; |
| public SyncInfoSetChangeEvent(ISyncInfoSetChangeEvent event) { |
| super(SYNC_INFO_SET_CHANGED); |
| this.event = event; |
| } |
| public ISyncInfoSetChangeEvent getEvent() { |
| return event; |
| } |
| } |
| |
| private IPropertyChangeListener listener = event -> { |
| if (event.getProperty() == ISynchronizeModelElement.BUSY_PROPERTY) { |
| Object source = event.getSource(); |
| if (source instanceof ISynchronizeModelElement) |
| updateBusyState((ISynchronizeModelElement)source, ((Boolean)event.getNewValue()).booleanValue()); |
| } |
| }; |
| |
| private boolean performingBackgroundUpdate; |
| |
| /* |
| * Map used to keep track of additions so they can be added in batch at the end of the update |
| */ |
| private Map<ISynchronizeModelElement, Set<ISynchronizeModelElement>> additionsMap; |
| |
| /** |
| * Create the marker update handler. |
| */ |
| public SynchronizeModelUpdateHandler(AbstractSynchronizeModelProvider provider) { |
| super(TeamUIMessages.SynchronizeModelProvider_0, TeamUIMessages.SynchronizeModelUpdateHandler_0); // |
| this.provider = provider; |
| ResourcesPlugin.getWorkspace().addResourceChangeListener(this); |
| provider.getSyncInfoSet().addSyncSetChangedListener(this); |
| } |
| |
| /** |
| * Return the marker types that are of interest to this handler. |
| * @return the marker types that are of interest to this handler |
| */ |
| protected String[] getMarkerTypes() { |
| return new String[] {IMarker.PROBLEM}; |
| } |
| |
| /** |
| * Return the <code>AbstractTreeViewer</code> associated with this |
| * provider or <code>null</code> if the viewer is not of the proper type. |
| * @return the structured viewer that is displaying the model managed by this provider |
| */ |
| public StructuredViewer getViewer() { |
| return provider.getViewer(); |
| } |
| |
| @Override |
| public void resourceChanged(final IResourceChangeEvent event) { |
| String[] markerTypes = getMarkerTypes(); |
| Set<IResource> handledResources = new HashSet<>(); |
| Set<ISynchronizeModelElement> changes = new HashSet<>(); |
| |
| // Accumulate all distinct resources that have had problem marker |
| // changes |
| for (String markerType : markerTypes) { |
| IMarkerDelta[] markerDeltas = event.findMarkerDeltas(markerType, true); |
| for (IMarkerDelta delta : markerDeltas) { |
| IResource resource = delta.getResource(); |
| if (!handledResources.contains(resource)) { |
| handledResources.add(resource); |
| ISynchronizeModelElement[] elements = provider.getClosestExistingParents(delta.getResource()); |
| if (elements != null && elements.length > 0) { |
| Collections.addAll(changes, elements); |
| } |
| } |
| } |
| } |
| |
| if (!changes.isEmpty()) { |
| updateMarkersFor(changes.toArray(new ISynchronizeModelElement[changes.size()])); |
| } |
| } |
| |
| private void updateMarkersFor(ISynchronizeModelElement[] elements) { |
| queueEvent(new MarkerChangeEvent(elements), false /* not on front of queue */); |
| } |
| |
| protected void updateBusyState(ISynchronizeModelElement element, boolean isBusy) { |
| queueEvent(new BusyStateChangeEvent(element, isBusy), false /* not on front of queue */); |
| } |
| |
| @Override |
| protected void processEvent(Event event, IProgressMonitor monitor) throws CoreException { |
| switch (event.getType()) { |
| case BackgroundEventHandler.RUNNABLE_EVENT : |
| executeRunnable(event, monitor); |
| break; |
| case MARKERS_CHANGED: |
| // Changes contains all elements that need their labels updated |
| long start = System.currentTimeMillis(); |
| ISynchronizeModelElement[] elements = getChangedElements(event); |
| for (ISynchronizeModelElement element : elements) { |
| propagateProblemMarkers(element); |
| updateParentLabels(element); |
| } |
| if (Policy.DEBUG_SYNC_MODELS) { |
| long time = System.currentTimeMillis() - start; |
| DateFormat TIME_FORMAT = new SimpleDateFormat("m:ss.SSS"); //$NON-NLS-1$ |
| String took = TIME_FORMAT.format(new Date(time)); |
| System.out.println(took + " for " + elements.length + " files"); //$NON-NLS-1$//$NON-NLS-2$ |
| } |
| break; |
| case BUSY_STATE_CHANGED: |
| BusyStateChangeEvent e = (BusyStateChangeEvent)event; |
| queueForLabelUpdate(e.getElement()); |
| if (e.isBusy()) { |
| // indicate that we want an early dispatch to show busy elements |
| dispatchEarly = true; |
| } |
| break; |
| case RESET: |
| // Perform the reset immediately |
| pendingLabelUpdates.clear(); |
| provider.reset(); |
| break; |
| case SYNC_INFO_SET_CHANGED: |
| // Handle the sync change immediately |
| handleChanges(((SyncInfoSetChangeEvent)event).getEvent(), monitor); |
| default: |
| break; |
| } |
| } |
| |
| private ISynchronizeModelElement[] getChangedElements(Event event) { |
| if (event.getType() == MARKERS_CHANGED) { |
| return ((MarkerChangeEvent)event).getElements(); |
| } |
| return new ISynchronizeModelElement[0]; |
| } |
| |
| @Override |
| protected boolean doDispatchEvents(IProgressMonitor monitor) throws TeamException { |
| // Fire label changed |
| dispatchEarly = false; |
| if (pendingLabelUpdates.isEmpty()) { |
| return false; |
| } else { |
| Utils.asyncExec((Runnable) this::firePendingLabelUpdates, getViewer()); |
| return true; |
| } |
| } |
| |
| /** |
| * Forces the viewer to update the labels for queued elemens |
| * whose label has changed during this round of changes. This method |
| * should only be invoked in the UI thread. |
| */ |
| protected void firePendingLabelUpdates() { |
| if (!Utils.canUpdateViewer(getViewer())) return; |
| try { |
| Object[] updates = pendingLabelUpdates.toArray(new Object[pendingLabelUpdates.size()]); |
| updateLabels(updates); |
| } finally { |
| pendingLabelUpdates.clear(); |
| } |
| } |
| |
| /* |
| * Forces the viewer to update the labels for the given elements |
| */ |
| private void updateLabels(Object[] elements) { |
| StructuredViewer tree = getViewer(); |
| if (Utils.canUpdateViewer(tree)) { |
| tree.update(elements, null); |
| } |
| } |
| |
| /** |
| * Queue all the parent elements for a label update. |
| * @param element the element whose label and parent labels need to be updated |
| */ |
| public void updateParentLabels(ISynchronizeModelElement element) { |
| queueForLabelUpdate(element); |
| while (element.getParent() != null) { |
| element = (ISynchronizeModelElement)element.getParent(); |
| queueForLabelUpdate(element); |
| } |
| } |
| |
| /** |
| * Update the label of the given diff node. Diff nodes |
| * are accumulated and updated in a single call. |
| * @param diffNode the diff node to be updated |
| */ |
| protected void queueForLabelUpdate(ISynchronizeModelElement diffNode) { |
| pendingLabelUpdates.add(diffNode); |
| } |
| |
| /** |
| * Calculate and propagate problem markers in the element model |
| * @param element the ssynchronize element |
| */ |
| private void propagateProblemMarkers(ISynchronizeModelElement element) { |
| IResource resource = element.getResource(); |
| if (resource != null) { |
| String property = provider.calculateProblemMarker(element); |
| // If it doesn't have a direct change, a parent might |
| boolean recalculateParentDecorations = hadProblemProperty(element, property); |
| if (recalculateParentDecorations) { |
| ISynchronizeModelElement parent = (ISynchronizeModelElement) element.getParent(); |
| if (parent != null) { |
| propagateProblemMarkers(parent); |
| } |
| } |
| } |
| } |
| |
| // none -> error |
| // error -> none |
| // none -> warning |
| // warning -> none |
| // warning -> error |
| // error -> warning |
| private boolean hadProblemProperty(ISynchronizeModelElement element, String property) { |
| boolean hadError = element.getProperty(ISynchronizeModelElement.PROPAGATED_ERROR_MARKER_PROPERTY); |
| boolean hadWarning = element.getProperty(ISynchronizeModelElement.PROPAGATED_WARNING_MARKER_PROPERTY); |
| |
| // Force recalculation of parents of phantom resources |
| IResource resource = element.getResource(); |
| if(resource != null && resource.isPhantom()) { |
| return true; |
| } |
| |
| if(hadError) { |
| if(! (property == ISynchronizeModelElement.PROPAGATED_ERROR_MARKER_PROPERTY)) { |
| element.setPropertyToRoot(ISynchronizeModelElement.PROPAGATED_ERROR_MARKER_PROPERTY, false); |
| if(property != null) { |
| // error -> warning |
| element.setPropertyToRoot(property, true); |
| } |
| // error -> none |
| // recalculate parents |
| return true; |
| } |
| return false; |
| } else if(hadWarning) { |
| if(! (property == ISynchronizeModelElement.PROPAGATED_WARNING_MARKER_PROPERTY)) { |
| element.setPropertyToRoot(ISynchronizeModelElement.PROPAGATED_WARNING_MARKER_PROPERTY, false); |
| if(property != null) { |
| // warning -> error |
| element.setPropertyToRoot(property, true); |
| return false; |
| } |
| // warning -> none |
| return true; |
| } |
| return false; |
| } else { |
| if(property == ISynchronizeModelElement.PROPAGATED_ERROR_MARKER_PROPERTY) { |
| // none -> error |
| element.setPropertyToRoot(property, true); |
| return false; |
| } else if(property == ISynchronizeModelElement.PROPAGATED_WARNING_MARKER_PROPERTY) { |
| // none -> warning |
| element.setPropertyToRoot(property, true); |
| return true; |
| } |
| return false; |
| } |
| } |
| |
| /* |
| * Queue an event that will reset the provider |
| */ |
| private void reset() { |
| queueEvent(new ResourceEvent(ROOT, RESET, IResource.DEPTH_INFINITE), false); |
| } |
| |
| public void dispose() { |
| shutdown(); |
| ResourcesPlugin.getWorkspace().removeResourceChangeListener(this); |
| provider.getSyncInfoSet().removeSyncSetChangedListener(this); |
| } |
| |
| @Override |
| protected long getShortDispatchDelay() { |
| if (dispatchEarly) { |
| dispatchEarly = false; |
| return EARLY_DISPATCH_INCREMENT; |
| } |
| return super.getShortDispatchDelay(); |
| } |
| |
| /** |
| * This method is invoked whenever a node is added to the viewer |
| * by the provider or a sub-provider. The handler adds an update |
| * listener to the node and notifies the root provider that |
| * a node was added. |
| * @param element the added element |
| * @param provider the provider that added the element |
| */ |
| public void nodeAdded(ISynchronizeModelElement element, AbstractSynchronizeModelProvider provider) { |
| element.addPropertyChangeListener(listener); |
| this.provider.nodeAdded(element, provider); |
| if (Policy.DEBUG_SYNC_MODELS) { |
| System.out.println("Node added: " + getDebugDisplayLabel(element) + " -> " + getDebugDisplayLabel((ISynchronizeModelElement)element.getParent()) + " : " + getDebugDisplayLabel(provider)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| } |
| } |
| |
| /** |
| * This method is invoked whenever a node is removed the viewer |
| * by the provider or a sub-provider. The handler removes any |
| * listener and notifies the root provider that |
| * a node was removed. The node removed may have children for which |
| * a nodeRemoved callback was not invoked (see modelObjectCleared). |
| * @param element the removed element |
| * @param provider the provider that added the element |
| */ |
| public void nodeRemoved(ISynchronizeModelElement element, AbstractSynchronizeModelProvider provider) { |
| element.removePropertyChangeListener(listener); |
| this.provider.nodeRemoved(element, provider); |
| if (Policy.DEBUG_SYNC_MODELS) { |
| System.out.println("Node removed: " + getDebugDisplayLabel(element) + " -> " + getDebugDisplayLabel((ISynchronizeModelElement)element.getParent()) + " : " + getDebugDisplayLabel(provider)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| } |
| } |
| |
| /** |
| * This method is invoked whenever a model object (i.e. node) |
| * is cleared from the model. This is similar to node removal but |
| * is deep. |
| * @param node the node that was cleared |
| */ |
| public void modelObjectCleared(ISynchronizeModelElement node) { |
| node.removePropertyChangeListener(listener); |
| this.provider.modelObjectCleared(node); |
| if (Policy.DEBUG_SYNC_MODELS) { |
| System.out.println("Node cleared: " + getDebugDisplayLabel(node)); //$NON-NLS-1$ |
| } |
| } |
| |
| private String getDebugDisplayLabel(ISynchronizeModelElement node) { |
| if (node == null) { |
| return "ROOT"; //$NON-NLS-1$ |
| } |
| if (node.getResource() != null) { |
| return node.getResource().getFullPath().toString(); |
| } |
| return node.getName(); |
| } |
| |
| private String getDebugDisplayLabel(AbstractSynchronizeModelProvider provider2) { |
| return provider2.toString(); |
| } |
| |
| @Override |
| public void syncInfoSetReset(SyncInfoSet set, IProgressMonitor monitor) { |
| if(provider.isDisposed()) { |
| set.removeSyncSetChangedListener(this); |
| } else { |
| reset(); |
| } |
| } |
| |
| @Override |
| public void syncInfoChanged(final ISyncInfoSetChangeEvent event, IProgressMonitor monitor) { |
| if (! (event instanceof ISyncInfoTreeChangeEvent)) { |
| reset(); |
| } else { |
| queueEvent(new SyncInfoSetChangeEvent(event), false); |
| } |
| } |
| |
| /* |
| * Handle the sync info set change event in the UI thread. |
| */ |
| private void handleChanges(final ISyncInfoSetChangeEvent event, final IProgressMonitor monitor) { |
| runViewUpdate(() -> { |
| provider.handleChanges((ISyncInfoTreeChangeEvent)event, monitor); |
| firePendingLabelUpdates(); |
| }, true /* preserve expansion */); |
| } |
| |
| @Override |
| public void syncInfoSetErrors(SyncInfoSet set, ITeamStatus[] errors, IProgressMonitor monitor) { |
| // When errors occur we currently don't process them. It may be possible to decorate |
| // elements in the model with errors, but currently we prefer to let ignore and except |
| // another listener to display them. |
| } |
| |
| public ISynchronizeModelProvider getProvider() { |
| return provider; |
| } |
| |
| public void connect(IProgressMonitor monitor) { |
| getProvider().getSyncInfoSet().connect(this, monitor); |
| } |
| |
| public void runViewUpdate(final Runnable runnable, final boolean preserveExpansion) { |
| if (Utils.canUpdateViewer(getViewer()) || isPerformingBackgroundUpdate()) { |
| internalRunViewUpdate(runnable, preserveExpansion); |
| } else { |
| if (Thread.currentThread() != getEventHandlerJob().getThread()) { |
| // Run view update should only be called from the UI thread or |
| // the update handler thread. |
| // We will log the problem for now and make it an assert later |
| TeamUIPlugin.log(IStatus.WARNING, "View update invoked from invalid thread", new TeamException("View update invoked from invalid thread")); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| final Control ctrl = getViewer().getControl(); |
| if (ctrl != null && !ctrl.isDisposed()) { |
| ctrl.getDisplay().syncExec(() -> { |
| if (!ctrl.isDisposed()) { |
| BusyIndicator.showWhile(ctrl.getDisplay(), () -> internalRunViewUpdate(runnable, preserveExpansion)); |
| } |
| }); |
| } |
| } |
| } |
| |
| /* |
| * Return whether the event handler is performing a background view update. |
| * In other words, a client has invoked <code>performUpdate</code>. |
| */ |
| public boolean isPerformingBackgroundUpdate() { |
| return Thread.currentThread() == getEventHandlerJob().getThread() && performingBackgroundUpdate; |
| } |
| |
| /* |
| * Method that can be called from the UI thread to update the view model. |
| */ |
| private void internalRunViewUpdate(final Runnable runnable, boolean preserveExpansion) { |
| StructuredViewer viewer = getViewer(); |
| IResource[] expanded = null; |
| IResource[] selected = null; |
| try { |
| if (Utils.canUpdateViewer(viewer)) { |
| viewer.getControl().setRedraw(false); |
| if (preserveExpansion) { |
| expanded = provider.getExpandedResources(); |
| selected = provider.getSelectedResources(); |
| } |
| if (viewer instanceof AbstractTreeViewer && additionsMap == null) |
| additionsMap = new HashMap<>(); |
| } |
| runnable.run(); |
| } finally { |
| if (Utils.canUpdateViewer(viewer)) { |
| try { |
| if (additionsMap != null && !additionsMap.isEmpty() && Utils.canUpdateViewer(viewer)) { |
| for (ISynchronizeModelElement parent : additionsMap.keySet()) { |
| if (Policy.DEBUG_SYNC_MODELS) { |
| System.out.println("Adding child view items of " + parent.getName()); //$NON-NLS-1$ |
| } |
| Set<ISynchronizeModelElement> toAdd = additionsMap.get(parent); |
| ((AbstractTreeViewer)viewer).add(parent, toAdd.toArray(new Object[toAdd.size()])); |
| } |
| additionsMap = null; |
| } |
| if (expanded != null) { |
| provider.expandResources(expanded); |
| } |
| if (selected != null) { |
| provider.selectResources(selected); |
| } |
| } finally { |
| viewer.getControl().setRedraw(true); |
| } |
| } |
| } |
| |
| ISynchronizeModelElement root = provider.getModelRoot(); |
| if(root instanceof SynchronizeModelElement) |
| ((SynchronizeModelElement)root).fireChanges(); |
| } |
| |
| /** |
| * Execute a runnable which performs an update of the model being displayed |
| * by the handler's provider. The runnable should be executed in a thread-safe manner |
| * which esults in the view being updated. |
| * @param runnable the runnable which updates the model. |
| * @param preserveExpansion whether the expansion of the view should be preserver |
| * @param updateInUIThread if <code>true</code>, the model will be updated in the |
| * UI thread. Otherwise, the model will be updated in the handler thread and the view |
| * updated in the UI thread at the end. |
| */ |
| public void performUpdate(final IWorkspaceRunnable runnable, boolean preserveExpansion, boolean updateInUIThread) { |
| if (updateInUIThread) { |
| queueEvent(new BackgroundEventHandler.RunnableEvent(getUIUpdateRunnable(runnable, preserveExpansion), true), true); |
| } else { |
| queueEvent(new BackgroundEventHandler.RunnableEvent(getBackgroundUpdateRunnable(runnable, preserveExpansion), true), true); |
| } |
| } |
| |
| /** |
| * Wrap the runnable in an outer runnable that preserves expansion. |
| */ |
| private IWorkspaceRunnable getUIUpdateRunnable(final IWorkspaceRunnable runnable, final boolean preserveExpansion) { |
| return monitor -> { |
| final CoreException[] exception = new CoreException[] { null }; |
| runViewUpdate(() -> { |
| try { |
| runnable.run(monitor); |
| } catch (CoreException e) { |
| exception[0] = e; |
| } |
| }, true /* preserve expansion */); |
| if (exception[0] != null) |
| throw exception[0]; |
| }; |
| } |
| |
| /* |
| * Wrap the runnable in an outer runnable that preserves expansion if requested |
| * and refreshes the view when the update is completed. |
| */ |
| private IWorkspaceRunnable getBackgroundUpdateRunnable(final IWorkspaceRunnable runnable, final boolean preserveExpansion) { |
| return new IWorkspaceRunnable() { |
| IResource[] expanded; |
| IResource[] selected; |
| @Override |
| public void run(IProgressMonitor monitor) throws CoreException { |
| if (preserveExpansion) |
| recordExpandedResources(); |
| try { |
| performingBackgroundUpdate = true; |
| runnable.run(monitor); |
| } finally { |
| performingBackgroundUpdate = false; |
| } |
| updateView(); |
| |
| } |
| private void recordExpandedResources() { |
| final StructuredViewer viewer = getViewer(); |
| if (viewer != null && !viewer.getControl().isDisposed() && viewer instanceof AbstractTreeViewer) { |
| viewer.getControl().getDisplay().syncExec(() -> { |
| if (viewer != null && !viewer.getControl().isDisposed()) { |
| expanded = provider.getExpandedResources(); |
| selected = provider.getSelectedResources(); |
| } |
| }); |
| } |
| } |
| private void updateView() { |
| // Refresh the view and then set the expansion |
| runViewUpdate(() -> { |
| provider.getViewer().refresh(); |
| if (expanded != null) |
| provider.expandResources(expanded); |
| if (selected != null) |
| provider.selectResources(selected); |
| }, false /* do not preserve expansion (since it is done above) */); |
| } |
| }; |
| } |
| |
| /* |
| * Execute the RunnableEvent |
| */ |
| private void executeRunnable(Event event, IProgressMonitor monitor) { |
| try { |
| // Dispatch any queued results to clear pending output events |
| dispatchEvents(Policy.subMonitorFor(monitor, 1)); |
| } catch (TeamException e) { |
| handleException(e); |
| } |
| try { |
| ((RunnableEvent)event).run(Policy.subMonitorFor(monitor, 1)); |
| } catch (CoreException e) { |
| handleException(e); |
| } |
| } |
| |
| /** |
| * Add the element to the viewer. |
| * @param parent the parent of the element which is already added to the viewer |
| * @param element the element to be added to the viewer |
| */ |
| protected void doAdd(ISynchronizeModelElement parent, ISynchronizeModelElement element) { |
| if (additionsMap == null) { |
| if (Policy.DEBUG_SYNC_MODELS) { |
| System.out.println("Added view item " + element.getName()); //$NON-NLS-1$ |
| } |
| AbstractTreeViewer viewer = (AbstractTreeViewer)getViewer(); |
| viewer.add(parent, element); |
| } else { |
| // Accumulate the additions |
| if (Policy.DEBUG_SYNC_MODELS) { |
| System.out.println("Queueing view item for addition " + element.getName()); //$NON-NLS-1$ |
| } |
| Set<ISynchronizeModelElement> toAdd = additionsMap.get(parent); |
| if (toAdd == null) { |
| toAdd = new HashSet<>(); |
| additionsMap.put(parent, toAdd); |
| } |
| toAdd.add(element); |
| } |
| } |
| } |