gt:                     repaint(0, caretLineY, getWidth(), 2);
context: /*
   * 08/10/2009
   *
   * ErrorStrip.java - A component that can visually show Parser messages (syntax
   * errors, etc.) in an RSyntaxTextArea.
   * Copyright (C) 2009 Robert Futrell
   * robert_futrell at users.sourceforge.net
   * http://fifesoft.com/rsyntaxtextarea
   *
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Lesser General Public
   * License as published by the Free Software Foundation; either
   * version 2.1 of the License, or (at your option) any later version.
   *
   * This library is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   * Lesser General Public License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA.
   */
  package org.fife.ui.rsyntaxtextarea;
  
  import java.awt.Color;
  import java.awt.Component;
  import java.awt.Cursor;
  import java.awt.Dimension;
  import java.awt.Graphics;
  import java.awt.Rectangle;
  import java.awt.event.MouseAdapter;
  import java.awt.event.MouseEvent;
  import java.beans.PropertyChangeEvent;
  import java.beans.PropertyChangeListener;
  import java.text.MessageFormat;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Map;
  import java.util.ResourceBundle;
  import javax.swing.JComponent;
  import javax.swing.ToolTipManager;
  import javax.swing.UIManager;
  import javax.swing.event.CaretEvent;
  import javax.swing.event.CaretListener;
  import javax.swing.text.BadLocationException;
  
  import org.fife.ui.rsyntaxtextarea.parser.Parser;
  import org.fife.ui.rsyntaxtextarea.parser.ParserNotice;
  import org.fife.ui.rsyntaxtextarea.parser.TaskTagParser.TaskNotice;
  
  /**
   * A component to sit alongside an {@link RSyntaxTextArea} that displays colored markers for locations of interest
   * (parser errors, marked occurrences, etc.).
   * <p>
   * 
   * An <code>ErrorStrip</code> can be added to a UI like so:
   * 
   * <pre>
   * textArea = createTextArea();
   * scrollPane = new RTextScrollPane(textArea, true);
   * ErrorStrip es = new ErrorStrip(textArea);
   * JPanel temp = new JPanel(new BorderLayout());
   * temp.add(scrollPane);
   * temp.add(es, BorderLayout.LINE_END);
   * </pre>
   * 
   * @author Robert Futrell
   * @version 0.1
   */
  /*
   * Possible improvements: 1. Handle marked occurrence changes separately from parser changes. For each property change,
   * call a method that removes the notices being reloaded from the Markers (removing any Markers that are now "empty").
   * 2. When 1.4 support is dropped, replace new Integer(int) with Integer.valueOf(int).
   */
  public class ErrorStrip extends JComponent {
  
      /**
       * The text area.
       */
      private RSyntaxTextArea textArea;
  
      /**
       * Listens for events in this component.
       */
      private Listener listener;
  
      /**
       * Whether "marked occurrences" in the text area should be shown in this error strip.
       */
      private boolean showMarkedOccurrences;
  
      /**
       * Mapping of colors to brighter colors. This is kept to prevent unnecessary creation of the same Colors over and
       * over.
       */
      private Map brighterColors;
  
      /**
       * Only notices of this severity (or worse) will be displayed in this error strip.
       */
      private int levelThreshold;
  
      /**
       * Whether the caret marker's location should be rendered.
       */
      private boolean followCaret;
  
      /**
       * The color to use for the caret marker.
       */
      private Color caretMarkerColor;
  
      /**
       * Where we paint the caret marker.
       */
      private int caretLineY;
  
      /**
       * The last location of the caret marker.
       */
      private int lastLineY;
  
      /**
       * The preferred width of this component.
       */
      private static final int PREFERRED_WIDTH = 14;
  
      private static final String MSG = "org.fife.ui.rsyntaxtextarea.ErrorStrip";
      private static final ResourceBundle msg = ResourceBundle.getBundle(MSG);
  
      /**
       * Constructor.
       * 
       * @param textArea
       *            The text area we are examining.
       */
      public ErrorStrip(RSyntaxTextArea textArea) {
          this.textArea = textArea;
          listener = new Listener();
          ToolTipManager.sharedInstance().registerComponent(this);
          setLayout(null); // Manually layout Markers as they can overlap
          addMouseListener(listener);
          setShowMarkedOccurrences(true);
          setLevelThreshold(ParserNotice.WARNING);
          setFollowCaret(true);
          setCaretMarkerColor(Color.BLACK);
      }
  
      /**
       * Overridden so we only start listening for parser notices when this component (and presumably the text area) are
       * visible.
       */
      public void addNotify() {
          super.addNotify();
          textArea.addCaretListener(listener);
          textArea.addPropertyChangeListener(
                  RSyntaxTextArea.PARSER_NOTICES_PROPERTY, listener);
          textArea.addPropertyChangeListener(
                  RSyntaxTextArea.MARK_OCCURRENCES_PROPERTY, listener);
          textArea.addPropertyChangeListener(
                  RSyntaxTextArea.MARKED_OCCURRENCES_CHANGED_PROPERTY, listener);
          refreshMarkers();
      }
  
      /**
       * Manually manages layout since this component uses no layout manager.
       */
      public void doLayout() {
          for (int i = 0; i < getComponentCount(); i++) {
              Marker m = (Marker) getComponent(i);
              m.updateLocation();
          }
          listener.caretUpdate(null); // Force recalculation of caret line pos
      }
  
      /**
       * Returns a "brighter" color.
       * 
       * @param c
       *            The color.
       * @return A brighter color.
       */
      private Color getBrighterColor(Color c) {
          if (brighterColors == null) {
              brighterColors = new HashMap(5); // Usually small
          }
          Color brighter = (Color) brighterColors.get(c);
          if (brighter == null) {
              // Don't use c.brighter() as it doesn't work well for blue, and
              // also doesn't return something brighter "enough."
              int r = possiblyBrighter(c.getRed());
              int g = possiblyBrighter(c.getGreen());
              int b = possiblyBrighter(c.getBlue());
              brighter = new Color(r, g, b);
              brighterColors.put(c, brighter);
          }
          return brighter;
      }
  
      /**
       * returns the color to use when painting the caret marker.
       * 
       * @return The caret marker color.
       * @see #setCaretMarkerColor(Color)
       */
      public Color getCaretMarkerColor() {
          return caretMarkerColor;
      }
  
      /**
       * Returns whether the caret's position should be drawn.
       * 
       * @return Whether the caret's position should be drawn.
       * @see #setFollowCaret(boolean)
       */
      public boolean getFollowCaret() {
          return followCaret;
      }
  
      /**
       * {@inheritDoc}
       */
      public Dimension getPreferredSize() {
          int height = textArea.getPreferredScrollableViewportSize().height;
          return new Dimension(PREFERRED_WIDTH, height);
      }
  
      /**
       * Returns the minimum severity a parser notice must be for it to be displayed in this error strip.
       * 
       * @return The minimum severity.
       * @see #setLevelThreshold(int)
       */
      public int getLevelThreshold() {
          return levelThreshold;
      }
  
      /**
       * Returns whether marked occurrences are shown in this error strip.
       * 
       * @return Whether marked occurrences are shown.
       * @see #setShowMarkedOccurrences(boolean)
       */
      public boolean getShowMarkedOccurrences() {
          return showMarkedOccurrences;
      }
  
      /**
       * {@inheritDoc}
       */
      public String getToolTipText(MouseEvent e) {
          String text = null;
          int line = yToLine(e.getY());
          if (line > -1) {
              text = msg.getString("Line");
              // TODO: 1.5: Use Integer.valueOf(line)
              text = MessageFormat.format(text,
                      new Object[] { new Integer(line) });
          }
          return text;
      }
  
      /**
       * Returns the y-offset in this component corresponding to a line in the text component.
       * 
       * @param line
       *            The line.
       * @return The y-offset.
       * @see #yToLine(int)
       */
      private int lineToY(int line) {
          int h = textArea.getVisibleRect().height;
          float lineCount = textArea.getLineCount();
          return (int) ((line / lineCount) * h) - 2;
      }
  
      /**
       * Overridden to (possibly) draw the caret's position.
       * 
       * @param g
       *            The graphics context.
       */
      protected void paintComponent(Graphics g) {
          super.paintComponent(g);
          if (caretLineY > -1) {
              g.setColor(getCaretMarkerColor());
              g.fillRect(0, caretLineY, getWidth(), 2);
          }
      }
  
      /**
       * Returns a possibly brighter component for a color.
       * 
       * @param i
       *            An RGB component for a color (0-255).
       * @return A possibly brighter value for the component.
       */
      private static final int possiblyBrighter(int i) {
          if (i < 255) {
              i += (int) ((255 - i) * 0.8f);
          }
          return i;
      }
  
      /**
       * Refreshes the markers displayed in this error strip.
       */
      private void refreshMarkers() {
  
          removeAll(); // listener is removed in Marker.removeNotify()
          Map markerMap = new HashMap();
  
          List notices = textArea.getParserNotices();
          for (Iterator i = notices.iterator(); i.hasNext();) {
              ParserNotice notice = (ParserNotice) i.next();
              if (notice.getLevel() <= levelThreshold ||
                      (notice instanceof TaskNotice)) {
                  // 1.5: Use Integer.valueOf(notice.getLine())
                  Integer key = new Integer(notice.getLine());
                  Marker m = (Marker) markerMap.get(key);
                  if (m == null) {
                      m = new Marker(notice);
                      m.addMouseListener(listener);
                      markerMap.put(key, m);
                      add(m);
                  }
                  else {
                      m.addNotice(notice);
                  }
              }
          }
  
          if (getShowMarkedOccurrences() && textArea.getMarkOccurrences()) {
              List occurrences = textArea.getMarkedOccurrences();
              for (Iterator i = occurrences.iterator(); i.hasNext();) {
                  DocumentRange range = (DocumentRange) i.next();
                  int line = 0;
                  try {
                      line = textArea.getLineOfOffset(range.getStartOffset());
                  } catch (BadLocationException ble) { // Never happens
                      continue;
                  }
                  ParserNotice notice = new MarkedOccurrenceNotice(range);
                  // 1.5: Use Integer.valueOf(notice.getLine())
                  Integer key = new Integer(line);
                  Marker m = (Marker) markerMap.get(key);
                  if (m == null) {
                      m = new Marker(notice);
                      m.addMouseListener(listener);
                      markerMap.put(key, m);
                      add(m);
                  }
                  else {
                      if (!m.containsMarkedOccurence()) {
                          m.addNotice(notice);
                      }
                  }
              }
          }
  
          revalidate();
          repaint();
  
      }
  
      /**
       * {@inheritDoc}
       */
      public void removeNotify() {
          super.removeNotify();
          textArea.removeCaretListener(listener);
          textArea.removePropertyChangeListener(
                  RSyntaxTextArea.PARSER_NOTICES_PROPERTY, listener);
          textArea.removePropertyChangeListener(
                  RSyntaxTextArea.MARK_OCCURRENCES_PROPERTY, listener);
          textArea.removePropertyChangeListener(
                  RSyntaxTextArea.MARKED_OCCURRENCES_CHANGED_PROPERTY, listener);
      }
  
      /**
       * Sets the color to use when painting the caret marker.
       * 
       * @param color
       *            The new caret marker color.
       * @see #getCaretMarkerColor()
       */
      public void setCaretMarkerColor(Color color) {
          if (color != null) {
              caretMarkerColor = color;
              listener.caretUpdate(null); // Force repaint
          }
      }
  
      /**
       * Toggles whether the caret's current location should be drawn.
       * 
       * @param follow
       *            Whether the caret's current location should be followed.
       * @see #getFollowCaret()
       */
      public void setFollowCaret(boolean follow) {
          if (followCaret != follow) {
              if (followCaret) {
                  repaint(0, caretLineY, getWidth(), 2); // Erase
              }
              caretLineY = -1;
              lastLineY = -1;
              followCaret = follow;
              listener.caretUpdate(null); // Possibly repaint
          }
      }
  
      /**
       * Sets the minimum severity a parser notice must be for it to be displayed in this error strip. The default value
       * is {@link ParserNotice#WARNING}.
       * 
       * @param level
       *            The new severity threshold.
       * @see #getLevelThreshold()
       */
      public void setLevelThreshold(int level) {
          levelThreshold = level;
          if (isDisplayable()) {
              refreshMarkers();
          }
      }
  
      /**
       * Sets whether marked occurrences are shown in this error strip.
       * 
       * @param show
       *            Whether to show marked occurrences.
       * @see #getShowMarkedOccurrences()
       */
      public void setShowMarkedOccurrences(boolean show) {
          if (show != showMarkedOccurrences) {
              showMarkedOccurrences = show;
              if (isDisplayable()) { // Skip this when we're first created
                  refreshMarkers();
              }
          }
      }
  
      /**
       * Returns the line in the text area corresponding to a y-offset in this component.
       * 
       * @param y
       *            The y-offset.
       * @return The line.
       * @see #lineToY(int)
       */
      private final int yToLine(int y) {
          int line = -1;
          int h = textArea.getVisibleRect().height;
          if (y < h) {
              float at = y / (float) h;
              line = (int) (textArea.getLineCount() * at);
          }
          return line;
      }
  
      /**
       * Listens for events in the error strip and its markers.
       */
      private class Listener extends MouseAdapter
              implements PropertyChangeListener, CaretListener {
  
          private Rectangle visibleRect = new Rectangle();
  
          public void caretUpdate(CaretEvent e) {
              if (getFollowCaret()) {
                  int line = textArea.getCaretLineNumber();
                  float percent = line / ((float) textArea.getLineCount());
                  textArea.computeVisibleRect(visibleRect);
                  caretLineY = (int) (visibleRect.height * percent);
                  if (caretLineY != lastLineY) {
                      repaint(0, lastLineY, getWidth(), 2); // Erase old position
  