Package org.fife.ui.rsyntaxtextarea

Source Code of org.fife.ui.rsyntaxtextarea.SyntaxView

/*
* 02/24/2004
*
* SyntaxView.java - The View object used by RSyntaxTextArea when word wrap is
* disabled.
*
* This library is distributed under a modified BSD license.  See the included
* RSyntaxTextArea.License.txt file for details.
*/
package org.fife.ui.rsyntaxtextarea;

import java.awt.*;
import javax.swing.event.*;
import javax.swing.text.*;

import org.fife.ui.rsyntaxtextarea.folding.Fold;
import org.fife.ui.rsyntaxtextarea.folding.FoldManager;


/**
* The <code>javax.swing.text.View</code> object used by {@link RSyntaxTextArea}
* when word wrap is disabled.  It implements syntax highlighting for
* programming languages using the colors and font styles specified by the
* <code>RSyntaxTextArea</code>.<p>
*
* You don't really have to do anything to use this class, as
* {@link RSyntaxTextAreaUI} automatically sets the text area's view to be
* an instance of this class if word wrap is disabled.<p>
*
* The tokens that specify how to paint the syntax-highlighted text are gleaned
* from the text area's {@link RSyntaxDocument}.
*
* @author Robert Futrell
* @version 0.3
*/
public class SyntaxView extends View implements TabExpander,
                      TokenOrientedView, RSTAView {

  /**
   * The default font used by the text area.  If this changes we need to
   * recalculate the longest line.
   */
  private Font font;

  /**
   * Font metrics for the current font.
   */
  private FontMetrics metrics;

  /**
   * The current longest line.  This is used to calculate the preferred width
   * of the view.  Since the calculation is potentially expensive, we try to
   * avoid it by stashing which line is currently the longest.
   */
  private Element longLine;
  private float longLineWidth;

  private int tabSize;
  private int tabBase;

  /**
   * Cached for each paint() call so each drawLine() call has access to it.
   */
  private RSyntaxTextArea host;

  /**
   * Cached values to speed up the painting a tad.
   */
  private int lineHeight = 0;
  private int ascent;
  private int clipStart;
  private int clipEnd;
 
  /**
   * Temporary token used when we need to "modify" tokens for rendering
   * purposes.  Since tokens returned from RSyntaxDocuments are treated as
   * immutable, we use this temporary token to do that work.
   */
  private TokenImpl tempToken;


  /**
   * Constructs a new <code>SyntaxView</code> wrapped around an element.
   *
   * @param elem The element representing the text to display.
   */
  public SyntaxView(Element elem) {
    super(elem);
    tempToken = new TokenImpl();
  }


  /**
   * Iterate over the lines represented by the child elements
   * of the element this view represents, looking for the line
   * that is the longest.  The <em>longLine</em> variable is updated to
   * represent the longest line contained.  The <em>font</em> variable
   * is updated to indicate the font used to calculate the
   * longest line.
   */
  void calculateLongestLine() {
    Component c = getContainer();
    font = c.getFont();
    metrics = c.getFontMetrics(font);
    tabSize = getTabSize() * metrics.charWidth(' ');
    Element lines = getElement();
    int n = lines.getElementCount();
    for (int i=0; i<n; i++) {
      Element line = lines.getElement(i);
      float w = getLineWidth(i);
      if (w > longLineWidth) {
        longLineWidth = w;
        longLine = line;
      }
    }
  }


  /**
   * Gives notification from the document that attributes were changed
   * in a location that this view is responsible for.
   *
   * @param changes the change information from the associated document
   * @param a the current allocation of the view
   * @param f the factory to use to rebuild if the view has children
   * @see View#changedUpdate
   */
  @Override
  public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
    updateDamage(changes, a, f);
  }


  /**
   * Repaint the given line range.
   *
   * @param line0 The starting line number to repaint.  This must
   *   be a valid line number in the model.
   * @param line1 The ending line number to repaint.  This must
   *   be a valid line number in the model.
   * @param a  The region allocated for the view to render into.
   * @param host The component hosting the view (used to call repaint).
   */
  protected void damageLineRange(int line0, int line1, Shape a,
                        Component host) {
    if (a != null) {
      Rectangle area0 = lineToRect(a, line0);
      Rectangle area1 = lineToRect(a, line1);
      if ((area0 != null) && (area1 != null)) {
        Rectangle dmg = area0.union(area1); // damage.
        host.repaint(dmg.x, dmg.y, dmg.width, dmg.height);
      }
      else
        host.repaint();
    }
  }


  /**
   * Draws the passed-in text using syntax highlighting for the current
   * language.  It is assumed that the entire line is either not in a
   * selected region, or painting with a selection-foreground color is turned
   * off.
   *
   * @param painter The painter to render the tokens.
   * @param token The list of tokens to draw.
   * @param g The graphics context in which to draw.
   * @param x The x-coordinate at which to draw.
   * @param y The y-coordinate at which to draw.
   * @return The x-coordinate representing the end of the painted text.
   */
  private float drawLine(TokenPainter painter, Token token, Graphics2D g,
      float x, float y) {

    float nextX = x;  // The x-value at the end of our text.

    while (token!=null && token.isPaintable() && nextX<clipEnd) {
      nextX = painter.paint(token, g, nextX,y, host, this, clipStart);
      token = token.getNextToken();
    }

    // NOTE: We should re-use code from Token (paintBackground()) here,
    // but don't because I'm just too lazy.
    if (host.getEOLMarkersVisible()) {
      g.setColor(host.getForegroundForTokenType(Token.WHITESPACE));
      g.setFont(host.getFontForTokenType(Token.WHITESPACE));
      g.drawString("\u00B6", nextX, y);
    }

    // Return the x-coordinate at the end of the painted text.
    return nextX;

  }


  /**
   * Draws the passed-in text using syntax highlighting for the current
   * language.  Tokens are checked for being in a selected region, and are
   * rendered appropriately if they are.
   *
   * @param painter The painter to render the tokens.
   * @param token The list of tokens to draw.
   * @param g The graphics context in which to draw.
   * @param x The x-coordinate at which to draw.
   * @param y The y-coordinate at which to draw.
   * @param selStart The start of the selection.
   * @param selEnd The end of the selection.
   * @return The x-coordinate representing the end of the painted text.
   */
  private float drawLineWithSelection(TokenPainter painter, Token token,
      Graphics2D g, float x, float y, int selStart, int selEnd) {

    float nextX = x;  // The x-value at the end of our text.

    while (token!=null && token.isPaintable() && nextX<clipEnd) {

      // Selection starts in this token
      if (token.containsPosition(selStart)) {

        if (selStart>token.getOffset()) {
          tempToken.copyFrom(token);
          tempToken.textCount = selStart - tempToken.getOffset();
          nextX = painter.paint(tempToken,g,nextX,y,host, this, clipStart);
          tempToken.textCount = token.length();
          tempToken.makeStartAt(selStart);
          // Clone required since token and tempToken must be
          // different tokens for else statement below
          token = new TokenImpl(tempToken);
        }

        int tokenLen = token.length();
        int selCount = Math.min(tokenLen, selEnd-token.getOffset());
        if (selCount==tokenLen) {
          nextX = painter.paintSelected(token, g, nextX,y, host,
                        this, clipStart);
        }
        else {
          tempToken.copyFrom(token);
          tempToken.textCount = selCount;
          nextX = painter.paintSelected(tempToken, g, nextX,y, host,
              this, clipStart);
          tempToken.textCount = token.length();
          tempToken.makeStartAt(token.getOffset() + selCount);
          token = tempToken;
          nextX = painter.paint(token, g, nextX,y, host, this,
                      clipStart);
        }

      }

      // Selection ends in this token
      else if (token.containsPosition(selEnd)) {
        tempToken.copyFrom(token);
        tempToken.textCount = selEnd - tempToken.getOffset();
        nextX = painter.paintSelected(tempToken, g, nextX,y, host, this,
            clipStart);
        tempToken.textCount = token.length();
        tempToken.makeStartAt(selEnd);
        token = tempToken;
        nextX = painter.paint(token, g, nextX,y, host, this, clipStart);
      }

      // This token is entirely selected
      else if (token.getOffset()>=selStart &&
          token.getEndOffset()<=selEnd) {
        nextX = painter.paintSelected(token, g, nextX,y, host, this,
            clipStart);
      }

      // This token is entirely unselected
      else {
        nextX = painter.paint(token, g, nextX,y, host, this, clipStart);
      }

      token = token.getNextToken();

    }

    // NOTE: We should re-use code from Token (paintBackground()) here,
    // but don't because I'm just too lazy.
    if (host.getEOLMarkersVisible()) {
      g.setColor(host.getForegroundForTokenType(Token.WHITESPACE));
      g.setFont(host.getFontForTokenType(Token.WHITESPACE));
      g.drawString("\u00B6", nextX, y);
    }

    // Return the x-coordinate at the end of the painted text.
    return nextX;

  }


  /**
   * Calculates the width of the line represented by the given element.
   *
   * @param line The line for which to get the length.
   * @param lineNumber The line number of the specified line in the document.
   * @return The width of the line.
   */
  private float getLineWidth(int lineNumber) {
    Token tokenList = ((RSyntaxDocument)getDocument()).
                  getTokenListForLine(lineNumber);
    return RSyntaxUtilities.getTokenListWidth(tokenList,
                (RSyntaxTextArea)getContainer(),
                this);
  }


  /**
   * Provides a way to determine the next visually represented model
   * location that one might place a caret.  Some views may not be visible,
   * they might not be in the same order found in the model, or they just
   * might not allow access to some of the locations in the model.
   *
   * @param pos the position to convert >= 0
   * @param a the allocated region to render into
   * @param direction the direction from the current position that can
   *  be thought of as the arrow keys typically found on a keyboard.
   *  This may be SwingConstants.WEST, SwingConstants.EAST,
   *  SwingConstants.NORTH, or SwingConstants.SOUTH. 
   * @return the location within the model that best represents the next
   *  location visual position.
   * @exception BadLocationException
   * @exception IllegalArgumentException for an invalid direction
   */
  @Override
  public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a,
              int direction, Position.Bias[] biasRet)
              throws BadLocationException {
    return RSyntaxUtilities.getNextVisualPositionFrom(pos, b, a,
                    direction, biasRet, this);
  }


  /**
   * Determines the preferred span for this view along an
   * axis.
   *
   * @param axis may be either View.X_AXIS or View.Y_AXIS
   * @return   the span the view would like to be rendered into >= 0.
   *           Typically the view is told to render into the span
   *           that is returned, although there is no guarantee. 
   *           The parent may choose to resize or break the view.
   * @exception IllegalArgumentException for an invalid axis
   */
  @Override
  public float getPreferredSpan(int axis) {
    updateMetrics();
    switch (axis) {
      case View.X_AXIS:
        float span = longLineWidth + getRhsCorrection(); // fudge factor
        if (host.getEOLMarkersVisible()) {
          span += metrics.charWidth('\u00B6');
        }
        return span;
      case View.Y_AXIS:
        // We update lineHeight here as when this method is first
        // called, lineHeight isn't initialized.  If we don't do it
        // here, we get no vertical scrollbar (as lineHeight==0).
        lineHeight = host!=null ? host.getLineHeight() : lineHeight;
//        return getElement().getElementCount() * lineHeight;
int visibleLineCount = getElement().getElementCount();
if (host.isCodeFoldingEnabled()) {
  visibleLineCount -= host.getFoldManager().getHiddenLineCount();
}
return visibleLineCount * lineHeight;
      default:
        throw new IllegalArgumentException("Invalid axis: " + axis);
    }
  }


  /**
   * Workaround for JTextComponents allowing the caret to be rendered
   * entirely off-screen if the entire "previous" character fit entirely.
   *
   * @return The amount of space to add to the x-axis preferred span.
   */
  private final int getRhsCorrection() {
    int rhsCorrection = 10;
    if (host!=null) {
      rhsCorrection = host.getRightHandSideCorrection();
    }
    return rhsCorrection;
  }


  /**
   * Returns the tab size set for the document, defaulting to 5.
   *
   * @return The tab size.
   */
  private int getTabSize() {
    Integer i = (Integer)getDocument().getProperty(
                  PlainDocument.tabSizeAttribute);
    int size = (i != null) ? i.intValue() : 5;
    return size;
  }


  /**
   * Returns a token list for the <i>physical</i> line above the physical
   * line containing the specified offset into the document.  Note that for
   * this plain (non-wrapped) view, this is simply the token list for the
   * logical line above the line containing <code>offset</code>, since lines
   * are not wrapped.
   *
   * @param offset The offset in question.
   * @return A token list for the physical (and in this view, logical) line
   *         before this one.  If <code>offset</code> is in the first line in
   *         the document, <code>null</code> is returned.
   */
  public Token getTokenListForPhysicalLineAbove(int offset) {
    RSyntaxDocument document = (RSyntaxDocument)getDocument();
    Element map = document.getDefaultRootElement();
int line = map.getElementIndex(offset);
FoldManager fm = host.getFoldManager();
if (fm==null) {
  line--;
  if (line>=0) {
    return document.getTokenListForLine(line);
  }
}
else {
  line = fm.getVisibleLineAbove(line);
  if (line>=0) {
    return document.getTokenListForLine(line);
  }
}
//    int line = map.getElementIndex(offset) - 1;
//    if (line>=0)
//      return document.getTokenListForLine(line);
    return null;
  }


  /**
   * Returns a token list for the <i>physical</i> line below the physical
   * line containing the specified offset into the document.  Note that for
   * this plain (non-wrapped) view, this is simply the token list for the
   * logical line below the line containing <code>offset</code>, since lines
   * are not wrapped.
   *
   * @param offset The offset in question.
   * @return A token list for the physical (and in this view, logical) line
   *         after this one.  If <code>offset</code> is in the last physical
   *         line in the document, <code>null</code> is returned.
   */
  public Token getTokenListForPhysicalLineBelow(int offset) {
    RSyntaxDocument document = (RSyntaxDocument)getDocument();
    Element map = document.getDefaultRootElement();
    int lineCount = map.getElementCount();
int line = map.getElementIndex(offset);
if (!host.isCodeFoldingEnabled()) {
  if (line<lineCount-1) {
    return document.getTokenListForLine(line+1);
  }
}
else {
  FoldManager fm = host.getFoldManager();
  line = fm.getVisibleLineBelow(line);
  if (line>=0 && line<lineCount) {
    return document.getTokenListForLine(line);
  }
}
//    int line = map.getElementIndex(offset);
//    int lineCount = map.getElementCount();
//    if (line<lineCount-1)
//      return document.getTokenListForLine(line+1);
    return null;
  }


  /**
   * Gives notification that something was inserted into the document
   * in a location that this view is responsible for.
   *
   * @param changes The change information from the associated document.
   * @param a The current allocation of the view.
   * @param f The factory to use to rebuild if the view has children.
   */
  @Override
  public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
    updateDamage(changes, a, f);
  }


  /**
   * Determine the rectangle that represents the given line.
   *
   * @param a The region allocated for the view to render into
   * @param line The line number to find the region of.  This must
   *        be a valid line number in the model.
   */
  protected Rectangle lineToRect(Shape a, int line) {
    Rectangle r = null;
    updateMetrics();
    if (metrics != null) {
      Rectangle alloc = a.getBounds();
      // NOTE:  lineHeight is not initially set here, leading to the
      // current line not being highlighted when a document is first
      // opened.  So, we set it here just in case.
      lineHeight = host!=null ? host.getLineHeight() : lineHeight;
if (host.isCodeFoldingEnabled()) {
  FoldManager fm = host.getFoldManager();
  int hiddenCount = fm.getHiddenLineCountAbove(line);
  line -= hiddenCount;
}
      r = new Rectangle(alloc.x, alloc.y + line*lineHeight,
                  alloc.width, lineHeight);
    }
    return r;
  }


  /**
   * Provides a mapping from the document model coordinate space
   * to the coordinate space of the view mapped to it.
   *
   * @param pos the position to convert >= 0
   * @param a the allocated region to render into
   * @return the bounding box of the given position
   * @exception BadLocationException  if the given position does not
   *   represent a valid location in the associated document
   * @see View#modelToView
   */
  @Override
  public Shape modelToView(int pos, Shape a, Position.Bias b)
                    throws BadLocationException {

    // line coordinates
    Element map = getElement();
    RSyntaxDocument doc = (RSyntaxDocument)getDocument();
    int lineIndex = map.getElementIndex(pos);
    Token tokenList = doc.getTokenListForLine(lineIndex);
    Rectangle lineArea = lineToRect(a, lineIndex);
    tabBase = lineArea.x; // Used by listOffsetToView().

    //int x = (int)RSyntaxUtilities.getTokenListWidthUpTo(tokenList,
    //              (RSyntaxTextArea)getContainer(),
    //              this, 0, pos);
    // We use this method instead as it returns the actual bounding box,
    // not just the x-coordinate.
    lineArea = tokenList.listOffsetToView(
            (RSyntaxTextArea)getContainer(), this, pos,
            tabBase, lineArea);

    return lineArea;

  }


  /**
   * Provides a mapping, for a given region, from the document model
   * coordinate space to the view coordinate space. The specified region is
   * created as a union of the first and last character positions.<p>
   *
   * This is implemented to subtract the width of the second character, as
   * this view's <code>modelToView</code> actually returns the width of the
   * character instead of "1" or "0" like the View implementations in
   * <code>javax.swing.text</code>.  Thus, if we don't override this method,
   * the <code>View</code> implementation will return one character's width
   * too much for its consumers (implementations of
   * <code>javax.swing.text.Highlighter</code>).
   *
   * @param p0 the position of the first character (>=0)
   * @param b0 The bias of the first character position, toward the previous
   *        character or the next character represented by the offset, in
   *        case the position is a boundary of two views; <code>b0</code>
   *        will have one of these values:
   * <ul>
   *    <li> <code>Position.Bias.Forward</code>
   *    <li> <code>Position.Bias.Backward</code>
   * </ul>
   * @param p1 the position of the last character (>=0)
   * @param b1 the bias for the second character position, defined
   *    one of the legal values shown above
   * @param a the area of the view, which encompasses the requested region
   * @return the bounding box which is a union of the region specified
   *    by the first and last character positions
   * @exception BadLocationException  if the given position does
   *   not represent a valid location in the associated document
   * @exception IllegalArgumentException if <code>b0</code> or
   *    <code>b1</code> are not one of the
   *    legal <code>Position.Bias</code> values listed above
   * @see View#viewToModel
   */
  @Override
  public Shape modelToView(int p0, Position.Bias b0,
              int p1, Position.Bias b1,
              Shape a) throws BadLocationException {

    Shape s0 = modelToView(p0, a, b0);
    Shape s1;
    if (p1 ==getEndOffset()) {
      try {
        s1 = modelToView(p1, a, b1);
      } catch (BadLocationException ble) {
        s1 = null;
      }
      if (s1 == null) {
        // Assume extends left to right.
        Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a :
                a.getBounds();
        s1 = new Rectangle(alloc.x + alloc.width - 1, alloc.y,
                1, alloc.height);
      }
    }
    else {
      s1 = modelToView(p1, a, b1);
    }
    Rectangle r0 = s0 instanceof Rectangle ? (Rectangle)s0 : s0.getBounds();
    Rectangle r1 = s1 instanceof Rectangle ? (Rectangle)s1 : s1.getBounds();
    if (r0.y != r1.y) {
      // If it spans lines, force it to be the width of the view.
      Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a :
                a.getBounds();
      r0.x = alloc.x;
      r0.width = alloc.width;
    }

    r0.add(r1);
    // The next line is the only difference between this method and
    // View's implementation.  We're subtracting the width of the second
    // character.  This is because this method is used by Highlighter
    // implementations to get the area to "highlight", and if we don't do
    // this, one character too many is highlighted thanks to our
    // modelToView() implementation returning the actual width of the
    // character requested!
    if (p1>p0) r0.width -= r1.width;

    return r0;

  }


  /**
   * Returns the next tab stop position after a given reference position.
   * This implementation does not support things like centering so it
   * ignores the tabOffset argument.
   *
   * @param x the current position >= 0
   * @param tabOffset the position within the text stream
   *   that the tab occurred at >= 0.
   * @return the tab stop, measured in points >= 0
   */
  public float nextTabStop(float x, int tabOffset) {
    if (tabSize == 0)
      return x;
    int ntabs = (((int)x) - tabBase) / tabSize;
    return tabBase + ((ntabs + 1) * tabSize);
  }


  /**
   * Actually paints the text area.  Only lines that have been damaged are
   * repainted.
   *
   * @param g The graphics context with which to paint.
   * @param a The allocated region in which to render.
   */
  @Override
  public void paint(Graphics g, Shape a) {

    RSyntaxDocument document = (RSyntaxDocument)getDocument();

    Rectangle alloc = a.getBounds();

    tabBase = alloc.x;
    host = (RSyntaxTextArea)getContainer();

    Rectangle clip = g.getClipBounds();
    // An attempt to speed things up for files with long lines.  Note that
    // this will actually slow things down a bit for the common case of
    // regular-length lines, but it doesn't make a perceivable difference.
    clipStart = clip.x;
    clipEnd = clipStart + clip.width;

    lineHeight = host.getLineHeight();
    ascent = host.getMaxAscent();//metrics.getAscent();
    int heightAbove = clip.y - alloc.y;
    int linesAbove = Math.max(0, heightAbove / lineHeight);

    FoldManager fm = host.getFoldManager();
    linesAbove += fm.getHiddenLineCountAbove(linesAbove, true);
    Rectangle lineArea = lineToRect(a, linesAbove);
    int y = lineArea.y + ascent;
    int x = lineArea.x;
    Element map = getElement();
    int lineCount = map.getElementCount();

    // Whether token styles should always be painted, even in selections
    int selStart = host.getSelectionStart();
    int selEnd = host.getSelectionEnd();
    boolean useSelectedTextColor = host.getUseSelectedTextColor();

    RSyntaxTextAreaHighlighter h =
          (RSyntaxTextAreaHighlighter)host.getHighlighter();

    Graphics2D g2d = (Graphics2D)g;
    Token token;
    //System.err.println("Painting lines: " + linesAbove + " to " + (endLine-1));

    TokenPainter painter = host.getTokenPainter();
    int line = linesAbove;
    //int count = 0;
    while (y<clip.y+clip.height+ascent && line<lineCount) {

      Fold fold = fm.getFoldForLine(line);
      Element lineElement = map.getElement(line);
      int startOffset = lineElement.getStartOffset();
      //int endOffset = (line==lineCount ? lineElement.getEndOffset()-1 :
      //              lineElement.getEndOffset()-1);
      int endOffset = lineElement.getEndOffset()-1; // Why always "-1"?
      h.paintLayeredHighlights(g2d, startOffset, endOffset,
                a, host, this);
 
      // Paint a line of text.
      token = document.getTokenListForLine(line);
      if (!useSelectedTextColor || selStart==selEnd ||
          (startOffset>=selEnd || endOffset<selStart)) {
        drawLine(painter, token, g2d, x,y);
      }
      else {
        //System.out.println("Drawing line with selection: " + line);
        drawLineWithSelection(painter,token,g2d, x,y, selStart, selEnd);
      }

      if (fold!=null && fold.isCollapsed()) {

        // Visible indicator of collapsed lines
        Color c = RSyntaxUtilities.getFoldedLineBottomColor(host);
        if (c!=null) {
          g.setColor(c);
          g.drawLine(x,y+lineHeight-ascent-1,
              host.getWidth(),y+lineHeight-ascent-1);
        }

        // Skip to next line to paint, taking extra care for lines with
        // block ends and begins together, e.g. "} else {"
        do {
          int hiddenLineCount = fold.getLineCount();
          if (hiddenLineCount==0) {
            // Fold parser identified a zero-line fold region.
            // This is really a bug, but we'll be graceful here
            // and avoid an infinite loop.
            break;
          }
          line += hiddenLineCount;
          fold = fm.getFoldForLine(line);
        } while (fold!=null && fold.isCollapsed());

      }

      y += lineHeight;
      line++;
      //count++;

    }

    //System.out.println("SyntaxView: lines painted=" + count);

  }


  /**
   * If the passed-in line is longer than the current longest line, then
   * the longest line is updated.
   *
   * @param line The line to test against the current longest.
   * @param lineNumber The line number of the passed-in line.
   * @return <code>true</code> iff the current longest line was updated.
   */
  private boolean possiblyUpdateLongLine(Element line, int lineNumber) {
    float w = getLineWidth(lineNumber);
    if (w > longLineWidth) {
      longLineWidth = w;
      longLine = line;
      return true;
    }
    return false;
  }


  /**
   * Gives notification that something was removed from the document
   * in a location that this view is responsible for.
   *
   * @param changes the change information from the associated document
   * @param a the current allocation of the view
   * @param f the factory to use to rebuild if the view has children
   */
  @Override
  public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
    updateDamage(changes, a, f);
  }


  @Override
  public void setSize(float width, float height) {
    super.setSize(width, height);
    updateMetrics();
  }


  /**
   * Repaint the region of change covered by the given document
   * event.  Damages the line that begins the range to cover
   * the case when the insert/remove is only on one line. 
   * If lines are added or removed, damages the whole
   * view.  The longest line is checked to see if it has
   * changed.
   */
  protected void updateDamage(DocumentEvent changes, Shape a, ViewFactory f) {
    Component host = getContainer();
    updateMetrics();
    Element elem = getElement();
    DocumentEvent.ElementChange ec = changes.getChange(elem);
    Element[] added = (ec != null) ? ec.getChildrenAdded() : null;
    Element[] removed = (ec != null) ? ec.getChildrenRemoved() : null;
    if (((added != null) && (added.length > 0)) ||
      ((removed != null) && (removed.length > 0))) {
      // lines were added or removed...
      if (added != null) {
        int addedAt = ec.getIndex(); // FIXME: Is this correct?????
        for (int i = 0; i < added.length; i++)
          possiblyUpdateLongLine(added[i], addedAt+i);
      }
      if (removed != null) {
        for (int i = 0; i < removed.length; i++) {
          if (removed[i] == longLine) {
            longLineWidth = -1; // Must do this!!
            calculateLongestLine();
            break;
          }
        }
      }
      preferenceChanged(null, true, true);
      host.repaint();
    }

    // This occurs when syntax highlighting only changes on lines
    // (i.e. beginning a multiline comment).
    else if (changes.getType()==DocumentEvent.EventType.CHANGE) {
      //System.err.println("Updating the damage due to a CHANGE event...");
      int startLine = changes.getOffset();
      int endLine = changes.getLength();
      damageLineRange(startLine,endLine, a, host);
    }

    else {
      Element map = getElement();
      int line = map.getElementIndex(changes.getOffset());
      damageLineRange(line, line, a, host);
      if (changes.getType() == DocumentEvent.EventType.INSERT) {
        // check to see if the line is longer than current
        // longest line.
        Element e = map.getElement(line);
        if (e == longLine) {
          // We must recalculate longest line's width here
          // because it has gotten longer.
          longLineWidth = getLineWidth(line);
          preferenceChanged(null, true, false);
        }
        else {
          // If long line gets updated, update the status bars too.
          if (possiblyUpdateLongLine(e, line))
            preferenceChanged(null, true, false);
        }
      }
      else if (changes.getType() == DocumentEvent.EventType.REMOVE) {
        if (map.getElement(line) == longLine) {
          // removed from longest line... recalc
          longLineWidth = -1; // Must do this!
          calculateLongestLine();
          preferenceChanged(null, true, false);
        }     
      }
    }
  }


  /**
   * Checks to see if the font metrics and longest line are up-to-date.
   */
  private void updateMetrics() {
    host = (RSyntaxTextArea)getContainer();
    Font f = host.getFont();
    if (font != f) {
      // The font changed, we need to recalculate the longest line!
      // This also updates cached font and tab size.
      calculateLongestLine();
    }
  }


  /**
   * Provides a mapping from the view coordinate space to the logical
   * coordinate space of the model.
   *
   * @param fx the X coordinate >= 0
   * @param fy the Y coordinate >= 0
   * @param a the allocated region to render into
   * @return the location within the model that best represents the
   *  given point in the view >= 0
   */
  @Override
  public int viewToModel(float fx, float fy, Shape a, Position.Bias[] bias) {

    bias[0] = Position.Bias.Forward;

    Rectangle alloc = a.getBounds();
    RSyntaxDocument doc = (RSyntaxDocument)getDocument();
    int x = (int) fx;
    int y = (int) fy;

    // If they're asking about a view position above the area covered by
    // this view, then the position is assumed to be the starting position
    // of this view.
    if (y < alloc.y) {
      return getStartOffset();
    }

    // If they're asking about a position below this view, the position
    // is assumed to be the ending position of this view.
    else if (y > alloc.y + alloc.height) {
      return host.getLastVisibleOffset();
    }

    // They're asking about a position within the coverage of this view
    // vertically.  So, we figure out which line the point corresponds to.
    // If the line is greater than the number of lines contained, then
    // simply use the last line as it represents the last possible place
    // we can position to.
    else {

      Element map = doc.getDefaultRootElement();
      int lineIndex = Math.abs((y - alloc.y) / lineHeight);//metrics.getHeight() );
FoldManager fm = host.getFoldManager();
//System.out.print("--- " + lineIndex);
lineIndex += fm.getHiddenLineCountAbove(lineIndex, true);
//System.out.println(" => " + lineIndex);
      if (lineIndex >= map.getElementCount()) {
        return host.getLastVisibleOffset();
      }

      Element line = map.getElement(lineIndex);

      // If the point is to the left of the line...
      if (x < alloc.x)
        return line.getStartOffset();

      // If the point is to the right of the line...
      else if (x > alloc.x + alloc.width)
        return line.getEndOffset() - 1;

      else {
        // Determine the offset into the text
        int p0 = line.getStartOffset();
        Token tokenList = doc.getTokenListForLine(lineIndex);
        tabBase = alloc.x;
        int offs = tokenList.getListOffset(
                  (RSyntaxTextArea)getContainer(),
                  this, tabBase, x);
        return offs!=-1 ? offs : p0;
      }

    } // End of else.

  }


  /**
   * {@inheritDoc}
   */
  public int yForLine(Rectangle alloc, int line) throws BadLocationException {

    //Rectangle lineArea = lineToRect(alloc, lineIndex);
    updateMetrics();
    if (metrics != null) {
      // NOTE:  lineHeight is not initially set here, leading to the
      // current line not being highlighted when a document is first
      // opened.  So, we set it here just in case.
      lineHeight = host!=null ? host.getLineHeight() : lineHeight;
      FoldManager fm = host.getFoldManager();
      if (!fm.isLineHidden(line)) {
        line -= fm.getHiddenLineCountAbove(line);
        return alloc.y + line*lineHeight;
      }
    }

    return -1;

  }


  /**
   * {@inheritDoc}
   */
  public int yForLineContaining(Rectangle alloc, int offs)
              throws BadLocationException {
    Element map = getElement();
    int line = map.getElementIndex(offs);
    return yForLine(alloc, line);
  }


}
TOP

Related Classes of org.fife.ui.rsyntaxtextarea.SyntaxView

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.