| /******************************************************************************* |
| * Copyright (c) 2000, 2015 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 |
| * Stephan Wahlbrink <sw@wahlbrink.eu> - Annotations not painted when length is 0 / at document end - http://bugs.eclipse.org/bugs/show_bug.cgi?id=227534 |
| *******************************************************************************/ |
| package org.eclipse.jface.text.source; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.SWTException; |
| import org.eclipse.swt.custom.StyleRange; |
| import org.eclipse.swt.custom.StyledText; |
| import org.eclipse.swt.events.PaintEvent; |
| import org.eclipse.swt.events.PaintListener; |
| import org.eclipse.swt.graphics.Color; |
| import org.eclipse.swt.graphics.GC; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.graphics.TextStyle; |
| import org.eclipse.swt.widgets.Display; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.Platform; |
| |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.IPaintPositionManager; |
| import org.eclipse.jface.text.IPainter; |
| import org.eclipse.jface.text.IRegion; |
| import org.eclipse.jface.text.ITextInputListener; |
| import org.eclipse.jface.text.ITextPresentationListener; |
| import org.eclipse.jface.text.ITextViewerExtension2; |
| import org.eclipse.jface.text.ITextViewerExtension4; |
| import org.eclipse.jface.text.ITextViewerExtension5; |
| import org.eclipse.jface.text.JFaceTextUtil; |
| import org.eclipse.jface.text.Position; |
| import org.eclipse.jface.text.Region; |
| import org.eclipse.jface.text.TextPresentation; |
| |
| |
| /** |
| * Paints decorations for annotations provided by an annotation model and/or |
| * highlights them in the associated source viewer. |
| * <p> |
| * The annotation painter can be configured with drawing strategies. A drawing |
| * strategy defines the visual presentation of a particular type of annotation |
| * decoration.</p> |
| * <p> |
| * Clients usually instantiate and configure objects of this class.</p> |
| * |
| * @since 2.1 |
| */ |
| public class AnnotationPainter implements IPainter, PaintListener, IAnnotationModelListener, IAnnotationModelListenerExtension, ITextPresentationListener { |
| |
| |
| /** |
| * A drawing strategy draws the decoration for an annotation onto the text widget. |
| * |
| * @since 3.0 |
| */ |
| public interface IDrawingStrategy { |
| /** |
| * Draws a decoration for an annotation onto the specified GC at the given text range. There |
| * are two different invocation modes of the <code>draw</code> method: |
| * <ul> |
| * <li><strong>drawing mode:</strong> the passed GC is the graphics context of a paint |
| * event occurring on the text widget. The strategy should draw the decoration onto the |
| * graphics context, such that the decoration appears at the given range in the text |
| * widget.</li> |
| * <li><strong>clearing mode:</strong> the passed GC is <code>null</code>. In this case |
| * the strategy must invalidate enough of the text widget's client area to cover any |
| * decoration drawn in drawing mode. This can usually be accomplished by calling |
| * {@linkplain StyledText#redrawRange(int, int, boolean) textWidget.redrawRange(offset, length, true)}.</li> |
| * </ul> |
| * |
| * @param annotation the annotation to be drawn |
| * @param gc the graphics context, <code>null</code> when in clearing mode |
| * @param textWidget the text widget to draw on |
| * @param offset the offset of the line |
| * @param length the length of the line |
| * @param color the color of the line |
| */ |
| void draw(Annotation annotation, GC gc, StyledText textWidget, int offset, int length, Color color); |
| } |
| |
| /** |
| * Squiggles drawing strategy. |
| * |
| * @since 3.0 |
| * @deprecated As of 3.4, replaced by {@link AnnotationPainter.UnderlineStrategy} |
| */ |
| @Deprecated |
| public static class SquigglesStrategy implements IDrawingStrategy { |
| |
| @Override |
| public void draw(Annotation annotation, GC gc, StyledText textWidget, int offset, int length, Color color) { |
| if (gc != null) { |
| |
| if (length < 1) |
| return; |
| |
| Point left= textWidget.getLocationAtOffset(offset); |
| Point right= textWidget.getLocationAtOffset(offset + length); |
| Rectangle rect= textWidget.getTextBounds(offset, offset + length - 1); |
| left.x= rect.x; |
| right.x= rect.x + rect.width; |
| |
| int[] polyline= computePolyline(left, right, textWidget.getBaseline(offset), textWidget.getLineHeight(offset)); |
| |
| gc.setLineWidth(0); // NOTE: 0 means width is 1 but with optimized performance |
| gc.setLineStyle(SWT.LINE_SOLID); |
| gc.setForeground(color); |
| gc.drawPolyline(polyline); |
| |
| } else { |
| textWidget.redrawRange(offset, length, true); |
| } |
| } |
| |
| /** |
| * Computes an array of alternating x and y values which are the corners of the squiggly line of the |
| * given height between the given end points. |
| * |
| * @param left the left end point |
| * @param right the right end point |
| * @param baseline the font's baseline |
| * @param lineHeight the height of the line |
| * @return the array of alternating x and y values which are the corners of the squiggly line |
| */ |
| private int[] computePolyline(Point left, Point right, int baseline, int lineHeight) { |
| |
| final int WIDTH= 4; // must be even |
| final int HEIGHT= 2; // can be any number |
| |
| int peaks= (right.x - left.x) / WIDTH; |
| if (peaks == 0 && right.x - left.x > 2) |
| peaks= 1; |
| |
| int leftX= left.x; |
| |
| // compute (number of point) * 2 |
| int length= ((2 * peaks) + 1) * 2; |
| if (length < 0) |
| return new int[0]; |
| |
| int[] coordinates= new int[length]; |
| |
| // cache peeks' y-coordinates |
| int top= left.y + Math.min(baseline + 1, lineHeight - HEIGHT - 1); |
| int bottom= top + HEIGHT; |
| |
| // populate array with peek coordinates |
| for (int i= 0; i < peaks; i++) { |
| int index= 4 * i; |
| coordinates[index]= leftX + (WIDTH * i); |
| coordinates[index+1]= bottom; |
| coordinates[index+2]= coordinates[index] + WIDTH/2; |
| coordinates[index+3]= top; |
| } |
| |
| // the last down flank is missing |
| coordinates[length-2]= Math.min(Math.max(0, right.x - 1), left.x + (WIDTH * peaks)); |
| coordinates[length-1]= bottom; |
| |
| return coordinates; |
| } |
| } |
| |
| /** |
| * Drawing strategy that does nothing. |
| * |
| * @since 3.0 |
| */ |
| public static final class NullStrategy implements IDrawingStrategy { |
| |
| @Override |
| public void draw(Annotation annotation, GC gc, StyledText textWidget, int offset, int length, Color color) { |
| // do nothing |
| } |
| } |
| |
| |
| /** |
| * A text style painting strategy draws the decoration for an annotation |
| * onto the text widget by applying a {@link TextStyle} on a given |
| * {@link StyleRange}. |
| * |
| * @since 3.4 |
| */ |
| public interface ITextStyleStrategy { |
| |
| /** |
| * Applies a text style on the given <code>StyleRange</code>. |
| * |
| * @param styleRange the style range on which to apply the text style |
| * @param annotationColor the color of the annotation |
| */ |
| void applyTextStyle(StyleRange styleRange, Color annotationColor); |
| } |
| |
| |
| /** |
| * @since 3.4 |
| */ |
| public static final class HighlightingStrategy implements ITextStyleStrategy { |
| @Override |
| public void applyTextStyle(StyleRange styleRange, Color annotationColor) { |
| styleRange.background= annotationColor; |
| } |
| } |
| |
| |
| /** |
| * Underline text style strategy. |
| * |
| * @since 3.4 |
| */ |
| public static final class UnderlineStrategy implements ITextStyleStrategy { |
| |
| int fUnderlineStyle; |
| |
| public UnderlineStrategy(int style) { |
| Assert.isLegal(style == SWT.UNDERLINE_SINGLE || style == SWT.UNDERLINE_DOUBLE || style == SWT.UNDERLINE_ERROR || style == SWT.UNDERLINE_SQUIGGLE); |
| fUnderlineStyle= style; |
| } |
| |
| @Override |
| public void applyTextStyle(StyleRange styleRange, Color annotationColor) { |
| styleRange.underline= true; |
| styleRange.underlineStyle= fUnderlineStyle; |
| styleRange.underlineColor= annotationColor; |
| } |
| } |
| |
| |
| /** |
| * Box text style strategy. |
| * |
| * @since 3.4 |
| */ |
| public static final class BoxStrategy implements ITextStyleStrategy { |
| |
| int fBorderStyle; |
| |
| public BoxStrategy(int style) { |
| Assert.isLegal(style == SWT.BORDER_DASH || style == SWT.BORDER_DOT || style == SWT.BORDER_SOLID); |
| fBorderStyle= style; |
| } |
| |
| @Override |
| public void applyTextStyle(StyleRange styleRange, Color annotationColor) { |
| styleRange.borderStyle= fBorderStyle; |
| styleRange.borderColor= annotationColor; |
| } |
| } |
| |
| |
| /** |
| * Implementation of <code>IRegion</code> that can be reused |
| * by setting the offset and the length. |
| */ |
| private static class ReusableRegion extends Position implements IRegion {} |
| |
| /** |
| * Tells whether this class is in debug mode. |
| * @since 3.0 |
| */ |
| private static boolean DEBUG= "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.jface.text/debug/AnnotationPainter")); //$NON-NLS-1$//$NON-NLS-2$ |
| /** |
| * The squiggly painter strategy. |
| * @since 3.0 |
| */ |
| private static final IDrawingStrategy SQUIGGLES_STRATEGY= new SquigglesStrategy(); |
| |
| /** |
| * This strategy is used to mark the <code>null</code> value in the chache |
| * maps. |
| * |
| * @since 3.4 |
| */ |
| private static final IDrawingStrategy NULL_STRATEGY= new NullStrategy(); |
| /** |
| * The squiggles painter id. |
| * @since 3.0 |
| */ |
| private static final Object SQUIGGLES= new Object(); |
| /** |
| * The squiggly painter strategy. |
| * |
| * @since 3.4 |
| */ |
| private static final ITextStyleStrategy HIGHLIGHTING_STRATEGY= new HighlightingStrategy(); |
| |
| /** |
| * The highlighting text style strategy id. |
| * |
| * @since 3.4 |
| */ |
| private static final Object HIGHLIGHTING= new Object(); |
| |
| /** |
| * The presentation information (decoration) for an annotation. Each such |
| * object represents one decoration drawn on the text area, such as squiggly lines |
| * and underlines. |
| */ |
| private static class Decoration { |
| /** The position of this decoration */ |
| private Position fPosition; |
| /** The color of this decoration */ |
| private Color fColor; |
| /** |
| * The annotation's layer |
| * @since 3.0 |
| */ |
| private int fLayer; |
| /** |
| * The painting strategy for this decoration. |
| * @since 3.0 |
| */ |
| private Object fPaintingStrategy; |
| } |
| |
| |
| /** Indicates whether this painter is active */ |
| private boolean fIsActive= false; |
| /** Indicates whether this painter is managing decorations */ |
| private boolean fIsPainting= false; |
| /** Indicates whether this painter is setting its annotation model */ |
| private volatile boolean fIsSettingModel= false; |
| /** The associated source viewer */ |
| private ISourceViewer fSourceViewer; |
| /** The cached widget of the source viewer */ |
| private StyledText fTextWidget; |
| /** The annotation model providing the annotations to be drawn */ |
| private IAnnotationModel fModel; |
| /** The annotation access */ |
| private IAnnotationAccess fAnnotationAccess; |
| /** |
| * The map with decorations |
| * @since 3.0 |
| */ |
| private Map<Annotation, Decoration> fDecorationsMap= new HashMap<>(); // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=50767 |
| /** |
| * The map with of highlighted decorations. |
| * @since 3.0 |
| */ |
| private Map<Annotation, Decoration> fHighlightedDecorationsMap= new HashMap<>(); // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=50767 |
| /** |
| * Mutex for highlighted decorations map. |
| * @since 3.0 |
| */ |
| private Object fDecorationMapLock= new Object(); |
| /** |
| * Mutex for for decorations map. |
| * @since 3.0 |
| */ |
| private Object fHighlightedDecorationsMapLock= new Object(); |
| /** |
| * Maps an annotation type to its registered color. |
| * |
| * @see #setAnnotationTypeColor(Object, Color) |
| */ |
| private Map<Object, Color> fAnnotationType2Color= new HashMap<>(); |
| |
| /** |
| * Cache that maps the annotation type to its color. |
| * @since 3.4 |
| */ |
| private Map<Object, Color> fCachedAnnotationType2Color= new HashMap<>(); |
| /** |
| * The range in which the current highlight annotations can be found. |
| * @since 3.0 |
| */ |
| private Position fCurrentHighlightAnnotationRange= null; |
| /** |
| * The range in which all added, removed and changed highlight |
| * annotations can be found since the last world change. |
| * @since 3.0 |
| */ |
| private Position fTotalHighlightAnnotationRange= null; |
| /** |
| * The range in which the currently drawn annotations can be found. |
| * @since 3.3 |
| */ |
| private Position fCurrentDrawRange= null; |
| /** |
| * The range in which all added, removed and changed drawn |
| * annotations can be found since the last world change. |
| * @since 3.3 |
| */ |
| private Position fTotalDrawRange= null; |
| /** |
| * The text input listener. |
| * @since 3.0 |
| */ |
| private ITextInputListener fTextInputListener; |
| /** |
| * Flag which tells that a new document input is currently being set. |
| * @since 3.0 |
| */ |
| private boolean fInputDocumentAboutToBeChanged; |
| /** |
| * Maps annotation types to painting strategy identifiers. |
| * |
| * @see #addAnnotationType(Object, Object) |
| * @since 3.0 |
| */ |
| private Map<Object, Object> fAnnotationType2PaintingStrategyId= new HashMap<>(); |
| /** |
| * Maps annotation types to painting strategy identifiers. |
| * @since 3.4 |
| */ |
| private Map<String, Object> fCachedAnnotationType2PaintingStrategy= new HashMap<>(); |
| |
| /** |
| * Maps painting strategy identifiers to painting strategies. |
| * |
| * @since 3.0 |
| */ |
| private Map<Object, Object> fPaintingStrategyId2PaintingStrategy= new HashMap<>(); |
| |
| /** |
| * Reuse this region for performance reasons. |
| * @since 3.3 |
| */ |
| private ReusableRegion fReusableRegion= new ReusableRegion(); |
| |
| /** |
| * Creates a new annotation painter for the given source viewer and with the |
| * given annotation access. The painter is not initialized, i.e. no |
| * annotation types are configured to be painted. |
| * |
| * @param sourceViewer the source viewer for this painter |
| * @param access the annotation access for this painter |
| */ |
| public AnnotationPainter(ISourceViewer sourceViewer, IAnnotationAccess access) { |
| fSourceViewer= sourceViewer; |
| fAnnotationAccess= access; |
| fTextWidget= sourceViewer.getTextWidget(); |
| |
| // default drawing strategies: squiggles were the only decoration style before version 3.0 |
| fPaintingStrategyId2PaintingStrategy.put(SQUIGGLES, SQUIGGLES_STRATEGY); |
| fPaintingStrategyId2PaintingStrategy.put(HIGHLIGHTING, HIGHLIGHTING_STRATEGY); |
| } |
| |
| /** |
| * Returns whether this painter has to draw any squiggles. |
| * |
| * @return <code>true</code> if there are squiggles to be drawn, <code>false</code> otherwise |
| */ |
| private boolean hasDecorations() { |
| synchronized (fDecorationMapLock) { |
| return !fDecorationsMap.isEmpty(); |
| } |
| } |
| |
| /** |
| * Enables painting. This painter registers a paint listener with the |
| * source viewer's widget. |
| */ |
| private void enablePainting() { |
| if (!fIsPainting && hasDecorations()) { |
| fIsPainting= true; |
| fTextWidget.addPaintListener(this); |
| handleDrawRequest(null); |
| } |
| } |
| |
| /** |
| * Disables painting, if is has previously been enabled. Removes |
| * any paint listeners registered with the source viewer's widget. |
| * |
| * @param redraw <code>true</code> if the widget should be redrawn after disabling |
| */ |
| private void disablePainting(boolean redraw) { |
| if (fIsPainting) { |
| fIsPainting= false; |
| fTextWidget.removePaintListener(this); |
| if (redraw && hasDecorations()) |
| handleDrawRequest(null); |
| } |
| } |
| |
| /** |
| * Sets the annotation model for this painter. Registers this painter |
| * as listener of the give model, if the model is not <code>null</code>. |
| * |
| * @param model the annotation model |
| */ |
| private void setModel(IAnnotationModel model) { |
| if (fModel != model) { |
| if (fModel != null) |
| fModel.removeAnnotationModelListener(this); |
| fModel= model; |
| if (fModel != null) { |
| try { |
| fIsSettingModel= true; |
| fModel.addAnnotationModelListener(this); |
| } finally { |
| fIsSettingModel= false; |
| } |
| } |
| } |
| } |
| |
| /** |
| * Updates the set of decorations based on the current state of |
| * the painter's annotation model. |
| * |
| * @param event the annotation model event |
| */ |
| private void catchupWithModel(AnnotationModelEvent event) { |
| |
| synchronized (fDecorationMapLock) { |
| if (fDecorationsMap == null) |
| return; |
| } |
| |
| if (fModel == null) { |
| // annotation model is null -> clear all |
| synchronized (fDecorationMapLock) { |
| fDecorationsMap.clear(); |
| } |
| synchronized (fHighlightedDecorationsMapLock) { |
| fHighlightedDecorationsMap.clear(); |
| } |
| return; |
| } |
| |
| IRegion clippingRegion= computeClippingRegion(null, true); |
| IDocument document= fSourceViewer.getDocument(); |
| |
| int highlightAnnotationRangeStart= Integer.MAX_VALUE; |
| int highlightAnnotationRangeEnd= -1; |
| |
| int drawRangeStart= Integer.MAX_VALUE; |
| int drawRangeEnd= -1; |
| |
| Map<Annotation, Decoration> decorationsMap; |
| Map<Annotation, Decoration> highlightedDecorationsMap; |
| |
| // Clone decoration maps |
| synchronized (fDecorationMapLock) { |
| decorationsMap= new HashMap<>(fDecorationsMap); |
| } |
| synchronized (fHighlightedDecorationsMapLock) { |
| highlightedDecorationsMap= new HashMap<>(fHighlightedDecorationsMap); |
| } |
| |
| boolean isWorldChange= false; |
| |
| Iterator<Annotation> e; |
| if (event == null || event.isWorldChange()) { |
| isWorldChange= true; |
| |
| if (DEBUG && event == null) |
| System.out.println("AP: INTERNAL CHANGE"); //$NON-NLS-1$ |
| |
| Iterator<Entry<Annotation, Decoration>> iter= decorationsMap.entrySet().iterator(); |
| while (iter.hasNext()) { |
| Entry<Annotation, Decoration> entry= iter.next(); |
| Annotation annotation= entry.getKey(); |
| Decoration decoration= entry.getValue(); |
| drawDecoration(decoration, null, annotation, clippingRegion, document); |
| } |
| |
| decorationsMap.clear(); |
| |
| highlightedDecorationsMap.clear(); |
| |
| e= fModel.getAnnotationIterator(); |
| |
| |
| } else { |
| |
| // Remove annotations |
| Annotation[] removedAnnotations= event.getRemovedAnnotations(); |
| for (int i= 0, length= removedAnnotations.length; i < length; i++) { |
| Annotation annotation= removedAnnotations[i]; |
| Decoration decoration= highlightedDecorationsMap.remove(annotation); |
| if (decoration != null) { |
| Position position= decoration.fPosition; |
| if (position != null) { |
| highlightAnnotationRangeStart= Math.min(highlightAnnotationRangeStart, position.offset); |
| highlightAnnotationRangeEnd= Math.max(highlightAnnotationRangeEnd, position.offset + position.length); |
| } |
| } |
| decoration= decorationsMap.remove(annotation); |
| if (decoration != null) { |
| drawDecoration(decoration, null, annotation, clippingRegion, document); |
| Position position= decoration.fPosition; |
| if (position != null) { |
| drawRangeStart= Math.min(drawRangeStart, position.offset); |
| drawRangeEnd= Math.max(drawRangeEnd, position.offset + position.length); |
| } |
| } |
| |
| } |
| |
| // Update existing annotations |
| Annotation[] changedAnnotations= event.getChangedAnnotations(); |
| for (int i= 0, length= changedAnnotations.length; i < length; i++) { |
| Annotation annotation= changedAnnotations[i]; |
| |
| boolean isHighlighting= false; |
| |
| Decoration decoration= highlightedDecorationsMap.get(annotation); |
| |
| if (decoration != null) { |
| isHighlighting= true; |
| // The call below updates the decoration - no need to create new decoration |
| decoration= getDecoration(annotation, decoration); |
| if (decoration == null) { |
| Decoration removedDecoration= highlightedDecorationsMap.remove(annotation); |
| if (removedDecoration != null) { |
| highlightAnnotationRangeStart= Math.min(highlightAnnotationRangeStart, removedDecoration.fPosition.offset); |
| highlightAnnotationRangeEnd= Math.max(highlightAnnotationRangeEnd, removedDecoration.fPosition.offset + removedDecoration.fPosition.length); |
| } |
| } |
| |
| } else { |
| decoration= getDecoration(annotation, decoration); |
| if (decoration != null && decoration.fPaintingStrategy instanceof ITextStyleStrategy) { |
| highlightedDecorationsMap.put(annotation, decoration); |
| isHighlighting= true; |
| } |
| } |
| |
| boolean usesDrawingStrategy= !isHighlighting && decoration != null; |
| |
| Position position= null; |
| if (decoration == null) |
| position= fModel.getPosition(annotation); |
| else |
| position= decoration.fPosition; |
| |
| if (position != null && !position.isDeleted()) { |
| if (isHighlighting) { |
| highlightAnnotationRangeStart= Math.min(highlightAnnotationRangeStart, position.offset); |
| highlightAnnotationRangeEnd= Math.max(highlightAnnotationRangeEnd, position.offset + position.length); |
| } |
| if (usesDrawingStrategy) { |
| drawRangeStart= Math.min(drawRangeStart, position.offset); |
| drawRangeEnd= Math.max(drawRangeEnd, position.offset + position.length); |
| } |
| } else { |
| Decoration removedDecoration= highlightedDecorationsMap.remove(annotation); |
| if (removedDecoration != null) { |
| highlightAnnotationRangeStart= Math.min(highlightAnnotationRangeStart, removedDecoration.fPosition.offset); |
| highlightAnnotationRangeEnd= Math.max(highlightAnnotationRangeEnd, removedDecoration.fPosition.offset + removedDecoration.fPosition.length); |
| } |
| } |
| |
| if (usesDrawingStrategy) { |
| Decoration oldDecoration= decorationsMap.get(annotation); |
| if (oldDecoration != null) { |
| drawDecoration(oldDecoration, null, annotation, clippingRegion, document); |
| if (decoration != null) |
| decorationsMap.put(annotation, decoration); |
| else |
| decorationsMap.remove(annotation); |
| } |
| } |
| } |
| |
| e= Arrays.asList(event.getAddedAnnotations()).iterator(); |
| } |
| |
| // Add new annotations |
| while (e.hasNext()) { |
| Annotation annotation= e.next(); |
| Decoration pp= getDecoration(annotation, null); |
| if (pp != null) { |
| if (pp.fPaintingStrategy instanceof IDrawingStrategy) { |
| decorationsMap.put(annotation, pp); |
| drawRangeStart= Math.min(drawRangeStart, pp.fPosition.offset); |
| drawRangeEnd= Math.max(drawRangeEnd, pp.fPosition.offset + pp.fPosition.length); |
| } else if (pp.fPaintingStrategy instanceof ITextStyleStrategy) { |
| highlightedDecorationsMap.put(annotation, pp); |
| highlightAnnotationRangeStart= Math.min(highlightAnnotationRangeStart, pp.fPosition.offset); |
| highlightAnnotationRangeEnd= Math.max(highlightAnnotationRangeEnd, pp.fPosition.offset + pp.fPosition.length); |
| } |
| |
| } |
| } |
| |
| synchronized (fDecorationMapLock) { |
| fDecorationsMap= decorationsMap; |
| updateDrawRanges(drawRangeStart, drawRangeEnd, isWorldChange); |
| } |
| |
| synchronized (fHighlightedDecorationsMapLock) { |
| fHighlightedDecorationsMap= highlightedDecorationsMap; |
| updateHighlightRanges(highlightAnnotationRangeStart, highlightAnnotationRangeEnd, isWorldChange); |
| } |
| } |
| |
| /** |
| * Updates the remembered highlight ranges. |
| * |
| * @param highlightAnnotationRangeStart the start of the range |
| * @param highlightAnnotationRangeEnd the end of the range |
| * @param isWorldChange tells whether the range belongs to a annotation model event reporting a world change |
| * @since 3.0 |
| */ |
| private void updateHighlightRanges(int highlightAnnotationRangeStart, int highlightAnnotationRangeEnd, boolean isWorldChange) { |
| if (highlightAnnotationRangeStart != Integer.MAX_VALUE) { |
| |
| int maxRangeStart= highlightAnnotationRangeStart; |
| int maxRangeEnd= highlightAnnotationRangeEnd; |
| |
| if (fTotalHighlightAnnotationRange != null) { |
| maxRangeStart= Math.min(maxRangeStart, fTotalHighlightAnnotationRange.offset); |
| maxRangeEnd= Math.max(maxRangeEnd, fTotalHighlightAnnotationRange.offset + fTotalHighlightAnnotationRange.length); |
| } |
| |
| if (fTotalHighlightAnnotationRange == null) |
| fTotalHighlightAnnotationRange= new Position(0); |
| if (fCurrentHighlightAnnotationRange == null) |
| fCurrentHighlightAnnotationRange= new Position(0); |
| |
| if (isWorldChange) { |
| fTotalHighlightAnnotationRange.offset= highlightAnnotationRangeStart; |
| fTotalHighlightAnnotationRange.length= highlightAnnotationRangeEnd - highlightAnnotationRangeStart; |
| fCurrentHighlightAnnotationRange.offset= maxRangeStart; |
| fCurrentHighlightAnnotationRange.length= maxRangeEnd - maxRangeStart; |
| } else { |
| fTotalHighlightAnnotationRange.offset= maxRangeStart; |
| fTotalHighlightAnnotationRange.length= maxRangeEnd - maxRangeStart; |
| fCurrentHighlightAnnotationRange.offset=highlightAnnotationRangeStart; |
| fCurrentHighlightAnnotationRange.length= highlightAnnotationRangeEnd - highlightAnnotationRangeStart; |
| } |
| } else { |
| if (isWorldChange) { |
| fCurrentHighlightAnnotationRange= fTotalHighlightAnnotationRange; |
| fTotalHighlightAnnotationRange= null; |
| } else { |
| fCurrentHighlightAnnotationRange= null; |
| } |
| } |
| |
| adaptToDocumentLength(fCurrentHighlightAnnotationRange); |
| adaptToDocumentLength(fTotalHighlightAnnotationRange); |
| } |
| |
| /** |
| * Updates the remembered decoration ranges. |
| * |
| * @param drawRangeStart the start of the range |
| * @param drawRangeEnd the end of the range |
| * @param isWorldChange tells whether the range belongs to a annotation model event reporting a world change |
| * @since 3.3 |
| */ |
| private void updateDrawRanges(int drawRangeStart, int drawRangeEnd, boolean isWorldChange) { |
| if (drawRangeStart != Integer.MAX_VALUE) { |
| |
| int maxRangeStart= drawRangeStart; |
| int maxRangeEnd= drawRangeEnd; |
| |
| if (fTotalDrawRange != null) { |
| maxRangeStart= Math.min(maxRangeStart, fTotalDrawRange.offset); |
| maxRangeEnd= Math.max(maxRangeEnd, fTotalDrawRange.offset + fTotalDrawRange.length); |
| } |
| |
| if (fTotalDrawRange == null) |
| fTotalDrawRange= new Position(0); |
| if (fCurrentDrawRange == null) |
| fCurrentDrawRange= new Position(0); |
| |
| if (isWorldChange) { |
| fTotalDrawRange.offset= drawRangeStart; |
| fTotalDrawRange.length= drawRangeEnd - drawRangeStart; |
| fCurrentDrawRange.offset= maxRangeStart; |
| fCurrentDrawRange.length= maxRangeEnd - maxRangeStart; |
| } else { |
| fTotalDrawRange.offset= maxRangeStart; |
| fTotalDrawRange.length= maxRangeEnd - maxRangeStart; |
| fCurrentDrawRange.offset=drawRangeStart; |
| fCurrentDrawRange.length= drawRangeEnd - drawRangeStart; |
| } |
| } else { |
| if (isWorldChange) { |
| fCurrentDrawRange= fTotalDrawRange; |
| fTotalDrawRange= null; |
| } else { |
| fCurrentDrawRange= null; |
| } |
| } |
| |
| adaptToDocumentLength(fCurrentDrawRange); |
| adaptToDocumentLength(fTotalDrawRange); |
| } |
| |
| /** |
| * Adapts the given position to the document length. |
| * |
| * @param position the position to adapt |
| * @since 3.0 |
| */ |
| private void adaptToDocumentLength(Position position) { |
| if (position == null) |
| return; |
| |
| int length= fSourceViewer.getDocument().getLength(); |
| position.offset= Math.min(position.offset, length); |
| position.length= Math.min(position.length, length - position.offset); |
| } |
| |
| /** |
| * Returns a decoration for the given annotation if this |
| * annotation is valid and shown by this painter. |
| * |
| * @param annotation the annotation |
| * @param decoration the decoration to be adapted and returned or <code>null</code> if a new one must be created |
| * @return the decoration or <code>null</code> if there's no valid one |
| * @since 3.0 |
| */ |
| private Decoration getDecoration(Annotation annotation, Decoration decoration) { |
| |
| if (annotation.isMarkedDeleted()) |
| return null; |
| |
| String type= annotation.getType(); |
| |
| Object paintingStrategy= getPaintingStrategy(type); |
| if (paintingStrategy == null || paintingStrategy instanceof NullStrategy) |
| return null; |
| |
| Color color= getColor(type); |
| if (color == null) |
| return null; |
| |
| Position position= fModel.getPosition(annotation); |
| if (position == null || position.isDeleted()) |
| return null; |
| |
| if (decoration == null) |
| decoration= new Decoration(); |
| |
| decoration.fPosition= position; |
| decoration.fColor= color; |
| if (fAnnotationAccess instanceof IAnnotationAccessExtension) { |
| IAnnotationAccessExtension extension= (IAnnotationAccessExtension) fAnnotationAccess; |
| decoration.fLayer= extension.getLayer(annotation); |
| } else { |
| decoration.fLayer= IAnnotationAccessExtension.DEFAULT_LAYER; |
| } |
| |
| decoration.fPaintingStrategy= paintingStrategy; |
| |
| return decoration; |
| } |
| |
| /** |
| * Returns the painting strategy for the given annotation. |
| * |
| * @param type the annotation type |
| * @return the annotation painter |
| * @since 3.0 |
| */ |
| private Object getPaintingStrategy(final String type) { |
| Object strategy= fCachedAnnotationType2PaintingStrategy.get(type); |
| if (strategy != null) |
| return strategy; |
| |
| strategy= fPaintingStrategyId2PaintingStrategy.get(fAnnotationType2PaintingStrategyId.get(type)); |
| if (strategy != null) { |
| fCachedAnnotationType2PaintingStrategy.put(type, strategy); |
| return strategy; |
| } |
| |
| if (fAnnotationAccess instanceof IAnnotationAccessExtension) { |
| IAnnotationAccessExtension ext = (IAnnotationAccessExtension) fAnnotationAccess; |
| Object[] sts = ext.getSupertypes(type); |
| for (int i= 0; i < sts.length; i++) { |
| strategy= fPaintingStrategyId2PaintingStrategy.get(fAnnotationType2PaintingStrategyId.get(sts[i])); |
| if (strategy != null) { |
| fCachedAnnotationType2PaintingStrategy.put(type, strategy); |
| return strategy; |
| } |
| } |
| } |
| |
| fCachedAnnotationType2PaintingStrategy.put(type, NULL_STRATEGY); |
| return null; |
| |
| } |
| |
| /** |
| * Returns the color for the given annotation type |
| * |
| * @param annotationType the annotation type |
| * @return the color |
| * @since 3.0 |
| */ |
| private Color getColor(final Object annotationType) { |
| Color color= fCachedAnnotationType2Color.get(annotationType); |
| if (color != null) |
| return color; |
| |
| color= fAnnotationType2Color.get(annotationType); |
| if (color != null) { |
| fCachedAnnotationType2Color.put(annotationType, color); |
| return color; |
| } |
| |
| if (fAnnotationAccess instanceof IAnnotationAccessExtension) { |
| IAnnotationAccessExtension extension= (IAnnotationAccessExtension) fAnnotationAccess; |
| Object[] superTypes= extension.getSupertypes(annotationType); |
| if (superTypes != null) { |
| for (int i= 0; i < superTypes.length; i++) { |
| color= fAnnotationType2Color.get(superTypes[i]); |
| if (color != null) { |
| fCachedAnnotationType2Color.put(annotationType, color); |
| return color; |
| } |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Recomputes the squiggles to be drawn and redraws them. |
| * |
| * @param event the annotation model event |
| * @since 3.0 |
| */ |
| private void updatePainting(AnnotationModelEvent event) { |
| disablePainting(event == null); |
| |
| catchupWithModel(event); |
| |
| if (!fInputDocumentAboutToBeChanged) |
| invalidateTextPresentation(); |
| |
| enablePainting(); |
| } |
| |
| private void invalidateTextPresentation() { |
| IRegion r= null; |
| synchronized (fHighlightedDecorationsMapLock) { |
| if (fCurrentHighlightAnnotationRange != null) |
| r= new Region(fCurrentHighlightAnnotationRange.getOffset(), fCurrentHighlightAnnotationRange.getLength()); |
| } |
| if (r == null) |
| return; |
| |
| if (fSourceViewer instanceof ITextViewerExtension2) { |
| if (DEBUG) |
| System.out.println("AP: invalidating offset: " + r.getOffset() + ", length= " + r.getLength()); //$NON-NLS-1$ //$NON-NLS-2$ |
| |
| ((ITextViewerExtension2)fSourceViewer).invalidateTextPresentation(r.getOffset(), r.getLength()); |
| |
| } else { |
| fSourceViewer.invalidateTextPresentation(); |
| } |
| } |
| |
| @Override |
| public void applyTextPresentation(TextPresentation tp) { |
| Set<Entry<Annotation, Decoration>> decorations; |
| |
| synchronized (fHighlightedDecorationsMapLock) { |
| if (fHighlightedDecorationsMap == null || fHighlightedDecorationsMap.isEmpty()) |
| return; |
| |
| decorations= new HashSet<>(fHighlightedDecorationsMap.entrySet()); |
| } |
| |
| IRegion region= tp.getExtent(); |
| |
| if (DEBUG) |
| System.out.println("AP: applying text presentation offset: " + region.getOffset() + ", length= " + region.getLength()); //$NON-NLS-1$ //$NON-NLS-2$ |
| |
| for (int layer= 0, maxLayer= 1; layer < maxLayer; layer++) { |
| |
| for (Iterator<Entry<Annotation, Decoration>> iter= decorations.iterator(); iter.hasNext();) { |
| Entry<Annotation, Decoration> entry= iter.next(); |
| |
| Annotation a= entry.getKey(); |
| if (a.isMarkedDeleted()) |
| continue; |
| |
| Decoration pp = entry.getValue(); |
| |
| maxLayer= Math.max(maxLayer, pp.fLayer + 1); // dynamically update layer maximum |
| if (pp.fLayer != layer) // wrong layer: skip annotation |
| continue; |
| |
| Position p= pp.fPosition; |
| if (fSourceViewer instanceof ITextViewerExtension5) { |
| ITextViewerExtension5 extension3= (ITextViewerExtension5) fSourceViewer; |
| if (null == extension3.modelRange2WidgetRange(new Region(p.getOffset(), p.getLength()))) |
| continue; |
| } else if (!fSourceViewer.overlapsWithVisibleRegion(p.offset, p.length)) { |
| continue; |
| } |
| |
| int regionEnd= region.getOffset() + region.getLength(); |
| int pEnd= p.getOffset() + p.getLength(); |
| if (pEnd >= region.getOffset() && regionEnd > p.getOffset()) { |
| int start= Math.max(p.getOffset(), region.getOffset()); |
| int end= Math.min(regionEnd, pEnd); |
| int length= Math.max(end - start, 0); |
| StyleRange styleRange= new StyleRange(start, length, null, null); |
| ((ITextStyleStrategy)pp.fPaintingStrategy).applyTextStyle(styleRange, pp.fColor); |
| tp.mergeStyleRange(styleRange); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public synchronized void modelChanged(final IAnnotationModel model) { |
| if (DEBUG) |
| System.err.println("AP: OLD API of AnnotationModelListener called"); //$NON-NLS-1$ |
| |
| modelChanged(new AnnotationModelEvent(model)); |
| } |
| |
| @Override |
| public void modelChanged(final AnnotationModelEvent event) { |
| Display textWidgetDisplay; |
| try { |
| StyledText textWidget= fTextWidget; |
| if (textWidget == null || textWidget.isDisposed()) |
| return; |
| textWidgetDisplay= textWidget.getDisplay(); |
| } catch (SWTException ex) { |
| if (ex.code == SWT.ERROR_WIDGET_DISPOSED) |
| return; |
| throw ex; |
| } |
| |
| if (fIsSettingModel) { |
| // inside the UI thread -> no need for posting |
| if (textWidgetDisplay == Display.getCurrent()) |
| updatePainting(event); |
| else { |
| /* |
| * we can throw away the changes since |
| * further update painting will happen |
| */ |
| return; |
| } |
| } else { |
| if (DEBUG && event != null && event.isWorldChange()) { |
| System.out.println("AP: WORLD CHANGED, stack trace follows:"); //$NON-NLS-1$ |
| new Throwable().printStackTrace(System.out); |
| } |
| |
| // XXX: posting here is a problem for annotations that are being |
| // removed and the positions of which are not updated to document |
| // changes any more. If the document gets modified between |
| // now and running the posted runnable, the position information |
| // is not accurate any longer. |
| textWidgetDisplay.asyncExec(new Runnable() { |
| @Override |
| public void run() { |
| if (fTextWidget != null && !fTextWidget.isDisposed()) |
| updatePainting(event); |
| } |
| }); |
| } |
| } |
| |
| /** |
| * Sets the color in which the squiggly for the given annotation type should be drawn. |
| * |
| * @param annotationType the annotation type |
| * @param color the color |
| */ |
| public void setAnnotationTypeColor(Object annotationType, Color color) { |
| if (color != null) |
| fAnnotationType2Color.put(annotationType, color); |
| else |
| fAnnotationType2Color.remove(annotationType); |
| fCachedAnnotationType2Color.clear(); |
| } |
| |
| /** |
| * Adds the given annotation type to the list of annotation types whose annotations should be |
| * painted by this painter using squiggly drawing. If the annotation type is already in this |
| * list, this method is without effect. |
| * |
| * @param annotationType the annotation type |
| * @deprecated As of 3.4 replaced by |
| * {@link #addTextStyleStrategy(Object, AnnotationPainter.ITextStyleStrategy)} and |
| * {@link UnderlineStrategy} |
| */ |
| @Deprecated |
| public void addAnnotationType(Object annotationType) { |
| addAnnotationType(annotationType, SQUIGGLES); |
| } |
| |
| /** |
| * Adds the given annotation type to the list of annotation types whose annotations should be |
| * painted by this painter using the given strategy. If the annotation type is already in this |
| * list, the old strategy gets replaced. |
| * |
| * @param annotationType the annotation type |
| * @param strategyID the id of the drawing or text style strategy that should be used for this |
| * annotation type |
| * @since 3.0 |
| */ |
| public void addAnnotationType(Object annotationType, Object strategyID) { |
| fAnnotationType2PaintingStrategyId.put(annotationType, strategyID); |
| fCachedAnnotationType2PaintingStrategy.clear(); |
| |
| if (fTextInputListener == null) { |
| fTextInputListener= new ITextInputListener() { |
| |
| @Override |
| public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) { |
| fInputDocumentAboutToBeChanged= true; |
| } |
| |
| @Override |
| public void inputDocumentChanged(IDocument oldInput, IDocument newInput) { |
| fInputDocumentAboutToBeChanged= false; |
| } |
| }; |
| fSourceViewer.addTextInputListener(fTextInputListener); |
| } |
| |
| } |
| |
| /** |
| * Registers a new drawing strategy under the given ID. If there is already a |
| * strategy registered under <code>id</code>, the old strategy gets replaced. |
| * <p>The given id can be referenced when adding annotation types, see |
| * {@link #addAnnotationType(Object, Object)}.</p> |
| * |
| * @param id the identifier under which the strategy can be referenced, not <code>null</code> |
| * @param strategy the new strategy |
| * @since 3.0 |
| */ |
| public void addDrawingStrategy(Object id, IDrawingStrategy strategy) { |
| // don't permit null as null is used to signal that an annotation type is not |
| // registered with a specific strategy, and that its annotation hierarchy should be searched |
| if (id == null) |
| throw new IllegalArgumentException(); |
| fPaintingStrategyId2PaintingStrategy.put(id, strategy); |
| fCachedAnnotationType2PaintingStrategy.clear(); |
| } |
| |
| /** |
| * Registers a new drawing strategy under the given ID. If there is already |
| * a strategy registered under <code>id</code>, the old strategy gets |
| * replaced. |
| * <p> |
| * The given id can be referenced when adding annotation types, see |
| * {@link #addAnnotationType(Object, Object)}.</p> |
| * <p> |
| * <strong>Note:</strong> The annotations will only be painted if this |
| * painter has been registered as text presentation listener on the viewer.</p> |
| * |
| * @param id the identifier under which the strategy can be referenced, not <code>null</code> |
| * @param strategy the new strategy |
| * @see ITextViewerExtension4#addTextPresentationListener(ITextPresentationListener) |
| * @since 3.4 |
| */ |
| public void addTextStyleStrategy(Object id, ITextStyleStrategy strategy) { |
| // don't permit null as null is used to signal that an annotation type is not |
| // registered with a specific strategy, and that its annotation hierarchy should be searched |
| if (id == null) |
| throw new IllegalArgumentException(); |
| fPaintingStrategyId2PaintingStrategy.put(id, strategy); |
| fCachedAnnotationType2PaintingStrategy.clear(); |
| } |
| |
| /** |
| * Adds the given annotation type to the list of annotation types whose |
| * annotations should be highlighted this painter. If the annotation type |
| * is already in this list, this method is without effect. |
| * <p> |
| * <strong>Note:</strong> The annotations will only be painted if this |
| * painter has been registered as text presentation listener on the viewer.</p> |
| * |
| * @param annotationType the annotation type |
| * @see ITextViewerExtension4#addTextPresentationListener(ITextPresentationListener) |
| * @since 3.0 |
| */ |
| public void addHighlightAnnotationType(Object annotationType) { |
| addAnnotationType(annotationType, HIGHLIGHTING); |
| } |
| |
| /** |
| * Removes the given annotation type from the list of annotation types whose |
| * annotations are painted by this painter. If the annotation type is not |
| * in this list, this method is without effect. |
| * |
| * @param annotationType the annotation type |
| */ |
| public void removeAnnotationType(Object annotationType) { |
| fCachedAnnotationType2PaintingStrategy.clear(); |
| fAnnotationType2PaintingStrategyId.remove(annotationType); |
| if (fAnnotationType2PaintingStrategyId.isEmpty() && fTextInputListener != null) { |
| fSourceViewer.removeTextInputListener(fTextInputListener); |
| fTextInputListener= null; |
| fInputDocumentAboutToBeChanged= false; |
| } |
| } |
| |
| /** |
| * Removes the given annotation type from the list of annotation types whose |
| * annotations are highlighted by this painter. If the annotation type is not |
| * in this list, this method is without effect. |
| * |
| * @param annotationType the annotation type |
| * @since 3.0 |
| */ |
| public void removeHighlightAnnotationType(Object annotationType) { |
| removeAnnotationType(annotationType); |
| } |
| |
| /** |
| * Clears the list of annotation types whose annotations are |
| * painted by this painter. |
| */ |
| public void removeAllAnnotationTypes() { |
| fCachedAnnotationType2PaintingStrategy.clear(); |
| fAnnotationType2PaintingStrategyId.clear(); |
| if (fTextInputListener != null) { |
| fSourceViewer.removeTextInputListener(fTextInputListener); |
| fTextInputListener= null; |
| } |
| } |
| |
| /** |
| * Returns whether the list of annotation types whose annotations are painted |
| * by this painter contains at least on element. |
| * |
| * @return <code>true</code> if there is an annotation type whose annotations are painted |
| */ |
| public boolean isPaintingAnnotations() { |
| return !fAnnotationType2PaintingStrategyId.isEmpty(); |
| } |
| |
| @Override |
| public void dispose() { |
| |
| if (fAnnotationType2Color != null) { |
| fAnnotationType2Color.clear(); |
| fAnnotationType2Color= null; |
| } |
| |
| if (fCachedAnnotationType2Color != null) { |
| fCachedAnnotationType2Color.clear(); |
| fCachedAnnotationType2Color= null; |
| } |
| |
| if (fCachedAnnotationType2PaintingStrategy != null) { |
| fCachedAnnotationType2PaintingStrategy.clear(); |
| fCachedAnnotationType2PaintingStrategy= null; |
| } |
| |
| if (fAnnotationType2PaintingStrategyId != null) { |
| fAnnotationType2PaintingStrategyId.clear(); |
| fAnnotationType2PaintingStrategyId= null; |
| } |
| |
| fTextWidget= null; |
| fSourceViewer= null; |
| fAnnotationAccess= null; |
| fModel= null; |
| synchronized (fDecorationMapLock) { |
| fDecorationsMap= null; |
| } |
| synchronized (fHighlightedDecorationsMapLock) { |
| fHighlightedDecorationsMap= null; |
| } |
| } |
| |
| /** |
| * Returns the document offset of the upper left corner of the source viewer's view port, |
| * possibly including partially visible lines. |
| * |
| * @return the document offset if the upper left corner of the view port |
| */ |
| private int getInclusiveTopIndexStartOffset() { |
| |
| if (fTextWidget != null && !fTextWidget.isDisposed()) { |
| int top= JFaceTextUtil.getPartialTopIndex(fSourceViewer); |
| try { |
| IDocument document= fSourceViewer.getDocument(); |
| return document.getLineOffset(top); |
| } catch (BadLocationException x) { |
| } |
| } |
| |
| return -1; |
| } |
| |
| /** |
| * Returns the first invisible document offset of the lower right corner of the source viewer's view port, |
| * possibly including partially visible lines. |
| * |
| * @return the first invisible document offset of the lower right corner of the view port |
| */ |
| private int getExclusiveBottomIndexEndOffset() { |
| |
| if (fTextWidget != null && !fTextWidget.isDisposed()) { |
| int bottom= JFaceTextUtil.getPartialBottomIndex(fSourceViewer); |
| try { |
| IDocument document= fSourceViewer.getDocument(); |
| |
| if (bottom >= document.getNumberOfLines()) |
| bottom= document.getNumberOfLines() - 1; |
| |
| return document.getLineOffset(bottom) + document.getLineLength(bottom); |
| } catch (BadLocationException x) { |
| } |
| } |
| |
| return -1; |
| } |
| |
| @Override |
| public void paintControl(PaintEvent event) { |
| if (fTextWidget != null) |
| handleDrawRequest(event); |
| } |
| |
| /** |
| * Handles the request to draw the annotations using the given graphical context. |
| * |
| * @param event the paint event or <code>null</code> |
| */ |
| private void handleDrawRequest(PaintEvent event) { |
| |
| if (fTextWidget == null) { |
| // is already disposed |
| return; |
| } |
| |
| IRegion clippingRegion= computeClippingRegion(event, false); |
| if (clippingRegion == null) |
| return; |
| |
| int vOffset= clippingRegion.getOffset(); |
| int vLength= clippingRegion.getLength(); |
| |
| final GC gc= event != null ? event.gc : null; |
| |
| // Clone decorations |
| Collection<Entry<Annotation, Decoration>> decorations; |
| synchronized (fDecorationMapLock) { |
| decorations= new ArrayList<>(fDecorationsMap.size()); |
| decorations.addAll(fDecorationsMap.entrySet()); |
| } |
| |
| /* |
| * Create a new list of annotations to be drawn, since removing from decorations is more |
| * expensive. One bucket per drawing layer. Use linked lists as addition is cheap here. |
| */ |
| ArrayList<LinkedList<Entry<Annotation, Decoration>>> toBeDrawn= new ArrayList<>(10); |
| for (Iterator<Entry<Annotation, Decoration>> e = decorations.iterator(); e.hasNext();) { |
| Entry<Annotation, Decoration> entry= e.next(); |
| |
| Annotation a= entry.getKey(); |
| Decoration pp = entry.getValue(); |
| // prune any annotation that is not drawable or does not need drawing |
| if (!(a.isMarkedDeleted() || skip(a) || !regionsTouchOrOverlap(pp.fPosition.getOffset(), pp.fPosition.getLength(), vOffset, vLength))) { |
| // ensure sized appropriately |
| for (int i= toBeDrawn.size(); i <= pp.fLayer; i++) |
| toBeDrawn.add(new LinkedList<>()); |
| toBeDrawn.get(pp.fLayer).add(entry); |
| } |
| } |
| IDocument document= fSourceViewer.getDocument(); |
| for (Iterator<LinkedList<Entry<Annotation, Decoration>>> it= toBeDrawn.iterator(); it.hasNext();) { |
| LinkedList<Entry<Annotation, Decoration>> layer= it.next(); |
| for (Iterator<Entry<Annotation, Decoration>> e = layer.iterator(); e.hasNext();) { |
| Entry<Annotation, Decoration> entry= e.next(); |
| Annotation a= entry.getKey(); |
| Decoration pp = entry.getValue(); |
| drawDecoration(pp, gc, a, clippingRegion, document); |
| } |
| } |
| } |
| |
| private void drawDecoration(Decoration pp, GC gc, Annotation annotation, IRegion clippingRegion, IDocument document) { |
| if (clippingRegion == null) |
| return; |
| |
| if (!(pp.fPaintingStrategy instanceof IDrawingStrategy)) |
| return; |
| |
| IDrawingStrategy drawingStrategy= (IDrawingStrategy)pp.fPaintingStrategy; |
| |
| int clippingOffset= clippingRegion.getOffset(); |
| int clippingLength= clippingRegion.getLength(); |
| |
| Position p= pp.fPosition; |
| try { |
| |
| int startLine= document.getLineOfOffset(p.getOffset()); |
| int lastInclusive= Math.max(p.getOffset(), p.getOffset() + p.getLength() - 1); |
| int endLine= document.getLineOfOffset(lastInclusive); |
| |
| for (int i= startLine; i <= endLine; i++) { |
| int lineOffset= document.getLineOffset(i); |
| int paintStart= Math.max(lineOffset, p.getOffset()); |
| String lineDelimiter= document.getLineDelimiter(i); |
| int delimiterLength= lineDelimiter != null ? lineDelimiter.length() : 0; |
| int paintLength= Math.min(lineOffset + document.getLineLength(i) - delimiterLength, p.getOffset() + p.getLength()) - paintStart; |
| if (paintLength >= 0 && regionsTouchOrOverlap(paintStart, paintLength, clippingOffset, clippingLength)) { |
| // otherwise inside a line delimiter |
| IRegion widgetRange= getWidgetRange(paintStart, paintLength); |
| if (widgetRange != null) { |
| drawingStrategy.draw(annotation, gc, fTextWidget, widgetRange.getOffset(), widgetRange.getLength(), pp.fColor); |
| } |
| } |
| } |
| |
| } catch (BadLocationException x) { |
| } |
| } |
| |
| /** |
| * Computes the model (document) region that is covered by the paint event's clipping region. If |
| * <code>event</code> is <code>null</code>, the model range covered by the visible editor |
| * area (viewport) is returned. |
| * |
| * @param event the paint event or <code>null</code> to use the entire viewport |
| * @param isClearing tells whether the clipping is need for clearing an annotation |
| * @return the model region comprised by either the paint event's clipping region or the |
| * viewport |
| * @since 3.2 |
| */ |
| private IRegion computeClippingRegion(PaintEvent event, boolean isClearing) { |
| if (event == null) { |
| |
| if (!isClearing && fCurrentDrawRange != null) |
| return new Region(fCurrentDrawRange.offset, fCurrentDrawRange.length); |
| |
| // trigger a repaint of the entire viewport |
| int vOffset= getInclusiveTopIndexStartOffset(); |
| if (vOffset == -1) |
| return null; |
| |
| // http://bugs.eclipse.org/bugs/show_bug.cgi?id=17147 |
| int vLength= getExclusiveBottomIndexEndOffset() - vOffset; |
| |
| return new Region(vOffset, vLength); |
| } |
| |
| int widgetOffset; |
| try { |
| int widgetClippingStartOffset= fTextWidget.getOffsetAtLocation(new Point(0, event.y)); |
| int firstWidgetLine= fTextWidget.getLineAtOffset(widgetClippingStartOffset); |
| widgetOffset= fTextWidget.getOffsetAtLine(firstWidgetLine); |
| } catch (IllegalArgumentException ex1) { |
| try { |
| int firstVisibleLine= JFaceTextUtil.getPartialTopIndex(fTextWidget); |
| widgetOffset= fTextWidget.getOffsetAtLine(firstVisibleLine); |
| } catch (IllegalArgumentException ex2) { // above try code might fail too |
| widgetOffset= 0; |
| } |
| } |
| |
| int widgetEndOffset; |
| try { |
| int widgetClippingEndOffset= fTextWidget.getOffsetAtLocation(new Point(0, event.y + event.height)); |
| int lastWidgetLine= fTextWidget.getLineAtOffset(widgetClippingEndOffset); |
| widgetEndOffset= fTextWidget.getOffsetAtLine(lastWidgetLine + 1); |
| } catch (IllegalArgumentException ex1) { |
| // happens if the editor is not "full", e.g. the last line of the document is visible in the editor |
| try { |
| int lastVisibleLine= JFaceTextUtil.getPartialBottomIndex(fTextWidget); |
| if (lastVisibleLine == fTextWidget.getLineCount() - 1) |
| // last line |
| widgetEndOffset= fTextWidget.getCharCount(); |
| else |
| widgetEndOffset= fTextWidget.getOffsetAtLine(lastVisibleLine + 1) - 1; |
| } catch (IllegalArgumentException ex2) { // above try code might fail too |
| widgetEndOffset= fTextWidget.getCharCount(); |
| } |
| } |
| |
| IRegion clippingRegion= getModelRange(widgetOffset, widgetEndOffset - widgetOffset); |
| |
| return clippingRegion; |
| } |
| |
| /** |
| * Should the given annotation be skipped when handling draw requests? |
| * |
| * @param annotation the annotation |
| * @return <code>true</code> iff the given annotation should be |
| * skipped when handling draw requests |
| * @since 3.0 |
| */ |
| protected boolean skip(Annotation annotation) { |
| return false; |
| } |
| |
| /** |
| * Returns the widget region that corresponds to the |
| * given offset and length in the viewer's document. |
| * |
| * The returned object can be the fReusableRegion and may used |
| * only to read the return values and must not used to store |
| * the region. |
| * |
| * @param modelOffset the model offset |
| * @param modelLength the model length |
| * @return the corresponding widget region |
| */ |
| private IRegion getWidgetRange(int modelOffset, int modelLength) { |
| if (modelOffset == Integer.MAX_VALUE) |
| return null; |
| |
| if (fSourceViewer instanceof ITextViewerExtension5) { |
| ITextViewerExtension5 extension= (ITextViewerExtension5) fSourceViewer; |
| fReusableRegion.setOffset(modelOffset); |
| fReusableRegion.setLength(modelLength); |
| return extension.modelRange2WidgetRange(fReusableRegion); |
| } |
| |
| IRegion region= fSourceViewer.getVisibleRegion(); |
| int offset= region.getOffset(); |
| int length= region.getLength(); |
| |
| if (regionsTouchOrOverlap(modelOffset, modelLength, offset, length)) { |
| int p1= Math.max(offset, modelOffset); |
| int p2= Math.min(offset + length, modelOffset + modelLength); |
| fReusableRegion.setOffset(p1 - offset); |
| fReusableRegion.setLength(p2 - p1); |
| return fReusableRegion; |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the model region that corresponds to the given region in the |
| * viewer's text widget. |
| * |
| * @param offset the offset in the viewer's widget |
| * @param length the length in the viewer's widget |
| * @return the corresponding document region |
| * @since 3.2 |
| */ |
| private IRegion getModelRange(int offset, int length) { |
| if (offset == Integer.MAX_VALUE) |
| return null; |
| |
| if (fSourceViewer instanceof ITextViewerExtension5) { |
| ITextViewerExtension5 extension= (ITextViewerExtension5) fSourceViewer; |
| return extension.widgetRange2ModelRange(new Region(offset, length)); |
| } |
| |
| IRegion region= fSourceViewer.getVisibleRegion(); |
| return new Region(region.getOffset() + offset, length); |
| } |
| |
| /** |
| * Checks whether the two given text regions touch or overlap each other. |
| * |
| * @param offset1 offset of the first region |
| * @param length1 length of the first region |
| * @param offset2 offset of the second region |
| * @param length2 length of the second region |
| * @return <code>true</code> if the regions touch or overlap |
| */ |
| private boolean regionsTouchOrOverlap(int offset1, int length1, int offset2, int length2) { |
| return (offset1 <= offset2+length2) && (offset2 <= offset1+length1); |
| } |
| |
| @Override |
| public void deactivate(boolean redraw) { |
| if (fIsActive) { |
| fIsActive= false; |
| disablePainting(redraw); |
| setModel(null); |
| catchupWithModel(null); |
| } |
| } |
| |
| /** |
| * Returns whether the given reason causes a repaint. |
| * |
| * @param reason the reason |
| * @return <code>true</code> if repaint reason, <code>false</code> otherwise |
| * @since 3.0 |
| */ |
| protected boolean isRepaintReason(int reason) { |
| return CONFIGURATION == reason || INTERNAL == reason; |
| } |
| |
| /** |
| * Retrieves the annotation model from the given source viewer. |
| * |
| * @param sourceViewer the source viewer |
| * @return the source viewer's annotation model or <code>null</code> if none can be found |
| * @since 3.0 |
| */ |
| protected IAnnotationModel findAnnotationModel(ISourceViewer sourceViewer) { |
| if(sourceViewer != null) |
| return sourceViewer.getAnnotationModel(); |
| return null; |
| } |
| |
| @Override |
| public void paint(int reason) { |
| if (fSourceViewer.getDocument() == null) { |
| deactivate(false); |
| return; |
| } |
| |
| if (!fIsActive) { |
| IAnnotationModel model= findAnnotationModel(fSourceViewer); |
| if (model != null) { |
| fIsActive= true; |
| setModel(model); |
| } |
| } else if (isRepaintReason(reason)) |
| updatePainting(null); |
| } |
| |
| @Override |
| public void setPositionManager(IPaintPositionManager manager) { |
| } |
| } |