Bug 529087 - [CodeMining] Improve draw of line content annotation.

Change-Id: I4aabd08ea87b650722d3e50a77832689feb61521
Signed-off-by: angelozerr <angelo.zerr@gmail.com>
diff --git a/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/sources/inlined/ColorAnnotation.java b/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/sources/inlined/ColorAnnotation.java
index a6e7a31..3a16961 100644
--- a/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/sources/inlined/ColorAnnotation.java
+++ b/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/sources/inlined/ColorAnnotation.java
@@ -30,19 +30,17 @@
 		super(pos, viewer);

 	}

 

-	@Override

-	public int getWidth() {

-		StyledText styledText = super.getTextWidget();

-		return getSquareWidth(styledText);

-	}

-

 	public void setColor(Color color) {

 		this.color = color;

 	}

 

 	@Override

-	public void draw(GC gc, StyledText textWidget, int offset, int length, Color color, int x, int y) {

-		int size = getSquareSize(gc.getFontMetrics());

+	protected int drawAndComputeWidth(GC gc, StyledText textWidget, int offset, int length, Color color, int x, int y) {

+		FontMetrics fontMetrics = gc.getFontMetrics();

+		int size = getSquareSize(fontMetrics);

+		x += fontMetrics.getLeading();

+		y += fontMetrics.getDescent();

+

 		Rectangle rect = new Rectangle(x, y, size, size);

 

 		// Fill square

@@ -52,6 +50,7 @@
 		// Draw square box

 		gc.setForeground(textWidget.getForeground());

 		gc.drawRectangle(rect);

+		return getSquareWidth(gc.getFontMetrics());

 	}

 

 	/**

@@ -70,12 +69,9 @@
 	 * @param styledText

 	 * @return the width of square

 	 */

-	private static int getSquareWidth(StyledText styledText) {

-		GC gc = new GC(styledText);

-		FontMetrics fontMetrics = gc.getFontMetrics();

+	private static int getSquareWidth(FontMetrics fontMetrics) {

 		// width = 2 spaces + size width of square

 		int width = 2 * fontMetrics.getAverageCharWidth() + getSquareSize(fontMetrics);

-		gc.dispose();

 		return width;

 	}

 }

diff --git a/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/sources/inlined/InlinedAnnotationDemo.java b/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/sources/inlined/InlinedAnnotationDemo.java
index 3f3d815..5043c0c 100644
--- a/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/sources/inlined/InlinedAnnotationDemo.java
+++ b/org.eclipse.jface.text.examples/src/org/eclipse/jface/text/examples/sources/inlined/InlinedAnnotationDemo.java
@@ -170,7 +170,7 @@
 

 					// Color annotation

 					if (color != null) {

-						Position colorPos = new Position(pos.offset + index + "color:".length(), rgb.length());

+						Position colorPos = new Position(pos.offset + index + "color:".length(), 1);

 						ColorAnnotation colorAnnotation = support.findExistingAnnotation(colorPos);

 						if (colorAnnotation == null) {

 							colorAnnotation = new ColorAnnotation(colorPos, viewer);

@@ -179,6 +179,24 @@
 						annotations.add(colorAnnotation);

 					}

 

+					// rgb parameter names annotations

+					int rgbIndex = line.indexOf("rgb");

+					if (rgbIndex != -1) {

+						rgbIndex = rgbIndex + "rgb".length();

+						int startOffset = pos.offset + rgbIndex;

+						String rgbContent = line.substring(rgbIndex, line.length());

+						int startIndex = addRGBParamNameAnnotation("red:", rgbContent, 0, startOffset, viewer, support,

+								annotations);

+						if (startIndex != -1) {

+							startIndex = addRGBParamNameAnnotation("green:", rgbContent, startIndex, startOffset, viewer,

+									support, annotations);

+							if (startIndex != -1) {

+								startIndex = addRGBParamNameAnnotation("blue:", rgbContent, startIndex, startOffset,

+										viewer, support, annotations);

+							}

+						}

+					}

+

 				} catch (BadLocationException e) {

 					e.printStackTrace();

 				}

@@ -188,6 +206,41 @@
 	}

 

 	/**

+	 * Add RGB parameter name annotation

+	 * 

+	 * @param paramName

+	 * @param rgbContent

+	 * @param startIndex

+	 * @param startOffset

+	 * @param viewer

+	 * @param support

+	 * @param annotations

+	 * @return the current parsed index

+	 */

