| /******************************************************************************* |
| * Copyright (c) 2000, 2018 IBM Corporation and others. |
| * |
| * 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: |
| * IBM Corporation - initial API and implementation |
| * Nikolay Botev <bono8106@hotmail.com> - [projection] Editor loses keyboard focus when expanding folded region - https://bugs.eclipse.org/bugs/show_bug.cgi?id=184255 |
| *******************************************************************************/ |
| package org.eclipse.jface.text.source; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.function.Consumer; |
| |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.custom.StyledText; |
| import org.eclipse.swt.events.ControlAdapter; |
| import org.eclipse.swt.events.ControlEvent; |
| import org.eclipse.swt.events.MouseEvent; |
| import org.eclipse.swt.events.MouseListener; |
| import org.eclipse.swt.graphics.Cursor; |
| import org.eclipse.swt.graphics.Font; |
| import org.eclipse.swt.graphics.GC; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.widgets.Canvas; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Display; |
| |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.IRegion; |
| import org.eclipse.jface.text.ITextListener; |
| import org.eclipse.jface.text.ITextViewer; |
| import org.eclipse.jface.text.ITextViewerExtension5; |
| import org.eclipse.jface.text.JFaceTextUtil; |
| import org.eclipse.jface.text.Position; |
| import org.eclipse.jface.text.TextEvent; |
| |
| |
| /** |
| * A vertical ruler column showing graphical representations of annotations. |
| * Will become final. |
| * <p> |
| * Do not subclass. |
| * </p> |
| * |
| * @since 2.0 |
| */ |
| public class AnnotationRulerColumn implements IVerticalRulerColumn, IVerticalRulerInfo, IVerticalRulerInfoExtension { |
| |
| /** |
| * Internal listener class. |
| */ |
| class AnnotationsListener implements IAnnotationModelListener { |
| @Override |
| public void modelChanged(IAnnotationModel model) { |
| postRedraw(); |
| } |
| } |
| |
| /** |
| * Implementation of <code>IRegion</code> that can be reused |
| * by setting the offset and the length. |
| */ |
| private static class ReusableRegion extends Position implements IRegion {} |
| |
| /** |
| * Holder for an annotation, their associated position and paint layer. Used inside the paint |
| * method for sorting annotations based on the offset of their position and layer. |
| * |
| * @since 3.0 |
| */ |
| private static class Tuple { |
| Annotation annotation; |
| Position position; |
| int layer; |
| |
| Tuple(Annotation annotation, Position position, int layer) { |
| this.annotation= annotation; |
| this.position= position; |
| this.layer= layer; |
| } |
| } |
| |
| /** |
| * Comparator for <code>Tuple</code>s to order them by layer and offset. |
| * |
| * @since 3.0 |
| */ |
| private static class TupleComparator implements Comparator<Tuple> { |
| @Override |
| public int compare(Tuple o1, Tuple o2) { |
| int cmp= Integer.compare(o1.layer, o2.layer); |
| if (cmp != 0) { |
| return cmp; |
| } |
| Position p1= o1.position; |
| Position p2= o2.position; |
| return Integer.compare(p1.getOffset(), p2.getOffset()); |
| } |
| } |
| |
| /** This column's parent ruler */ |
| private CompositeRuler fParentRuler; |
| /** The cached text viewer */ |
| private ITextViewer fCachedTextViewer; |
| /** The cached text widget */ |
| private StyledText fCachedTextWidget; |
| /** The ruler's canvas */ |
| private Canvas fCanvas; |
| /** The vertical ruler's model */ |
| private IAnnotationModel fModel; |
| /** Cache for the actual scroll position in pixels */ |
| private int fScrollPos; |
| /** The buffer for double buffering */ |
| private Image fBuffer; |
| /** The internal listener */ |
| private AnnotationsListener fAnnotationListener= new AnnotationsListener(); |
| /** The width of this vertical ruler */ |
| private int fWidth; |
| /** Switch for enabling/disabling the setModel method. */ |
| private boolean fAllowSetModel= true; |
| /** |
| * The list of annotation types to be shown in this ruler. |
| * @since 3.0 |
| */ |
| private Set<Object> fConfiguredAnnotationTypes= new HashSet<>(); |
| /** |
| * The list of allowed annotation types to be shown in this ruler. |
| * An allowed annotation type maps to <code>true</code>, a disallowed |
| * to <code>false</code>. |
| * @since 3.0 |
| */ |
| private Map<Object, Boolean> fAllowedAnnotationTypes= new HashMap<>(); |
| /** |
| * The annotation access extension. |
| * @since 3.0 |
| */ |
| private IAnnotationAccessExtension fAnnotationAccessExtension; |
| /** |
| * The hover for this column. |
| * @since 3.0 |
| */ |
| private IAnnotationHover fHover; |
| /** |
| * The cached annotations. |
| * @since 3.0 |
| */ |
| private List<Tuple> fCachedAnnotations= new ArrayList<>(); |
| /** |
| * The comparator for sorting annotations according to the offset of their position. |
| * @since 3.0 |
| */ |
| private Comparator<Tuple> fTupleComparator= new TupleComparator(); |
| /** |
| * The hit detection cursor. Do not dispose. |
| * @since 3.0 |
| */ |
| private Cursor fHitDetectionCursor; |
| /** |
| * The last cursor. Do not dispose. |
| * @since 3.0 |
| */ |
| private Cursor fLastCursor; |
| /** |
| * This ruler's mouse listener. |
| * @since 3.0 |
| */ |
| private MouseListener fMouseListener; |
| |
| private Consumer<StyledText> lineHeightChangeHandler= t -> postRedraw(); |
| |
| private ITextListener fLineListener = new ITextListener() { |
| private int previousLineCount = -1; |
| |
| @Override |
| public void textChanged(TextEvent event) { |
| if (event.getViewerRedrawState() && fCachedTextWidget.getLineCount() != previousLineCount) { |
| previousLineCount= fCachedTextWidget.getLineCount(); |
| postRedraw(); |
| } |
| } |
| }; |
| |
| /** |
| * Constructs this column with the given arguments. |
| * |
| * @param model the annotation model to get the annotations from |
| * @param width the width of the vertical ruler |
| * @param annotationAccess the annotation access |
| * @since 3.0 |
| */ |
| public AnnotationRulerColumn(IAnnotationModel model, int width, IAnnotationAccess annotationAccess) { |
| this(width, annotationAccess); |
| fAllowSetModel= false; |
| fModel= model; |
| fModel.addAnnotationModelListener(fAnnotationListener); |
| } |
| |
| /** |
| * Constructs this column with the given arguments. |
| * |
| * @param width the width of the vertical ruler |
| * @param annotationAccess the annotation access |
| * @since 3.0 |
| */ |
| public AnnotationRulerColumn(int width, IAnnotationAccess annotationAccess) { |
| fWidth= width; |
| if (annotationAccess instanceof IAnnotationAccessExtension) |
| fAnnotationAccessExtension= (IAnnotationAccessExtension) annotationAccess; |
| } |
| |
| /** |
| * Constructs this column with the given arguments. |
| * |
| * @param model the annotation model to get the annotations from |
| * @param width the width of the vertical ruler |
| */ |
| public AnnotationRulerColumn(IAnnotationModel model, int width) { |
| fWidth= width; |
| fAllowSetModel= false; |
| fModel= model; |
| fModel.addAnnotationModelListener(fAnnotationListener); |
| } |
| |
| /** |
| * Constructs this column with the given width. |
| * |
| * @param width the width of the vertical ruler |
| */ |
| public AnnotationRulerColumn(int width) { |
| fWidth= width; |
| } |
| |
| @Override |
| public Control getControl() { |
| return fCanvas; |
| } |
| |
| @Override |
| public int getWidth() { |
| return fWidth; |
| } |
| |
| @Override |
| public Control createControl(CompositeRuler parentRuler, Composite parentControl) { |
| |
| fParentRuler= parentRuler; |
| fCachedTextViewer= parentRuler.getTextViewer(); |
| fCachedTextWidget= fCachedTextViewer.getTextWidget(); |
| |
| fHitDetectionCursor= parentControl.getDisplay().getSystemCursor(SWT.CURSOR_HAND); |
| |
| fCanvas= createCanvas(parentControl); |
| |
| fCanvas.addPaintListener(event -> { |
| if (fCachedTextViewer != null) |
| doubleBufferPaint(event.gc); |
| }); |
| |
| fCanvas.addDisposeListener(e -> { |
| handleDispose(); |
| fCachedTextViewer= null; |
| fCachedTextWidget= null; |
| }); |
| |
| fMouseListener= new MouseListener() { |
| @Override |
| public void mouseUp(MouseEvent event) { |
| int lineNumber; |
| if (isPropagatingMouseListener()) { |
| fParentRuler.setLocationOfLastMouseButtonActivity(event.x, event.y); |
| lineNumber= fParentRuler.getLineOfLastMouseButtonActivity(); |
| } else |
| lineNumber= fParentRuler.toDocumentLineNumber(event.y); |
| |
| if (1 == event.button) |
| mouseClicked(lineNumber); |
| } |
| |
| @Override |
| public void mouseDown(MouseEvent event) { |
| int lineNumber; |
| if (isPropagatingMouseListener()) { |
| fParentRuler.setLocationOfLastMouseButtonActivity(event.x, event.y); |
| lineNumber= fParentRuler.getLineOfLastMouseButtonActivity(); |
| } else |
| lineNumber= fParentRuler.toDocumentLineNumber(event.y); |
| |
| if (1 == event.button) |
| AnnotationRulerColumn.this.mouseDown(lineNumber); |
| } |
| |
| @Override |
| public void mouseDoubleClick(MouseEvent event) { |
| int lineNumber; |
| if (isPropagatingMouseListener()) { |
| fParentRuler.setLocationOfLastMouseButtonActivity(event.x, event.y); |
| lineNumber= fParentRuler.getLineOfLastMouseButtonActivity(); |
| } else |
| lineNumber= fParentRuler.toDocumentLineNumber(event.y); |
| |
| if (1 == event.button) |
| mouseDoubleClicked(lineNumber); |
| } |
| }; |
| fCanvas.addMouseListener(fMouseListener); |
| |
| fCanvas.addMouseMoveListener(this::handleMouseMove); |
| |
| fCanvas.addMouseWheelListener(this::handleMouseScrolled); |
| |
| if (fCachedTextViewer != null) { |
| VisibleLinesTracker.track(fCachedTextViewer, lineHeightChangeHandler); |
| fCachedTextViewer.addTextListener(fLineListener); |
| // on word wrap toggle a "resized" ControlEvent is fired: suggest a redraw of the ruler |
| fCachedTextWidget.addControlListener(new ControlAdapter() { |
| @Override |
| public void controlResized(ControlEvent e) { |
| if (fCachedTextWidget != null && fCachedTextWidget.getWordWrap()) { |
| redraw(); |
| } |
| } |
| }); |
| } |
| |
| return fCanvas; |
| } |
| |
| /** |
| * Creates a canvas with the given parent. |
| * |
| * @param parent the parent |
| * @return the created canvas |
| */ |
| private Canvas createCanvas(Composite parent) { |
| return new Canvas(parent, SWT.NO_BACKGROUND | SWT.NO_FOCUS) { |
| @Override |
| public void addMouseListener(MouseListener listener) { |
| if (isPropagatingMouseListener() || listener == fMouseListener) |
| super.addMouseListener(listener); |
| } |
| }; |
| } |
| |
| /** |
| * Tells whether this ruler column propagates mouse listener |
| * events to its parent. |
| * |
| * @return <code>true</code> if propagating to parent |
| * @since 3.0 |
| */ |
| protected boolean isPropagatingMouseListener() { |
| return true; |
| } |
| |
| /** |
| * Hook method for a mouse down event on the given ruler line. |
| * |
| * @param rulerLine the ruler line |
| * @since 3.5 |
| */ |
| protected void mouseDown(int rulerLine) { |
| } |
| |
| /** |
| * Hook method for a mouse double click event on the given ruler line. |
| * |
| * @param rulerLine the ruler line |
| */ |
| protected void mouseDoubleClicked(int rulerLine) { |
| } |
| |
| /** |
| * Hook method for a mouse click event on the given ruler line. |
| * <p> |
| * <strong>Note:</strong> The event is sent on mouse up. |
| * </p> |
| * |
| * @param rulerLine the ruler line |
| * @since 3.0 |
| */ |
| protected void mouseClicked(int rulerLine) { |
| } |
| |
| /** |
| * Handles mouse moves. |
| * |
| * @param event the mouse move event |
| */ |
| private void handleMouseMove(MouseEvent event) { |
| fParentRuler.setLocationOfLastMouseButtonActivity(event.x, event.y); |
| if (fCachedTextViewer != null) { |
| int line= toDocumentLineNumber(event.y); |
| Cursor cursor= (hasAnnotation(line) ? fHitDetectionCursor : null); |
| if (cursor != fLastCursor) { |
| fCanvas.setCursor(cursor); |
| fLastCursor= cursor; |
| } |
| } |
| } |
| |
| /** |
| * Handles mouse scrolls. |
| * |
| * @param event the mouse scrolled event |
| */ |
| private void handleMouseScrolled(MouseEvent event) { |
| if (fCachedTextViewer instanceof ITextViewerExtension5) { |
| ITextViewerExtension5 extension= (ITextViewerExtension5) fCachedTextViewer; |
| StyledText textWidget= fCachedTextViewer.getTextWidget(); |
| int topIndex= textWidget.getTopIndex(); |
| int newTopIndex= Math.max(0, topIndex - event.count); |
| fCachedTextViewer.setTopIndex(extension.widgetLine2ModelLine(newTopIndex)); |
| } else if (fCachedTextViewer != null) { |
| int topIndex= fCachedTextViewer.getTopIndex(); |
| int newTopIndex= Math.max(0, topIndex - event.count); |
| fCachedTextViewer.setTopIndex(newTopIndex); |
| } |
| } |
| |
| /** |
| * Tells whether the given line contains an annotation. |
| * |
| * @param lineNumber the line number |
| * @return <code>true</code> if the given line contains an annotation |
| */ |
| protected boolean hasAnnotation(int lineNumber) { |
| |
| IAnnotationModel model= fModel; |
| if (fModel instanceof IAnnotationModelExtension) |
| model= ((IAnnotationModelExtension)fModel).getAnnotationModel(SourceViewer.MODEL_ANNOTATION_MODEL); |
| |
| if (model == null) |
| return false; |
| |
| IRegion line; |
| try { |
| IDocument d= fCachedTextViewer.getDocument(); |
| if (d == null) |
| return false; |
| |
| line= d.getLineInformation(lineNumber); |
| } catch (BadLocationException ex) { |
| return false; |
| } |
| |
| int lineStart= line.getOffset(); |
| int lineLength= line.getLength(); |
| |
| Iterator<Annotation> e; |
| if (fModel instanceof IAnnotationModelExtension2) |
| e= ((IAnnotationModelExtension2)fModel).getAnnotationIterator(lineStart, lineLength + 1, true, true); |
| else |
| e= model.getAnnotationIterator(); |
| |
| while (e.hasNext()) { |
| Annotation a= e.next(); |
| |
| if (a.isMarkedDeleted()) |
| continue; |
| |
| if (skip(a)) |
| continue; |
| |
| Position p= model.getPosition(a); |
| if (p == null || p.isDeleted()) |
| continue; |
| |
| if (p.overlapsWith(lineStart, lineLength) || p.length == 0 && p.offset == lineStart + lineLength) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Disposes the ruler's resources. |
| */ |
| private void handleDispose() { |
| |
| if (fCachedTextViewer != null) { |
| VisibleLinesTracker.untrack(fCachedTextViewer, lineHeightChangeHandler); |
| fCachedTextViewer.removeTextListener(fLineListener); |
| } |
| |
| if (fModel != null) |
| fModel.removeAnnotationModelListener(fAnnotationListener); |
| |
| if (fBuffer != null) { |
| fBuffer.dispose(); |
| fBuffer= null; |
| } |
| |
| fConfiguredAnnotationTypes.clear(); |
| fAllowedAnnotationTypes.clear(); |
| fAnnotationAccessExtension= null; |
| |
| } |
| |
| /** |
| * Double buffer drawing. |
| * |
| * @param dest the GC to draw into |
| */ |
| private void doubleBufferPaint(GC dest) { |
| |
| Point size= fCanvas.getSize(); |
| |
| if (size.x <= 0 || size.y <= 0) |
| return; |
| |
| if (fBuffer != null) { |
| Rectangle r= fBuffer.getBounds(); |
| if (r.width != size.x || r.height != size.y) { |
| fBuffer.dispose(); |
| fBuffer= null; |
| } |
| } |
| if (fBuffer == null) |
| fBuffer= new Image(fCanvas.getDisplay(), size.x, size.y); |
| |
| GC gc= new GC(fBuffer); |
| gc.setFont(fCachedTextWidget.getFont()); |
| try { |
| gc.setBackground(fCanvas.getBackground()); |
| gc.fillRectangle(0, 0, size.x, size.y); |
| |
| if (fCachedTextViewer instanceof ITextViewerExtension5) |
| doPaint1(gc); |
| else |
| doPaint(gc); |
| } finally { |
| gc.dispose(); |
| } |
| |
| dest.drawImage(fBuffer, 0, 0); |
| } |
| |
| /** |
| * Returns the document offset of the upper left corner of the source viewer's |
| * view port, possibly including partially visible lines. |
| * |
| * @return document offset of the upper left corner including partially visible lines |
| */ |
| protected int getInclusiveTopIndexStartOffset() { |
| if (fCachedTextWidget == null || fCachedTextWidget.isDisposed()) |
| return -1; |
| |
| IDocument document= fCachedTextViewer.getDocument(); |
| if (document == null) |
| return -1; |
| |
| int top= JFaceTextUtil.getPartialTopIndex(fCachedTextViewer); |
| try { |
| 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 (fCachedTextWidget == null || fCachedTextWidget.isDisposed()) |
| return -1; |
| |
| IDocument document= fCachedTextViewer.getDocument(); |
| if (document == null) |
| return -1; |
| |
| int bottom= JFaceTextUtil.getPartialBottomIndex(fCachedTextViewer); |
| try { |
| if (bottom >= document.getNumberOfLines()) |
| bottom= document.getNumberOfLines() - 1; |
| return document.getLineOffset(bottom) + document.getLineLength(bottom); |
| } catch (BadLocationException x) { |
| return -1; |
| } |
| } |
| |
| /** |
| * Draws the vertical ruler w/o drawing the Canvas background. |
| * |
| * @param gc the GC to draw into |
| */ |
| protected void doPaint(GC gc) { |
| |
| if (fModel == null || fCachedTextViewer == null) |
| return; |
| |
| int topLeft= getInclusiveTopIndexStartOffset(); |
| // http://dev.eclipse.org/bugs/show_bug.cgi?id=14938 |
| // http://dev.eclipse.org/bugs/show_bug.cgi?id=22487 |
| // we want the exclusive offset (right after the last character) |
| int bottomRight= getExclusiveBottomIndexEndOffset(); |
| int viewPort= bottomRight - topLeft; |
| |
| fScrollPos= fCachedTextWidget.getTopPixel(); |
| Point dimension= fCanvas.getSize(); |
| |
| IDocument doc= fCachedTextViewer.getDocument(); |
| if (doc == null) |
| return; |
| |
| int topLine= -1, bottomLine= -1; |
| try { |
| IRegion region= fCachedTextViewer.getVisibleRegion(); |
| topLine= doc.getLineOfOffset(region.getOffset()); |
| bottomLine= doc.getLineOfOffset(region.getOffset() + region.getLength()); |
| } catch (BadLocationException x) { |
| return; |
| } |
| |
| // draw Annotations |
| Rectangle r= new Rectangle(0, 0, 0, 0); |
| int maxLayer= 1; // loop at least once through layers. |
| boolean isWrapActive= fCachedTextWidget.getWordWrap(); |
| for (int layer= 0; layer < maxLayer; layer++) { |
| Iterator<Annotation> iter; |
| if (fModel instanceof IAnnotationModelExtension2) |
| iter= ((IAnnotationModelExtension2)fModel).getAnnotationIterator(topLeft, viewPort + 1, true, true); |
| else |
| iter= fModel.getAnnotationIterator(); |
| |
| while (iter.hasNext()) { |
| Annotation annotation= iter.next(); |
| |
| int lay= IAnnotationAccessExtension.DEFAULT_LAYER; |
| if (fAnnotationAccessExtension != null) |
| lay= fAnnotationAccessExtension.getLayer(annotation); |
| maxLayer= Math.max(maxLayer, lay+1); // dynamically update layer maximum |
| if (lay != layer) // wrong layer: skip annotation |
| continue; |
| |
| if (skip(annotation)) |
| continue; |
| |
| Position position= fModel.getPosition(annotation); |
| if (position == null) |
| continue; |
| |
| // https://bugs.eclipse.org/bugs/show_bug.cgi?id=20284 |
| // Position.overlapsWith returns false if the position just starts at the end |
| // of the specified range. If the position has zero length, we want to include it anyhow |
| int viewPortSize= position.getLength() == 0 ? viewPort + 1 : viewPort; |
| if (!position.overlapsWith(topLeft, viewPortSize)) |
| continue; |
| |
| try { |
| |
| int offset= position.getOffset(); |
| int length= position.getLength(); |
| |
| int startLine= doc.getLineOfOffset(offset); |
| if (startLine < topLine) |
| startLine= topLine; |
| |
| int endLine= startLine; |
| if (length > 0) |
| endLine= doc.getLineOfOffset(offset + length - 1); |
| if (endLine > bottomLine) |
| endLine= bottomLine; |
| |
| startLine -= topLine; |
| endLine -= topLine; |
| |
| r.x= 0; |
| |
| r.width= dimension.x; |
| int lines= endLine - startLine; |
| |
| if (startLine != endLine || !isWrapActive || length <= 0) { |
| // line height for different lines includes wrapped line info already, |
| // end we show annotations without offset info at very first line anyway |
| r.height= JFaceTextUtil.computeLineHeight(fCachedTextWidget, startLine, endLine + 1, lines + 1); |
| r.y= JFaceTextUtil.computeLineHeight(fCachedTextWidget, 0, startLine, startLine) - fScrollPos; |
| } else { |
| // annotate only the part of the line related to the given offset |
| Rectangle textBounds= fCachedTextWidget.getTextBounds(offset, offset + length); |
| r.height= textBounds.height; |
| r.y= textBounds.y; |
| } |
| if (r.y < dimension.y && fAnnotationAccessExtension != null) // annotation within visible area |
| fAnnotationAccessExtension.paint(annotation, gc, fCanvas, r); |
| |
| } catch (BadLocationException x) { |
| } |
| } |
| } |
| } |
| |
| /** |
| * Draws the vertical ruler w/o drawing the Canvas background. Implementation based |
| * on <code>ITextViewerExtension5</code>. Will replace <code>doPaint(GC)</code>. |
| * |
| * @param gc the GC to draw into |
| */ |
| protected void doPaint1(GC gc) { |
| |
| if (fModel == null || fCachedTextViewer == null) |
| return; |
| |
| ITextViewerExtension5 extension= (ITextViewerExtension5) fCachedTextViewer; |
| |
| fScrollPos= fCachedTextWidget.getTopPixel(); |
| Point dimension= fCanvas.getSize(); |
| |
| int vOffset= getInclusiveTopIndexStartOffset(); |
| int vLength= getExclusiveBottomIndexEndOffset() - vOffset; |
| |
| // draw Annotations |
| Rectangle r= new Rectangle(0, 0, 0, 0); |
| ReusableRegion range= new ReusableRegion(); |
| boolean isWrapActive= fCachedTextWidget.getWordWrap(); |
| fCachedAnnotations.clear(); |
| Iterator<Annotation> iter; |
| if (fModel instanceof IAnnotationModelExtension2) |
| iter= ((IAnnotationModelExtension2)fModel).getAnnotationIterator(vOffset, vLength + 1, true, true); |
| else |
| iter= fModel.getAnnotationIterator(); |
| |
| while (iter.hasNext()) { |
| Annotation annotation= iter.next(); |
| |
| if (skip(annotation)) |
| continue; |
| |
| Position position= fModel.getPosition(annotation); |
| if (position == null) |
| continue; |
| |
| // https://bugs.eclipse.org/bugs/show_bug.cgi?id=217710 |
| int extendedVLength= position.getLength() == 0 ? vLength + 1 : vLength; |
| if (!position.overlapsWith(vOffset, extendedVLength)) |
| continue; |
| |
| int lay= IAnnotationAccessExtension.DEFAULT_LAYER; |
| if (fAnnotationAccessExtension != null) |
| lay= fAnnotationAccessExtension.getLayer(annotation); |
| |
| fCachedAnnotations.add(new Tuple(annotation, position, lay)); |
| } |
| Collections.sort(fCachedAnnotations, fTupleComparator); |
| |
| for (Tuple tuple : fCachedAnnotations) { |
| Annotation annotation= tuple.annotation; |
| Position position= tuple.position; |
| |
| range.setOffset(position.getOffset()); |
| range.setLength(position.getLength()); |
| IRegion widgetRegion= extension.modelRange2WidgetRange(range); |
| if (widgetRegion == null) |
| continue; |
| |
| int offset= widgetRegion.getOffset(); |
| int startLine= extension.widgetLineOfWidgetOffset(offset); |
| if (startLine == -1) |
| continue; |
| |
| int length= Math.max(widgetRegion.getLength() - 1, 0); |
| int endLine= extension.widgetLineOfWidgetOffset(offset + length); |
| if (endLine == -1) |
| continue; |
| |
| r.x= 0; |
| |
| r.width= dimension.x; |
| int lines= endLine - startLine; |
| |
| if (startLine != endLine || !isWrapActive || length <= 0) { |
| // line height for different lines includes wrapped line info already, |
| // end we show annotations without offset info at very first line anyway |
| r.height= JFaceTextUtil.computeLineHeight(fCachedTextWidget, startLine, endLine + 1, lines + 1); |
| r.y= JFaceTextUtil.computeLineHeight(fCachedTextWidget, 0, startLine, startLine) - fScrollPos; |
| } else { |
| // annotate only the part of the line related to the given offset |
| Rectangle textBounds= fCachedTextWidget.getTextBounds(offset, offset + length); |
| r.height= textBounds.height; |
| r.y= textBounds.y; |
| } |
| // adjust the annotation position at the bottom of the line if line height has custom vertical line indent |
| int verticalIndent= fCachedTextViewer.getTextWidget().getLineVerticalIndent(startLine); |
| if (verticalIndent > 0) { |
| r.y+= verticalIndent; |
| r.height-= verticalIndent; |
| } |
| if (r.y < dimension.y && fAnnotationAccessExtension != null) // annotation within visible area |
| fAnnotationAccessExtension.paint(annotation, gc, fCanvas, r); |
| } |
| |
| fCachedAnnotations.clear(); |
| } |
| |
| |
| /** |
| * Post a redraw request for this column into the UI thread. |
| */ |
| private void postRedraw() { |
| if (fCanvas != null && !fCanvas.isDisposed()) { |
| Display d= fCanvas.getDisplay(); |
| if (d != null) { |
| d.asyncExec(this::redraw); |
| } |
| } |
| } |
| |
| @Override |
| public void redraw() { |
| if (fCanvas != null && !fCanvas.isDisposed()) { |
| if (VerticalRuler.AVOID_NEW_GC) { |
| fCanvas.redraw(); |
| } else { |
| GC gc= new GC(fCanvas); |
| doubleBufferPaint(gc); |
| gc.dispose(); |
| } |
| } |
| } |
| |
| /* |
| * @see IVerticalRulerColumn#setModel |
| */ |
| @Override |
| public void setModel(IAnnotationModel model) { |
| if (fAllowSetModel && model != fModel) { |
| |
| if (fModel != null) |
| fModel.removeAnnotationModelListener(fAnnotationListener); |
| |
| fModel= model; |
| |
| if (fModel != null) |
| fModel.addAnnotationModelListener(fAnnotationListener); |
| |
| postRedraw(); |
| } |
| } |
| |
| @Override |
| public void setFont(Font font) { |
| } |
| |
| /** |
| * Returns the cached text viewer. |
| * |
| * @return the cached text viewer |
| */ |
| protected ITextViewer getCachedTextViewer() { |
| return fCachedTextViewer; |
| } |
| |
| @Override |
| public IAnnotationModel getModel() { |
| return fModel; |
| } |
| |
| /** |
| * Adds the given annotation type to this annotation ruler column. Starting |
| * with this call, annotations of the given type are shown in this annotation |
| * ruler column. |
| * |
| * @param annotationType the annotation type |
| * @since 3.0 |
| */ |
| public void addAnnotationType(Object annotationType) { |
| fConfiguredAnnotationTypes.add(annotationType); |
| fAllowedAnnotationTypes.clear(); |
| } |
| |
| @Override |
| public int getLineOfLastMouseButtonActivity() { |
| return fParentRuler.getLineOfLastMouseButtonActivity(); |
| } |
| |
| @Override |
| public int toDocumentLineNumber(int y_coordinate) { |
| return fParentRuler.toDocumentLineNumber(y_coordinate); |
| } |
| |
| /** |
| * Removes the given annotation type from this annotation ruler column. |
| * Annotations of the given type are no longer shown in this annotation |
| * ruler column. |
| * |
| * @param annotationType the annotation type |
| * @since 3.0 |
| */ |
| public void removeAnnotationType(Object annotationType) { |
| fConfiguredAnnotationTypes.remove(annotationType); |
| fAllowedAnnotationTypes.clear(); |
| } |
| |
| /** |
| * Returns whether the given annotation should be skipped by the drawing |
| * routine. |
| * |
| * @param annotation the annotation |
| * @return <code>true</code> if annotation of the given type should be |
| * skipped, <code>false</code> otherwise |
| * @since 3.0 |
| */ |
| private boolean skip(Annotation annotation) { |
| Object annotationType= annotation.getType(); |
| Boolean allowed= fAllowedAnnotationTypes.get(annotationType); |
| if (allowed != null) |
| return !allowed.booleanValue(); |
| |
| boolean skip= skip(annotationType); |
| fAllowedAnnotationTypes.put(annotationType, !skip ? Boolean.TRUE : Boolean.FALSE); |
| return skip; |
| } |
| |
| /** |
| * Computes whether the annotation of the given type should be skipped or |
| * not. |
| * |
| * @param annotationType the annotation type |
| * @return <code>true</code> if annotation should be skipped, <code>false</code> |
| * otherwise |
| * @since 3.0 |
| */ |
| private boolean skip(Object annotationType) { |
| if (fAnnotationAccessExtension != null) { |
| Iterator<Object> e= fConfiguredAnnotationTypes.iterator(); |
| while (e.hasNext()) { |
| if (fAnnotationAccessExtension.isSubtype(annotationType, e.next())) |
| return false; |
| } |
| return true; |
| } |
| return !fConfiguredAnnotationTypes.contains(annotationType); |
| } |
| |
| @Override |
| public IAnnotationHover getHover() { |
| return fHover; |
| } |
| |
| /** |
| * @param hover The hover to set. |
| * @since 3.0 |
| */ |
| public void setHover(IAnnotationHover hover) { |
| fHover= hover; |
| } |
| |
| @Override |
| public void addVerticalRulerListener(IVerticalRulerListener listener) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public void removeVerticalRulerListener(IVerticalRulerListener listener) { |
| throw new UnsupportedOperationException(); |
| } |
| } |