/******************************************************************************* * Copyright (c) 2000, 2013 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 * Sergey Prigogin (Google) *******************************************************************************/ package org.eclipse.cdt.internal.ui.viewsupport; import java.util.HashMap; import java.util.Map; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceStatus; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.ListenerList; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.viewers.IBaseLabelProvider; import org.eclipse.jface.viewers.IDecoration; import org.eclipse.jface.viewers.ILabelDecorator; import org.eclipse.jface.viewers.ILabelProviderListener; import org.eclipse.jface.viewers.ILightweightLabelDecorator; import org.eclipse.jface.viewers.LabelProviderChangedEvent; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.ui.texteditor.MarkerUtilities; import org.eclipse.cdt.core.model.ICElement; import org.eclipse.cdt.core.model.ISourceRange; import org.eclipse.cdt.core.model.ISourceReference; import org.eclipse.cdt.core.model.ITranslationUnit; import org.eclipse.cdt.ui.CElementImageDescriptor; import org.eclipse.cdt.ui.CUIPlugin; import org.eclipse.cdt.internal.ui.CPluginImages; import org.eclipse.cdt.internal.ui.util.IProblemChangedListener; import org.eclipse.cdt.internal.ui.util.ImageDescriptorRegistry; /** * LabelDecorator that decorates an element's image with error and warning overlays that * represent the severity of markers attached to the element's underlying resource. To see * a problem decoration for a marker, the marker needs to be a subtype of {@code IMarker.PROBLEM}. * <p> * Note: Only images for elements in Java projects are currently updated on marker changes. * </p> * * @since 2.0 */ public class ProblemsLabelDecorator implements ILabelDecorator, ILightweightLabelDecorator { /** * This is a special {@code LabelProviderChangedEvent} carrying additional * information whether the event originates from a maker change. * <p> * {@code ProblemsLabelChangedEvent}s are only generated by {@code ProblemsLabelDecorator}s. */ public static class ProblemsLabelChangedEvent extends LabelProviderChangedEvent { private boolean fMarkerChange; /** * Note: This constructor is for internal use only. Clients should not call this constructor. */ public ProblemsLabelChangedEvent(IBaseLabelProvider source, IResource[] changedResource, boolean isMarkerChange) { super(source, changedResource); fMarkerChange= isMarkerChange; } /** * Returns whether this event origins from marker changes. If <code>false</code> an annotation * model change is the origin. In this case viewers not displaying working copies can ignore these * events. * * @return if this event origins from a marker change. */ public boolean isMarkerChange() { return fMarkerChange; } } private static class MarkersCacheKey { private IResource res; private int depth; public MarkersCacheKey(IResource res, int depth) { this.res = res; this.depth = depth; } @Override public int hashCode() { return res.hashCode() + 31 * depth; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; MarkersCacheKey other = (MarkersCacheKey) obj; return depth == other.depth && res.equals(other.res); } } private static final int ERRORTICK_WARNING= CElementImageDescriptor.WARNING; private static final int ERRORTICK_ERROR= CElementImageDescriptor.ERROR; private static final IMarker[] EMPTY_MARKER_ARRAY = {}; private ImageDescriptorRegistry fRegistry; private boolean fUseNewRegistry; private IProblemChangedListener fProblemChangedListener; private ListenerList fListeners; private Map<MarkersCacheKey, IMarker[]> fMarkersCache = new HashMap<MarkersCacheKey, IMarker[]>(); /** * Creates a new <code>ProblemsLabelDecorator</code>. */ public ProblemsLabelDecorator() { this(null); fUseNewRegistry= true; } /** * Creates decorator with a shared image registry. * Note: This constructor is for internal use only. Clients should not call this constructor. * * @param registry The registry to use or <code>null</code> to use the Java plugin's * image registry. */ public ProblemsLabelDecorator(ImageDescriptorRegistry registry) { fRegistry= registry; fProblemChangedListener= null; } private ImageDescriptorRegistry getRegistry() { if (fRegistry == null) { fRegistry= fUseNewRegistry ? new ImageDescriptorRegistry() : CUIPlugin.getImageDescriptorRegistry(); } return fRegistry; } @Override public String decorateText(String text, Object element) { return text; } @Override public Image decorateImage(Image image, Object obj) { int adornmentFlags= computeAdornmentFlags(obj); if (adornmentFlags != 0) { ImageDescriptor baseImage= new ImageImageDescriptor(image); Rectangle bounds= image.getBounds(); return getRegistry().get(new CElementImageDescriptor(baseImage, adornmentFlags, new Point(bounds.width, bounds.height))); } return image; } /** * Note: This method is for internal use only. Clients should not call this method. */ protected int computeAdornmentFlags(Object obj) { try { if (obj instanceof ICElement) { ICElement element= (ICElement) obj; int type= element.getElementType(); switch (type) { case ICElement.C_PROJECT: case ICElement.C_CCONTAINER: return getErrorTicksFromMarkers(element.getResource(), IResource.DEPTH_INFINITE, null); case ICElement.C_UNIT: return getErrorTicksFromMarkers(element.getResource(), IResource.DEPTH_ONE, null); case ICElement.C_FUNCTION: case ICElement.C_CLASS: case ICElement.C_UNION: case ICElement.C_STRUCT: case ICElement.C_VARIABLE: case ICElement.C_METHOD: ITranslationUnit tu= ((ISourceReference)element).getTranslationUnit(); if (tu != null && tu.exists()) { return getErrorTicksFromMarkers(tu.getResource(), IResource.DEPTH_ONE, (ISourceReference)element); } break; } } else if (obj instanceof IResource) { return getErrorTicksFromMarkers((IResource) obj, IResource.DEPTH_INFINITE, null); } } catch (CoreException e) { if (e.getStatus().getCode() == IResourceStatus.MARKER_NOT_FOUND) { return 0; } CUIPlugin.log(e); } return 0; } private int getErrorTicksFromMarkers(IResource res, int depth, ISourceReference sourceElement) throws CoreException { if (res == null || !res.isAccessible()) { return 0; } int info= 0; MarkersCacheKey cacheKey = new MarkersCacheKey(res, depth); IMarker[] markers = fMarkersCache .get(cacheKey); if (markers == null) { markers= res.findMarkers(IMarker.PROBLEM, true, depth); if (markers == null) markers = EMPTY_MARKER_ARRAY; fMarkersCache.put(cacheKey, markers); } for (int i= 0; i < markers.length && (info != ERRORTICK_ERROR); i++) { IMarker curr= markers[i]; if (sourceElement == null || isMarkerInRange(curr, sourceElement)) { int priority= curr.getAttribute(IMarker.SEVERITY, -1); if (priority == IMarker.SEVERITY_WARNING) { info= ERRORTICK_WARNING; } else if (priority == IMarker.SEVERITY_ERROR) { info= ERRORTICK_ERROR; } } } return info; } private boolean isMarkerInRange(IMarker marker, ISourceReference sourceElement) throws CoreException { if (marker.isSubtypeOf(IMarker.TEXT)) { int pos= marker.getAttribute(IMarker.CHAR_START, -1); if (pos == -1) { int line= MarkerUtilities.getLineNumber(marker); if (line >= 0) { return isInside( -1, line, sourceElement); } } return isInside(pos, -1, sourceElement); } return false; } /** * Tests if a position is inside the source range of an element. Usually this is done * by looking at the offset. In case the offset equals <code>-1</code>, the line is * tested. * @param offSet offset to be tested * @param line line to be tested * @param sourceElement Source element (must be a ICElement) * @return boolean Return <code>true</code> if position is located inside the source element. * @throws CoreException Exception thrown if element range could not be accessed. * * @since 2.1 */ protected boolean isInside(int offSet, int line, ISourceReference sourceElement) throws CoreException { ISourceRange range= sourceElement.getSourceRange(); if (range != null) { if (offSet == -1) { return (line >= range.getStartLine() && line <= range.getEndLine()); } int rangeOffset= range.getStartPos(); return (rangeOffset <= offSet && rangeOffset + range.getLength() > offSet); } return false; } @Override public void dispose() { if (fProblemChangedListener != null) { CUIPlugin.getDefault().getProblemMarkerManager().removeListener(fProblemChangedListener); fProblemChangedListener= null; } if (fRegistry != null && fUseNewRegistry) { fRegistry.dispose(); } } @Override public boolean isLabelProperty(Object element, String property) { return true; } @Override public void addListener(ILabelProviderListener listener) { if (fListeners == null) { fListeners= new ListenerList(); } fListeners.add(listener); if (fProblemChangedListener == null) { fProblemChangedListener= new IProblemChangedListener() { @Override public void problemsChanged(IResource[] changedResources, boolean isMarkerChange) { fireProblemsChanged(changedResources, isMarkerChange); } }; CUIPlugin.getDefault().getProblemMarkerManager().addListener(fProblemChangedListener); } } @Override public void removeListener(ILabelProviderListener listener) { if (fListeners != null) { fListeners.remove(listener); if (fListeners.isEmpty() && fProblemChangedListener != null) { CUIPlugin.getDefault().getProblemMarkerManager().removeListener(fProblemChangedListener); fProblemChangedListener= null; } } } protected void fireProblemsChanged(IResource[] changedResources, boolean isMarkerChange) { fMarkersCache.clear(); if (fListeners != null && !fListeners.isEmpty()) { LabelProviderChangedEvent event= new ProblemsLabelChangedEvent(this, changedResources, isMarkerChange); Object[] listeners= fListeners.getListeners(); for (Object listener : listeners) { ((ILabelProviderListener) listener).labelProviderChanged(event); } } } @Override public void decorate(Object element, IDecoration decoration) { int adornmentFlags= computeAdornmentFlags(element); if (adornmentFlags == ERRORTICK_ERROR) { decoration.addOverlay(CPluginImages.DESC_OVR_ERROR); } else if (adornmentFlags == ERRORTICK_WARNING) { decoration.addOverlay(CPluginImages.DESC_OVR_WARNING); } } }