![JAR search and dependency download from the Maven repository](/logo.png)
org.netbeans.editor.DrawEngine Maven / Gradle / Ivy
/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is NetBeans. The Initial Developer of the Original
* Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun
* Microsystems, Inc. All Rights Reserved.
*/
package org.netbeans.editor;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Color;
import java.awt.Component;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Insets;
import java.text.AttributedCharacterIterator;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import javax.swing.text.Segment;
import javax.swing.text.View;
/**
* Class responsible for drawing the editor component.
*
* @author Miloslav Metelka
* @version 1.00
*/
class DrawEngine {
/** Whether debug messages should be displayed */
private static final boolean debug
= Boolean.getBoolean("netbeans.debug.editor.draw"); // NOI18N
/** Whether debug messages for each token fragment should be displayed */
private static final boolean debugFragment
= Boolean.getBoolean("netbeans.debug.editor.draw.fragment"); // NOI18N
/** Only one instance of draw-engine */
private static DrawEngine drawEngine;
private static final char[] SPACE = new char[] { ' ' };
/** Prevent creation */
private DrawEngine() {
}
/** Get the static instance of draw-engine */
public static DrawEngine getDrawEngine() {
if (drawEngine == null) {
drawEngine = new DrawEngine();
}
return drawEngine;
}
public PreinitializedDrawEngine getDrawEngine(View view,
DrawGraphics drawGraphics, EditorUI editorUI, int startOffset, int endOffset,
int startX, int startY, int targetOffset) throws BadLocationException
{
// Some correctness tests at the begining
if (startOffset < 0 || endOffset < 0 || startOffset > endOffset
|| startX < 0 || startY < 0
) {
return null;
}
BaseDocument doc = (BaseDocument)((view != null) ? view.getDocument() : editorUI.getDocument());
PreinitializedDrawEngine preinitializedDrawEngine = new PreinitializedDrawEngine(drawGraphics);
preinitializedDrawEngine.preinitialize(doc, editorUI, startOffset, endOffset, startX, startY, targetOffset);
return preinitializedDrawEngine;
}
private void initLineNumbering(DrawInfo ctx) {
// Resolve whether line numbers will be painted
ctx.lineNumbering = ctx.editorUI.lineNumberVisible
&& ctx.drawGraphics.supportsLineNumbers();
// create buffer for showing line numbers
if (ctx.lineNumbering) {
try {
ctx.startLineNumber = Utilities.getLineOffset(ctx.doc, ctx.startOffset) + 1;
} catch (BadLocationException e) {
Utilities.annotateLoggable(e);
}
ctx.lineNumberColoring = ctx.editorUI.getColoring(SettingsNames.LINE_NUMBER_COLORING);
if (ctx.lineNumberColoring == null) {
ctx.lineNumberColoring = ctx.defaultColoring; // no number coloring found
} else { // lineNumberColoring not null
ctx.lineNumberColoring = ctx.lineNumberColoring.apply(ctx.defaultColoring);
}
Font lnFont = ctx.lineNumberColoring.getFont();
if (lnFont == null) {
lnFont = ctx.defaultColoring.getFont();
}
Color lnBackColor = ctx.lineNumberColoring.getBackColor();
if (lnBackColor == null) {
lnBackColor = ctx.defaultColoring.getBackColor();
}
Color lnForeColor = ctx.lineNumberColoring.getForeColor();
if (lnForeColor == null) {
lnForeColor = ctx.defaultColoring.getForeColor();
}
ctx.lineNumberChars = new char[Math.max(ctx.editorUI.lineNumberMaxDigitCount, 1)];
if (ctx.graphics == null) {
ctx.syncedLineNumbering = true;
} else { // non-synced line numbering - need to remember line start offsets
try {
int endLineNumber = Utilities.getLineOffset(ctx.doc, ctx.endOffset) + 1;
ctx.lineStartOffsets = new int[endLineNumber - ctx.startLineNumber + 2]; // reserve more
} catch (BadLocationException e) {
Utilities.annotateLoggable(e);
}
}
}
}
private void initInfo(DrawInfo ctx) throws BadLocationException {
ctx.x = ctx.startX;
ctx.y = ctx.startY;
ctx.lineHeight = ctx.editorUI.getLineHeight();
ctx.defaultColoring = ctx.editorUI.getDefaultColoring();
ctx.tabSize = ctx.doc.getTabSize();
ctx.fragmentOffset = ctx.startOffset; // actual painting position
ctx.graphics = ctx.drawGraphics.getGraphics();
if (ctx.graphics != null) {
if (ctx.editorUI.renderingHints != null) {
((Graphics2D)ctx.graphics).setRenderingHints(ctx.editorUI.renderingHints);
}
}
initLineNumbering(ctx);
// Initialize draw context
ctx.foreColor = ctx.defaultColoring.getForeColor();
ctx.backColor = ctx.defaultColoring.getBackColor();
ctx.font = ctx.defaultColoring.getFont();
ctx.bol = true; // draw must always start at line begin
// Init draw graphics
ctx.drawGraphics.init(ctx);
ctx.drawGraphics.setDefaultBackColor(ctx.defaultColoring.getBackColor());
ctx.drawGraphics.setLineHeight(ctx.lineHeight);
ctx.drawGraphics.setLineAscent(ctx.editorUI.getLineAscent());
ctx.drawGraphics.setX(ctx.x);
ctx.drawGraphics.setY(ctx.y);
// Init all draw-layers
ctx.layers = ctx.editorUI.getDrawLayerList().currentLayers();
int layersLength = ctx.layers.length;
ctx.layerActives = new boolean[layersLength];
ctx.layerActivityChangeOffsets = new int[layersLength];
for (int i = 0; i < layersLength; i++) {
ctx.layers[i].init(ctx); // init all layers
}
ctx.drawMarkList = new ArrayList();
Map docMarks = ctx.doc.marks;
MarkVector docMarksStorage = ctx.doc.marksStorage;
synchronized (docMarks) {
int offset = ctx.startOffset;
int low = 0;
int markCount = docMarksStorage.getMarkCount();
int high = markCount - 1;
while (low <= high) {
int mid = (low + high) >> 1;
int cmp = docMarksStorage.getMarkOffsetInternal(mid) - offset;
if (cmp < 0)
low = mid + 1;
else if (cmp > 0)
high = mid - 1;
else { // found
while (--mid >= 0
&& docMarksStorage.getMarkOffsetInternal(mid) == offset
) { }
low = mid + 1;
break;
}
}
// the low contains first mark to consider
offset = ctx.endOffset;
while (low < markCount) {
MultiMark m = (MultiMark)docMarksStorage.getMark(low);
if (m.isValid()) {
if (m.getOffset() > offset) {
break;
}
Mark mark = (Mark)docMarks.get(m);
if (mark == null) {
throw new IllegalStateException("No mark for m=" + m); // NOI18N
}
if (mark instanceof MarkFactory.DrawMark) {
ctx.drawMarkList.add(mark);
}
}
low++;
}
}
// Get current draw mark
ctx.drawMarkIndex = 0;
ctx.drawMarkOffset = Integer.MAX_VALUE;
if (ctx.drawMarkList.size() > 0) {
ctx.drawMark = (MarkFactory.DrawMark)ctx.drawMarkList.get(ctx.drawMarkIndex++);
try {
ctx.drawMarkOffset = ctx.drawMark.getOffset();
} catch (InvalidMarkException e) {
throw new IllegalStateException(e.toString());
}
if (ctx.drawMarkOffset < ctx.updateOffset) {
ctx.updateOffset = ctx.drawMarkOffset;
ctx.drawMarkUpdate = true;
}
}
// Prepare syntax scanner and then cycle through all the syntax segments
int endTokenSafeOffset = ctx.doc.getTokenSafeOffset(ctx.endOffset);
ctx.doc.prepareSyntax(ctx.text, ctx.syntax,
ctx.startOffset, endTokenSafeOffset - ctx.startOffset,
false, false
);
ctx.textArray = ctx.text.array;
ctx.buffer = ctx.textArray;
ctx.bufferStartOffset = ctx.startOffset - ctx.syntax.getOffset();
ctx.drawGraphics.setBuffer(ctx.textArray);
ctx.continueDraw = true;
}
private void handleBOL(DrawInfo ctx) {
if (ctx.lineNumbering) {
if (ctx.syncedLineNumbering) {
// Draw line numbers synchronously at begining of each line
// Init context
ctx.foreColor = ctx.lineNumberColoring.getForeColor();
ctx.backColor = ctx.lineNumberColoring.getBackColor();
ctx.font = ctx.lineNumberColoring.getFont();
ctx.strikeThroughColor = null;
ctx.underlineColor = null;
ctx.waveUnderlineColor = null;
int lineNumber = ctx.startLineNumber + ctx.lineIndex;
// Update line-number by layers
int layersLength = ctx.layers.length;
for (int i = 0; i < layersLength; i++) {
lineNumber = ctx.layers[i].updateLineNumberContext(lineNumber, ctx);
}
// Fill the buffer with digit chars
int i = Math.max(ctx.lineNumberChars.length - 1, 0);
do {
ctx.lineNumberChars[i--] = (char)('0' + (lineNumber % 10));
lineNumber /= 10;
} while (lineNumber != 0 && i >= 0);
// Fill the rest with spaces
while (i >= 0) {
ctx.lineNumberChars[i--] = ' ';
}
// Fill the DG's attributes and draw
int numX = ctx.x - ctx.editorUI.lineNumberWidth;
if (ctx.editorUI.getLineNumberMargin() != null) {
numX += ctx.editorUI.getLineNumberMargin().left;
}
ctx.drawGraphics.setX(numX);
ctx.drawGraphics.setBuffer(ctx.lineNumberChars);
ctx.drawGraphics.setForeColor(ctx.foreColor);
ctx.drawGraphics.setBackColor(ctx.backColor);
ctx.drawGraphics.setStrikeThroughColor(ctx.strikeThroughColor);
ctx.drawGraphics.setUnderlineColor(ctx.underlineColor);
ctx.drawGraphics.setWaveUnderlineColor(ctx.waveUnderlineColor);
ctx.drawGraphics.setFont(ctx.font);
ctx.drawGraphics.drawChars(0, ctx.lineNumberChars.length,
ctx.editorUI.lineNumberWidth);
// When printing there should be an additional space between
// line number and the text
if (ctx.drawGraphics.getGraphics() == null) {
ctx.drawGraphics.setBuffer(SPACE);
ctx.drawGraphics.drawChars(0, 1,
ctx.editorUI.lineNumberDigitWidth);
}
ctx.drawGraphics.setX(ctx.x);
ctx.drawGraphics.setBuffer(ctx.textArray);
} else { // non-synced line numbering
ctx.lineStartOffsets[ctx.lineIndex] = ctx.fragmentOffset; // store the line number
}
}
ctx.lineIndex++;
}
/** Handle the end-of-line */
private void handleEOL(DrawInfo ctx) {
ctx.drawGraphics.setX(ctx.x);
ctx.drawGraphics.setY(ctx.y);
ctx.drawGraphics.eol(); // sign EOL to DG
ctx.widestWidth = Math.max(ctx.widestWidth, ctx.x); // update widest width
ctx.visualColumn = 0;
ctx.x = ctx.startX;
ctx.y += ctx.lineHeight;
ctx.drawGraphics.setX(ctx.x);
ctx.drawGraphics.setY(ctx.y);
}
/** Called when the current fragment starts at the offset that corresponds
* to the update-offset.
*/
private void updateOffsetReached(DrawInfo ctx) {
if (ctx.drawMarkUpdate) { // update because of draw mark
// means no-mark update yet performed
int layersLength = ctx.layers.length;
for (int i = 0; i < layersLength; i++) {
DrawLayer l = ctx.layers[i];
if (l.getName().equals(ctx.drawMark.layerName)
&& (ctx.drawMark.isDocumentMark()
|| ctx.editorUI == ctx.drawMark.getEditorUI())
) {
ctx.layerActives[i] = l.isActive(ctx, ctx.drawMark);
int naco = l.getNextActivityChangeOffset(ctx);
ctx.layerActivityChangeOffsets[i] = naco;
if (naco > ctx.fragmentOffset && naco < ctx.layerUpdateOffset) {
ctx.layerUpdateOffset = naco;
}
}
}
// Get next mark
if (ctx.drawMarkIndex < ctx.drawMarkList.size()) {
ctx.drawMark = (MarkFactory.DrawMark)ctx.drawMarkList.get(ctx.drawMarkIndex++);
try {
ctx.drawMarkOffset = ctx.drawMark.getOffset();
} catch (InvalidMarkException e) {
throw new IllegalStateException(e.toString());
}
} else { // no more draw marks
ctx.drawMark = null;
ctx.drawMarkOffset = Integer.MAX_VALUE;
}
} else { // update because activity-change-offset set in some layer
ctx.layerUpdateOffset = Integer.MAX_VALUE;
int layersLength = ctx.layers.length;
for (int i = 0; i < layersLength; i++) {
// Update only layers with the same offset as fragmentOffset
int naco = ctx.layerActivityChangeOffsets[i];
if (naco == ctx.fragmentOffset) {
DrawLayer l = ctx.layers[i];
ctx.layerActives[i] = l.isActive(ctx, null);
naco = l.getNextActivityChangeOffset(ctx);
ctx.layerActivityChangeOffsets[i] = naco;
}
if (naco > ctx.fragmentOffset && naco < ctx.layerUpdateOffset) {
ctx.layerUpdateOffset = naco;
}
}
}
// Check next update position
if (ctx.drawMarkOffset < ctx.layerUpdateOffset) {
ctx.drawMarkUpdate = true;
ctx.updateOffset = ctx.drawMarkOffset;
} else {
ctx.drawMarkUpdate = false;
ctx.updateOffset = ctx.layerUpdateOffset;
}
}
/** Compute the length of the fragment. */
private void computeFragmentLength(DrawInfo ctx) {
// Compute initial fragment (of token) length
ctx.fragmentStartIndex = ctx.fragmentOffset - ctx.bufferStartOffset;
ctx.fragmentLength = Math.min(ctx.updateOffset - ctx.fragmentOffset,
ctx.tokenLength - ctx.drawnLength);
// Find first TAB or LF
int stopIndex = Analyzer.findFirstTabOrLF(ctx.textArray,
ctx.fragmentStartIndex, ctx.fragmentLength);
// There must be extra EOL at the end of the document
ctx.eol = (ctx.fragmentOffset == ctx.docLen);
ctx.tabsFragment = false;
// Check whether there are no tabs in the fragment and possibly shrink
// Get the first offset of the tab character or -1 if no tabs in fragment
if (stopIndex >= 0) { // either '\t' or '\n' found
if (stopIndex == ctx.fragmentStartIndex) { // since fragment start
if (ctx.textArray[stopIndex] == '\t') { //
ctx.tabsFragment = true;
// Find first non-tab char
int ntInd = Analyzer.findFirstNonTab(ctx.textArray, ctx.fragmentStartIndex,
ctx.fragmentLength);
if (ntInd != -1) { // not whole fragment are tabs
ctx.fragmentLength = ntInd - ctx.fragmentStartIndex;
}
} else { // '\n' found
ctx.eol = true;
ctx.fragmentLength = 1; // only one EOL in fragment
}
} else { // inside fragment start
ctx.fragmentLength = stopIndex - ctx.fragmentStartIndex; // shrink fragment size
}
}
}
/** Compute the display width of the fragment */
private void computeFragmentDisplayWidth(DrawInfo ctx) {
// First go through all layers to update draw context
// to get up-to-date fonts and colors
if (!ctx.eol) { // handled later
int layersLength = ctx.layers.length;
for (int i = 0; i < layersLength; i++) {
DrawLayer l = ctx.layers[i];
if (ctx.layerActives[i]) {
ctx.layers[i].updateContext(ctx); // Let the layer to update the context
}
}
}
// Handle possible white space expansion and compute display width
FontMetricsCache.Info fmcInfo = FontMetricsCache.getInfo(ctx.font);
ctx.spaceWidth = (ctx.component != null)
? fmcInfo.getSpaceWidth(ctx.component) : ctx.editorUI.defaultSpaceWidth;
// Compute real count of chars in fragment - can differ if tabs
ctx.fragmentCharCount = ctx.fragmentLength;
if (ctx.tabsFragment) { // tabs in fragment
ctx.fragmentCharCount = Analyzer.getColumn(ctx.textArray,
ctx.fragmentStartIndex, ctx.fragmentLength, ctx.tabSize, ctx.visualColumn) - ctx.visualColumn;
ctx.fragmentWidth = ctx.fragmentCharCount * ctx.spaceWidth;
} else if (ctx.eol) { // EOL will have the spaceWidth
ctx.fragmentWidth = ctx.spaceWidth;
} else { // regular fragment
if (ctx.fragmentLength > 0) {
if (ctx.component != null) {
ctx.fragmentWidth = FontMetricsCache.getFontMetrics(ctx.font, ctx.component).charsWidth(
ctx.textArray, ctx.fragmentStartIndex, ctx.fragmentLength);
} else { // non-valid component
ctx.fragmentWidth = ctx.fragmentLength * ctx.spaceWidth;
}
} else {
ctx.fragmentWidth = 0; // empty fragment
}
}
}
/** Draw the fragment. Handle the EOL in special way
* as it needs to care about the empty-lines.
*/
private void drawFragment(DrawInfo ctx) {
if (ctx.eol) { // special handling for EOL
int layersLength = ctx.layers.length;
boolean emptyLine = false;
int blankWidth = ctx.fragmentWidth;
/** Need to do one or two cycles.
* In the first pass
* the check is performed whether the line is empty.
* If so all the layers that extend the empty line are
* called to update the context and the resulting half-space
* is drawn.
* In the second pass all the layers that extend EOL
* are called to update the context and the resulting
* whitespace is drawn.
*/
do {
blankWidth = 0;
if (ctx.bol) { // empty line found
if (!emptyLine) { // not yet processed
for (int i = 0; i < layersLength; i++) {
if (ctx.layerActives[i]) {
DrawLayer l = ctx.layers[i];
if (l.extendsEmptyLine()) {
emptyLine = true; // for at least one layer
l.updateContext(ctx);
}
}
}
if (emptyLine) { // count only if necessary
blankWidth = ctx.spaceWidth / 2; // display half of char
}
} else { // already went through the cycle once for empty line
emptyLine = false;
}
}
if (!emptyLine) { // EOL and currently not servicing empty line
boolean extendEOL = false;
for (int i = 0; i < layersLength; i++) {
if (ctx.layerActives[i]) {
DrawLayer l = ctx.layers[i];
if (l.extendsEOL()) {
extendEOL = true; // for at least one layer
l.updateContext(ctx);
}
}
}
if (extendEOL && ctx.component != null) {
// Disable the underlines and strikethrough for EOL extension
ctx.drawGraphics.setStrikeThroughColor(null);
ctx.drawGraphics.setUnderlineColor(null);
ctx.drawGraphics.setWaveUnderlineColor(null);
blankWidth = ctx.component.getWidth();
}
}
if (blankWidth > 0) {
ctx.drawGraphics.setBackColor(ctx.backColor);
ctx.drawGraphics.fillRect(blankWidth);
if (emptyLine) {
ctx.x += blankWidth;
}
}
} while (emptyLine);
} else { // Draw regular fragment
ctx.drawGraphics.setBackColor(ctx.backColor);
ctx.drawGraphics.setForeColor(ctx.foreColor);
ctx.drawGraphics.setStrikeThroughColor(ctx.strikeThroughColor);
ctx.drawGraphics.setUnderlineColor(ctx.underlineColor);
ctx.drawGraphics.setWaveUnderlineColor(ctx.waveUnderlineColor);
ctx.drawGraphics.setFont(ctx.font);
if (ctx.tabsFragment) {
ctx.drawGraphics.drawTabs(ctx.fragmentStartIndex,
ctx.fragmentLength, ctx.fragmentCharCount, ctx.fragmentWidth);
} else { // non-tabs
ctx.drawGraphics.drawChars(ctx.fragmentStartIndex,
ctx.fragmentLength, ctx.fragmentWidth);
}
}
}
/** Check whether the target offset was reached. */
private void checkTargetOffsetReached(DrawInfo ctx) {
ctx.continueDraw = true;
// Check whether at the end of the line
if (ctx.eol
&& (ctx.targetOffset == ctx.fragmentOffset || (ctx.targetOffset == -1))
) {
/** Special case for the emulating the EOL at the end of the document
* The EOL is emulated to process the layers that extend empty-line or EOL
*/
char ch = '\n';
ctx.continueDraw = ctx.drawGraphics.targetOffsetReached(
ctx.fragmentOffset, ch, ctx.x, ctx.spaceWidth, ctx);
// Check whether targeting all characters
} else if (ctx.targetOffset == -1
&& ctx.fragmentLength > 0 // Not sure whether it's necessary
) { // When targeting all chars
FontMetrics fm = FontMetricsCache.getFontMetrics(ctx.font, ctx.component);
// Use binary search to find the right offset
int low = -1;
int high = ctx.fragmentLength - 1;
// Cache the widths and first check whether past the end of fragment
int lastMid = high;
int lastWidth; // cache
if (ctx.tabsFragment) { // fragment contains tabs
int spaceCount = Analyzer.getColumn(ctx.textArray,
ctx.fragmentStartIndex, high, ctx.tabSize, ctx.visualColumn)
- ctx.visualColumn;
lastWidth = spaceCount * ctx.spaceWidth;
} else { // no tabs inside fragment
lastWidth = fm.charsWidth(ctx.textArray, ctx.fragmentStartIndex, high);
}
int lastWidthP1; // plus one char
if (ctx.tabsFragment) { // fragment contains tabs
int spaceCount = Analyzer.getColumn(ctx.textArray,
ctx.fragmentStartIndex, ctx.fragmentLength, ctx.tabSize, ctx.visualColumn)
- ctx.visualColumn;
lastWidthP1 = spaceCount * ctx.spaceWidth;
} else { // no tabs inside fragment
lastWidthP1 = fm.charsWidth(ctx.textArray, ctx.fragmentStartIndex, ctx.fragmentLength);
}
// Test whether the end of the fragment is accepted
ctx.continueDraw = ctx.drawGraphics.targetOffsetReached(
ctx.fragmentOffset + high, ctx.textArray[ctx.fragmentStartIndex + high],
ctx.x + lastWidth, lastWidthP1 - lastWidth, ctx
);
if (!ctx.continueDraw) {
// Binary search of the first offset that returns false follows
while (low <= high) {
int mid =(low + high) / 2;
// Compute width that will be passed as x coordinate
int width = 0;
if (mid == lastMid + 1) { // try to use cached value
width = lastWidthP1;
} else {
if (ctx.tabsFragment) { // fragment contains tabs
int spaceCount = Analyzer.getColumn(ctx.textArray,
ctx.fragmentStartIndex, mid, ctx.tabSize, ctx.visualColumn)
- ctx.visualColumn;
width = spaceCount * ctx.spaceWidth;
} else { // no tabs inside fragment
width = fm.charsWidth(ctx.textArray, ctx.fragmentStartIndex, mid);
}
}
// Compute width plus one char and substract the previous width
// to get the width of the char
int widthP1 = 0;
if (mid == lastMid - 1) { // try to use cached value
widthP1 = lastWidth;
} else {
if (ctx.tabsFragment) { // fragment contains tabs
int spaceCount = Analyzer.getColumn(ctx.textArray,
ctx.fragmentStartIndex, mid + 1, ctx.tabSize, ctx.visualColumn)
- ctx.visualColumn;
widthP1 = spaceCount * ctx.spaceWidth;
} else { // no tabs inside fragment
widthP1 = fm.charsWidth(ctx.textArray, ctx.fragmentStartIndex, mid + 1);
}
}
lastWidth = width;
lastWidthP1 = widthP1;
lastMid = mid;
ctx.continueDraw = ctx.drawGraphics.targetOffsetReached(
ctx.fragmentOffset + mid, ctx.textArray[ctx.fragmentStartIndex + mid],
ctx.x + width, widthP1 - width, ctx
);
if (ctx.continueDraw) {
low = mid + 1;
} else {
//Bug #34130, search should not end when mid == low + 1
if (mid > low && mid != high) {
high = mid; // last that rejected
} else {
break;
}
}
}
}
// Check whether target offset is inside current fragment
} else if (ctx.targetOffset < ctx.fragmentOffset + ctx.fragmentLength
&& ctx.fragmentOffset <= ctx.targetOffset
) {
int curWidth;
int prevWidth = 0;
int i = (ctx.targetOffset - ctx.fragmentOffset);
if (i > 0) {
if (ctx.tabsFragment) { // fragment contains tabs
int spaceCount = Analyzer.getColumn(ctx.textArray,
ctx.fragmentStartIndex, i, ctx.tabSize, ctx.visualColumn)
- ctx.visualColumn;
prevWidth = spaceCount * ctx.spaceWidth;
} else { // no tabs inside fragment
prevWidth = FontMetricsCache.getFontMetrics(ctx.font,
ctx.component).charsWidth(ctx.textArray, ctx.fragmentStartIndex, i);
}
}
if (ctx.tabsFragment) { // fragment contains tabs
int spaceCount = Analyzer.getColumn(ctx.textArray,
ctx.fragmentStartIndex, i + 1, ctx.tabSize, ctx.visualColumn)
- ctx.visualColumn;
curWidth = spaceCount * ctx.spaceWidth;
} else { // no tabs inside fragment
curWidth = FontMetricsCache.getFontMetrics(ctx.font,
ctx.component).charsWidth(ctx.textArray, ctx.fragmentStartIndex, i + 1);
}
ctx.continueDraw = ctx.drawGraphics.targetOffsetReached(
ctx.fragmentOffset + i, ctx.textArray[ctx.fragmentStartIndex + i],
ctx.x + prevWidth, curWidth - prevWidth, ctx);
}
}
/** Draw current fragment of the token. */
private void drawCurrentTokenFragment(DrawInfo ctx) {
// Fill in the draw context
ctx.foreColor = ctx.defaultColoring.getForeColor();
ctx.backColor = ctx.defaultColoring.getBackColor();
ctx.font = ctx.defaultColoring.getFont();
ctx.strikeThroughColor = null;
ctx.underlineColor = null;
ctx.waveUnderlineColor = null;
if (ctx.bol) { // if we are on the line begining
Color fg = ctx.foreColor;
Color bg = ctx.backColor;
handleBOL(ctx);
ctx.foreColor = fg;
ctx.backColor = bg;
}
// Check for status updates in planes at the begining of this fragment
while (ctx.fragmentOffset == ctx.updateOffset) {
updateOffsetReached(ctx); // while() because can be more marks at same pos
}
// Compute the length of the fragment
computeFragmentLength(ctx);
// Compute the display width of the fragment
computeFragmentDisplayWidth(ctx);
// Draw the fragment
drawFragment(ctx);
if (debugFragment) {
System.err.println("DrawEngine: FRAGMENT='" // NOI18N
+ EditorDebug.debugChars(ctx.buffer, ctx.fragmentStartIndex, ctx.fragmentLength)
+ "', at pos=" + ctx.fragmentOffset // NOI18N
+ ", bol=" + ctx.bol + ", eol=" + ctx.eol // NOI18N
);
}
// Check whether target-offset was reached
if (ctx.component != null) {
checkTargetOffsetReached(ctx);
}
// Move the variables to the next fragment in token
ctx.fragmentOffset += ctx.fragmentLength;
ctx.drawnLength += ctx.fragmentLength;
ctx.visualColumn += ctx.fragmentCharCount;
ctx.x += ctx.fragmentWidth;
ctx.bol = false;
// Update coordinates at the end of each line
if (ctx.eol) {
handleEOL(ctx);
ctx.bol = true; // now at BOL
}
if (ctx.fragmentOffset >= ctx.endOffset && ctx.endOffset < ctx.docLen) {
ctx.continueDraw = false;
}
}
/** Draw one token. This is repeatedly called until all the tokens
* were drawn.
* @return true when the drawing of the next token should be done
* or false when the drawing should stop.
*/
private void drawCurrentToken(DrawInfo ctx) {
// Get the token
if (ctx.tokenID != null) {
ctx.tokenContextPath = ctx.syntax.getTokenContextPath();
ctx.tokenOffset = ctx.syntax.getTokenOffset() + ctx.bufferStartOffset;
ctx.tokenLength = ctx.syntax.getTokenLength();
/* Check whether the token isn't totally before the area to be drawn.
* It must cover it at least by one character. For the more complicated lexical
* analyzers it's possible that they return several tokens that
* will cover only the prescan area.
*/
if (ctx.tokenOffset + ctx.tokenLength <= ctx.startOffset) {
return;
}
} else { // end of drawing area
ctx.tokenContextPath = null;
ctx.tokenOffset = ctx.fragmentOffset;
ctx.tokenLength = 0;
}
// Ask all the contexts first to possibly find out
// the first fragment length
if (ctx.tokenOffset <= ctx.startOffset) {
ctx.layerUpdateOffset = Integer.MAX_VALUE;
int layersLength = ctx.layers.length;
for (int i = 0; i < layersLength; i++) { // update status of all layers
DrawLayer l = ctx.layers[i];
ctx.layerActives[i] = l.isActive(ctx, null);
int naco = l.getNextActivityChangeOffset(ctx);
ctx.layerActivityChangeOffsets[i] = naco;
if (naco > ctx.fragmentOffset && naco < ctx.layerUpdateOffset) {
ctx.layerUpdateOffset = naco;
}
}
ctx.updateOffset = Math.min(ctx.layerUpdateOffset, ctx.drawMarkOffset);
}
ctx.drawnLength = ctx.fragmentOffset - ctx.tokenOffset;
ctx.fragmentLength = 0; // length of current token fragment
if (debug) {
System.err.println("DrawEngine: TOKEN='" // NOI18N
+ EditorDebug.debugChars(ctx.getBuffer(), ctx.getTokenOffset()
- ctx.getBufferStartOffset(), ctx.getTokenLength())
+ "', tokenID=<" + (ctx.getTokenID() == null ? "null" : ctx.tokenID.getName()) // NOI18N
+ ">, tcp=" + ctx.getTokenContextPath() // NOI18N
+ ", pos=" + ctx.getTokenOffset() // NOI18N
);
}
// Process all the fragments of one token
do {
drawCurrentTokenFragment(ctx);
} while (ctx.continueDraw && ctx.drawnLength < ctx.tokenLength);
}
private void graphicsSpecificUpdates(DrawInfo ctx) {
Rectangle bounds = ctx.editorUI.getExtentBounds();
Rectangle clip = ctx.graphics.getClipBounds();
Insets textMargin = ctx.editorUI.getTextMargin();
int leftMarginWidth = textMargin.left - ctx.editorUI.lineNumberWidth
- ctx.editorUI.textLeftMarginWidth;
// Draw line numbers bar and all the line nummbers
if (ctx.lineNumbering && !ctx.syncedLineNumbering) {
Color lnBackColor = ctx.lineNumberColoring.getBackColor();
int numY = ctx.startY;
int lnBarX = bounds.x + leftMarginWidth;
if (!lnBackColor.equals(ctx.defaultColoring.getBackColor()) || bounds.x > 0) {
ctx.graphics.setColor(lnBackColor);
ctx.graphics.fillRect(lnBarX, numY, ctx.editorUI.lineNumberWidth,
ctx.lineIndex * ctx.lineHeight); // can't use dg because of height
}
ctx.drawGraphics.setDefaultBackColor(lnBackColor); // will paint into bar
int lastDigitInd = Math.max(ctx.lineNumberChars.length - 1, 0);
int numX = lnBarX;
if (ctx.editorUI.getLineNumberMargin() != null) {
numX += ctx.editorUI.getLineNumberMargin().left;
}
ctx.bol = true; //
for (int j = 0; j < ctx.lineIndex; j++) { // draw all line numbers
// Init the context
ctx.fragmentOffset = ctx.lineStartOffsets[j];
ctx.foreColor = ctx.lineNumberColoring.getForeColor();
ctx.backColor = lnBackColor;
ctx.font = ctx.lineNumberColoring.getFont();
ctx.strikeThroughColor = null;
ctx.underlineColor = null;
ctx.waveUnderlineColor = null;
int lineNumber = ctx.startLineNumber + j;
// Update line-number by layers
int layersLength = ctx.layers.length;
for (int i = 0; i < layersLength; i++) {
lineNumber = ctx.layers[i].updateLineNumberContext(lineNumber, ctx);
}
int i = lastDigitInd;
// Fill in the digit chars
do {
ctx.lineNumberChars[i--] = (char)('0' + (lineNumber % 10));
lineNumber /= 10;
} while (lineNumber != 0 && i >= 0);
// Fill in the spaces
while (i >= 0) {
ctx.lineNumberChars[i--] = ' ';
}
ctx.drawGraphics.setY(numY);
ctx.drawGraphics.setBuffer(ctx.lineNumberChars);
ctx.drawGraphics.setForeColor(ctx.foreColor);
ctx.drawGraphics.setBackColor(ctx.backColor);
ctx.drawGraphics.setStrikeThroughColor(ctx.strikeThroughColor);
ctx.drawGraphics.setUnderlineColor(ctx.underlineColor);
ctx.drawGraphics.setWaveUnderlineColor(ctx.waveUnderlineColor);
ctx.drawGraphics.setFont(ctx.font);
ctx.drawGraphics.setX(lnBarX);
ctx.drawGraphics.fillRect(ctx.editorUI.lineNumberWidth);
ctx.drawGraphics.setX(numX);
ctx.drawGraphics.drawChars(0, ctx.lineNumberChars.length,
ctx.lineNumberChars.length * ctx.editorUI.lineNumberDigitWidth);
ctx.drawGraphics.setBuffer(null); // will do changes in buffer
numY += ctx.lineHeight;
}
}
// Clear margins
ctx.graphics.setColor(ctx.defaultColoring.getBackColor());
/** The margin is cleared only in case the line is scrolled
* horizontally and therefore the margin is dirty because
* of the previous text on the line. Otherwise the margin
* is not cleared. The condition (bounds.x gt 0) gives the answer.
*/
// Left margin
if (leftMarginWidth > 0 && bounds.x > 0) {
ctx.graphics.fillRect(bounds.x, ctx.startY, leftMarginWidth, ctx.lineIndex * ctx.lineHeight);
}
// Text left margin
if (ctx.editorUI.textLeftMarginWidth > 0 && bounds.x > 0) {
ctx.graphics.fillRect(bounds.x + textMargin.left - ctx.editorUI.textLeftMarginWidth,
ctx.startY, ctx.editorUI.textLeftMarginWidth, ctx.lineIndex * ctx.lineHeight);
}
// Right margin
if (textMargin.right > 0) {
ctx.graphics.fillRect(bounds.x + bounds.width - textMargin.right,
ctx.startY, textMargin.right, ctx.lineIndex * ctx.lineHeight);
}
// Top margin
/** The margin is cleared only in case the extent bounds
* are high enough so that the margin could be dirty
* because of drawing too much to the right.
*/
if (textMargin.top > 0 && clip.y < bounds.y + textMargin.top) {
ctx.graphics.fillRect(bounds.x, bounds.y,
bounds.width, textMargin.top);
}
// Bottom margin
int bY = bounds.y + bounds.height - textMargin.bottom;
if (textMargin.bottom > 0 && clip.y + clip.height > bY) {
ctx.graphics.fillRect(bounds.x, bY,
bounds.width, textMargin.bottom);
}
}
/** Draw on the specified area.
* @param drawGraphics draw graphics through which the drawing is done
* @param editorUI extended UI to use
* @param startOffset position from which the drawing starts.
* It must BOL of the first line to be drawn.
* @param endOffset position where the drawing stops.
* It must be EOL of the last line to be drawn.
* @param startX x-coordinate at which the drawing starts
* @param startY x-coordinate at which the drawing starts
* @param targetOffset position where the targetOffsetReached() method
* of drawGraphics is called. This is useful for caret update or modelToView.
* The Integer.MAX_VALUE can be passed to ignore that behavior. The -1 value
* has special meaning there so that it calls targetOffsetReached() after each
* character processed. This is used by viewToModel to find the position
* for some point.
*/
void draw(DrawGraphics drawGraphics, EditorUI editorUI, int startOffset, int endOffset,
int startX, int startY, int targetOffset) throws BadLocationException {
draw(null, drawGraphics, editorUI, startOffset, endOffset,
startX, startY, targetOffset);
}
void draw(View view, DrawGraphics drawGraphics, EditorUI editorUI, int startOffset, int endOffset,
int startX, int startY, int targetOffset) throws BadLocationException {
// Some correctness tests at the begining
if (startOffset < 0 || endOffset < 0 || startOffset > endOffset
|| startX < 0 || startY < 0
) {
return;
}
BaseDocument doc = (BaseDocument)((view != null) ? view.getDocument() : editorUI.getDocument());
if (debug) {
javax.swing.text.Element lineRoot = doc.getParagraphElement(0).getParentElement();
int startLine = lineRoot.getElementIndex(startOffset);
int startLineOffset = lineRoot.getElement(startLine).getStartOffset();
int endLine = lineRoot.getElementIndex(endOffset);
int endLineOffset = lineRoot.getElement(endLine).getStartOffset();
Graphics g = drawGraphics.getGraphics();
System.err.println("DrawEngine:---------- DRAWING startOffset="
+ startOffset + ", startLine=" + startLine // NOI18N
+ "(o=" + startLineOffset + "), endOffset=" + endOffset // NOI18N
+ ", endLine=" + endLine + "(o=" + endLineOffset // NOI18N
+ "), clip=" + ((g != null) ? g.getClipBounds().toString() : "null") // NOI18N
+ " ------------------"); // NOI18N
}
// Draw-context and other info
DrawInfo ctx = new DrawInfo();
ctx.drawGraphics = drawGraphics;
ctx.drawGraphics.setView(view);
ctx.editorUI = editorUI;
ctx.startOffset = startOffset;
ctx.endOffset = endOffset;
ctx.startX = startX;
ctx.startY = startY;
ctx.targetOffset = targetOffset;
synchronized (editorUI) { // lock operations manipulating draw layer chain
ctx.doc = doc;
if (ctx.doc == null) { // no base-document available
return;
}
ctx.text = DocumentUtilities.SEGMENT_CACHE.getSegment();
ctx.syntax = ctx.doc.getFreeSyntax();
ctx.doc.readLock();
try {
ctx.component = editorUI.getComponent();
ctx.docLen = ctx.doc.getLength();
if (ctx.startOffset > ctx.docLen || ctx.endOffset > ctx.docLen) {
return;
}
/* Correct the ending position to be at the begining
* of the next line. The only exception is when the
* endOffset is equal to docLen.
*/
if (ctx.endOffset < ctx.docLen) {
ctx.endOffset++;
}
// Initialize the draw-info
initInfo(ctx);
// Cycle through all the tokens found in the buffer
do {
ctx.tokenID = ctx.syntax.nextToken();
// Draw the current token
drawCurrentToken(ctx);
} while (ctx.continueDraw && ctx.tokenID != null);
if (ctx.endOffset == ctx.docLen) {
handleEOL(ctx);
// paint text limit line till the end of visible editor area
if (ctx.editorUI.textLimitLineVisible) { // draw limit line
int lineX = ctx.startX + ctx.editorUI.textLimitWidth * ctx.editorUI.defaultSpaceWidth;
if (ctx.graphics !=null){
ctx.graphics.setColor(ctx.editorUI.textLimitLineColor);
Rectangle clip = ctx.graphics.getClipBounds();
if (clip.height>ctx.editorUI.getLineHeight()){
ctx.graphics.drawLine(lineX, ctx.y, lineX, ctx.y+clip.height);
}
}
}
}
ctx.editorUI.updateVirtualWidth(ctx.widestWidth
+ ctx.editorUI.lineNumberWidth + 2 * ctx.editorUI.defaultSpaceWidth);
// When drawing to graphics, the line numbers and insets will be drawn now
if (ctx.graphics != null) {
graphicsSpecificUpdates(ctx);
}
} finally {
ctx.drawGraphics.setBuffer(null);
ctx.drawGraphics.finish();
if (ctx.syntax != null) {
ctx.doc.releaseSyntax(ctx.syntax);
}
DocumentUtilities.SEGMENT_CACHE.releaseSegment(ctx.text);
ctx.doc.readUnlock();
}
} // synchronized on editorUI
}
static class DrawInfo implements DrawContext {
// DrawContext -----------------------------------------------
/** Current foreground color. */
Color foreColor;
/** Current background color. */
Color backColor;
/** Current background color. */
Color underlineColor;
/** Current background color. */
Color waveUnderlineColor;
/** Color of the strike-through line or null. */
Color strikeThroughColor;
/** Current font. */
Font font;
/** Starting position of the drawing. */
int startOffset;
/** Ending position of the drawing. */
int endOffset;
/** Whether we are currently at the line begining. */
boolean bol;
/** Whether we are currently at the line end. */
boolean eol;
/** Editor-UI of the component for which we are drawing. */
EditorUI editorUI;
/** Buffer from which the chars are being drawn. */
char[] buffer;
/** Starting poisition of the buffer inside the document. */
int bufferStartOffset;
/** Token-id of the token being drawn. */
TokenID tokenID;
/** Token-context-path of the token being drawn. */
TokenContextPath tokenContextPath;
/** Position of the token in the document */
int tokenOffset;
/** Length of the token's text. */
int tokenLength;
/** Position of the fragment of the token being drawn in the document. */
int fragmentOffset;
/** Length of the fragment of the token in the document. */
int fragmentLength;
// Other variables ---------------------------------------------
/** Draw graphics */
DrawGraphics drawGraphics;
/** Target document position for the drawing or -1 if all the positions
* are the potential targets.
*/
int targetOffset;
/** Text of the document being used by drawing. */
Segment text;
/** Char array in the text segment. */
char[] textArray;
/** Syntax scanning the input. */
Syntax syntax;
/** Component being painted */
JTextComponent component;
/** Document of the component. */
BaseDocument doc;
/** Current length of the document */
int docLen;
/** Current visual column. */
int visualColumn;
/** Current x-coordinate. */
int x;
/** Current y-coordinate. */
int y;
/** Starting x-coordinate. */
int startX;
/** Starting y-coordinate. */
int startY;
/** Height of the line being drawn. */
int lineHeight;
/** Default coloring of the component. */
Coloring defaultColoring;
/** Size of the TAB character being drawn. */
int tabSize;
/** Widest width of the line that was painted. */
int widestWidth;
/** Whether the draw should continue or not. */
boolean continueDraw;
/** Line number of the first painted line. */
int startLineNumber;
/** Index of the line being drawn. It is added
* to the startLineNumber to form the resulting line number.
*/
int lineIndex;
/** Array of the start positions of all the lines drawn. */
int[] lineStartOffsets;
/** Characters forming the line-number. It is reused for drawing
* all the lines.
*/
char[] lineNumberChars;
/** Coloring for the line-number. */
Coloring lineNumberColoring;
/** Graphics object. It can be null when not drawing to the component. */
Graphics graphics;
/** Whether line-numbers are visible and allowed by the draw-graphics. */
boolean lineNumbering;
/** Whether the line-numbers should be painted after each is painted.
* By default the line-numbers are drawn as one block at the end
* of the drawing.
*/
boolean syncedLineNumbering;
/** Array of the draw-layers to be used in the painting */
DrawLayer[] layers;
/** Whether the particular layer is currently active or not. */
boolean[] layerActives;
/** Next position where the layer will be asked whether it's active
* or not.
*/
int[] layerActivityChangeOffsets;
/** Position where either the next draw-mark is
* or which matches the activity-change-offset of one or more layers.
*/
int updateOffset;
/** Next activity-change-offset of one or more layers. */
int layerUpdateOffset;
/** Update of the layers because of the draw-mark is at the given position.
* False means the update is because the activity-change-offset
* was reached.
*/
boolean drawMarkUpdate;
/** List of draw marks in the painted area. */
List drawMarkList;
/** Current index in the drawMarkList */
int drawMarkIndex;
/** Current draw-mark */
MarkFactory.DrawMark drawMark;
/** Position of the current draw-mark */
int drawMarkOffset;
/** Length of the current token that was already drawn. */
int drawnLength;
/** Offset of the fragment starting character in the buffer */
int fragmentStartIndex;
/** Whether the fragment contains TABs only. */
boolean tabsFragment;
/** Width of one space character for the current context font. */
int spaceWidth;
/** Display width of the fragment */
int fragmentWidth;
/** Number of characters that the fragment stands for. It can differ
* from fragmentLength for tabsFragment.
*/
int fragmentCharCount;
public Color getForeColor() {
return foreColor;
}
public void setForeColor(Color foreColor) {
this.foreColor = foreColor;
}
public Color getBackColor() {
return backColor;
}
public void setBackColor(Color backColor) {
this.backColor = backColor;
}
public Color getUnderlineColor() {
return underlineColor;
}
public void setUnderlineColor(Color underlineColor) {
this.underlineColor = underlineColor;
}
public Color getWaveUnderlineColor() {
return waveUnderlineColor;
}
public void setWaveUnderlineColor(Color waveUnderlineColor) {
this.waveUnderlineColor = waveUnderlineColor;
}
public Color getStrikeThroughColor() {
return strikeThroughColor;
}
public void setStrikeThroughColor(Color strikeThroughColor) {
this.strikeThroughColor = strikeThroughColor;
}
public Font getFont() {
return font;
}
public void setFont(Font font) {
this.font = font;
}
public int getStartOffset() {
return startOffset;
}
public int getEndOffset() {
return endOffset;
}
public boolean isBOL() {
return bol;
}
public boolean isEOL() {
return eol;
}
public EditorUI getEditorUI() {
return editorUI;
}
public char[] getBuffer() {
return buffer;
}
public int getBufferStartOffset() {
return bufferStartOffset;
}
public TokenID getTokenID() {
return tokenID;
}
public TokenContextPath getTokenContextPath() {
return tokenContextPath;
}
public int getTokenOffset() {
return tokenOffset;
}
public int getTokenLength() {
return tokenLength;
}
public int getFragmentOffset() {
return fragmentOffset;
}
public int getFragmentLength() {
return fragmentLength;
}
}
class PreinitializedDrawEngine extends DrawEngine{
DrawGraphics drawGraphics;
DrawInfo drawInfo;
public PreinitializedDrawEngine(DrawGraphics drawGraphics){
super();
this.drawGraphics = drawGraphics;
}
public void release(){
drawInfo.drawGraphics.setBuffer(null);
drawInfo.drawGraphics.finish();
if (drawInfo.syntax != null) {
drawInfo.doc.releaseSyntax(drawInfo.syntax);
}
DocumentUtilities.SEGMENT_CACHE.releaseSegment(drawInfo.text);
}
public void preinitialize(BaseDocument doc, EditorUI editorUI, int startOffset, int endOffset,
int startX, int startY, int targetOffset) throws BadLocationException{
// Draw-context and other info
drawInfo = new DrawInfo();
drawInfo.drawGraphics = drawGraphics;
drawInfo.editorUI = editorUI;
drawInfo.startOffset = startOffset;
drawInfo.endOffset = endOffset;
drawInfo.startX = startX;
drawInfo.startY = startY;
drawInfo.targetOffset = targetOffset;
synchronized (editorUI) { // lock operations manipulating draw layer chain
drawInfo.doc = doc;
if (drawInfo.doc == null) { // no base-document available
return;
}
try{
drawInfo.text = DocumentUtilities.SEGMENT_CACHE.getSegment();
drawInfo.syntax = drawInfo.doc.getFreeSyntax();
drawInfo.doc.readLock();
drawInfo.component = editorUI.getComponent();
drawInfo.docLen = drawInfo.doc.getLength();
if (drawInfo.startOffset > drawInfo.docLen || drawInfo.endOffset > drawInfo.docLen) {
return;
}
/* Correct the ending position to be at the begining
* of the next line. The only exception is when the
* endOffset is equal to docLen.
*/
if (drawInfo.endOffset < drawInfo.docLen) {
drawInfo.endOffset++;
}
// Initialize the draw-info
initInfo(drawInfo);
} finally{
drawInfo.doc.readUnlock();
}
}
}
void draw(DrawGraphics dg, EditorUI editorUI, int startOffset, int endOffset,
int startX, int startY, int targetOffset) throws BadLocationException {
// Some correctness tests at the begining
if (startOffset < 0 || endOffset < 0 || startOffset > endOffset
|| startX < 0 || startY < 0
) {
return;
}
if (dg == null){
dg = drawGraphics;
}else{
super.draw(dg, editorUI, startOffset, endOffset, startX, startY, targetOffset);
return;
}
synchronized (editorUI){
DrawInfo ctx = drawInfo;
ctx.doc.readLock();
try{
do {
ctx.tokenID = ctx.syntax.nextToken();
// Draw the current token
drawCurrentToken(ctx);
} while (ctx.continueDraw && ctx.tokenID != null);
if (ctx.endOffset == ctx.docLen) {
handleEOL(ctx);
}
ctx.editorUI.updateVirtualWidth(ctx.widestWidth
+ ctx.editorUI.lineNumberWidth + 2 * ctx.editorUI.defaultSpaceWidth);
// When drawing to graphics, the line numbers and insets will be drawn now
if (ctx.graphics != null) {
graphicsSpecificUpdates(ctx);
}
}finally{
ctx.doc.readUnlock();
}
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy