blob: 0e672e95f2351db4f8afa49f0da453542e56f4b1 [file] [log] [blame]
package org.eclipse.team.internal.ccvs.ui;
/*
* (c) Copyright IBM Corp. 2000, 2001.
* All Rights Reserved.
*/
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.ILabelDecorator;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.LabelProviderChangedEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Display;
import org.eclipse.team.ccvs.core.CVSTeamProvider;
import org.eclipse.team.core.IResourceStateChangeListener;
import org.eclipse.team.core.ITeamProvider;
import org.eclipse.team.core.TeamPlugin;
import org.eclipse.team.internal.ccvs.core.CVSProvider;
import org.eclipse.team.internal.ccvs.core.util.Assert;
/**
* Classes registered with the workbench decoration extension point. The <code>CVSDecorationRunnable</code> class
* actually calculates the decoration, while this class is responsible for listening to the following sources
* for indications that the decorators need updating:
* <ul>
* <li>workbench label requests (decorateText/decorateImage)
* <li>workspace resource change events (resourceChanged)
* <li>cvs state changes (resourceStateChanged)
* </ul>
* <p>
* [Note: There are several optimization that can be implemented in this class: (1) cache something
* so that computing the dirty state of containers does not imply traversal of all children, (2) improve
* the queue used between the decorator and the decorator runnable such that priority can be
* given to visible elements when decoration requests are made.]
*/
public class CVSDecorator extends LabelProvider implements ILabelDecorator, IResourceStateChangeListener, IDecorationNotifier {
private static CVSDecorator theDecorator = null;
// Resources that need an icon and text computed for display to the user, no order
private Set decoratorNeedsUpdating = Collections.synchronizedSet(new HashSet());
// When decorations are computed they are added to this cache via decorated() method
private Map cache = Collections.synchronizedMap(new HashMap());
// Updater thread, computes decoration labels and images
private Thread decoratorUpdateThread;
private boolean shutdown = false;
private Hashtable imageCache = new Hashtable();
public CVSDecorator() {
// The decorator is a singleton, there should never be more than one instance.
// temporary until the UI component properly calls dispose when the workbench shutsdown
// UI Bug 9633
Assert.isTrue(theDecorator==null);
theDecorator = this;
decoratorUpdateThread = new Thread(new CVSDecorationRunnable(this), "CVS"); //$NON-NLS-1$
decoratorUpdateThread.start();
TeamPlugin.getManager().addResourceStateChangeListener(this);
}
public String decorateText(String text, Object o) {
IResource resource = getResource(o);
if (resource == null || text == null || resource.getType() == IResource.ROOT)
return text;
if (getCVSProviderFor(resource) == null)
return text;
CVSDecoration decoration = (CVSDecoration) cache.get(resource);
if (decoration != null) {
String format = decoration.getFormat();
if (format == null) {
return text;
} else {
Map bindings = decoration.getBindings();
if (bindings.isEmpty())
return text;
bindings.put(CVSDecoratorConfiguration.RESOURCE_NAME, text);
return CVSDecoratorConfiguration.bind(format, bindings);
}
} else {
addResourcesToBeDecorated(new IResource[] { resource });
return text;
}
}
public Image decorateImage(Image image, Object o) {
IResource resource = getResource(o);
if (resource == null || image == null || resource.getType() == IResource.ROOT)
return image;
if (getCVSProviderFor(resource) == null)
return image;
CVSDecoration decoration = (CVSDecoration) cache.get(resource);
if (decoration != null) {
List overlays = decoration.getOverlays();
return overlays == null ? image : getCachedImage(image, overlays);
} else {
addResourcesToBeDecorated(new IResource[] { resource });
return image;
}
}
/**
* Get the composite image for the given image and overlays. Return one from the cache if
* it exists. If not, create it, cache it, and return it.
*/
private Image getCachedImage(Image image, List overlays) {
Hashtable overlayTable = (Hashtable)imageCache.get(image);
if (overlayTable == null) {
overlayTable = new Hashtable();
imageCache.put(image, overlayTable);
}
Image cachedImage = (Image)overlayTable.get(overlays);
if (cachedImage == null) {
cachedImage = new OverlayIcon(image.getImageData(), new ImageDescriptor[][] {(ImageDescriptor[])overlays.toArray(new ImageDescriptor[overlays.size()])}, new Point(16, 16)).createImage();
overlayTable.put(overlays, cachedImage);
}
return cachedImage;
}
/*
* @see IDecorationNotifier#next()
*/
public synchronized IResource next() {
try {
if (shutdown) return null;
if (decoratorNeedsUpdating.isEmpty()) {
wait();
}
// We were awakened.
if (shutdown) {
// The decorator was awakened by the plug-in as it was shutting down.
return null;
}
Iterator iterator = decoratorNeedsUpdating.iterator();
IResource resource = (IResource) iterator.next();
iterator.remove();
//System.out.println("++ Next: " + resource.getFullPath() + " remaining in cache: " + cache.size());
return resource;
} catch (InterruptedException e) {
}
return null;
}
/*
* @see IDecorationNotifier#notify(IResource, CVSDecoration)
*/
public synchronized void decorated(IResource resource, CVSDecoration decoration) {
// ignore resources that aren't in the workbench anymore.
if(resource.exists() && !shutdown) {
cache.put(resource, decoration);
postLabelEvents(new LabelProviderChangedEvent[] { new LabelProviderChangedEvent(this, resource)});
}
}
/*
* @see IResourceChangeListener#resourceChanged(IResourceChangeEvent)
*/
/*
* @see IResourceStateChangeListener#resourceStateChanged(IResource[])
*/
public void resourceStateChanged(IResource[] changedResources) {
// add depth first so that update thread processes parents first.
//System.out.println(">> State Change Event");
List resources = new ArrayList();
List noProviderResources = new ArrayList();
for (int i = 0; i < changedResources.length; i++) {
// ignore subtrees that aren't associated with a provider, this can happen on import
// of a new project to CVS.
if (getCVSProviderFor(changedResources[i]) == null) {
noProviderResources.add(changedResources[i]);
}
resources.addAll(computeParents(changedResources[i]));
}
addResourcesToBeDecorated((IResource[]) resources.toArray(new IResource[resources.size()]));
if(!noProviderResources.isEmpty()) {
List events = new ArrayList();
for (Iterator it = resources.iterator(); it.hasNext();) {
IResource element = (IResource) it.next();
events.add(new LabelProviderChangedEvent(this, element));
}
postLabelEvents((LabelProviderChangedEvent[]) events.toArray(new LabelProviderChangedEvent[events.size()]));
}
}
public static void refresh() {
try {
IResource[] resources = ResourcesPlugin.getWorkspace().getRoot().members();
for (int i = 0; i < resources.length; i++) {
if (getCVSProviderFor(resources[i]) != null) {
refresh(resources[i]);
}
}
} catch (CoreException e) {
}
}
public static void refresh(IResource resource) {
final List resources = new ArrayList();
try {
resource.accept(new IResourceVisitor() {
public boolean visit(IResource resource) {
resources.add(resource);
return true;
}
});
TeamPlugin.getManager().broadcastResourceStateChanges((IResource[]) resources.toArray(new IResource[resources.size()]));
} catch (CoreException e) {
}
}
private List computeParents(IResource resource) {
IResource current = resource;
List resources = new ArrayList();
while (current.getType() != IResource.ROOT) {
resources.add(current);
current = current.getParent();
}
return resources;
}
private synchronized void addResourcesToBeDecorated(IResource[] resources) {
if (resources.length > 0) {
for (int i = 0; i < resources.length; i++) {
//System.out.println("\t to update: " + resources[i].getFullPath());
decoratorNeedsUpdating.add(resources[i]);
}
notify();
}
}
/**
* Answers null if a provider does not exist or the provider is not a CVS provider. These resources
* will be ignored by the decorator.
*/
private static CVSTeamProvider getCVSProviderFor(IResource resource) {
ITeamProvider p = TeamPlugin.getManager().getProvider(resource);
if (p == null || !(p instanceof CVSTeamProvider)) {
return null;
}
return (CVSTeamProvider) p;
}
/**
* Returns the resource for the given input object, or
* null if there is no resource associated with it.
*
* @param object the object to find the resource for
* @return the resource for the given object, or null
*/
private IResource getResource(Object object) {
if (object instanceof IResource) {
return (IResource)object;
}
if (object instanceof IAdaptable) {
return (IResource)((IAdaptable)object).getAdapter(IResource.class);
}
return null;
}
/**
* Post the label events to the UI thread
*
* @param events the events to post
*/
private void postLabelEvents(final LabelProviderChangedEvent[] events) {
// now post the change events to the UI thread
if (events.length > 0) {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
for (int i = 0; i < events.length; i++) {
fireLabelProviderChanged(events[i]);
}
}
});
}
}
private void shutdown() {
shutdown = true;
// Wake the thread up if it is asleep.
synchronized (this) {
notifyAll();
}
try {
// Wait for the decorator thread to finish before returning.
decoratorUpdateThread.join();
} catch (InterruptedException e) {
}
}
public static void shutdownAll() {
if(theDecorator!=null) {
theDecorator.dispose();
}
}
public static String getFileTypeString(String name, String keyword) {
StringBuffer buffer = new StringBuffer();
boolean isBinary = false;
if(keyword!=null) {
if (keyword.equals("-kb")) { //$NON-NLS-1$
isBinary = true;
}
} else {
isBinary = !CVSProvider.isText(name);
}
if(isBinary) {
buffer.append(Policy.bind("CVSFilePropertiesPage.binary"));
} else {
buffer.append(Policy.bind("CVSFilePropertiesPage.text"));
if(keyword!=null && !keyword.equals("-ko") && !"".equals(keyword)) { //$NON-NLS-1$ //$NON-NLS-2$
buffer.append(" " + keyword); //$NON-NLS-1$
}
}
return buffer.toString();
}
/*
* @see IBaseLabelProvider#dispose()
*/
public void dispose() {
super.dispose();
shutdown();
TeamPlugin.getManager().removeResourceStateChangeListener(this);
decoratorNeedsUpdating.clear();
cache.clear();
Iterator it = imageCache.values().iterator();
while (it.hasNext()) {
Hashtable overlayTable = (Hashtable)it.next();
Iterator it2 = overlayTable.values().iterator();
while (it2.hasNext()) {
((Image)it2.next()).dispose();
}
}
imageCache = new Hashtable();
theDecorator = null;
}
}