@Override public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) { int offs = -1; if (!isAllocationValid()) { Rectangle alloc = a.getBounds(); setSize(alloc.width, alloc.height); } // Get the child view for the line at (x,y), and ask it for the // specific offset. Rectangle alloc = getInsideAllocation(a); View v = getViewAtPoint((int) x, (int) y, alloc); if (v != null) { offs = v.viewToModel(x, y, alloc, bias); } // Code folding may have hidden the last line. If so, return the last // visible offset instead of the last offset. if (host.isCodeFoldingEnabled() && v == getView(getViewCount() - 1) && offs == v.getEndOffset() - 1) { offs = host.getLastVisibleOffset(); } return offs; }
/** * 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 * (float) lineHeight; default: throw new IllegalArgumentException("Invalid axis: " + axis); } }
/** * 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(); } 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. }
/** * 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 * @param a the allocated region to render into * @return the bounding box of the given position is returned * @exception BadLocationException if the given position does not represent a valid location in * the associated document. */ @Override public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException { // System.err.println("--- begin modelToView ---"); Rectangle alloc = a.getBounds(); RSyntaxTextArea textArea = (RSyntaxTextArea) getContainer(); alloc.height = textArea.getLineHeight(); // metrics.getHeight(); alloc.width = 1; int p0 = getStartOffset(); int p1 = getEndOffset(); int testP = (b == Position.Bias.Forward) ? pos : Math.max(p0, pos - 1); // Get the token list for this line so we don't have to keep // recomputing it if this logical line spans multiple physical // lines. RSyntaxDocument doc = (RSyntaxDocument) getDocument(); Element map = doc.getDefaultRootElement(); int line = map.getElementIndex(p0); Token tokenList = doc.getTokenListForLine(line); float x0 = alloc.x; // 0; while (p0 < p1) { TokenSubList subList = TokenUtils.getSubTokenList( tokenList, p0, WrappedSyntaxView.this, textArea, x0, lineCountTempToken); x0 = subList != null ? subList.x : x0; tokenList = subList != null ? subList.tokenList : null; int p = calculateBreakPosition(p0, tokenList, x0); if ((pos >= p0) && (testP < p)) { // pos < p)) { // it's in this line alloc = RSyntaxUtilities.getLineWidthUpTo( textArea, s, p0, pos, WrappedSyntaxView.this, alloc, alloc.x); // System.err.println("--- end modelToView ---"); return alloc; } // if (p == p1 && pos == p1) { if (p == p1 - 1 && pos == p1 - 1) { // Wants end. if (pos > p0) { alloc = RSyntaxUtilities.getLineWidthUpTo( textArea, s, p0, pos, WrappedSyntaxView.this, alloc, alloc.x); } // System.err.println("--- end modelToView ---"); return alloc; } p0 = (p == p0) ? p1 : p; // System.err.println("... ... Incrementing y"); alloc.y += alloc.height; } throw new BadLocationException(null, pos); }
/** * 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 int getRhsCorrection() { int rhsCorrection = 10; if (host != null) { rhsCorrection = host.getRightHandSideCorrection(); } return rhsCorrection; }
/** Overridden to allow for folded regions. */ @Override protected View getViewAtPoint(int x, int y, Rectangle alloc) { int lineCount = getViewCount(); int curY = alloc.y + getOffset(Y_AXIS, 0); // Always at least 1 line host = (RSyntaxTextArea) getContainer(); FoldManager fm = host.getFoldManager(); for (int line = 1; line < lineCount; line++) { int span = getSpan(Y_AXIS, line - 1); if (y < curY + span) { childAllocation2(line - 1, curY, alloc); return getView(line - 1); } curY += span; Fold fold = fm.getFoldForLine(line - 1); if (fold != null && fold.isCollapsed()) { line += fold.getCollapsedLineCount(); } } // Not found - return last line's view. childAllocation2(lineCount - 1, curY, alloc); return getView(lineCount - 1); }
/** * Paints the word-wrapped text. * * @param g The graphics context in which to paint. * @param a The shape (usually a rectangle) in which to paint. */ public void paint(Graphics g, Shape a) { Rectangle alloc = (a instanceof Rectangle) ? (Rectangle) a : a.getBounds(); tabBase = alloc.x; Graphics2D g2d = (Graphics2D) g; host = (RSyntaxTextArea) getContainer(); int ascent = host.getMaxAscent(); int fontHeight = host.getLineHeight(); FoldManager fm = host.getFoldManager(); TokenPainter painter = host.getTokenPainter(); Element root = getElement(); // Whether token styles should always be painted, even in selections int selStart = host.getSelectionStart(); int selEnd = host.getSelectionEnd(); boolean useSelectedTextColor = host.getUseSelectedTextColor(); int n = getViewCount(); // Number of lines. int x = alloc.x + getLeftInset(); tempRect.y = alloc.y + getTopInset(); Rectangle clip = g.getClipBounds(); for (int i = 0; i < n; i++) { tempRect.x = x + getOffset(X_AXIS, i); // tempRect.y = y + getOffset(Y_AXIS, i); tempRect.width = getSpan(X_AXIS, i); tempRect.height = getSpan(Y_AXIS, i); // System.err.println("For line " + i + ": tempRect==" + tempRect); if (tempRect.intersects(clip)) { Element lineElement = root.getElement(i); int startOffset = lineElement.getStartOffset(); int endOffset = lineElement.getEndOffset() - 1; // Why always "-1"? View view = getView(i); if (!useSelectedTextColor || selStart == selEnd || (startOffset >= selEnd || endOffset < selStart)) { drawView(painter, g2d, alloc, view, fontHeight, tempRect.y + ascent); } else { // System.out.println("Drawing line with selection: " + i); drawViewWithSelection( painter, g2d, alloc, view, fontHeight, tempRect.y + ascent, selStart, selEnd); } } tempRect.y += tempRect.height; Fold possibleFold = fm.getFoldForLine(i); if (possibleFold != null && possibleFold.isCollapsed()) { i += possibleFold.getCollapsedLineCount(); // Visible indicator of collapsed lines Color c = RSyntaxUtilities.getFoldedLineBottomColor(host); if (c != null) { g.setColor(c); g.drawLine(x, tempRect.y - 1, alloc.width, tempRect.y - 1); } } } }
/** {@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 { if (isAllocationValid()) { // TODO: make cached Y_AXIS offsets valid even with folding enabled // to speed this back up! Rectangle r = (Rectangle) modelToView(offs, alloc, Bias.Forward); if (r != null) { if (host.isCodeFoldingEnabled()) { int line = host.getLineOfOffset(offs); FoldManager fm = host.getFoldManager(); if (fm.isLineHidden(line)) { return -1; } } return r.y; } } return -1; }
/** * 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 != null && 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; }
/** 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(); } }
/** * Sets the allocation rectangle for a given line's view, but sets the y value to the passed-in * value. This should be used instead of {@link #childAllocation(int, Rectangle)} since it allows * you to account for hidden lines in collapsed fold regions. * * @param line * @param y * @param alloc */ private void childAllocation2(int line, int y, Rectangle alloc) { alloc.x += getOffset(X_AXIS, line); alloc.y += y; alloc.width = getSpan(X_AXIS, line); alloc.height = getSpan(Y_AXIS, line); // FIXME: This is required due to a bug that I can't track down. The // top margin is being added twice somewhere in wrapped views, so we // have to adjust for it here. alloc.y -= host.getMargin().top; }
private void handleDocumentEvent(DocumentEvent e, Shape a, ViewFactory f) { int n = calculateLineCount(); if (this.nlines != n) { this.nlines = n; WrappedSyntaxView.this.preferenceChanged(this, false, true); // have to repaint any views after the receiver. RSyntaxTextArea textArea = (RSyntaxTextArea) getContainer(); textArea.repaint(); // Must also revalidate container so gutter components, such // as line numbers, get updated for this line's new height Gutter gutter = RSyntaxUtilities.getGutter(textArea); if (gutter != null) { gutter.revalidate(); gutter.repaint(); } } else if (a != null) { Component c = getContainer(); Rectangle alloc = (Rectangle) a; c.repaint(alloc.x, alloc.y, alloc.width, alloc.height); } }
/** * 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; }
/** * 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, int line) { float nextX = x; // The x-value at the end of our text. boolean paintBG = host.getPaintTokenBackgrounds(line, y); while (token != null && token.isPaintable() && nextX < clipEnd) { nextX = painter.paint(token, g, nextX, y, host, this, clipStart, paintBG); 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; }
/** * Paints the word-wrapped text. * * @param g The graphics context in which to paint. * @param a The shape (usually a rectangle) in which to paint. */ public void paint(Graphics g, Shape a) { Rectangle alloc = (a instanceof Rectangle) ? (Rectangle) a : a.getBounds(); tabBase = alloc.x; Graphics2D g2d = (Graphics2D) g; host = (RSyntaxTextArea) getContainer(); int ascent = host.getMaxAscent(); int fontHeight = host.getLineHeight(); FoldManager fm = host.getFoldManager(); int n = getViewCount(); // Number of lines. int x = alloc.x + getLeftInset(); tempRect.y = alloc.y + getTopInset(); Rectangle clip = g.getClipBounds(); for (int i = 0; i < n; i++) { tempRect.x = x + getOffset(X_AXIS, i); // tempRect.y = y + getOffset(Y_AXIS, i); tempRect.width = getSpan(X_AXIS, i); tempRect.height = getSpan(Y_AXIS, i); // System.err.println("For line " + i + ": tempRect==" + tempRect); if (tempRect.intersects(clip)) { View view = getView(i); drawView(g2d, alloc, view, fontHeight, tempRect.y + ascent); } tempRect.y += tempRect.height; Fold possibleFold = fm.getFoldForLine(i); if (possibleFold != null && possibleFold.isCollapsed()) { i += possibleFold.getCollapsedLineCount(); // Visible indicator of collapsed lines Color c = RSyntaxUtilities.getFoldedLineBottomColor(host); if (c != null) { g.setColor(c); g.drawLine(x, tempRect.y - 1, alloc.width, tempRect.y - 1); } } } }
/** * Determines the preferred span for this view along an axis. This is implemented to provide the * superclass behavior after first making sure that the current font metrics are cached (for the * nested lines which use the metrics to determine the height of the potentially wrapped lines). * * @param axis may be either View.X_AXIS or View.Y_AXIS * @return the span the view would like to be rendered into. 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. * @see View#getPreferredSpan */ public float getPreferredSpan(int axis) { updateMetrics(); float span = 0; if (axis == View.X_AXIS) { // Add EOL marker span = super.getPreferredSpan(axis); span += metrics.charWidth('\u00b6'); // metrics set in updateMetrics } else { span = super.getPreferredSpan(axis); host = (RSyntaxTextArea) getContainer(); if (host.isCodeFoldingEnabled()) { // TODO: Cache y-offsets again to speed this up // System.out.println("y-axis baby"); int lineCount = host.getLineCount(); FoldManager fm = host.getFoldManager(); for (int i = 0; i < lineCount; i++) { if (fm.isLineHidden(i)) { span -= getSpan(View.Y_AXIS, i); } } } } return span; }
/** * Fetches the allocation for the given child view to render into. * * <p>Overridden to account for lines hidden by collapsed folded regions. * * @param line The index of the child, >= 0 && < getViewCount() * @param a The allocation to this view * @return The allocation to the child */ public Shape getChildAllocationImpl(int line, Shape a) { Rectangle alloc = getInsideAllocation(a); host = (RSyntaxTextArea) getContainer(); FoldManager fm = host.getFoldManager(); int y = alloc.y; // TODO: Make cached getOffset() calls for Y_AXIS valid even for // folding, to speed this up! for (int i = 0; i < line; i++) { y += getSpan(Y_AXIS, i); Fold fold = fm.getFoldForLine(i); if (fold != null && fold.isCollapsed()) { i += fold.getCollapsedLineCount(); } } childAllocation2(line, y, alloc); return alloc; }
/** * 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; }
/** * Draws a single view (i.e., a line of text for a wrapped view), wrapping the text onto multiple * lines if necessary. Any selected text is rendered with the editor's "selected text" color. * * @param painter The painter to use to render tokens. * @param g The graphics context in which to paint. * @param r The rectangle in which to paint. * @param view The <code>View</code> to paint. * @param fontHeight The height of the font being used. * @param y The y-coordinate at which to begin painting. * @param selStart The start of the selection. * @param selEnd The end of the selection. */ protected void drawViewWithSelection( TokenPainter painter, Graphics2D g, Rectangle r, View view, int fontHeight, int y, int selStart, int selEnd) { float x = r.x; LayeredHighlighter h = (LayeredHighlighter) host.getHighlighter(); RSyntaxDocument document = (RSyntaxDocument) getDocument(); Element map = getElement(); int p0 = view.getStartOffset(); int lineNumber = map.getElementIndex(p0); int p1 = view.getEndOffset(); // - 1; setSegment(p0, p1 - 1, document, drawSeg); // System.err.println("drawSeg=='" + drawSeg + "' (p0/p1==" + p0 + "/" + p1 + ")"); int start = p0 - drawSeg.offset; Token token = document.getTokenListForLine(lineNumber); // If this line is an empty line, then the token list is simply a // null token. In this case, the line highlight will be skipped in // the loop below, so unfortunately we must manually do it here. if (token != null && token.getType() == Token.NULL) { h.paintLayeredHighlights(g, p0, p1, r, host, this); return; } // Loop through all tokens in this view and paint them! while (token != null && token.isPaintable()) { int p = calculateBreakPosition(p0, token, x); x = r.x; h.paintLayeredHighlights(g, p0, p, r, host, this); while (token != null && token.isPaintable() && token.getEndOffset() - 1 < p) { // <=p) { // Selection starts in this token if (token.containsPosition(selStart)) { if (selStart > token.getOffset()) { tempToken.copyFrom(token); tempToken.textCount = selStart - tempToken.getOffset(); x = painter.paint(tempToken, g, x, y, host, this); 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 selCount = Math.min(token.length(), selEnd - token.getOffset()); if (selCount == token.length()) { x = painter.paintSelected(token, g, x, y, host, this); } else { tempToken.copyFrom(token); tempToken.textCount = selCount; x = painter.paintSelected(tempToken, g, x, y, host, this); tempToken.textCount = token.length(); tempToken.makeStartAt(token.getOffset() + selCount); token = tempToken; x = painter.paint(token, g, x, y, host, this); } } // Selection ends in this token else if (token.containsPosition(selEnd)) { tempToken.copyFrom(token); tempToken.textCount = selEnd - tempToken.getOffset(); x = painter.paintSelected(tempToken, g, x, y, host, this); tempToken.textCount = token.length(); tempToken.makeStartAt(selEnd); token = tempToken; x = painter.paint(token, g, x, y, host, this); } // This token is entirely selected else if (token.getOffset() >= selStart && token.getEndOffset() <= selEnd) { x = painter.paintSelected(token, g, x, y, host, this); } // This token is entirely unselected else { x = painter.paint(token, g, x, y, host, this); } token = token.getNextToken(); } // If there's a token that's going to be split onto the next line if (token != null && token.isPaintable() && token.getOffset() < p) { int tokenOffset = token.getOffset(); Token orig = token; token = new TokenImpl( drawSeg, tokenOffset - start, p - 1 - start, tokenOffset, token.getType()); // Selection starts in this token if (token.containsPosition(selStart)) { if (selStart > token.getOffset()) { tempToken.copyFrom(token); tempToken.textCount = selStart - tempToken.getOffset(); x = painter.paint(tempToken, g, x, y, host, this); 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 selCount = Math.min(token.length(), selEnd - token.getOffset()); if (selCount == token.length()) { x = painter.paintSelected(token, g, x, y, host, this); } else { tempToken.copyFrom(token); tempToken.textCount = selCount; x = painter.paintSelected(tempToken, g, x, y, host, this); tempToken.textCount = token.length(); tempToken.makeStartAt(token.getOffset() + selCount); token = tempToken; x = painter.paint(token, g, x, y, host, this); } } // Selection ends in this token else if (token.containsPosition(selEnd)) { tempToken.copyFrom(token); tempToken.textCount = selEnd - tempToken.getOffset(); x = painter.paintSelected(tempToken, g, x, y, host, this); tempToken.textCount = token.length(); tempToken.makeStartAt(selEnd); token = tempToken; x = painter.paint(token, g, x, y, host, this); } // This token is entirely selected else if (token.getOffset() >= selStart && token.getEndOffset() <= selEnd) { x = painter.paintSelected(token, g, x, y, host, this); } // This token is entirely unselected else { x = painter.paint(token, g, x, y, host, this); } token = new TokenImpl(orig); ((TokenImpl) token).makeStartAt(p); } p0 = (p == p0) ? p1 : p; y += fontHeight; } // End of while (token!=null && token.isPaintable()). // 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", x, y - fontHeight); } }
/** * Draws a single view (i.e., a line of text for a wrapped view), wrapping the text onto multiple * lines if necessary. * * @param painter The painter to use to render tokens. * @param g The graphics context in which to paint. * @param r The rectangle in which to paint. * @param view The <code>View</code> to paint. * @param fontHeight The height of the font being used. * @param y The y-coordinate at which to begin painting. */ protected void drawView( TokenPainter painter, Graphics2D g, Rectangle r, View view, int fontHeight, int y) { float x = r.x; LayeredHighlighter h = (LayeredHighlighter) host.getHighlighter(); RSyntaxDocument document = (RSyntaxDocument) getDocument(); Element map = getElement(); int p0 = view.getStartOffset(); int lineNumber = map.getElementIndex(p0); int p1 = view.getEndOffset(); // - 1; setSegment(p0, p1 - 1, document, drawSeg); // System.err.println("drawSeg=='" + drawSeg + "' (p0/p1==" + p0 + "/" + p1 + ")"); int start = p0 - drawSeg.offset; Token token = document.getTokenListForLine(lineNumber); // If this line is an empty line, then the token list is simply a // null token. In this case, the line highlight will be skipped in // the loop below, so unfortunately we must manually do it here. if (token != null && token.getType() == Token.NULL) { h.paintLayeredHighlights(g, p0, p1, r, host, this); return; } // Loop through all tokens in this view and paint them! while (token != null && token.isPaintable()) { int p = calculateBreakPosition(p0, token, x); x = r.x; h.paintLayeredHighlights(g, p0, p, r, host, this); while (token != null && token.isPaintable() && token.getEndOffset() - 1 < p) { // <=p) { x = painter.paint(token, g, x, y, host, this); token = token.getNextToken(); } if (token != null && token.isPaintable() && token.getOffset() < p) { int tokenOffset = token.getOffset(); tempToken.set( drawSeg.array, tokenOffset - start, p - 1 - start, tokenOffset, token.getType()); painter.paint(tempToken, g, x, y, host, this); tempToken.copyFrom(token); tempToken.makeStartAt(p); token = new TokenImpl(tempToken); } p0 = (p == p0) ? p1 : p; y += fontHeight; } // End of while (token!=null && token.isPaintable()). // 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", x, y - fontHeight); } }
/** * Provides a mapping from the view coordinate space to the logical coordinate space of the * model. * * @param fx the X coordinate * @param fy the Y coordinate * @param a the allocated region to render into * @return the location within the model that best represents the given point in the view * @see View#viewToModel */ @Override public int viewToModel(float fx, float fy, Shape a, Position.Bias[] bias) { // PENDING(prinz) implement bias properly bias[0] = Position.Bias.Forward; Rectangle alloc = (Rectangle) a; RSyntaxDocument doc = (RSyntaxDocument) getDocument(); int x = (int) fx; int y = (int) fy; if (y < alloc.y) { // above the area covered by this icon, so the the position // is assumed to be the start of the coverage for this view. return getStartOffset(); } else if (y > alloc.y + alloc.height) { // below the area covered by this icon, so the the position // is assumed to be the end of the coverage for this view. return getEndOffset() - 1; } else { // positioned 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. RSyntaxTextArea textArea = (RSyntaxTextArea) getContainer(); alloc.height = textArea.getLineHeight(); int p1 = getEndOffset(); // Get the token list for this line so we don't have to keep // recomputing it if this logical line spans multiple // physical lines. Element map = doc.getDefaultRootElement(); int p0 = getStartOffset(); int line = map.getElementIndex(p0); Token tlist = doc.getTokenListForLine(line); // Look at each physical line-chunk of this logical line. while (p0 < p1) { // We can always use alloc.x since we always break // lines so they start at the beginning of a physical // line. TokenSubList subList = TokenUtils.getSubTokenList( tlist, p0, WrappedSyntaxView.this, textArea, alloc.x, lineCountTempToken); tlist = subList != null ? subList.tokenList : null; int p = calculateBreakPosition(p0, tlist, alloc.x); // If desired view position is in this physical chunk. if ((y >= alloc.y) && (y < (alloc.y + alloc.height))) { // Point is to the left of the line if (x < alloc.x) { return p0; } // Point is to the right of the line else if (x > alloc.x + alloc.width) { return p - 1; } // Point is in this physical line! else { // Start at alloc.x since this chunk starts // at the beginning of a physical line. int n = tlist.getListOffset(textArea, WrappedSyntaxView.this, alloc.x, x); // NOTE: We needed to add the max() with // p0 as getTokenListForLine returns -1 // for empty lines (just a null token). // How did this work before? // FIXME: Have null tokens have their // offset but a -1 length. return Math.max(Math.min(n, p1 - 1), p0); } // End of else. } // End of if ((y>=alloc.y) && ... p0 = (p == p0) ? p1 : p; alloc.y += alloc.height; } // End of while (p0<p1). return getEndOffset() - 1; } // End of else. }
/** * 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(); 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 (selStart == selEnd || startOffset >= selEnd || endOffset < selStart) { drawLine(painter, token, g2d, x, y, line); } 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); }
/** * 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. boolean useSTC = host.getUseSelectedTextColor(); 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, useSTC); } else { tempToken.copyFrom(token); tempToken.textCount = selCount; nextX = painter.paintSelected(tempToken, g, nextX, y, host, this, clipStart, useSTC); 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, useSTC); 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, useSTC); } // 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; }