+	private static int addRGBParamNameAnnotation(String paramName, String rgbContent, int startIndex, int startOffset,

+			ISourceViewer viewer, InlinedAnnotationSupport support, Set<AbstractInlinedAnnotation> annotations) {

+		char startChar = startIndex == 0 ? '(' : ',';

+		char[] chars = rgbContent.toCharArray();

+		for (int i = startIndex; i < chars.length; i++) {

+			char c = chars[i];

+			if (c == startChar) {

+				if (i == chars.length - 1) {

+					return -1;

+				}

+				Position paramPos = new Position(startOffset + i + 1, 1);

+				LineContentAnnotation colorParamAnnotation = support.findExistingAnnotation(paramPos);

+				if (colorParamAnnotation == null) {

+					colorParamAnnotation = new LineContentAnnotation(paramPos, viewer);

+				}

+				colorParamAnnotation.setText(paramName);

+				annotations.add(colorParamAnnotation);

+				return i + 1;

+			}

+		}

+		return -1;

+	}

+

+	/**

 	 * Parse the given input rgb color and returns an instance of SWT Color and null

 	 * otherwise.

 	 *

diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/InlinedAnnotationDrawingStrategy.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/InlinedAnnotationDrawingStrategy.java
index 8a9647f..cb9dd36 100644
--- a/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/InlinedAnnotationDrawingStrategy.java
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/InlinedAnnotationDrawingStrategy.java
@@ -13,7 +13,6 @@
 import org.eclipse.swt.custom.StyleRange;
 import org.eclipse.swt.custom.StyledText;
 import org.eclipse.swt.graphics.Color;
-import org.eclipse.swt.graphics.FontMetrics;
 import org.eclipse.swt.graphics.GC;
 import org.eclipse.swt.graphics.GlyphMetrics;
 import org.eclipse.swt.graphics.Rectangle;
@@ -52,14 +51,6 @@
 	 */
 	public static void draw(AbstractInlinedAnnotation annotation, GC gc, StyledText textWidget, int offset, int length,
 			Color color) {
-		if (annotation.isMarkedDeleted()) {
-			// When annotation is deleted, redraw the styled text to hide old draw of
-			// annotations
-			textWidget.redraw();
-			// update caret offset since line spacing has changed.
-			textWidget.setCaretOffset(textWidget.getCaretOffset());
-			return;
-		}
 		if (annotation instanceof LineHeaderAnnotation) {
 			draw((LineHeaderAnnotation) annotation, gc, textWidget, offset, length, color);
 		} else {
@@ -79,6 +70,14 @@
 	 */
 	private static void draw(LineHeaderAnnotation annotation, GC gc, StyledText textWidget, int offset, int length,
 			Color color) {
+		if (annotation.isMarkedDeleted()) {
+			// When annotation is deleted, redraw the styled text to hide old draw of
+			// annotations
+			textWidget.redraw();
+			// update caret offset since line spacing has changed.
+			textWidget.setCaretOffset(textWidget.getCaretOffset());
+			return;
+		}
 		// compute current, previous line index.
 		int lineIndex= -1;
 		try {
@@ -93,6 +92,10 @@
 		if (viewer instanceof ITextViewerExtension5) {
 			firstLineIndex= ((ITextViewerExtension5) viewer).modelLine2WidgetLine(firstLineIndex);
 		}
+		if (previousLineIndex < firstLineIndex) {
+			// the previous line index where annotation must be drawn in line spacing is hidden, don't draw it.
+			return;
+		}
 		if (gc != null) {
 			// Compute the location of the annotation
 			int x= textWidget.getLocationAtOffset(offset).x;
@@ -146,31 +149,65 @@
 	 */
 	private static void draw(LineContentAnnotation annotation, GC gc, StyledText textWidget, int offset, int length,
 			Color color) {
+		StyleRange style= null;
+		try {
+			style= textWidget.getStyleRangeAtOffset(offset);
+		} catch (Exception e) {
+			return;
+		}
+		if (annotation.isMarkedDeleted()) {
+			// When annotation is deleted, update metrics to null to remove extra spaces of the line content annotation.
+			if (style != null) {
+				style.metrics= null;
+				textWidget.setStyleRange(style);
+			}
+			return;
+		}
 		if (gc != null) {
 			// Compute the location of the annotation
-			FontMetrics fontMetrics= gc.getFontMetrics();
 			Rectangle bounds= textWidget.getTextBounds(offset, offset);
-			int x= bounds.x + fontMetrics.getLeading();
-			int y= bounds.y + fontMetrics.getDescent();
+			int x= bounds.x;
+			int y= bounds.y;
 
 			// Draw the line content annotation
 			annotation.draw(gc, textWidget, offset, length, color, x, y);
-
-			// The inline annotation replaces one character by taking a place width
-			// GlyphMetrics
-			// Here we need to redraw this first character because GlyphMetrics clip this
-			// character.
-			String s= textWidget.getText(offset, offset);
-			StyleRange style= textWidget.getStyleRangeAtOffset(offset);
-			if (style != null) {
-				if (style.background != null) {
-					gc.setBackground(style.background);
+			// check the width annotation
+			int width= annotation.getWidth();
+			GlyphMetrics metrics= style != null ? style.metrics : null;
+			if (metrics == null || metrics.width != width) {
+				// The annotation drawn width is not the same than metrics width, update it.
+				if (metrics == null) {
+					metrics= new GlyphMetrics(0, 0, width);
+				} else {
+					metrics.width= width;
 				}
-				if (style.foreground != null) {
-					gc.setForeground(style.foreground);
+				if (style == null) {
+					style= new StyleRange();
+					style.start= offset;
+					style.length= 1;
+					style.background= textWidget.getBackground();
+					style.foreground= textWidget.getForeground();
 				}
+				style.metrics= metrics;
+				textWidget.setStyleRange(style);
+				return;
 			}
-			gc.drawString(s, bounds.x + bounds.width - gc.stringExtent(s).x, bounds.y, true);
+			if (width != 0) {
+				// The inline annotation replaces one character by taking a place width
+				// GlyphMetrics
+				// Here we need to redraw this first character because GlyphMetrics clip this
+				// character.
+				String s= textWidget.getText(offset, offset);
+				if (style != null) {
+					if (style.background != null) {
+						gc.setBackground(style.background);
+					}
+					if (style.foreground != null) {
+						gc.setForeground(style.foreground);
+					}
+				}
+				gc.drawString(s, bounds.x + bounds.width - gc.stringExtent(s).x, bounds.y, true);
+			}
 		} else {
 			textWidget.redrawRange(offset, length, true);
 		}
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/InlinedAnnotationSupport.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/InlinedAnnotationSupport.java
index 33df72f..0d20e10 100644
--- a/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/InlinedAnnotationSupport.java
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/InlinedAnnotationSupport.java
@@ -21,7 +21,6 @@
 import java.util.function.Consumer;
 
 import org.eclipse.swt.SWT;
-import org.eclipse.swt.custom.StyleRange;
 import org.eclipse.swt.custom.StyledText;
 import org.eclipse.swt.custom.StyledTextLineSpacingProvider;
 import org.eclipse.swt.graphics.Color;
@@ -140,7 +139,6 @@
 		if (annotationModel == null) {
 			return;
 		}
-		StyledText styledText= fViewer.getTextWidget();
 		Map<AbstractInlinedAnnotation, Position> annotationsToAdd= new HashMap<>();
 		List<AbstractInlinedAnnotation> annotationsToRemove= fInlinedAnnotations != null
 				? new ArrayList<>(fInlinedAnnotations)
@@ -151,32 +149,11 @@
 				// The annotation was not created, add it
 				annotationsToAdd.put(ann, ann.getPosition());
 			}
-			if (ann instanceof LineContentAnnotation) {
-				// Create metrics with well width to add space where the inline annotation must
-				// be drawn.
-				runInUIThread(styledText, (text) -> {
-					StyleRange s= new StyleRange();
-					s.start= ann.getPosition().getOffset();
-					s.length= 1;
-					s.metrics= ((LineContentAnnotation) ann).createMetrics();
-					text.setStyleRange(s);
-				});
-			}
 		}
 		// Process annotations to remove
 		for (AbstractInlinedAnnotation ann : annotationsToRemove) {
 			// Mark annotation as deleted to ignore the draw
 			ann.markDeleted(true);
-			if (ann instanceof LineContentAnnotation) {
-				// Set metrics to null to remove space of the inline annotation
-				runInUIThread(styledText, (text) -> {
-					StyleRange s= new StyleRange();
-					s.start= ann.getPosition().getOffset();
-					s.length= 1;
-					s.metrics= null;
-					text.setStyleRange(s);
-				});
-			}
 		}
 		// Update annotation model
 		synchronized (getLockObject(annotationModel)) {
@@ -217,7 +194,7 @@
 			return null;
 		}
 		for (AbstractInlinedAnnotation ann : fInlinedAnnotations) {
-			if (ann.getPosition().offset == pos.offset) {
+			if (pos.equals(ann.getPosition()) && !ann.getPosition().isDeleted()) {
 				try {
 					return (T) ann;
 				} catch (ClassCastException e) {
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/LineContentAnnotation.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/LineContentAnnotation.java
index e2aae83..7b136c7 100644
--- a/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/LineContentAnnotation.java
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/source/inlined/LineContentAnnotation.java
@@ -11,9 +11,8 @@
 package org.eclipse.jface.text.source.inlined;

 

 import org.eclipse.swt.custom.StyledText;

-import org.eclipse.swt.graphics.FontMetrics;

+import org.eclipse.swt.graphics.Color;

 import org.eclipse.swt.graphics.GC;

-import org.eclipse.swt.graphics.GlyphMetrics;

 

 import org.eclipse.jface.text.Position;

 import org.eclipse.jface.text.source.ISourceViewer;

@@ -27,6 +26,11 @@
 public class LineContentAnnotation extends AbstractInlinedAnnotation {

 

 	/**

+	 * The annotation width

+	 */

+	private int width;

+

+	/**

 	 * Line content annotation constructor.

 	 *

 	 * @param position the position where the annotation must be drawn.

@@ -37,32 +41,41 @@
 	}

 

 	/**

-	 * Returns an instance of GlyphMetrics used to takes 'width' place when the annotation is drawn

-	 * inside the line.

-	 *

-	 * @return an instance of GlyphMetrics used to takes 'width' place when the annotation is drawn

-	 *         inside the line.

-	 */

-	public GlyphMetrics createMetrics() {

-		return new GlyphMetrics(0, 0, getWidth());

-	}

-

-	/**

 	 * Returns the annotation width. By default it computes the well width for the text annotation.

 	 *

 	 * @return the annotation width.

 	 */

-	public int getWidth() {

-		String text= super.getText();

-		if (text == null) {

-			return 0;

-		}

-		int nbChars= text.length() + 1;

-		StyledText styledText= super.getTextWidget();

-		GC gc= new GC(styledText);

-		FontMetrics fontMetrics= gc.getFontMetrics();

-		int width= nbChars * fontMetrics.getAverageCharWidth();

-		gc.dispose();

+	public final int getWidth() {

 		return width;

 	}

+

+	/**

+	 * {@inheritDoc}

+	 * <p>

+	 * After drawn, compute the text width and update it.

+	 * </p>

+	 */

+	@Override

+	public final void draw(GC gc, StyledText textWidget, int offset, int length, Color color, int x, int y) {

+		width= drawAndComputeWidth(gc, textWidget, offset, length, color, x, y);

+	}

+

+	/**

+	 * Draw the inlined annotation. By default it draws the text of the annotation with gray color.

+	 * User can override this method to draw anything.

+	 *

+	 * @param gc the graphics context

+	 * @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

+	 * @param x the x position of the annotation

+	 * @param y the y position of the annotation

+	 * @return the text width.

+	 */

+	protected int drawAndComputeWidth(GC gc, StyledText textWidget, int offset, int length, Color color, int x, int y) {

+		// Draw the text annotation and returns the width

+		super.draw(gc, textWidget, offset, length, color, x, y);

+		return gc.stringExtent(getText()).x + 2 * gc.getFontMetrics().getAverageCharWidth();

+	}

 }