mirror of
https://github.com/SuspiciousActivity/ByteEdit.git
synced 2026-08-13 17:56:17 +00:00
1244 lines
45 KiB
Java
1244 lines
45 KiB
Java
/*
|
|
* 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
|
|
* RSyntaxTextArea.License.txt 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.LayeredHighlighter;
|
|
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 boolean widthChanging;
|
|
private int tabBase;
|
|
private int tabSize;
|
|
/**
|
|
* This is reused to keep from allocating/deallocating.
|
|
*/
|
|
private Segment s, 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 end-of-line marker.
|
|
// */
|
|
// private static final char[] eolMarker = { '.' };
|
|
/**
|
|
* 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
|
|
* @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.
|
|
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 <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,
|
|
int line) {
|
|
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)
|
|
// {
|
|
boolean paintBG = host.getPaintTokenBackgrounds(line, y);
|
|
x = painter.paint(token, g, x, y, host, this, 0, paintBG);
|
|
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());
|
|
tempToken.setLanguageIndex(token.getLanguageIndex());
|
|
boolean paintBG = host.getPaintTokenBackgrounds(line, y);
|
|
painter.paint(tempToken, g, x, y, host, this, 0, paintBG);
|
|
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, (float) y - fontHeight);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
boolean useSTC = host.getUseSelectedTextColor();
|
|
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, 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() < p) {
|
|
int tokenOffset = token.getOffset();
|
|
Token orig = token;
|
|
token = new TokenImpl(drawSeg, tokenOffset - start, p - 1 - start, tokenOffset, token.getType(),
|
|
token.getLanguageIndex());
|
|
token.setLanguageIndex(token.getLanguageIndex());
|
|
// 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, 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);
|
|
}
|
|
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.
|
|
* <p>
|
|
* 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 <code>null</code> if <code>a</code>
|
|
* is <code>null</code>; or <code>null</code> 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.
|
|
* <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;
|
|
}
|
|
|
|
/**
|
|
* Determines the maximum 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#getMaximumSpan
|
|
*/
|
|
@Override
|
|
public float getMaximumSpan(int axis) {
|
|
updateMetrics();
|
|
float span = super.getPreferredSpan(axis);
|
|
if (axis == View.X_AXIS) { // EOL marker
|
|
span += metrics.charWidth('\u00b6'); // metrics set in updateMetrics
|
|
}
|
|
return span;
|
|
}
|
|
|
|
/**
|
|
* Determines the minimum 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#getMinimumSpan
|
|
*/
|
|
@Override
|
|
public float getMinimumSpan(int axis) {
|
|
updateMetrics();
|
|
float span = super.getPreferredSpan(axis);
|
|
if (axis == View.X_AXIS) { // EOL marker
|
|
span += metrics.charWidth('\u00b6'); // metrics set in updateMetrics
|
|
}
|
|
return span;
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
@Override
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Returns the tab size set for the document, defaulting to 5.
|
|
*
|
|
* @return the tab size
|
|
*/
|
|
protected int getTabSize() {
|
|
Integer i = (Integer) getDocument().getProperty(PlainDocument.tabSizeAttribute);
|
|
int size = (i != null) ? i.intValue() : 5;
|
|
return size;
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
/**
|
|
* Gives notification that something was inserted into the document in a
|
|
* location that this view is responsible for. This is implemented to simply
|
|
* update the children.
|
|
*
|
|
* @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#insertUpdate
|
|
*/
|
|
@Override
|
|
public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
|
|
updateChildren(changes, a);
|
|
Rectangle alloc = ((a != null) && isAllocationValid()) ? getInsideAllocation(a) : null;
|
|
int pos = changes.getOffset();
|
|
View v = getViewAtPosition(pos, alloc);
|
|
if (v != null) {
|
|
v.insertUpdate(changes, alloc, f);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Loads all of the children to initialize the view. This is called by the
|
|
* <code>setParent</code> 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.
|
|
* <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.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 || endOffset < selStart) {
|
|
drawView(painter, g2d, alloc, view, fontHeight, tempRect.y + ascent, i);
|
|
} 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, host.getWidth(), tempRect.y - 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gives notification that something was removed from the document in a location
|
|
* that this view is responsible for. This is implemented to simply update the
|
|
* children.
|
|
*
|
|
* @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#removeUpdate
|
|
*/
|
|
@Override
|
|
public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
|
|
updateChildren(changes, a);
|
|
Rectangle alloc = ((a != null) && isAllocationValid()) ? getInsideAllocation(a) : null;
|
|
int pos = changes.getOffset();
|
|
View v = getViewAtPosition(pos, alloc);
|
|
if (v != null) {
|
|
v.removeUpdate(changes, alloc, f);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Makes a <code>Segment</code> 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 childrens
|
|
// desired widths will be based upon this views width.
|
|
preferenceChanged(null, true, true);
|
|
widthChanging = true;
|
|
}
|
|
super.setSize(width, height);
|
|
widthChanging = false;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public int yForLine(Rectangle alloc, int line) throws BadLocationException {
|
|
return yForLineContaining(alloc, getElement().getElement(line).getStartOffset());
|
|
// return alloc.y + getOffset(Y_AXIS, line);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@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 it's own and sharing the state of the outer class with it's siblings.
|
|
*/
|
|
class WrappedLine extends View {
|
|
|
|
private int nlines;
|
|
|
|
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 < p1;) {
|
|
// System.err.println("... ... " + p0 + ", " + p1);
|
|
nlines += 1;
|
|
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);
|
|
// System.err.println("... ... ... break position p==" + p);
|
|
p0 = (p == p0) ? ++p : p; // this is the fix of #4410243
|
|
// we check on situation when
|
|
// width is too small and
|
|
// break position is calculated
|
|
// incorrectly.
|
|
// System.err.println("... ... ... new p0==" + p0);
|
|
}
|
|
/*
|
|
* int numLines = 0; try { numLines = textArea.getLineCount(); } catch
|
|
* (BadLocationException ble) { ble.printStackTrace(); } System.err.
|
|
* println(">>> >>> calculated number of lines for this view (line " + 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 || widthChanging) {
|
|
nlines = calculateLineCount();
|
|
}
|
|
int h = nlines * ((RSyntaxTextArea) getContainer()).getLineHeight();
|
|
return h;
|
|
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 < 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);
|
|
}
|
|
|
|
/**
|
|
* 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 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, 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.
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
|
|
handleDocumentEvent(e, a, f);
|
|
}
|
|
|
|
@Override
|
|
public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
|
|
handleDocumentEvent(e, a, f);
|
|
}
|
|
}
|
|
}
|