1 /******************************************************************************* 2 * Copyright (c) 2000, 2015 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 *******************************************************************************/ 14 package org.eclipse.ui.model; 15 16 import java.util.ArrayList; 17 import java.util.Collection; 18 import java.util.Iterator; 19 20 import org.eclipse.core.resources.IContainer; 21 import org.eclipse.core.resources.IResource; 22 import org.eclipse.core.resources.IResourceChangeEvent; 23 import org.eclipse.core.resources.IResourceChangeListener; 24 import org.eclipse.core.resources.IResourceDelta; 25 import org.eclipse.core.resources.IWorkspace; 26 import org.eclipse.jface.viewers.AbstractTreeViewer; 27 import org.eclipse.jface.viewers.StructuredViewer; 28 import org.eclipse.jface.viewers.Viewer; 29 import org.eclipse.swt.widgets.Control; 30 31 /** 32 * Tree content provider for resource objects that can be adapted to the 33 * interface {@link org.eclipse.ui.model.IWorkbenchAdapter IWorkbenchAdapter}. 34 * This provider will listen for resource changes within the workspace and 35 * update the viewer as necessary. 36 * <p> 37 * This class may be instantiated, or subclassed by clients. 38 * </p> 39 */ 40 public class WorkbenchContentProvider extends BaseWorkbenchContentProvider 41 implements IResourceChangeListener { 42 private Viewer viewer; 43 44 /** 45 * Creates the resource content provider. 46 */ WorkbenchContentProvider()47 public WorkbenchContentProvider() { 48 super(); 49 } 50 51 @Override dispose()52 public void dispose() { 53 if (viewer != null) { 54 IWorkspace workspace = null; 55 Object obj = viewer.getInput(); 56 if (obj instanceof IWorkspace) { 57 workspace = (IWorkspace) obj; 58 } else if (obj instanceof IContainer) { 59 workspace = ((IContainer) obj).getWorkspace(); 60 } 61 if (workspace != null) { 62 workspace.removeResourceChangeListener(this); 63 } 64 } 65 66 super.dispose(); 67 } 68 69 @Override inputChanged(Viewer viewer, Object oldInput, Object newInput)70 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { 71 super.inputChanged(viewer, oldInput, newInput); 72 73 this.viewer = viewer; 74 IWorkspace oldWorkspace = null; 75 IWorkspace newWorkspace = null; 76 77 if (oldInput instanceof IWorkspace) { 78 oldWorkspace = (IWorkspace) oldInput; 79 } else if (oldInput instanceof IContainer) { 80 oldWorkspace = ((IContainer) oldInput).getWorkspace(); 81 } 82 83 if (newInput instanceof IWorkspace) { 84 newWorkspace = (IWorkspace) newInput; 85 } else if (newInput instanceof IContainer) { 86 newWorkspace = ((IContainer) newInput).getWorkspace(); 87 } 88 89 if (oldWorkspace != newWorkspace) { 90 if (oldWorkspace != null) { 91 oldWorkspace.removeResourceChangeListener(this); 92 } 93 if (newWorkspace != null) { 94 newWorkspace.addResourceChangeListener(this, 95 IResourceChangeEvent.POST_CHANGE); 96 } 97 } 98 } 99 100 @Override resourceChanged(final IResourceChangeEvent event)101 public final void resourceChanged(final IResourceChangeEvent event) { 102 103 processDelta(event.getDelta()); 104 105 } 106 107 /** 108 * Process the resource delta. 109 * 110 * @param delta delta to process; not <code>null</code> 111 */ processDelta(IResourceDelta delta)112 protected void processDelta(IResourceDelta delta) { 113 114 Control ctrl = viewer.getControl(); 115 if (ctrl == null || ctrl.isDisposed()) { 116 return; 117 } 118 119 120 final Collection runnables = new ArrayList(); 121 processDelta(delta, runnables); 122 123 if (runnables.isEmpty()) { 124 return; 125 } 126 127 //Are we in the UIThread? If so spin it until we are done 128 if (ctrl.getDisplay().getThread() == Thread.currentThread()) { 129 runUpdates(runnables); 130 } else { 131 ctrl.getDisplay().asyncExec(() -> { 132 //Abort if this happens after disposes 133 Control ctrl1 = viewer.getControl(); 134 if (ctrl1 == null || ctrl1.isDisposed()) { 135 return; 136 } 137 138 runUpdates(runnables); 139 }); 140 } 141 142 } 143 144 /** 145 * Run all of the runnables that are the widget updates 146 * @param runnables 147 */ runUpdates(Collection runnables)148 private void runUpdates(Collection runnables) { 149 Iterator runnableIterator = runnables.iterator(); 150 while(runnableIterator.hasNext()){ 151 ((Runnable)runnableIterator.next()).run(); 152 } 153 154 } 155 156 /** 157 * Process a resource delta. Add any runnables 158 */ processDelta(IResourceDelta delta, Collection runnables)159 private void processDelta(IResourceDelta delta, Collection runnables) { 160 //he widget may have been destroyed 161 // by the time this is run. Check for this and do nothing if so. 162 Control ctrl = viewer.getControl(); 163 if (ctrl == null || ctrl.isDisposed()) { 164 return; 165 } 166 167 // Get the affected resource 168 final IResource resource = delta.getResource(); 169 170 // If any children have changed type, just do a full refresh of this 171 // parent, 172 // since a simple update on such children won't work, 173 // and trying to map the change to a remove and add is too dicey. 174 // The case is: folder A renamed to existing file B, answering yes to 175 // overwrite B. 176 IResourceDelta[] affectedChildren = delta.getAffectedChildren(IResourceDelta.CHANGED); 177 for (IResourceDelta affectedChild : affectedChildren) { 178 if ((affectedChild.getFlags() & IResourceDelta.TYPE) != 0) { 179 runnables.add(getRefreshRunnable(resource)); 180 return; 181 } 182 } 183 184 // Opening a project just affects icon, but we need to refresh when 185 // a project is closed because if child items have not yet been created 186 // in the tree we still need to update the item's children 187 int changeFlags = delta.getFlags(); 188 if ((changeFlags & IResourceDelta.OPEN) != 0) { 189 if (resource.isAccessible()) { 190 runnables.add(getUpdateRunnable(resource)); 191 } else { 192 runnables.add(getRefreshRunnable(resource)); 193 return; 194 } 195 } 196 // Check the flags for changes the Navigator cares about. 197 // See ResourceLabelProvider for the aspects it cares about. 198 // Notice we don't care about F_CONTENT or F_MARKERS currently. 199 if ((changeFlags & (IResourceDelta.SYNC 200 | IResourceDelta.TYPE | IResourceDelta.DESCRIPTION)) != 0) { 201 runnables.add(getUpdateRunnable(resource)); 202 } 203 // Replacing a resource may affect its label and its children 204 if ((changeFlags & IResourceDelta.REPLACED) != 0) { 205 runnables.add(getRefreshRunnable(resource)); 206 return; 207 } 208 209 // Handle changed children . 210 for (IResourceDelta affectedChild : affectedChildren) { 211 processDelta(affectedChild, runnables); 212 } 213 214 // @issue several problems here: 215 // - should process removals before additions, to avoid multiple equal 216 // elements in viewer 217 // - Kim: processing removals before additions was the indirect cause of 218 // 44081 and its varients 219 // - Nick: no delta should have an add and a remove on the same element, 220 // so processing adds first is probably OK 221 // - using setRedraw will cause extra flashiness 222 // - setRedraw is used even for simple changes 223 // - to avoid seeing a rename in two stages, should turn redraw on/off 224 // around combined removal and addition 225 // - Kim: done, and only in the case of a rename (both remove and add 226 // changes in one delta). 227 228 IResourceDelta[] addedChildren = delta 229 .getAffectedChildren(IResourceDelta.ADDED); 230 IResourceDelta[] removedChildren = delta 231 .getAffectedChildren(IResourceDelta.REMOVED); 232 233 if (addedChildren.length == 0 && removedChildren.length == 0) { 234 return; 235 } 236 237 final Object[] addedObjects; 238 final Object[] removedObjects; 239 240 // Process additions before removals as to not cause selection 241 // preservation prior to new objects being added 242 // Handle added children. Issue one update for all insertions. 243 int numMovedFrom = 0; 244 int numMovedTo = 0; 245 if (addedChildren.length > 0) { 246 addedObjects = new Object[addedChildren.length]; 247 for (int i = 0; i < addedChildren.length; i++) { 248 addedObjects[i] = addedChildren[i].getResource(); 249 if ((addedChildren[i].getFlags() & IResourceDelta.MOVED_FROM) != 0) { 250 ++numMovedFrom; 251 } 252 } 253 } else { 254 addedObjects = new Object[0]; 255 } 256 257 // Handle removed children. Issue one update for all removals. 258 if (removedChildren.length > 0) { 259 removedObjects = new Object[removedChildren.length]; 260 for (int i = 0; i < removedChildren.length; i++) { 261 removedObjects[i] = removedChildren[i].getResource(); 262 if ((removedChildren[i].getFlags() & IResourceDelta.MOVED_TO) != 0) { 263 ++numMovedTo; 264 } 265 } 266 } else { 267 removedObjects = new Object[0]; 268 } 269 // heuristic test for items moving within same folder (i.e. renames) 270 final boolean hasRename = numMovedFrom > 0 && numMovedTo > 0; 271 272 Runnable addAndRemove = () -> { 273 if (viewer instanceof AbstractTreeViewer) { 274 AbstractTreeViewer treeViewer = (AbstractTreeViewer) viewer; 275 // Disable redraw until the operation is finished so we don't 276 // get a flash of both the new and old item (in the case of 277 // rename) 278 // Only do this if we're both adding and removing files (the 279 // rename case) 280 if (hasRename) { 281 treeViewer.getControl().setRedraw(false); 282 } 283 try { 284 if (addedObjects.length > 0) { 285 treeViewer.add(resource, addedObjects); 286 } 287 if (removedObjects.length > 0) { 288 treeViewer.remove(removedObjects); 289 } 290 } 291 finally { 292 if (hasRename) { 293 treeViewer.getControl().setRedraw(true); 294 } 295 } 296 } else { 297 ((StructuredViewer) viewer).refresh(resource); 298 } 299 }; 300 runnables.add(addAndRemove); 301 } 302 /** 303 * Return a runnable for refreshing a resource. 304 * @param resource 305 * @return Runnable 306 */ getRefreshRunnable(final IResource resource)307 private Runnable getRefreshRunnable(final IResource resource) { 308 return () -> ((StructuredViewer) viewer).refresh(resource); 309 } 310 311 /** 312 * Return a runnable for refreshing a resource. 313 * @param resource 314 * @return Runnable 315 */ getUpdateRunnable(final IResource resource)316 private Runnable getUpdateRunnable(final IResource resource) { 317 return () -> ((StructuredViewer) viewer).update(resource, null); 318 } 319 } 320