blob: 35dba97c4f6b5e9880126c664c14d71105710d2e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2006 IBM Corporation 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.ui.internal.decorators;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.viewers.DecorationContext;
import org.eclipse.jface.viewers.IDecorationContext;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.LabelProviderChangedEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.internal.WorkbenchMessages;
import org.eclipse.ui.progress.UIJob;
import org.eclipse.ui.progress.WorkbenchJob;
/**
* The DecorationScheduler is the class that handles the decoration of elements
* using a background thread.
*/
public class DecorationScheduler {
static final ILabelProviderListener[] EMPTY_LISTENER_LIST = new ILabelProviderListener[0];
// When decorations are computed they are added to this cache via
// decorated() method
Map resultCache = new HashMap();
// Objects that need an icon and text computed for display to the user
List awaitingDecoration = new ArrayList();
// Objects that are awaiting a label update.
Set pendingUpdate = new HashSet();
Map awaitingDecorationValues = new HashMap();
DecoratorManager decoratorManager;
boolean shutdown = false;
Job decorationJob;
UIJob updateJob;
private Collection removedListeners = Collections
.synchronizedSet(new HashSet());
private Job clearJob;
// Static used for the updates to indicate an update is required
static int NEEDS_INIT = -1;
/**
* Return a new instance of the receiver configured for the supplied
* DecoratorManager.
*
* @param manager
*/
DecorationScheduler(DecoratorManager manager) {
decoratorManager = manager;
createDecorationJob();
}
/**
* Decorate the text for the receiver. If it has already been done then
* return the result, otherwise queue it for decoration.
*
* @return String
* @param text
* @param element
* @param adaptedElement
* The adapted value of element. May be null.
* @param context
* the decoration context
*/
public String decorateWithText(String text, Object element,
Object adaptedElement, IDecorationContext context) {
DecorationResult decoration = getResult(element, adaptedElement,
context);
if (decoration == null) {
return text;
}
return decoration.decorateWithText(text);
}
/**
* Queue the element and its adapted value if it has not been already.
*
* @param element
* @param adaptedElement
* The adapted value of element. May be null.
* @param forceUpdate
* If true then a labelProviderChanged is fired whether
* decoration occurred or not.
* @param undecoratedText
* The original text for the element if it is known.
* @param context
* The decoration context
*/
synchronized void queueForDecoration(Object element, Object adaptedElement,
boolean forceUpdate, String undecoratedText,
IDecorationContext context) {
DecorationReference reference = (DecorationReference) awaitingDecorationValues
.get(element);
if (reference != null) {
if (forceUpdate) {// Make sure we don't loose a force
reference.setForceUpdate(forceUpdate);
}
reference.addContext(context);
} else {
reference = new DecorationReference(element, adaptedElement,
context);
reference.setForceUpdate(forceUpdate);
reference.setUndecoratedText(undecoratedText);
awaitingDecorationValues.put(element, reference);
awaitingDecoration.add(element);
if (shutdown) {
return;
}
if (decorationJob.getState() == Job.SLEEPING) {
decorationJob.wakeUp();
}
decorationJob.schedule();
}
}
/**
* Decorate the supplied image, element and its adapted value.
*
* @return Image
* @param image
* @param element
* @param adaptedElement
* The adapted value of element. May be null.
* @param context
* the decoration context
*
*/
public Image decorateWithOverlays(Image image, Object element,
Object adaptedElement, IDecorationContext context) {
DecorationResult decoration = getResult(element, adaptedElement,
context);
if (decoration == null) {
return image;
}
return decoration.decorateWithOverlays(image, decoratorManager
.getLightweightManager().getOverlayCache());
}
/**
* Return the DecorationResult for element. If there isn't one queue for
* decoration and return <code>null</code>.
*
* @param element
* The element to be decorated. If it is <code>null</code>
* return <code>null</code>.
* @param adaptedElement
* It's adapted value.
* @param context
* The deocration context
* @return DecorationResult or <code>null</code>
*/
private DecorationResult getResult(Object element, Object adaptedElement,
IDecorationContext context) {
// We do not support decoration of null
if (element == null) {
return null;
}
DecorationResult decoration = internalGetResult(element, context);
if (decoration == null) {
queueForDecoration(element, adaptedElement, false, null, context);
return null;
}
return decoration;
}
private DecorationResult internalGetResult(Object element,
IDecorationContext context) {
Map results = (Map) resultCache.get(context);
if (results != null) {
return (DecorationResult) results.get(element);
}
return null;
}
protected void internalPutResult(Object element,
IDecorationContext context, DecorationResult result) {
Map results = (Map) resultCache.get(context);
if (results == null) {
results = new HashMap();
resultCache.put(context, results);
}
results.put(element, result);
}
/**
* Execute a label update using the pending decorations.
*/
void decorated() {
// Don't bother if we are shutdown now
if (shutdown) {
return;
}
// Lazy initialize the job
if (updateJob == null) {
updateJob = getUpdateJob();
updateJob.setPriority(Job.DECORATE);
}
// Give it a big of a lag for other updates to occur
updateJob.schedule(100);
}
/**
* Shutdown the decoration.
*/
void shutdown() {
shutdown = true;
}
/**
* Get the next resource to be decorated.
*
* @return IResource
*/
synchronized DecorationReference nextElement() {
if (shutdown || awaitingDecoration.isEmpty()) {
return null;
}
Object element = awaitingDecoration.remove(0);
return (DecorationReference) awaitingDecorationValues.remove(element);
}
/**
* Create the Thread used for running decoration.
*/
private void createDecorationJob() {
decorationJob = new Job(
WorkbenchMessages.DecorationScheduler_CalculationJobName) {
/*
* (non-Javadoc)
*
* @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
*/
public IStatus run(IProgressMonitor monitor) {
if (shutdown) {
return Status.CANCEL_STATUS;
}
while (updatesPending()) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// Cancel and try again if there was an error
schedule();
return Status.CANCEL_STATUS;
}
}
monitor.beginTask(
WorkbenchMessages.DecorationScheduler_CalculatingTask,
100);
// will block if there are no resources to be decorated
DecorationReference reference;
monitor.worked(5);
int workCount = 5;
while ((reference = nextElement()) != null) {
// Count up to 90 to give the appearance of updating
if (workCount < 90) {
monitor.worked(1);
workCount++;
}
monitor.subTask(reference.getSubTask());
Object element = reference.getElement();
boolean force = reference.shouldForceUpdate();
IDecorationContext[] contexts = reference.getContexts();
for (int i = 0; i < contexts.length; i++) {
IDecorationContext context = contexts[i];
ensureResultCached(element, force, context);
}
// Only notify listeners when we have exhausted the
// queue of decoration requests.
if (awaitingDecoration.isEmpty()) {
decorated();
}
}
monitor.worked(100 - workCount);
monitor.done();
return Status.OK_STATUS;
}
/**
* Ensure that a result is cached for the given element and context
*
* @param element
* the elements
* @param force
* whether an update should be forced
* @param context
* the decoration context
*/
private void ensureResultCached(Object element, boolean force,
IDecorationContext context) {
boolean elementIsCached = internalGetResult(element, context) != null;
if (elementIsCached) {
pendingUpdate.add(element);
}
if (!elementIsCached) {
DecorationBuilder cacheResult = new DecorationBuilder(
context);
// Calculate the decoration
decoratorManager.getLightweightManager().getDecorations(
element, cacheResult);
// If we should update regardless then put a result
// anyways
if (cacheResult.hasValue() || force) {
// Synchronize on the result lock as we want to
// be sure that we do not try and decorate during
// label update servicing.
// Note: resultCache and pendingUpdate modifications
// must be done atomically.
// Add the decoration even if it's empty in
// order to indicate that the decoration is
// ready
internalPutResult(element, context, cacheResult
.createResult());
// Add an update for only the original element
// to
// prevent multiple updates and clear the cache.
pendingUpdate.add(element);
}
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.runtime.jobs.Job#belongsTo(java.lang.Object)
*/
public boolean belongsTo(Object family) {
return DecoratorManager.FAMILY_DECORATE == family;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.runtime.jobs.Job#shouldRun()
*/
public boolean shouldRun() {
return PlatformUI.isWorkbenchRunning();
}
};
decorationJob.setSystem(true);
decorationJob.setPriority(Job.DECORATE);
decorationJob.schedule();
}
/**
* Return whether or not we are waiting on updated
*
* @return <code>true</code> if there are updates waiting to be served
*/
protected boolean updatesPending() {
if (updateJob != null && updateJob.getState() != Job.NONE) {
return true;
}
if (clearJob != null && clearJob.getState() != Job.NONE) {
return true;
}
return false;
}
/**
* An external update request has been made. Clear the results as they are
* likely obsolete now.
*/
void clearResults() {
if (clearJob == null) {
clearJob = getClearJob();
}
clearJob.schedule();
}
private Job getClearJob() {
Job clear = new Job(
WorkbenchMessages.DecorationScheduler_ClearResultsJob) {
/*
* (non-Javadoc)
*
* @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
*/
protected IStatus run(IProgressMonitor monitor) {
resultCache.clear();
return Status.OK_STATUS;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.runtime.jobs.Job#shouldRun()
*/
public boolean shouldRun() {
return PlatformUI.isWorkbenchRunning();
}
};
clear.setSystem(true);
return clear;
}
/**
* Get the update WorkbenchJob.
*
* @return WorkbenchJob
*/
private WorkbenchJob getUpdateJob() {
WorkbenchJob job = new WorkbenchJob(
WorkbenchMessages.DecorationScheduler_UpdateJobName) {
int currentIndex = NEEDS_INIT;
LabelProviderChangedEvent labelProviderChangedEvent;
ILabelProviderListener[] listeners;
public IStatus runInUIThread(IProgressMonitor monitor) {
if (shutdown) {
return Status.CANCEL_STATUS;
}
// If this is the first one check again in case
// someone has already cleared it out.
if (currentIndex == NEEDS_INIT) {
if (pendingUpdate.isEmpty()) {
// If the removal came in while we were waiting clear it
// anyways
removedListeners.clear();
return Status.OK_STATUS;
}
setUpUpdates();
}
monitor.beginTask(
WorkbenchMessages.DecorationScheduler_UpdatingTask, 10);
monitor.worked(5);
if (listeners.length == 0) {
return Status.OK_STATUS;
}
// If it was removed in the meantime then leave.
ILabelProviderListener listener = listeners[currentIndex];
currentIndex++;
if (!removedListeners.contains(listener)) {
decoratorManager.fireListener(labelProviderChangedEvent,
listener);
}
monitor.done();
if (currentIndex >= listeners.length) {
// Other decoration requests may have occured due to
// updates. Only clear the results if there are none
// pending.
if (awaitingDecoration.isEmpty()) {
resultCache.clear();
}
if (!pendingUpdate.isEmpty()) {
decorated();
}
currentIndex = NEEDS_INIT;// Reset
labelProviderChangedEvent = null;
removedListeners.clear();
listeners = EMPTY_LISTENER_LIST;
} else {
schedule();// Reschedule if we are not done
}
return Status.OK_STATUS;
}
private void setUpUpdates() {
// Get the elements awaiting update and then
// clear the list
removedListeners.clear();
currentIndex = 0;
Object[] elements = pendingUpdate
.toArray(new Object[pendingUpdate.size()]);
pendingUpdate.clear();
labelProviderChangedEvent = new LabelProviderChangedEvent(
decoratorManager, elements);
listeners = decoratorManager.getListeners();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.runtime.jobs.Job#belongsTo(java.lang.Object)
*/
public boolean belongsTo(Object family) {
return DecoratorManager.FAMILY_DECORATE == family;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.runtime.jobs.Job#shouldRun()
*/
public boolean shouldRun() {
return PlatformUI.isWorkbenchRunning();
}
};
job.setSystem(true);
return job;
}
/**
* Return whether or not there is a decoration for this element ready.
*
* @param element
* @param context
* The decoration context
* @return boolean true if the element is ready.
*/
public boolean isDecorationReady(Object element, IDecorationContext context) {
return internalGetResult(element, context) != null;
}
/**
* Return the background Color for element. If there is no result cue for
* decoration and return null, otherwise return the value in the result.
*
* @param element
* The Object to be decorated
* @param adaptedElement
* @return Color or <code>null</code> if there is no value or if it is has
* not been decorated yet.
*/
public Color getBackgroundColor(Object element, Object adaptedElement) {
DecorationResult decoration = getResult(element, adaptedElement,
DecorationContext.DEFAULT_CONTEXT);
if (decoration == null) {
return null;
}
return decoration.getBackgroundColor();
}
/**
* Return the font for element. If there is no result cue for decoration and
* return null, otherwise return the value in the result.
*
* @param element
* The Object to be decorated
* @param adaptedElement
* @return Font or <code>null</code> if there is no value or if it is has
* not been decorated yet.
*/
public Font getFont(Object element, Object adaptedElement) {
DecorationResult decoration = getResult(element, adaptedElement,
DecorationContext.DEFAULT_CONTEXT);
if (decoration == null) {
return null;
}
return decoration.getFont();
}
/**
* Return the foreground Color for element. If there is no result cue for
* decoration and return null, otherwise return the value in the result.
*
* @param element
* The Object to be decorated
* @param adaptedElement
* @return Color or <code>null</code> if there is no value or if it is has
* not been decorated yet.
*/
public Color getForegroundColor(Object element, Object adaptedElement) {
DecorationResult decoration = getResult(element, adaptedElement,
DecorationContext.DEFAULT_CONTEXT);
if (decoration == null) {
return null;
}
return decoration.getForegroundColor();
}
/**
* Return whether or not any updates are being processed/
*
* @return boolean
*/
public boolean processingUpdates() {
return !pendingUpdate.isEmpty() && !awaitingDecoration.isEmpty();
}
/**
* A listener has been removed. If we are updating then skip it.
*
* @param listener
*/
void listenerRemoved(ILabelProviderListener listener) {
if (updatesPending()) {// Only keep track of them if there are updates
// pending
removedListeners.add(listener);
}
if (!updatesPending()) {
removedListeners.remove(listener);
}
}
}