blob: 2b28b01f0717f056979af0ce29c7d44c76b74887 [file] [log] [blame]
/**
* Copyright (c) 2017 Angelo ZERR.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Angelo Zerr <angelo.zerr@gmail.com> - [CodeMining] Provide inline annotations support - Bug 527675
*/
package org.eclipse.jface.text.source.inlined;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.GlyphMetrics;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.AnnotationPainter.IDrawingStrategy;
/**
* {@link IDrawingStrategy} implementation to render {@link AbstractInlinedAnnotation}.
*
* @since 3.13
*/
class InlinedAnnotationDrawingStrategy implements IDrawingStrategy {
@Override
public void draw(Annotation annotation, GC gc, StyledText textWidget, int widgetOffset, int length, Color color) {
if (!(annotation instanceof AbstractInlinedAnnotation)) {
return;
}
AbstractInlinedAnnotation inlinedAnnotation= (AbstractInlinedAnnotation) annotation;
if (inlinedAnnotation.isInVisibleLines() && inlinedAnnotation.isFirstVisibleOffset(widgetOffset)) {
draw((AbstractInlinedAnnotation) annotation, gc, textWidget, widgetOffset, length,
color);
}
}
/**
* Draw the inlined annotation.
*
* @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 widgetOffset the offset of the line
* @param length the length of the line
* @param color the color of the line
*/
public static void draw(AbstractInlinedAnnotation annotation, GC gc, StyledText textWidget, int widgetOffset, int length,
Color color) {
if (annotation instanceof LineHeaderAnnotation) {
draw((LineHeaderAnnotation) annotation, gc, textWidget, widgetOffset, length, color);
} else {
draw((LineContentAnnotation) annotation, gc, textWidget, widgetOffset, length, color);
}
}
/**
* Draw the line header annotation in the line spacing of the previous line.
*
* @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
*/
private static void draw(LineHeaderAnnotation annotation, GC gc, StyledText textWidget, int offset, int length,
Color color) {
int line= textWidget.getLineAtOffset(offset);
if (isDeleted(annotation)) {
// When annotation is deleted, update metrics to null to remove extra spaces of the line header annotation.
if (textWidget.getLineVerticalIndent(line) > 0)
textWidget.setLineVerticalIndent(line, 0);
return;
}
if (gc != null) {
// Setting vertical indent first, before computing bounds
int height= annotation.getHeight();
if (height != 0) {
if (height != textWidget.getLineVerticalIndent(line)) {
if (annotation.oldLine != -1 && annotation.oldLine < textWidget.getLineCount()) {
textWidget.setLineVerticalIndent(annotation.oldLine, 0);
}
textWidget.setLineVerticalIndent(line, height);
}
annotation.oldLine= line;
} else if (textWidget.getLineVerticalIndent(line) > 0) {
textWidget.setLineVerticalIndent(line, 0);
}
// Compute the location of the annotation
Rectangle bounds= textWidget.getTextBounds(offset, offset);
int x= bounds.x;
int y= bounds.y;
// Draw the line header annotation
gc.setBackground(textWidget.getBackground());
annotation.setLocation(x, y);
annotation.draw(gc, textWidget, offset, length, color, x, y);
} else if (textWidget.getLineVerticalIndent(line) > 0) {
// Here vertical indent is done, the redraw of the full line width is done to avoid annotation clipping
Rectangle bounds= textWidget.getTextBounds(offset, offset);
Rectangle client= textWidget.getClientArea();
textWidget.redraw(0, bounds.y, client.width, bounds.height, false);
} else {
textWidget.redrawRange(offset, length, true);
}
}
/**
* Draw the line content annotation inside line in the empty area computed by
* {@link GlyphMetrics}.
*
* @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 widgetOffset the offset of the line in the widget (not model)
* @param length the length of the line
* @param color the color of the line
*/
private static void draw(LineContentAnnotation annotation, GC gc, StyledText textWidget, int widgetOffset, int length,
Color color) {
if (annotation.isEndOfLine(widgetOffset)) {
drawAfterLine(annotation, gc, textWidget, widgetOffset, length, color);
} else if (annotation.drawRightToPreviousChar(widgetOffset)) {
drawAsRightOfPreviousCharacter(annotation, gc, textWidget, widgetOffset, length, color);
} else {
drawAsLeftOf1stCharacter(annotation, gc, textWidget, widgetOffset, length, color);
}
}
private static void drawAfterLine(LineContentAnnotation annotation, GC gc, StyledText textWidget, int widgetOffset, int length, Color color) {
if (gc == null) {
return;
}
if (textWidget.getCharCount() == 0) {
annotation.draw(gc, textWidget, widgetOffset, length, color, 0, 0);
} else {
int line= textWidget.getLineAtOffset(widgetOffset);
int lineEndOffset= widgetOffset < textWidget.getCharCount() ? widgetOffset : textWidget.getCharCount() - 1;
Rectangle bounds= textWidget.getTextBounds(lineEndOffset, lineEndOffset);
int lineEndX= bounds.x + bounds.width + gc.stringExtent(" ").x; //$NON-NLS-1$
annotation.setLocation(lineEndX, textWidget.getLinePixel(line) + textWidget.getLineVerticalIndent(line));
annotation.draw(gc, textWidget, widgetOffset, length, color, lineEndX, textWidget.getLinePixel(line) + textWidget.getLineVerticalIndent(line));
}
}
protected static void drawAsLeftOf1stCharacter(LineContentAnnotation annotation, GC gc, StyledText textWidget, int widgetOffset, int length, Color color) {
StyleRange style= null;
try {
style= textWidget.getStyleRangeAtOffset(widgetOffset);
} catch (Exception e) {
return;
}
if (isDeleted(annotation)) {
// When annotation is deleted, update metrics to null to remove extra spaces of the line content annotation.
if (style != null && style.metrics != null) {
style.metrics= null;
textWidget.setStyleRange(style);
}
return;
}
if (gc != null) {
String hostCharacter= textWidget.getText(widgetOffset, widgetOffset);
boolean isEndOfLine= ("\r".equals(hostCharacter) || "\n".equals(hostCharacter)); //$NON-NLS-1$ //$NON-NLS-2$
// Compute the location of the annotation
Rectangle bounds= textWidget.getTextBounds(widgetOffset, widgetOffset);
int x= bounds.x + (isEndOfLine ? bounds.width * 2 : 0);
int y= bounds.y;
// When line text has line header annotation, there is a space on the top, adjust the y by using char height
y+= bounds.height - textWidget.getLineHeight();
// Draw the line content annotation
annotation.setLocation(x, y);
annotation.draw(gc, textWidget, widgetOffset, length, color, x, y);
int width= annotation.getWidth();
if (width != 0) {
if (isEndOfLine) {
if (!gc.getClipping().contains(x, y)) {
// The draw of mining is not inside the gc clipping, redraw the area which contains the mining to draw.
Rectangle client= textWidget.getClientArea();
textWidget.redraw(x, y, client.width, bounds.height, false);
}
} else {
// Get size of the character where GlyphMetrics width is added
Point charBounds= gc.stringExtent(hostCharacter);
int charWidth= charBounds.x;
// FIXME: remove this code when we need not redraw the character (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=531769)
// START TO REMOVE
annotation.setRedrawnCharacterWidth(charWidth);
// END TO REMOVE
// Annotation takes place, add GlyphMetrics width to the style
StyleRange newStyle= annotation.updateStyle(style);
if (newStyle != null) {
textWidget.setStyleRange(newStyle);
return;
}
// FIXME: remove this code when we need not redraw the character (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=531769)
// START TO REMOVE
// 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.
int redrawnHostCharX= x + bounds.width - charWidth;
int redrawnHostCharY= y;
gc.setForeground(textWidget.getForeground());
gc.setBackground(textWidget.getBackground());
gc.setFont(textWidget.getFont());
if (style != null) {
if (style.background != null) {
gc.setBackground(style.background);
gc.fillRectangle(redrawnHostCharX, redrawnHostCharY, charWidth + 1, bounds.height);
}
if (style.foreground != null) {
gc.setForeground(style.foreground);
}
if (style.font != null) {
gc.setFont(style.font);
}
}
if (textWidget.getSelection().x <= widgetOffset && textWidget.getSelection().y > widgetOffset) {
gc.setForeground(textWidget.getSelectionForeground());
gc.setBackground(textWidget.getSelectionBackground());
}
gc.drawString(hostCharacter, redrawnHostCharX, redrawnHostCharY, true);
}
// END TO REMOVE
} else if (style != null && style.metrics != null && style.metrics.width != 0) {
// line content annotation had an , reset it
style.metrics= null;
textWidget.setStyleRange(style);
}
} else {
textWidget.redrawRange(widgetOffset, length, true);
}
}
protected static void drawAsRightOfPreviousCharacter(LineContentAnnotation annotation, GC gc, StyledText textWidget, int widgetOffset, int length, Color color) {
StyleRange style= null;
try {
style= textWidget.getStyleRangeAtOffset(widgetOffset - 1);
} catch (Exception e) {
return;
}
if (isDeleted(annotation)) {
// When annotation is deleted, update metrics to null to remove extra spaces of the line content annotation.
if (style != null && style.metrics != null) {
style.metrics= null;
textWidget.setStyleRange(style);
}
return;
}
if (gc != null) {
char hostCharacter= textWidget.getText(widgetOffset - 1, widgetOffset - 1).charAt(0);
// use gc.stringExtent instead of gc.geCharWidth because of bug 548866
int redrawnCharacterWidth= hostCharacter != '\t' ? gc.stringExtent(Character.toString(hostCharacter)).x : textWidget.getTabs() * gc.stringExtent(" ").x; //$NON-NLS-1$
Rectangle charBounds= textWidget.getTextBounds(widgetOffset - 1, widgetOffset - 1);
Rectangle annotationBounds= new Rectangle(charBounds.x + redrawnCharacterWidth, charBounds.y, annotation.getWidth(), charBounds.height);
// When line text has line header annotation, there is a space on the top, adjust the y by using char height
int verticalDrawingOffset= charBounds.height - textWidget.getLineHeight();
annotationBounds.y+= verticalDrawingOffset;
// Draw the line content annotation
annotation.setLocation(annotationBounds.x, annotationBounds.y);
annotation.draw(gc, textWidget, widgetOffset, length, color, annotationBounds.x, annotationBounds.y);
int width= annotation.getWidth();
if (width != 0) {
// FIXME: remove this code when we need not redraw the character (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=531769)
// START TO REMOVE
annotation.setRedrawnCharacterWidth(redrawnCharacterWidth);
// END TO REMOVE
// Annotation takes place, add GlyphMetrics width to the style
StyleRange newStyle= annotation.updateStyle(style);
if (newStyle != null) {
textWidget.setStyleRange(newStyle);
return;
}
// FIXME: remove this code when we need not redraw the character (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=531769)
// START TO REMOVE
// 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.
gc.setForeground(textWidget.getForeground());
gc.setBackground(textWidget.getBackground());
gc.setFont(textWidget.getFont());
if (style != null) {
if (style.background != null) {
gc.setBackground(style.background);
gc.fillRectangle(charBounds.x, annotationBounds.y, redrawnCharacterWidth, charBounds.height);
}
if (style.foreground != null) {
gc.setForeground(style.foreground);
}
if (style.font != null) {
gc.setFont(style.font);
}
}
int toRedrawCharOffset= widgetOffset - 1;
if (textWidget.getSelection().x <= toRedrawCharOffset && textWidget.getSelection().y > toRedrawCharOffset) {
gc.setForeground(textWidget.getSelectionForeground());
gc.setBackground(textWidget.getSelectionBackground());
}
gc.drawString(Character.toString(hostCharacter), charBounds.x, charBounds.y + verticalDrawingOffset, true);
// END TO REMOVE
} else if (style != null && style.metrics != null && style.metrics.width != 0) {
// line content annotation had an , reset it
style.metrics= null;
textWidget.setStyleRange(style);
}
} else {
textWidget.redrawRange(widgetOffset, length, true);
}
}
/**
* Returns <code>true</code> if inlined annotation is deleted and <code>false</code> otherwise.
*
* @param annotation the inlined annotation to check
* @return <code>true</code> if inlined annotation is deleted and <code>false</code> otherwise.
*/
private static boolean isDeleted(AbstractInlinedAnnotation annotation) {
return annotation.isMarkedDeleted() || annotation.getPosition().isDeleted() || annotation.getPosition().getLength() == 0;
}
}