Added PHPUnitEditor and corresponding PHPPreferencePage
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpeclipse / phpeditor / OverviewRuler.java
diff --git a/net.sourceforge.phpeclipse/src/net/sourceforge/phpeclipse/phpeditor/OverviewRuler.java b/net.sourceforge.phpeclipse/src/net/sourceforge/phpeclipse/phpeditor/OverviewRuler.java
new file mode 100644 (file)
index 0000000..dfd4408
--- /dev/null
@@ -0,0 +1,775 @@
+/**********************************************************************
+Copyright (c) 2000, 2002 IBM Corp. and others.
+All rights reserved. This program and the accompanying materials
+are made available under the terms of the Common Public License v1.0
+which accompanies this distribution, and is available at
+http://www.eclipse.org/legal/cpl-v10.html
+
+Contributors:
+    IBM Corporation - Initial implementation
+**********************************************************************/
+
+package net.sourceforge.phpeclipse.phpeditor;
+
+import java.util.ArrayList;
+import java.util.Collections;
+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 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.ITextViewerExtension3;
+import org.eclipse.jface.text.Position;
+import org.eclipse.jface.text.Region;
+import org.eclipse.jface.text.TextEvent;
+import org.eclipse.jface.text.source.Annotation;
+import org.eclipse.jface.text.source.IAnnotationModel;
+import org.eclipse.jface.text.source.IAnnotationModelListener;
+import org.eclipse.jface.text.source.IVerticalRulerInfo;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.events.MouseAdapter;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseMoveListener;
+import org.eclipse.swt.events.PaintEvent;
+import org.eclipse.swt.events.PaintListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Cursor;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.RGB;
+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;
+
+/**
+ * 
+ */
+public class OverviewRuler implements IVerticalRulerInfo {
+
+  /**
+   * Internal listener class.
+   */
+  class InternalListener implements ITextListener, IAnnotationModelListener {
+
+    /*
+     * @see ITextListener#textChanged
+     */
+    public void textChanged(TextEvent e) {
+      if (fTextViewer != null
+        && e.getDocumentEvent() == null
+        && e.getViewerRedrawState()) {
+        // handle only changes of visible document
+        redraw();
+      }
+    }
+
+    /*
+     * @see IAnnotationModelListener#modelChanged(IAnnotationModel)
+     */
+    public void modelChanged(IAnnotationModel model) {
+      update();
+    }
+  }
+
+  /**
+   * Filters problems based on their types.
+   */
+  class FilterIterator implements Iterator {
+
+    private final static int IGNORE = 0;
+    private final static int TEMPORARY = 1;
+    private final static int PERSISTENT = 2;
+
+    private Iterator fIterator;
+    private AnnotationType fType;
+    private IProblemAnnotation fNext;
+    private int fTemporary;
+
+    public FilterIterator(AnnotationType type) {
+      this(type, IGNORE);
+    }
+
+    public FilterIterator(AnnotationType type, boolean temporary) {
+      this(type, temporary ? TEMPORARY : PERSISTENT);
+    }
+
+    private FilterIterator(AnnotationType type, int temporary) {
+      fType = type;
+      fTemporary = temporary;
+      if (fModel != null) {
+        fIterator = fModel.getAnnotationIterator();
+        skip();
+      }
+    }
+
+    private void skip() {
+      while (fIterator.hasNext()) {
+        Object next = fIterator.next();
+        if (next instanceof IProblemAnnotation) {
+          fNext = (IProblemAnnotation) next;
+          AnnotationType type = fNext.getAnnotationType();
+          if (fType == AnnotationType.ALL || fType == type) {
+            if (fTemporary == IGNORE)
+              return;
+            if (fTemporary == TEMPORARY && fNext.isTemporary())
+              return;
+            if (fTemporary == PERSISTENT && !fNext.isTemporary())
+              return;
+          }
+        }
+      }
+      fNext = null;
+    }
+
+    /*
+     * @see Iterator#hasNext()
+     */
+    public boolean hasNext() {
+      return fNext != null;
+    }
+    /*
+     * @see Iterator#next()
+     */
+    public Object next() {
+      try {
+        return fNext;
+      } finally {
+        if (fModel != null)
+          skip();
+      }
+    }
+    /*
+     * @see Iterator#remove()
+     */
+    public void remove() {
+      throw new UnsupportedOperationException();
+    }
+  };
+
+  private static final int INSET = 2;
+  private static final int PROBLEM_HEIGHT_MIN = 4;
+  private static boolean PROBLEM_HEIGHT_SCALABLE = false;
+
+  /** The model of the overview ruler */
+  private IAnnotationModel fModel;
+  /** The view to which this ruler is connected */
+  private ITextViewer fTextViewer;
+  /** The ruler's canvas */
+  private Canvas fCanvas;
+  /** The drawable for double buffering */
+  private Image fBuffer;
+  /** The internal listener */
+  private InternalListener fInternalListener = new InternalListener();
+  /** The width of this vertical ruler */
+  private int fWidth;
+  /** The hit detection cursor */
+  private Cursor fHitDetectionCursor;
+  /** The last cursor */
+  private Cursor fLastCursor;
+  /** Cache for the actual scroll position in pixels */
+  private int fScrollPos;
+  /** The line of the last mouse button activity */
+  private int fLastMouseButtonActivityLine = -1;
+  /** The actual problem height */
+  private int fProblemHeight = -1;
+
+  private Set fAnnotationSet = new HashSet();
+  private Map fLayers = new HashMap();
+  private Map fColorTable = new HashMap();
+
+  /**
+   * Constructs a vertical ruler with the given width.
+   *
+   * @param width the width of the vertical ruler
+   */
+  public OverviewRuler(int width) {
+    fWidth = width;
+  }
+
+  public Control getControl() {
+    return fCanvas;
+  }
+
+  public int getWidth() {
+    return fWidth;
+  }
+
+  public void setModel(IAnnotationModel model) {
+    if (model != fModel || model != null) {
+
+      if (fModel != null)
+        fModel.removeAnnotationModelListener(fInternalListener);
+
+      fModel = model;
+
+      if (fModel != null)
+        fModel.addAnnotationModelListener(fInternalListener);
+
+      update();
+    }
+  }
+
+  public Control createControl(Composite parent, ITextViewer textViewer) {
+
+    fTextViewer = textViewer;
+
+    fHitDetectionCursor = new Cursor(parent.getDisplay(), SWT.CURSOR_HAND);
+    fCanvas = new Canvas(parent, SWT.NO_BACKGROUND);
+
+    fCanvas.addPaintListener(new PaintListener() {
+      public void paintControl(PaintEvent event) {
+        if (fTextViewer != null)
+          doubleBufferPaint(event.gc);
+      }
+    });
+
+    fCanvas.addDisposeListener(new DisposeListener() {
+      public void widgetDisposed(DisposeEvent event) {
+        handleDispose();
+        fTextViewer = null;
+      }
+    });
+
+    fCanvas.addMouseListener(new MouseAdapter() {
+      public void mouseDown(MouseEvent event) {
+        handleMouseDown(event);
+      }
+    });
+
+    fCanvas.addMouseMoveListener(new MouseMoveListener() {
+      public void mouseMove(MouseEvent event) {
+        handleMouseMove(event);
+      }
+    });
+
+    if (fTextViewer != null)
+      fTextViewer.addTextListener(fInternalListener);
+
+    return fCanvas;
+  }
+
+  /**
+   * Disposes the ruler's resources.
+   */
+  private void handleDispose() {
+
+    if (fTextViewer != null) {
+      fTextViewer.removeTextListener(fInternalListener);
+      fTextViewer = null;
+    }
+
+    if (fModel != null)
+      fModel.removeAnnotationModelListener(fInternalListener);
+
+    if (fBuffer != null) {
+      fBuffer.dispose();
+      fBuffer = null;
+    }
+
+    if (fHitDetectionCursor != null) {
+      fHitDetectionCursor.dispose();
+      fHitDetectionCursor = null;
+    }
+
+    fAnnotationSet.clear();
+    fLayers.clear();
+    fColorTable.clear();
+  }
+
+  /**
+   * Double buffer drawing.
+   */
+  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);
+    try {
+      gc.setBackground(fCanvas.getBackground());
+      gc.fillRectangle(0, 0, size.x, size.y);
+
+      if (fTextViewer instanceof ITextViewerExtension3)
+        doPaint1(gc);
+      else
+        doPaint(gc);
+
+    } finally {
+      gc.dispose();
+    }
+
+    dest.drawImage(fBuffer, 0, 0);
+  }
+
+  private void doPaint(GC gc) {
+
+    if (fTextViewer == null)
+      return;
+
+    Rectangle r = new Rectangle(0, 0, 0, 0);
+    int yy, hh = PROBLEM_HEIGHT_MIN;
+
+    IDocument document = fTextViewer.getDocument();
+    IRegion visible = fTextViewer.getVisibleRegion();
+
+    StyledText textWidget = fTextViewer.getTextWidget();
+    int maxLines = textWidget.getLineCount();
+    fScrollPos = textWidget.getTopPixel();
+
+    Point size = fCanvas.getSize();
+    int writable = maxLines * textWidget.getLineHeight();
+    if (size.y > writable)
+      size.y = writable;
+
+    List indices = new ArrayList(fLayers.keySet());
+    Collections.sort(indices);
+
+    for (Iterator iterator = indices.iterator(); iterator.hasNext();) {
+      Object layer = iterator.next();
+      AnnotationType annotationType = (AnnotationType) fLayers.get(layer);
+
+      if (skip(annotationType))
+        continue;
+
+      boolean[] temporary = new boolean[] { false, true };
+      for (int t = 0; t < temporary.length; t++) {
+
+        Iterator e = new FilterIterator(annotationType, temporary[t]);
+        Color fill = getFillColor(annotationType, temporary[t]);
+        Color stroke = getStrokeColor(annotationType, temporary[t]);
+
+        for (int i = 0; e.hasNext(); i++) {
+
+          Annotation a = (Annotation) e.next();
+          Position p = fModel.getPosition(a);
+
+          if (p == null
+            || !p.overlapsWith(visible.getOffset(), visible.getLength()))
+            continue;
+
+          int problemOffset = Math.max(p.getOffset(), visible.getOffset());
+          int problemEnd =
+            Math.min(
+              p.getOffset() + p.getLength(),
+              visible.getOffset() + visible.getLength());
+          int problemLength = problemEnd - problemOffset;
+
+          try {
+            if (PROBLEM_HEIGHT_SCALABLE) {
+              int numbersOfLines =
+                document.getNumberOfLines(problemOffset, problemLength);
+              hh = (numbersOfLines * size.y) / maxLines;
+              if (hh < PROBLEM_HEIGHT_MIN)
+                hh = PROBLEM_HEIGHT_MIN;
+            }
+            fProblemHeight = hh;
+
+            int startLine =
+              textWidget.getLineAtOffset(problemOffset - visible.getOffset());
+            yy = Math.min((startLine * size.y) / maxLines, size.y - hh);
+
+            if (fill != null) {
+              gc.setBackground(fill);
+              gc.fillRectangle(INSET, yy, size.x - (2 * INSET), hh);
+            }
+
+            if (stroke != null) {
+              gc.setForeground(stroke);
+              r.x = INSET;
+              r.y = yy;
+              r.width = size.x - (2 * INSET) - 1;
+              r.height = hh;
+              gc.setLineWidth(1);
+              gc.drawRectangle(r);
+            }
+          } catch (BadLocationException x) {
+          }
+        }
+      }
+    }
+  }
+
+  private void doPaint1(GC gc) {
+
+    if (fTextViewer == null)
+      return;
+
+    Rectangle r = new Rectangle(0, 0, 0, 0);
+    int yy, hh = PROBLEM_HEIGHT_MIN;
+
+    ITextViewerExtension3 extension = (ITextViewerExtension3) fTextViewer;
+    IDocument document = fTextViewer.getDocument();
+    StyledText textWidget = fTextViewer.getTextWidget();
+    fScrollPos = textWidget.getTopPixel();
+
+    int maxLines = textWidget.getLineCount();
+    Point size = fCanvas.getSize();
+    int writable = maxLines * textWidget.getLineHeight();
+    if (size.y > writable)
+      size.y = writable;
+
+    List indices = new ArrayList(fLayers.keySet());
+    Collections.sort(indices);
+
+    for (Iterator iterator = indices.iterator(); iterator.hasNext();) {
+      Object layer = iterator.next();
+      AnnotationType annotationType = (AnnotationType) fLayers.get(layer);
+
+      if (skip(annotationType))
+        continue;
+
+      boolean[] temporary = new boolean[] { false, true };
+      for (int t = 0; t < temporary.length; t++) {
+
+        Iterator e = new FilterIterator(annotationType, temporary[t]);
+        Color fill = getFillColor(annotationType, temporary[t]);
+        Color stroke = getStrokeColor(annotationType, temporary[t]);
+
+        for (int i = 0; e.hasNext(); i++) {
+
+          Annotation a = (Annotation) e.next();
+          Position p = fModel.getPosition(a);
+
+          if (p == null)
+            continue;
+
+          IRegion widgetRegion =
+            extension.modelRange2WidgetRange(
+              new Region(p.getOffset(), p.getLength()));
+          if (widgetRegion == null)
+            continue;
+
+          try {
+            if (PROBLEM_HEIGHT_SCALABLE) {
+              int numbersOfLines =
+                document.getNumberOfLines(p.getOffset(), p.getLength());
+              hh = (numbersOfLines * size.y) / maxLines;
+              if (hh < PROBLEM_HEIGHT_MIN)
+                hh = PROBLEM_HEIGHT_MIN;
+            }
+            fProblemHeight = hh;
+
+            int startLine =
+              textWidget.getLineAtOffset(widgetRegion.getOffset());
+            yy = Math.min((startLine * size.y) / maxLines, size.y - hh);
+
+            if (fill != null) {
+              gc.setBackground(fill);
+              gc.fillRectangle(INSET, yy, size.x - (2 * INSET), hh);
+            }
+
+            if (stroke != null) {
+              gc.setForeground(stroke);
+              r.x = INSET;
+              r.y = yy;
+              r.width = size.x - (2 * INSET) - 1;
+              r.height = hh;
+              gc.setLineWidth(1);
+              gc.drawRectangle(r);
+            }
+          } catch (BadLocationException x) {
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Thread-safe implementation.
+   * Can be called from any thread.
+   */
+  public void update() {
+    if (fCanvas != null && !fCanvas.isDisposed()) {
+      Display d = fCanvas.getDisplay();
+      if (d != null) {
+        d.asyncExec(new Runnable() {
+          public void run() {
+            redraw();
+          }
+        });
+      }
+    }
+  }
+
+  /**
+   * Redraws the overview ruler.
+   */
+  private void redraw() {
+    if (fCanvas != null && !fCanvas.isDisposed()) {
+      GC gc = new GC(fCanvas);
+      doubleBufferPaint(gc);
+      gc.dispose();
+    }
+  }
+
+  private int[] toLineNumbers(int y_coordinate) {
+
+    StyledText textWidget = fTextViewer.getTextWidget();
+    int maxLines = textWidget.getContent().getLineCount();
+
+    int rulerLength = fCanvas.getSize().y;
+    int writable = maxLines * textWidget.getLineHeight();
+
+    if (rulerLength > writable)
+      rulerLength = writable;
+
+    if (y_coordinate >= writable)
+      return new int[] { -1, -1 };
+
+    int[] lines = new int[2];
+
+    int pixel = Math.max(y_coordinate - 1, 0);
+    lines[0] = (pixel * maxLines) / rulerLength;
+
+    pixel = Math.min(rulerLength, y_coordinate + 1);
+    lines[1] = (pixel * maxLines) / rulerLength;
+
+    if (fTextViewer instanceof ITextViewerExtension3) {
+      ITextViewerExtension3 extension = (ITextViewerExtension3) fTextViewer;
+      lines[0] = extension.widgetlLine2ModelLine(lines[0]);
+      lines[1] = extension.widgetlLine2ModelLine(lines[1]);
+    } else {
+      try {
+        IRegion visible = fTextViewer.getVisibleRegion();
+        int lineNumber =
+          fTextViewer.getDocument().getLineOfOffset(visible.getOffset());
+        lines[0] += lineNumber;
+        lines[1] += lineNumber;
+      } catch (BadLocationException x) {
+      }
+    }
+
+    return lines;
+  }
+
+  boolean hasAnnotationAt(int y_coordinate) {
+    return findBestMatchingLineNumber(toLineNumbers(y_coordinate)) != -1;
+  }
+
+  private Position getProblemPositionAt(int[] lineNumbers) {
+    if (lineNumbers[0] == -1)
+      return null;
+
+    Position found = null;
+
+    try {
+      IDocument d = fTextViewer.getDocument();
+      IRegion line = d.getLineInformation(lineNumbers[0]);
+
+      int start = line.getOffset();
+
+      line = d.getLineInformation(lineNumbers[lineNumbers.length - 1]);
+      int end = line.getOffset() + line.getLength();
+
+      Iterator e = new FilterIterator(AnnotationType.ALL);
+      while (e.hasNext()) {
+        Annotation a = (Annotation) e.next();
+        Position p = fModel.getPosition(a);
+        if (start <= p.getOffset() && p.getOffset() < end) {
+          if (found == null || p.getOffset() < found.getOffset())
+            found = p;
+        }
+      }
+
+    } catch (BadLocationException x) {
+    }
+
+    return found;
+  }
+
+  /**
+   * Returns the line which best corresponds to one of
+   * the underlying problem annotations at the given
+   * y ruler coordinate.
+   * 
+   * @return the best matching line or <code>-1</code> if no such line can be found
+   * @since 2.1
+   */
+  private int findBestMatchingLineNumber(int[] lineNumbers) {
+    if (lineNumbers == null || lineNumbers.length < 1)
+      return -1;
+
+    try {
+      Position pos = getProblemPositionAt(lineNumbers);
+      if (pos == null)
+        return -1;
+      return fTextViewer.getDocument().getLineOfOffset(pos.getOffset());
+    } catch (BadLocationException ex) {
+      return -1;
+    }
+  }
+
+  private void handleMouseDown(MouseEvent event) {
+    if (fTextViewer != null) {
+      int[] lines = toLineNumbers(event.y);
+      Position p = getProblemPositionAt(lines);
+      if (p != null) {
+        fTextViewer.revealRange(p.getOffset(), p.getLength());
+        fTextViewer.setSelectedRange(p.getOffset(), p.getLength());
+      }
+      fTextViewer.getTextWidget().setFocus();
+    }
+    fLastMouseButtonActivityLine = toDocumentLineNumber(event.y);
+  }
+
+  private void handleMouseMove(MouseEvent event) {
+    if (fTextViewer != null) {
+      int[] lines = toLineNumbers(event.y);
+      Position p = getProblemPositionAt(lines);
+      Cursor cursor = (p != null ? fHitDetectionCursor : null);
+      if (cursor != fLastCursor) {
+        fCanvas.setCursor(cursor);
+        fLastCursor = cursor;
+      }
+    }
+  }
+
+  private void handleMouseDoubleClick(MouseEvent event) {
+    fLastMouseButtonActivityLine = toDocumentLineNumber(event.y);
+  }
+
+  public void showAnnotation(AnnotationType annotationType, boolean show) {
+    if (show)
+      fAnnotationSet.add(annotationType);
+    else
+      fAnnotationSet.remove(annotationType);
+  }
+
+  public void setLayer(AnnotationType annotationType, int layer) {
+    if (layer >= 0)
+      fLayers.put(new Integer(layer), annotationType);
+    else {
+      Iterator e = fLayers.keySet().iterator();
+      while (e.hasNext()) {
+        Object key = e.next();
+        if (annotationType.equals(fLayers.get(key))) {
+          fLayers.remove(key);
+          return;
+        }
+      }
+    }
+  }
+
+  public void setColor(AnnotationType annotationType, Color color) {
+    if (color != null)
+      fColorTable.put(annotationType, color);
+    else
+      fColorTable.remove(annotationType);
+  }
+
+  private boolean skip(AnnotationType annotationType) {
+    return !fAnnotationSet.contains(annotationType);
+  }
+
+  private static RGB interpolate(RGB fg, RGB bg, double scale) {
+    return new RGB(
+      (int) ((1.0 - scale) * fg.red + scale * bg.red),
+      (int) ((1.0 - scale) * fg.green + scale * bg.green),
+      (int) ((1.0 - scale) * fg.blue + scale * bg.blue));
+  }
+
+  private static double greyLevel(RGB rgb) {
+    if (rgb.red == rgb.green && rgb.green == rgb.blue)
+      return rgb.red;
+    return (0.299 * rgb.red + 0.587 * rgb.green + 0.114 * rgb.blue + 0.5);
+  }
+
+  private static boolean isDark(RGB rgb) {
+    return greyLevel(rgb) > 128;
+  }
+
+  private static Color getColor(RGB rgb) {
+    //         PHPTextTools textTools= PHPeclipsePlugin.getDefault().getPHPTextTools();
+    //         return textTools.getColorManager().getColor(rgb);
+    return PHPEditorEnvironment.getPHPColorProvider().getColor(rgb);
+  }
+
+  private Color getColor(AnnotationType annotationType, double scale) {
+    Color base = (Color) fColorTable.get(annotationType);
+    if (base == null)
+      return null;
+
+    RGB baseRGB = base.getRGB();
+    RGB background = fCanvas.getBackground().getRGB();
+
+    boolean darkBase = isDark(baseRGB);
+    boolean darkBackground = isDark(background);
+    if (darkBase && darkBackground)
+      background = new RGB(255, 255, 255);
+    else if (!darkBase && !darkBackground)
+      background = new RGB(0, 0, 0);
+
+    return getColor(interpolate(baseRGB, background, scale));
+  }
+
+  private Color getStrokeColor(
+    AnnotationType annotationType,
+    boolean temporary) {
+    return getColor(annotationType, temporary ? 0.5 : 0.2);
+  }
+
+  private Color getFillColor(
+    AnnotationType annotationType,
+    boolean temporary) {
+    return getColor(annotationType, temporary ? 0.9 : 0.6);
+  }
+
+  /**
+   * @see IVerticalRulerInfo#getLineOfLastMouseButtonActivity()
+   * @since 2.1
+   */
+  public int getLineOfLastMouseButtonActivity() {
+    return fLastMouseButtonActivityLine;
+  }
+
+  /**
+   * @see IVerticalRulerInfo#toDocumentLineNumber(int)
+   * @since 2.1
+   */
+  public int toDocumentLineNumber(int y_coordinate) {
+
+    if (fTextViewer == null || y_coordinate == -1)
+      return -1;
+
+    int[] lineNumbers = toLineNumbers(y_coordinate);
+    int bestLine = findBestMatchingLineNumber(lineNumbers);
+    if (bestLine == -1 && lineNumbers.length > 0)
+      return lineNumbers[0];
+    return bestLine;
+  }
+
+  /**
+   * Returns the height of the problem rectangle.
+   * 
+   * @return the height of the problem rectangle
+   * @since 2.1
+   */
+  int getAnnotationHeight() {
+    return fProblemHeight;
+  }
+}