All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.fife.ui.rsyntaxtextarea.SyntaxView Maven / Gradle / Ivy

/*
 * 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
 * LICENSE 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 javax.swing.text.View 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
 * RSyntaxTextArea.

* * 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.

* * 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 SyntaxView 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 longLine variable is updated to * represent the longest line contained. The font 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 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, 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() && nextXtoken.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; } /** * Calculates the width of the line represented by the given element. * * @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 If the offset specified is invalid. * @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 * (float) 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 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 : 5; return size; } /** * Returns a token list for the physical 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 offset, 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 offset is in the first line in * the document, null is returned. */ @Override 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 physical 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 offset, 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 offset is in the last physical * line in the document, null is returned. */ @Override 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=0 && line * * This is implemented to subtract the width of the second character, as * this view's modelToView actually returns the width of the * character instead of "1" or "0" like the View implementations in * javax.swing.text. Thus, if we don't override this method, * the View implementation will return one character's width * too much for its consumers (implementations of * javax.swing.text.Highlighter). * * @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; b0 * will have one of these values: *

    *
  • Position.Bias.Forward *
  • Position.Bias.Backward *
* @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 b0 or * b1 are not one of the * legal Position.Bias 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 */ @Override public float nextTabStop(float x, int tabOffset) { if (tabSize == 0) { return x; } int ntabs = (((int)x) - tabBase) / tabSize; return tabBase + ((ntabs + 1f) * 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(); 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=selEnd || endOffset 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(); lineHeight = host.getLineHeight(); 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. } @Override 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; if (host != null) { FoldManager fm = host.getFoldManager(); if (!fm.isLineHidden(line)) { line -= fm.getHiddenLineCountAbove(line); return alloc.y + line * lineHeight; } } } return -1; } @Override public int yForLineContaining(Rectangle alloc, int offs) throws BadLocationException { Element map = getElement(); int line = map.getElementIndex(offs); return yForLine(alloc, line); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy