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

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

Go to download

RSyntaxTextArea is the syntax highlighting text editor for Swing applications. Features include syntax highlighting for 40+ languages, code folding, code completion, regex find and replace, macros, code templates, undo/redo, line numbering and bracket matching.

There is a newer version: 3.5.2
Show newest version
/*
 * 08/06/2004
 *
 * WrappedSyntaxView.java - Test implementation of WrappedSyntaxView that
 * is also aware of RSyntaxTextArea's different fonts per token type.
 *
 * This library is distributed under a modified BSD license.  See the included
 * LICENSE file for details.
 */
package org.fife.ui.rsyntaxtextarea;

import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Shape;

import javax.swing.event.DocumentEvent;
import javax.swing.text.BadLocationException;
import javax.swing.text.BoxView;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.PlainDocument;
import javax.swing.text.Position;
import javax.swing.text.Position.Bias;
import javax.swing.text.Segment;
import javax.swing.text.TabExpander;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;

import org.fife.ui.rsyntaxtextarea.TokenUtils.TokenSubList;
import org.fife.ui.rsyntaxtextarea.folding.Fold;
import org.fife.ui.rsyntaxtextarea.folding.FoldManager;
import org.fife.ui.rtextarea.Gutter;


/**
 * The view used by {@link RSyntaxTextArea} when word wrap is enabled.
 *
 * @author Robert Futrell
 * @version 0.2
 */
public class WrappedSyntaxView extends BoxView implements TabExpander,
												RSTAView {

    private int tabBase;
    private int tabSize;

	/**
	 * This is reused to keep from allocating/deallocating.
	 */
	private Segment s;

	/**
	 * This is reused to keep from allocating/deallocating.
	 */
	private Segment drawSeg;

	/**
	 * Another variable initialized once to keep from allocating/deallocating.
	 */
	private Rectangle tempRect;

	/**
	 * Cached for each paint() call so each drawView() call has access to it.
	 */
	private RSyntaxTextArea host;
	private FontMetrics metrics;
	private TokenImpl tempToken;
	private TokenImpl lineCountTempToken;

	/**
	 * The width of this view cannot be below this amount, as if the width
	 * is ever 0 (really a bug), we'll go into an infinite loop.
	 */
	private static final int MIN_WIDTH		= 20;


	/**
	 * Creates a new WrappedSyntaxView.  Lines will be wrapped
	 * on character boundaries.
	 *
	 * @param elem the element underlying the view
	 */
	public WrappedSyntaxView(Element elem) {
		super(elem, Y_AXIS);
		tempToken = new TokenImpl();
		s = new Segment();
		drawSeg = new Segment();
		tempRect = new Rectangle();
		lineCountTempToken = new TokenImpl();
	}



	/**
	 * This is called by the nested wrapped line
	 * views to determine the break location.  This can
	 * be reimplemented to alter the breaking behavior.
	 * It will either break at word or character boundaries
	 * depending upon the break argument given at
	 * construction.
	 */
	protected int calculateBreakPosition(int p0, Token tokenList, float x0) {
		//System.err.println("------ beginning calculateBreakPosition() --------");
		int p = p0;
		RSyntaxTextArea textArea = (RSyntaxTextArea)getContainer();
		float currentWidth = getWidth();
		if (currentWidth==Integer.MAX_VALUE) {
			currentWidth = getPreferredSpan(X_AXIS);
		}
		// Make sure width>0; this is a huge hack to fix a bug where
		// loading text into an RTextArea before it is visible if word wrap
		// is enabled causes an infinite loop in calculateBreakPosition()
		// because of the 0-width!  We cannot simply check in setSize()
		// because the width is set to 0 somewhere else too somehow...
		currentWidth = Math.max(currentWidth, MIN_WIDTH);
		Token t = tokenList;
		while (t!=null && t.isPaintable()) {
			// FIXME:  Replace the code below with the commented-out line below.  This will
			// allow long tokens to be broken at embedded spaces (such as MLC's).  But it
			// currently throws BadLocationExceptions sometimes...
			float tokenWidth = t.getWidth(textArea, this, x0);
			if (tokenWidth>currentWidth) {
				// If the current token alone is too long for this line,
				// break at a character boundary.
				if (p==p0) {
					return t.getOffsetBeforeX(textArea, this, 0, currentWidth);
				}
				// Return the first non-whitespace char (i.e., don't start
				// off the continuation of a wrapped line with whitespace).
				return t.isWhitespace() ? p+t.length() : p;
				//return getBreakLocation(t, fm, x0, currentWidth, this);
			}
			currentWidth -= tokenWidth;
			x0 += tokenWidth;
			p += t.length();
			//System.err.println("*** *** *** token fit entirely (width==" + tokenWidth + "), adding " +
			// t.textCount + " to p, now p==" + p);
			t = t.getNextToken();
		}
		//System.err.println("... ... whole line fits; returning p==" + p);
		//System.err.println("------ ending calculateBreakPosition() --------");

		//		return p;
		return p + 1;
	}

	//private int getBreakLocation(Token t, FontMetrics fm, int x0, int x,
	//								TabExpander e) {
	//	Segment s = new Segment();
	//	s.array = t.text;
	//	s.offset = t.getTextOffset();
	//	s.count = t.textCount;
	//	return t.offset + Utilities.getBreakLocation(s, fm, x0, x, e, t.offset);
	//}

	/**
	 * Gives notification from the document that attributes were changed
	 * in a location that this view is responsible for.
	 *
	 * @param e 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 e, Shape a, ViewFactory f) {
		updateChildren(e, a);
	}


	/**
	 * 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 The line to get the view allocation for.
	 * @param y The y-offset to set for the allocation.
	 * @param alloc The set allocation value.
	 */
	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.
		Insets margin = host.getMargin();
		if (margin!=null) {
			alloc.y -= margin.top;
		}

	}


	/**
	 * 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 View 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, int line) {

		float x = r.x;

		RSyntaxTextAreaHighlighter h =
			(RSyntaxTextAreaHighlighter)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()-1View 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;
		boolean useSTC = host.getUseSelectedTextColor();

		RSyntaxTextAreaHighlighter h =
			(RSyntaxTextAreaHighlighter)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()-1token.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,
								useSTC);
					}
					else {
						tempToken.copyFrom(token);
						tempToken.textCount = selCount;
						x = painter.paintSelected(tempToken, g, x,y, host, this,
								useSTC);
						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,
							useSTC);
					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,useSTC);
				}

				// 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()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,
								useSTC);
					}
					else {
						tempToken.copyFrom(token);
						tempToken.textCount = selCount;
						x = painter.paintSelected(tempToken, g, x,y, host,
								this, useSTC);
						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,
							useSTC);
					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,useSTC);
				}

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

				token = new TokenImpl(orig);
				((TokenImpl)token).makeStartAt(p);

			}

			// Paint parser (e.g. squiggle underline) highlights after
			// text and selection
			h.paintParserHighlights(g, p0,p, r, host, this);

			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, (float) y-fontHeight);
		}

	}


	/**
	 * Fetches the allocation for the given child view.

* Overridden to account for code folding. * * @param index The index of the child, >= 0 &&< getViewCount(). * @param a The allocation to this view * @return The allocation to the child; or null if * a is null; or null if the * layout is invalid */ @Override public Shape getChildAllocation(int index, Shape a) { if (a != null) { Shape ca = getChildAllocationImpl(index, a); if ((ca != null) && (!isAllocationValid())) { // The child allocation may not have been set yet. Rectangle r = (ca instanceof Rectangle) ? (Rectangle) ca : ca .getBounds(); if ((r.width == 0) && (r.height == 0)) { return null; } } return ca; } return null; } /** * Fetches the allocation for the given child view to render into.

* 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; isetParent method. * Subclasses can re-implement this to initialize their * child views in a different manner. The default * implementation creates a child view for each * child element. * * @param f the view factory */ @Override protected void loadChildren(ViewFactory f) { Element e = getElement(); int n = e.getElementCount(); if (n > 0) { View[] added = new View[n]; for (int i = 0; i < n; i++) { added[i] = new WrappedLine(e.getElement(i)); } replace(0, 0, added); } } @Override public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException { if (! isAllocationValid()) { Rectangle alloc = a.getBounds(); setSize(alloc.width, alloc.height); } boolean isBackward = (b == Position.Bias.Backward); int testPos = (isBackward) ? Math.max(0, pos - 1) : pos; if(isBackward && testPos < getStartOffset()) { return null; } int vIndex = getViewIndexAtPosition(testPos); if ((vIndex != -1) && (vIndex < getViewCount())) { View v = getView(vIndex); if(v != null && testPos >= v.getStartOffset() && testPos < v.getEndOffset()) { Shape childShape = getChildAllocation(vIndex, a); if (childShape == null) { // We are likely invalid, fail. return null; } Shape retShape = v.modelToView(pos, childShape, b); if(retShape == null && v.getEndOffset() == pos) { if(++vIndex < getViewCount()) { v = getView(vIndex); retShape = v.modelToView(pos, getChildAllocation(vIndex, a), b); } } return retShape; } } throw new BadLocationException("Position not represented by view", pos); } /** * 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.

* * 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 * @throws BadLocationException if the given position does * not represent a valid location in the associated document * @throws 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.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); } /** * 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. */ @Override 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(); 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 (selStart==selEnd || startOffset>=selEnd || endOffsetSegment point to the text in our * document between the given positions. Note that the positions MUST be * valid positions in the document. * * @param p0 The first position in the document. * @param p1 The second position in the document. * @param document The document from which you want to get the text. * @param seg The segment in which to load the text. */ private void setSegment(int p0, int p1, Document document, Segment seg) { try { //System.err.println("... in setSharedSegment, p0/p1==" + p0 + "/" + p1); document.getText(p0, p1-p0, seg); //System.err.println("... in setSharedSegment: s=='" + s + "'; line/numLines==" + line + "/" + numLines); } catch (BadLocationException ble) { // Never happens ble.printStackTrace(); } } /** * Sets the size of the view. This should cause layout of the view along * the given axis, if it has any layout duties. * * @param width the width >= 0 * @param height the height >= 0 */ @Override public void setSize(float width, float height) { updateMetrics(); if ((int) width != getWidth()) { // invalidate the view itself since the children's // desired widths will be based upon this view's width. preferenceChanged(null, true, true); setWidthChangePending(true); } super.setSize(width, height); setWidthChangePending(false); } private void setWidthChangePending(boolean widthChangePending) { int count = getViewCount(); for (int i = 0; i < count; i++) { View v = getView(i); if (v instanceof WrappedLine) { ((WrappedLine) v).widthChangePending = widthChangePending; } } } /** * Update the child views in response to a * document event. */ void updateChildren(DocumentEvent e, Shape a) { Element elem = getElement(); DocumentEvent.ElementChange ec = e.getChange(elem); // This occurs when syntax highlighting only changes on lines // (i.e. beginning a multiline comment). if (e.getType()==DocumentEvent.EventType.CHANGE) { //System.err.println("Updating the damage due to a CHANGE event..."); // FIXME: Make me repaint more intelligently. getContainer().repaint(); //damageLineRange(startLine,endLine, a, host); } else if (ec != null) { // the structure of this element changed. Element[] removedElems = ec.getChildrenRemoved(); Element[] addedElems = ec.getChildrenAdded(); View[] added = new View[addedElems.length]; for (int i = 0; i < addedElems.length; i++) { added[i] = new WrappedLine(addedElems[i]); } //System.err.println("Replacing " + removedElems.length + // " children with " + addedElems.length); replace(ec.getIndex(), removedElems.length, added); // should damge a little more intelligently. if (a != null) { preferenceChanged(null, true, true); getContainer().repaint(); } } // update font metrics which may be used by the child views updateMetrics(); } final void updateMetrics() { Component host = getContainer(); Font f = host.getFont(); metrics = host.getFontMetrics(f); // Metrics for the default font. tabSize = getTabSize() * metrics.charWidth('m'); } @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; } @Override public int yForLine(Rectangle alloc, int line) throws BadLocationException { return yForLineContaining(alloc, getElement().getElement(line).getStartOffset()); //return alloc.y + getOffset(Y_AXIS, line); } @Override 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; } /** * Simple view of a line that wraps if it doesn't * fit within the horizontal space allocated. * This class tries to be lightweight by carrying little * state of its own and sharing the state of the outer class * with its siblings. */ class WrappedLine extends View { private int nlines; private boolean widthChangePending; WrappedLine(Element elem) { super(elem); } /** * Calculate the number of lines that will be rendered * by logical line when it is wrapped. */ final int calculateLineCount() { int nlines = 0; int startOffset = getStartOffset(); 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. RSyntaxTextArea textArea = (RSyntaxTextArea)getContainer(); RSyntaxDocument doc = (RSyntaxDocument)getDocument(); Element map = doc.getDefaultRootElement(); int line = map.getElementIndex(startOffset); Token tokenList = doc.getTokenListForLine(line); float x0 = 0;// FIXME: should be alloc.x!! alloc.x;//0; //System.err.println(">>> calculateLineCount: " + startOffset + "-" + p1); for (int p0=startOffset; p0>> >>> calculated # of lines for this view (" + line + "/" + numLines + ": " + nlines); */ return nlines; } /** * Determines the preferred span for this view along an * axis. * * @param axis may be either X_AXIS or 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 */ @Override public float getPreferredSpan(int axis) { switch (axis) { case View.X_AXIS: float width = getWidth(); if (width == Integer.MAX_VALUE) { // We have been initially set to MAX_VALUE, but we don't // want this as our preferred. return 100f; } return width; case View.Y_AXIS: if (nlines == 0 || widthChangePending) { nlines = calculateLineCount(); widthChangePending = false; } return nlines * ((RSyntaxTextArea)getContainer()).getLineHeight(); default: throw new IllegalArgumentException("Invalid axis: " + axis); } } /** * Renders using the given rendering surface and area on that * surface. The view may need to do layout and create child * views to enable itself to render into the given allocation. * * @param g the rendering surface to use * @param a the allocated region to render into * @see View#paint */ @Override public void paint(Graphics g, Shape a) { // This is done by drawView() above. } /** * 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 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); } /** * 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=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 if (tlist != null) { // 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, p - 1), p0); } // End of else. } // End of if ((y>=alloc.y) && ... p0 = (p == p0) ? p1 : p; alloc.y += alloc.height; } // End of while (p0




© 2015 - 2024 Weber Informatics LLC | Privacy Policy