| /******************************************************************************* |
| * Copyright (c) 2000, 2017 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 |
| * |
| *******************************************************************************/ |
| package org.eclipse.dltk.ui; |
| |
| import java.util.Iterator; |
| |
| import org.eclipse.core.resources.IFile; |
| 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.dltk.core.IExternalSourceModule; |
| import org.eclipse.dltk.core.IModelElement; |
| import org.eclipse.dltk.core.ISourceModule; |
| import org.eclipse.dltk.core.ISourceRange; |
| import org.eclipse.dltk.core.ISourceReference; |
| import org.eclipse.dltk.core.ModelException; |
| import org.eclipse.dltk.internal.core.SourceModule; |
| import org.eclipse.dltk.internal.ui.editor.ExternalStorageEditorInput; |
| import org.eclipse.dltk.ui.viewsupport.IProblemChangedListener; |
| import org.eclipse.dltk.ui.viewsupport.ImageDescriptorRegistry; |
| import org.eclipse.dltk.ui.viewsupport.ImageImageDescriptor; |
| import org.eclipse.jface.resource.ImageDescriptor; |
| import org.eclipse.jface.text.Position; |
| import org.eclipse.jface.text.source.Annotation; |
| import org.eclipse.jface.text.source.IAnnotationModel; |
| 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.part.FileEditorInput; |
| import org.eclipse.ui.texteditor.MarkerAnnotation; |
| |
| /** |
| * 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</code>. |
| * <p> |
| * <b>Important</b>: Although this decorator implements |
| * ILightweightLabelDecorator, do not contribute this class as a decorator to |
| * the <code>org.eclipse.ui.decorators</code> extension. Only use this class in |
| * your own views and label providers. |
| */ |
| public class ProblemsLabelDecorator |
| implements ILabelDecorator, ILightweightLabelDecorator { |
| |
| /** |
| * This is a special <code>LabelProviderChangedEvent</code> carrying |
| * additional information whether the event origins from a maker change. |
| * <p> |
| * <code>ProblemsLabelChangedEvent</code>s are only generated by <code> |
| * ProblemsLabelDecorator</code>s. |
| * </p> |
| */ |
| public static class ProblemsLabelChangedEvent |
| extends LabelProviderChangedEvent { |
| |
| private static final long serialVersionUID = 1L; |
| |
| private boolean fMarkerChange; |
| |
| /** |
| * Note: This constructor is for internal use only. Clients should not |
| * call this constructor. |
| * |
| * @param eventSource |
| * the base label provider |
| * @param changedResource |
| * the changed resources |
| * @param isMarkerChange |
| * <code>true<code> if the change is a marker change; otherwise |
| * <code>false</code> |
| */ |
| public ProblemsLabelChangedEvent(IBaseLabelProvider eventSource, |
| IResource[] changedResource, boolean isMarkerChange) { |
| super(eventSource, 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 final int ERRORTICK_WARNING = ScriptElementImageDescriptor.WARNING; |
| private static final int ERRORTICK_ERROR = ScriptElementImageDescriptor.ERROR; |
| |
| private ImageDescriptorRegistry fRegistry; |
| private boolean fUseNewRegistry = false; |
| private IProblemChangedListener fProblemChangedListener; |
| |
| private ListenerList<ILabelProviderListener> fListeners; |
| private ISourceRange fCachedRange; |
| |
| /** |
| * Creates a new <code>ProblemsLabelDecorator</code>. |
| */ |
| public ProblemsLabelDecorator() { |
| this(null); |
| fUseNewRegistry = true; |
| } |
| |
| /** |
| * 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 Script |
| * plugin's image registry |
| */ |
| public ProblemsLabelDecorator(ImageDescriptorRegistry registry) { |
| fRegistry = registry; |
| fProblemChangedListener = null; |
| } |
| |
| private ImageDescriptorRegistry getRegistry() { |
| if (fRegistry == null) { |
| fRegistry = fUseNewRegistry ? new ImageDescriptorRegistry() |
| : DLTKUIPlugin.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 ScriptElementImageDescriptor(baseImage, |
| adornmentFlags, new Point(bounds.width, bounds.height))); |
| } |
| return image; |
| } |
| |
| /** |
| * Note: This method is for internal use only. Clients should not call this |
| * method. |
| * |
| * @param obj |
| * the element to compute the flags for |
| * |
| * @return the adornment flags |
| */ |
| protected int computeAdornmentFlags(Object obj) { |
| try { |
| if (obj instanceof IModelElement) { |
| IModelElement element = (IModelElement) obj; |
| int type = element.getElementType(); |
| switch (type) { |
| case IModelElement.SCRIPT_MODEL: |
| case IModelElement.SCRIPT_PROJECT: |
| case IModelElement.PROJECT_FRAGMENT: |
| return getErrorTicksFromMarkers(element.getResource(), |
| IResource.DEPTH_INFINITE, null); |
| case IModelElement.SCRIPT_FOLDER: |
| /** |
| * At the moment IScriptFolder refers to the current folder |
| * only (used in flat view mode). If you want problem status |
| * with subfolders then use |
| * {@link org.eclipse.dltk.internal.ui.navigator.TreeHierarchyLayoutProblemsDecorator} |
| * which handles it as |
| * {@link org.eclipse.core.resources.IFolder} |
| */ |
| case IModelElement.SOURCE_MODULE: |
| return getErrorTicksFromMarkers(element.getResource(), |
| IResource.DEPTH_ONE, null); |
| case IModelElement.TYPE: |
| case IModelElement.METHOD: |
| case IModelElement.FIELD: |
| ISourceModule cu = (ISourceModule) element |
| .getAncestor(IModelElement.SOURCE_MODULE); |
| if (cu != null) { |
| ISourceReference ref = (type == IModelElement.SOURCE_MODULE) |
| ? null |
| : (ISourceReference) element; |
| // The assumption is that only source elements in |
| // compilation unit can have markers |
| IAnnotationModel model = isInScriptAnnotationModel(cu); |
| int result = 0; |
| if (model != null) { |
| // open in Script editor: look at annotation model |
| result = getErrorTicksFromAnnotationModel(model, |
| ref); |
| } else { |
| result = getErrorTicksFromMarkers(cu.getResource(), |
| IResource.DEPTH_ONE, ref); |
| } |
| fCachedRange = null; |
| return result; |
| } |
| break; |
| default: |
| } |
| } else if (obj instanceof IResource) { |
| return getErrorTicksFromMarkers((IResource) obj, |
| IResource.DEPTH_INFINITE, null); |
| } |
| } catch (CoreException e) { |
| if (e instanceof ModelException) { |
| if (((ModelException) e).isDoesNotExist()) { |
| return 0; |
| } |
| } |
| if (e.getStatus().getCode() == IResourceStatus.MARKER_NOT_FOUND) { |
| return 0; |
| } |
| |
| DLTKUIPlugin.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; |
| |
| IMarker[] markers = res.findMarkers(IMarker.PROBLEM, true, depth); |
| if (markers != null) { |
| 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); |
| return isInside(pos, sourceElement); |
| } |
| return false; |
| } |
| |
| private IAnnotationModel isInScriptAnnotationModel(ISourceModule original) { |
| if (original.isWorkingCopy()) { |
| if (original instanceof SourceModule) { |
| IFile file = (IFile) original.getResource(); |
| if (file != null) { |
| FileEditorInput editorInput = new FileEditorInput(file); |
| return DLTKUIPlugin.getDefault() |
| .getSourceModuleDocumentProvider() |
| .getAnnotationModel(editorInput); |
| } |
| } else if (original instanceof IExternalSourceModule) { |
| ExternalStorageEditorInput editorInput = new ExternalStorageEditorInput( |
| (IExternalSourceModule) original); |
| return DLTKUIPlugin.getDefault() |
| .getSourceModuleDocumentProvider() |
| .getAnnotationModel(editorInput); |
| } |
| } |
| return null; |
| } |
| |
| private int getErrorTicksFromAnnotationModel(IAnnotationModel model, |
| ISourceReference sourceElement) throws CoreException { |
| int info = 0; |
| Iterator<Annotation> iter = model.getAnnotationIterator(); |
| while ((info != ERRORTICK_ERROR) && iter.hasNext()) { |
| Annotation annot = iter.next(); |
| IMarker marker = isAnnotationInRange(model, annot, sourceElement); |
| if (marker != null) { |
| int priority = marker.getAttribute(IMarker.SEVERITY, -1); |
| if (priority == IMarker.SEVERITY_WARNING) { |
| info = ERRORTICK_WARNING; |
| } else if (priority == IMarker.SEVERITY_ERROR) { |
| info = ERRORTICK_ERROR; |
| } |
| } |
| } |
| return info; |
| } |
| |
| private IMarker isAnnotationInRange(IAnnotationModel model, |
| Annotation annot, ISourceReference sourceElement) |
| throws CoreException { |
| if (annot instanceof MarkerAnnotation) { |
| if (sourceElement == null |
| || isInside(model.getPosition(annot), sourceElement)) { |
| IMarker marker = ((MarkerAnnotation) annot).getMarker(); |
| if (marker.exists() && marker.isSubtypeOf(IMarker.PROBLEM)) { |
| return marker; |
| } |
| } |
| } |
| return null; |
| } |
| |
| private boolean isInside(Position pos, ISourceReference sourceElement) |
| throws CoreException { |
| return pos != null && isInside(pos.getOffset(), sourceElement); |
| } |
| |
| /** |
| * Tests if a position is inside the source range of an element. |
| * |
| * @param pos |
| * Position to be tested. |
| * @param sourceElement |
| * Source element (must be a IModelElement) |
| * @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. |
| */ |
| protected boolean isInside(int pos, ISourceReference sourceElement) |
| throws CoreException { |
| if (fCachedRange == null) { |
| fCachedRange = sourceElement.getSourceRange(); |
| } |
| ISourceRange range = fCachedRange; |
| if (range != null) { |
| int rangeOffset = range.getOffset(); |
| return (rangeOffset <= pos |
| && rangeOffset + range.getLength() > pos); |
| } |
| return false; |
| } |
| |
| @Override |
| public void dispose() { |
| if (fProblemChangedListener != null) { |
| DLTKUIPlugin.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 = (changedResources, |
| isMarkerChange) -> fireProblemsChanged(changedResources, |
| isMarkerChange); |
| DLTKUIPlugin.getDefault().getProblemMarkerManager() |
| .addListener(fProblemChangedListener); |
| } |
| } |
| |
| @Override |
| public void removeListener(ILabelProviderListener listener) { |
| if (fListeners != null) { |
| fListeners.remove(listener); |
| if (fListeners.isEmpty() && fProblemChangedListener != null) { |
| DLTKUIPlugin.getDefault().getProblemMarkerManager() |
| .removeListener(fProblemChangedListener); |
| fProblemChangedListener = null; |
| } |
| } |
| } |
| |
| private void fireProblemsChanged(IResource[] changedResources, |
| boolean isMarkerChange) { |
| if (fListeners != null && !fListeners.isEmpty()) { |
| LabelProviderChangedEvent event = new ProblemsLabelChangedEvent( |
| this, changedResources, isMarkerChange); |
| for (ILabelProviderListener listener : fListeners) { |
| listener.labelProviderChanged(event); |
| } |
| } |
| } |
| |
| @Override |
| public void decorate(Object element, IDecoration decoration) { |
| int adornmentFlags = computeAdornmentFlags(element); |
| if (adornmentFlags == ERRORTICK_ERROR) { |
| decoration.addOverlay(DLTKPluginImages.DESC_OVR_ERROR); |
| } else if (adornmentFlags == ERRORTICK_WARNING) { |
| decoration.addOverlay(DLTKPluginImages.DESC_OVR_WARNING); |
| } |
| } |
| |
| } |