RedPanda-CPP/RedPandaIDE/qsynedit/TextPainter.cpp

1101 lines
46 KiB
C++

/*
* Copyright (C) 2020-2022 Roy Qu (royqh1979@gmail.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "TextPainter.h"
#include "SynEdit.h"
#include "Constants.h"
#include <cmath>
#include <QDebug>
SynEditTextPainter::SynEditTextPainter(SynEdit *edit, QPainter *painter, int FirstRow, int LastRow, int FirstCol, int LastCol)
{
this->edit = edit;
this->painter = painter;
this->aFirstRow = FirstRow;
this->aLastRow = LastRow;
this->FirstCol = FirstCol;
this->LastCol = LastCol;
}
void SynEditTextPainter::paintTextLines(const QRect& clip)
{
painter->fillRect(clip, edit->mBackgroundColor);
AClip = clip;
vFirstLine = edit->rowToLine(aFirstRow);
vLastLine = edit->rowToLine(aLastRow);
bCurrentLine = false;
// If the right edge is visible and in the invalid area, prepare to paint it.
// Do this first to realize the pen when getting the dc variable.
bDoRightEdge = false;
if (edit->mRightEdge > 0) { // column value
nRightEdge = edit->textOffset()+ edit->mRightEdge * edit->mCharWidth; // pixel value
if (nRightEdge >= AClip.left() &&nRightEdge <= AClip.right()) {
bDoRightEdge = true;
QPen pen(edit->mRightEdgeColor,1);
painter->setPen(pen);
}
}
// Paint the visible text lines. To make this easier, compute first the
// necessary information about the selected area: is there any visible
// selected area, and what are its lines / columns?
if (vLastLine >= vFirstLine) {
ComputeSelectionInfo();
PaintLines();
}
//painter->setClipping(false);
// If anything of the two pixel space before the text area is visible, then
// fill it with the component background color.
if (AClip.left() <edit->mGutterWidth + 2) {
rcToken = AClip;
rcToken.setLeft( std::max(AClip.left(), edit->mGutterWidth));
rcToken.setRight(edit->mGutterWidth + 2);
// Paint whole left edge of the text with same color.
// (value of WhiteAttribute can vary in e.g. MultiSyn)
painter->fillRect(rcToken,colEditorBG());
// Adjust the invalid area to not include this area.
AClip.setLeft(rcToken.right());
}
// If there is anything visible below the last line, then fill this as well.
rcToken = AClip;
rcToken.setTop((aLastRow - edit->mTopLine + 1) * edit->mTextHeight);
if (rcToken.top() < rcToken.bottom()) {
painter->fillRect(rcToken,colEditorBG());
// Draw the right edge if necessary.
if (bDoRightEdge) {
QPen pen(edit->mRightEdgeColor,1);
painter->setPen(pen);
painter->drawLine(nRightEdge, rcToken.top(),nRightEdge, rcToken.bottom() + 1);
}
}
// This messes with pen colors, so draw after right margin has been drawn
PaintFoldAttributes();
}
void SynEditTextPainter::paintGutter(const QRect& clip)
{
int cRow;
QRect rcLine, rcFold;
QString s;
int vLine;
int vLineTop;
int x;
AClip = clip;
painter->fillRect(AClip,edit->mGutter.color());
rcLine=AClip;
if (edit->mGutter.showLineNumbers()) {
// prepare the rect initially
rcLine = AClip;
rcLine.setRight( std::max(rcLine.right(), edit->mGutterWidth - 2));
rcLine.setBottom(rcLine.top());
if (edit->mGutter.useFontStyle()) {
painter->setFont(edit->mGutter.font());
} else {
QFont newFont = painter->font();
newFont.setBold(false);
newFont.setItalic(false);
newFont.setStrikeOut(false);
newFont.setUnderline(false);
painter->setFont(newFont);
}
QColor textColor;
if (edit->mGutter.textColor().isValid()) {
textColor = edit->mGutter.textColor();
} else {
textColor = edit->mForegroundColor;
}
// draw each line if it is not hidden by a fold
BufferCoord selectionStart = edit->blockBegin();
BufferCoord selectionEnd = edit->blockEnd();
for (int cRow = aFirstRow; cRow <= aLastRow; cRow++) {
vLine = edit->rowToLine(cRow);
if ((vLine > edit->mDocument->count()) && (edit->mDocument->count() > 0 ))
break;
if (edit->mGutter.activeLineTextColor().isValid()) {
if (
(edit->mCaretY==vLine) ||
(edit->mActiveSelectionMode == SynSelectionMode::Column && vLine >= selectionStart.line && vLine <= selectionEnd.line)
)
painter->setPen(edit->mGutter.activeLineTextColor());
else
painter->setPen(textColor);
} else {
painter->setPen(textColor);
}
vLineTop = (cRow - edit->mTopLine) * edit->mTextHeight;
// next line rect
rcLine.setTop(vLineTop);
rcLine.setBottom(rcLine.top() + edit->mTextHeight);
s = edit->mGutter.formatLineNumber(vLine);
edit->onGutterGetText(vLine,s);
QRectF textRect;
textRect = painter->boundingRect(textRect, Qt::AlignLeft,s);
painter->drawText(
(edit->mGutterWidth - edit->mGutter.rightOffset() - 2) - textRect.width(),
rcLine.bottom() + ((edit->mTextHeight - int(textRect.height())) / 2 - painter->fontMetrics().descent()),
s
);
}
}
// Draw the folding lines and squares
if (edit->mUseCodeFolding) {
for (cRow = aLastRow; cRow>=aFirstRow; cRow--) {
vLine = edit->rowToLine(cRow);
if ((vLine > edit->mDocument->count()) && (edit->mDocument->count() != 0))
continue;
// Form a rectangle for the square the user can click on
rcFold.setLeft(edit->mGutterWidth - edit->mGutter.rightOffset());
rcFold.setTop((cRow - edit->mTopLine) * edit->mTextHeight);
rcFold.setRight(rcFold.left() + edit->mGutter.rightOffset() - 4);
rcFold.setBottom(rcFold.top() + edit->mTextHeight);
painter->setPen(edit->mCodeFolding.folderBarLinesColor);
// Need to paint a line?
if (edit->foldAroundLine(vLine)) {
x = rcFold.left() + (rcFold.width() / 2);
painter->drawLine(x,rcFold.top(), x, rcFold.bottom());
}
// Need to paint a line end?
if (edit->foldEndAtLine(vLine)) {
x = rcFold.left() + (rcFold.width() / 2);
painter->drawLine(x,rcFold.top(), x, rcFold.top() + rcFold.height() / 2);
painter->drawLine(x,
rcFold.top() + rcFold.height() / 2,
rcFold.right() - 2 ,
rcFold.top() + rcFold.height() / 2);
}
// Any fold ranges beginning on this line?
PSynEditFoldRange FoldRange = edit->foldStartAtLine(vLine);
if (FoldRange) {
// Draw the bottom part of a line
if (!FoldRange->collapsed) {
x = rcFold.left() + (rcFold.width() / 2);
painter->drawLine(x, rcFold.top() + rcFold.height() / 2,
x, rcFold.bottom());
}
// make a square rect
inflateRect(rcFold,-2, 0);
rcFold.setTop(
rcFold.top() + ((edit->mTextHeight - rcFold.width()) / 2));
rcFold.setBottom(rcFold.top() + rcFold.width());
// Paint the square the user can click on
painter->setBrush(edit->mGutter.color());
painter->setPen(edit->mCodeFolding.folderBarLinesColor);
painter->drawRect(rcFold);
// Paint minus sign
painter->drawLine(
rcFold.left() + 2, rcFold.top() + (rcFold.height() / 2 ),
rcFold.right() - 2, rcFold.top() + (rcFold.height() / 2 ));
// Paint vertical line of plus sign
if (FoldRange->collapsed) {
x = rcFold.left() + (rcFold.width() / 2);
painter->drawLine(x, rcFold.top() + 2,
x, rcFold.bottom() - 2);
}
}
}
}
for (cRow = aFirstRow; cRow <=aLastRow; cRow++) {
vLine = edit->rowToLine(cRow);
if ((vLine > edit->mDocument->count()) && (edit->mDocument->count() != 0))
break;
edit->onGutterPaint(*painter,vLine, 0, (cRow - edit->mTopLine) * edit->mTextHeight);
}
}
QColor SynEditTextPainter::colEditorBG()
{
if (edit->mActiveLineColor.isValid() && bCurrentLine) {
return edit->mActiveLineColor;
} else {
return edit->mBackgroundColor;
}
}
void SynEditTextPainter::ComputeSelectionInfo()
{
BufferCoord vStart;
BufferCoord vEnd;
bAnySelection = false;
// Only if selection is visible anyway.
if (!edit->mHideSelection || edit->hasFocus()) {
bAnySelection = true;
// Get the *real* start of the selected area.
if (edit->mBlockBegin.line < edit->mBlockEnd.line) {
vStart = edit->mBlockBegin;
vEnd = edit->mBlockEnd;
} else if (edit->mBlockBegin.line > edit->mBlockEnd.line) {
vEnd = edit->mBlockBegin;
vStart = edit->mBlockEnd;
} else if (edit->mBlockBegin.ch != edit->mBlockEnd.ch) {
// it is only on this line.
vStart.line = edit->mBlockBegin.line;
vEnd.line = vStart.line;
if (edit->mBlockBegin.ch < edit->mBlockEnd.ch) {
vStart.ch = edit->mBlockBegin.ch;
vEnd.ch = edit->mBlockEnd.ch;
} else {
vStart.ch = edit->mBlockEnd.ch;
vEnd.ch = edit->mBlockBegin.ch;
}
} else
bAnySelection = false;
if (edit->mInputPreeditString.length()>0) {
if (vStart.line == edit->mCaretY && vStart.ch >=edit->mCaretX) {
vStart.ch+=edit->mInputPreeditString.length();
}
if (vEnd.line == edit->mCaretY && vEnd.ch >edit->mCaretX) {
vEnd.ch+=edit->mInputPreeditString.length();
}
}
// If there is any visible selection so far, then test if there is an
// intersection with the area to be painted.
if (bAnySelection) {
// Don't care if the selection is not visible.
bAnySelection = (vEnd.line >= vFirstLine) && (vStart.line <= vLastLine);
if (bAnySelection) {
// Transform the selection from text space into screen space
vSelStart = edit->bufferToDisplayPos(vStart);
vSelEnd = edit->bufferToDisplayPos(vEnd);
if (edit->mInputPreeditString.length()
&& vStart.line == edit->mCaretY) {
QString sLine = edit->lineText().left(edit->mCaretX-1)
+ edit->mInputPreeditString
+ edit->lineText().mid(edit->mCaretX-1);
vSelStart.Column = edit->charToColumn(sLine,vStart.ch);
}
if (edit->mInputPreeditString.length()
&& vEnd.line == edit->mCaretY) {
QString sLine = edit->lineText().left(edit->mCaretX-1)
+ edit->mInputPreeditString
+ edit->lineText().mid(edit->mCaretX-1);
vSelEnd.Column = edit->charToColumn(sLine,vEnd.ch);
}
// In the column selection mode sort the begin and end of the selection,
// this makes the painting code simpler.
if (edit->mActiveSelectionMode == SynSelectionMode::Column && vSelStart.Column > vSelEnd.Column)
std::swap(vSelStart.Column, vSelEnd.Column);
}
}
}
}
void SynEditTextPainter::setDrawingColors(bool Selected)
{
if (Selected) {
if (colSelFG.isValid())
painter->setPen(colSelFG);
else
painter->setPen(colFG);
if (colSelBG.isValid())
painter->setBrush(colSelBG);
else
painter->setBrush(colBG);
painter->setBackground(edit->mBackgroundColor);
} else {
painter->setPen(colFG);
painter->setBrush(colBG);
painter->setBackground(edit->mBackgroundColor);
}
}
int SynEditTextPainter::ColumnToXValue(int Col)
{
return edit->textOffset() + (Col - 1) * edit->mCharWidth;
}
void SynEditTextPainter::PaintToken(const QString &Token, int TokenCols, int ColumnsBefore,
int First, int Last, bool /*isSelection*/, const QFont& font,
const QFont& fontForNonAscii)
{
bool startPaint;
int nX;
if (Last >= First && rcToken.right() > rcToken.left()) {
// qDebug()<<"Paint Token"<<Token<<ColumnsBefore<<TokenCols<<First<<Last;
nX = ColumnToXValue(First);
First -= ColumnsBefore;
Last -= ColumnsBefore;
QRect rcTokenBack = rcToken;
rcTokenBack.setWidth(rcTokenBack.width()-1);
painter->fillRect(rcTokenBack,painter->brush());
if (First > TokenCols) {
} else {
int tokenColLen=0;
startPaint = false;
for (int i=0;i<Token.length();i++) {
int charCols=0;
QString textToPaint = Token[i];
if (Token[i] == SynTabChar) {
charCols = edit->tabWidth() - ((ColumnsBefore+tokenColLen) % edit->tabWidth());
} else {
charCols = edit->charColumns(Token[i]);
}
if (tokenColLen+charCols>=First) {
if (!startPaint && (tokenColLen+1!=First)) {
nX-= (First - tokenColLen - 1) * edit->mCharWidth;
}
startPaint = true;
}
if (tokenColLen+charCols > Last)
break;
//painter->drawText(nX,rcToken.bottom()-painter->fontMetrics().descent()*edit->dpiFactor() , Token[i]);
if (startPaint) {
bool drawed = false;
if (painter->fontInfo().fixedPitch()
&& edit->mOptions.testFlag(eoLigatureSupport)
&& !Token[i].isSpace()
&& (Token[i].unicode()<=0xFF)) {
while(i+1<Token.length()) {
if (Token[i+1].unicode()>0xFF || Token[i+1].isSpace())
break;
i+=1;
charCols += edit->charColumns(Token[i]);
textToPaint+=Token[i];
}
painter->drawText(nX,rcToken.bottom()-painter->fontMetrics().descent() , textToPaint);
drawed = true;
}
if (!drawed) {
if (Token[i].unicode()<=0xFF)
painter->drawText(nX,rcToken.bottom()-painter->fontMetrics().descent() , Token[i]);
else {
painter->setFont(fontForNonAscii);
painter->drawText(nX,rcToken.bottom()-painter->fontMetrics().descent() , Token[i]);
painter->setFont(font);
}
drawed = true;
}
nX += charCols * edit->mCharWidth;
}
tokenColLen += charCols;
}
}
rcToken.setLeft(rcToken.right());
}
}
void SynEditTextPainter::PaintEditAreas(const SynEditingAreaList &areaList)
{
QRect rc;
int x1,x2;
int offset;
//painter->setClipRect(rcLine);
rc=rcLine;
rc.setBottom(rc.bottom()-1);
setDrawingColors(false);
for (const PSynEditingArea& p:areaList) {
if (p->beginX > LastCol)
continue;
if (p->endX < FirstCol)
continue;
if (p->beginX < FirstCol)
x1 = FirstCol;
else
x1 = p->beginX;
if (p->endX > LastCol)
x2 = LastCol;
else
x2 = p->endX;
rc.setLeft(ColumnToXValue(x1));
rc.setRight(ColumnToXValue(x2));
painter->setPen(p->color);
painter->setBrush(Qt::NoBrush);
switch(p->type) {
case SynEditingAreaType::eatRectangleBorder:
painter->drawRect(rc);
break;
case SynEditingAreaType::eatUnderLine:
painter->drawLine(rc.left(),rc.bottom(),rc.right(),rc.bottom());
break;
case SynEditingAreaType::eatWaveUnderLine:
offset=3;
int lastX=rc.left();
int lastY=rc.bottom()-offset;
int t = rc.left();
while (t<rc.right()) {
t+=3;
if (t>rc.right())
t = rc.right();
offset = 3 - offset;
painter->drawLine(lastX,lastY,t,rc.bottom()-offset);
lastX = t;
lastY = rc.bottom()-offset;
}
}
}
}
void SynEditTextPainter::PaintHighlightToken(bool bFillToEOL)
{
bool bComplexToken;
int nC1, nC2, nC1Sel, nC2Sel;
bool bU1, bSel, bU2;
int nX1, nX2;
// Compute some helper variables.
nC1 = std::max(FirstCol, TokenAccu.ColumnsBefore + 1);
nC2 = std::min(LastCol, TokenAccu.ColumnsBefore + TokenAccu.Columns + 1);
if (bComplexLine) {
bU1 = (nC1 < nLineSelStart);
bSel = (nC1 < nLineSelEnd) && (nC2 >= nLineSelStart);
bU2 = (nC2 >= nLineSelEnd);
bComplexToken = bSel && (bU1 || bU2);
} else {
bSel = bLineSelected;
bComplexToken = false;
bU1 = false; // to shut up compiler warning.
bU2 = false; // to shut up compiler warning.
}
// Any token chars accumulated?
if (TokenAccu.Columns > 0) {
// Initialize the colors and the font style.
colBG = TokenAccu.BG;
colFG = TokenAccu.FG;
if (bSpecialLine) {
if (colSpFG.isValid())
colFG = colSpFG;
if (colSpBG.isValid())
colBG = colSpBG;
}
// if (bSpecialLine && edit->mOptions.testFlag(eoSpecialLineDefaultFg))
// colFG = TokenAccu.FG;
QFont font = edit->font();
font.setBold(TokenAccu.Style & SynFontStyle::fsBold);
font.setItalic(TokenAccu.Style & SynFontStyle::fsItalic);
font.setStrikeOut(TokenAccu.Style & SynFontStyle::fsStrikeOut);
font.setUnderline(TokenAccu.Style & SynFontStyle::fsUnderline);
painter->setFont(font);
QFont nonAsciiFont = edit->fontForNonAscii();
nonAsciiFont.setBold(TokenAccu.Style & SynFontStyle::fsBold);
nonAsciiFont.setItalic(TokenAccu.Style & SynFontStyle::fsItalic);
nonAsciiFont.setStrikeOut(TokenAccu.Style & SynFontStyle::fsStrikeOut);
nonAsciiFont.setUnderline(TokenAccu.Style & SynFontStyle::fsUnderline);
// Paint the chars
if (bComplexToken) {
// first unselected part of the token
if (bU1) {
setDrawingColors(false);
rcToken.setRight(ColumnToXValue(nLineSelStart));
PaintToken(TokenAccu.s,TokenAccu.Columns,TokenAccu.ColumnsBefore,nC1,nLineSelStart,false,font,nonAsciiFont);
}
// selected part of the token
setDrawingColors(true);
nC1Sel = std::max(nLineSelStart, nC1);
nC2Sel = std::min(nLineSelEnd, nC2);
rcToken.setRight(ColumnToXValue(nC2Sel));
PaintToken(TokenAccu.s, TokenAccu.Columns, TokenAccu.ColumnsBefore, nC1Sel, nC2Sel,true,font,nonAsciiFont);
// second unselected part of the token
if (bU2) {
setDrawingColors(false);
rcToken.setRight(ColumnToXValue(nC2));
PaintToken(TokenAccu.s, TokenAccu.Columns, TokenAccu.ColumnsBefore,nLineSelEnd, nC2,false,font,nonAsciiFont);
}
} else {
setDrawingColors(bSel);
rcToken.setRight(ColumnToXValue(nC2));
PaintToken(TokenAccu.s, TokenAccu.Columns, TokenAccu.ColumnsBefore, nC1, nC2,bSel,font,nonAsciiFont);
}
}
// Fill the background to the end of this line if necessary.
if (bFillToEOL && rcToken.left() < rcLine.right()) {
if (bSpecialLine && colSpBG.isValid())
colBG = colSpBG;
else
colBG = colEditorBG();
if (bComplexLine) {
nX1 = ColumnToXValue(nLineSelStart);
nX2 = ColumnToXValue(nLineSelEnd);
if (rcToken.left() < nX1) {
setDrawingColors(false);
rcToken.setRight(nX1);
// if (TokenAccu.Len != 0 && TokenAccu.Style != SynFontStyle::fsNone)
// AdjustEndRect();
painter->fillRect(rcToken,painter->brush());
rcToken.setLeft(nX1);
}
if (rcToken.left() < nX2) {
setDrawingColors(true);
rcToken.setRight(nX2);
painter->fillRect(rcToken,painter->brush());
rcToken.setLeft(nX2);
}
if (rcToken.left() < rcLine.right()) {
setDrawingColors(false);
rcToken.setRight(rcLine.right());
painter->fillRect(rcToken,painter->brush());
}
} else {
setDrawingColors(bLineSelected);
rcToken.setRight(rcLine.right());
// if (TokenAccu.Len != 0 && TokenAccu.Style != SynFontStyle::fsNone)
// AdjustEndRect();
painter->fillRect(rcToken,painter->brush());
}
}
}
bool SynEditTextPainter::TokenIsSpaces(bool &bSpacesTest, const QString& Token, bool& bIsSpaces)
{
if (!bSpacesTest) {
bSpacesTest = true;
for (QChar ch:Token) {
//todo: should include tabs?
if (ch!= ' ') {
bIsSpaces = false;
return bIsSpaces;
}
}
bIsSpaces = true;
}
return bIsSpaces;
}
// Store the token chars with the attributes in the TokenAccu
// record. This will paint any chars already stored if there is
// a (visible) change in the attributes.
void SynEditTextPainter::AddHighlightToken(const QString &Token, int ColumnsBefore,
int TokenColumns, int cLine, PSynHighlighterAttribute p_Attri)
{
bool bCanAppend;
QColor Foreground, Background;
SynFontStyles Style;
bool bSpacesTest,bIsSpaces;
if (p_Attri) {
Foreground = p_Attri->foreground();
Background = p_Attri->background();
Style = p_Attri->styles();
} else {
Foreground = colFG;
Background = colBG;
Style = getFontStyles(edit->font());
}
// if (!Background.isValid() || (edit->mActiveLineColor.isValid() && bCurrentLine)) {
// Background = colEditorBG();
// }
if (!Background.isValid() ) {
Background = colEditorBG();
}
if (!Foreground.isValid()) {
Foreground = edit->mForegroundColor;
}
edit->onPreparePaintHighlightToken(cLine,edit->mHighlighter->getTokenPos()+1,
Token,p_Attri,Style,Foreground,Background);
// Do we have to paint the old chars first, or can we just append?
bCanAppend = false;
bSpacesTest = false;
if (TokenAccu.Columns > 0) {
// font style must be the same or token is only spaces
if (TokenAccu.Style == Style || ( (Style & SynFontStyle::fsUnderline) == (TokenAccu.Style & fsUnderline)
&& TokenIsSpaces(bSpacesTest,Token,bIsSpaces)) ) {
if (
// background color must be the same and
((TokenAccu.BG == Background) &&
// foreground color must be the same or token is only spaces
((TokenAccu.FG == Foreground) || (TokenIsSpaces(bSpacesTest,Token,bIsSpaces) && !edit->mOptions.testFlag(eoShowSpecialChars))))) {
bCanAppend = true;
}
}
// If we can't append it, then we have to paint the old token chars first.
if (!bCanAppend)
PaintHighlightToken(false);
}
// Don't use AppendStr because it's more expensive.
if (bCanAppend) {
TokenAccu.s.append(Token);
TokenAccu.Columns+=TokenColumns;
} else {
TokenAccu.Columns = TokenColumns;
TokenAccu.s = Token;
TokenAccu.ColumnsBefore = ColumnsBefore;
TokenAccu.FG = Foreground;
TokenAccu.BG = Background;
TokenAccu.Style = Style;
}
}
void SynEditTextPainter::PaintFoldAttributes()
{
int TabSteps, LineIndent, LastNonBlank, X, Y, cRow, vLine;
// Paint indent guides. Use folds to determine indent value of these
// Use a separate loop so we can use a custom pen
// Paint indent guides using custom pen
if (edit->mCodeFolding.indentGuides || edit->mCodeFolding.fillIndents) {
QColor paintColor;
if (edit->mCodeFolding.indentGuidesColor.isValid()) {
paintColor = edit->mCodeFolding.indentGuidesColor;
} else {
paintColor = edit->palette().color(QPalette::Text);
}
QColor gradientStart = paintColor;
QColor gradientEnd = paintColor;
QPen oldPen = painter->pen();
// Now loop through all the lines. The indices are valid for Lines.
for (cRow = aFirstRow; cRow<=aLastRow;cRow++) {
vLine = edit->rowToLine(cRow);
if (vLine > edit->mDocument->count() && edit->mDocument->count() > 0)
break;
// Set vertical coord
Y = (cRow - edit->mTopLine) * edit->mTextHeight; // limit inside clip rect
if (edit->mTextHeight % 2 == 1 && vLine % 2 == 0) {
Y++;
}
// Get next nonblank line
LastNonBlank = vLine - 1;
while (LastNonBlank + 1 < edit->mDocument->count() && edit->mDocument->getString(LastNonBlank).isEmpty())
LastNonBlank++;
if (LastNonBlank>=edit->document()->count())
continue;
LineIndent = edit->getLineIndent(edit->mDocument->getString(LastNonBlank));
int braceLevel = edit->mDocument->ranges(LastNonBlank).braceLevel;
int indentLevel = braceLevel ;
if (edit->tabWidth()>0)
indentLevel = LineIndent / edit->tabWidth();
// Step horizontal coord
//TabSteps = edit->mTabWidth;
TabSteps = 0;
indentLevel = 0;
while (TabSteps < LineIndent) {
X = TabSteps * edit->mCharWidth + edit->textOffset() - 2;
TabSteps+=edit->tabWidth();
indentLevel++ ;
if (edit->mHighlighter) {
if (edit->mCodeFolding.indentGuides) {
PSynHighlighterAttribute attr = edit->mHighlighter->symbolAttribute();
GetBraceColorAttr(indentLevel,attr);
paintColor = attr->foreground();
}
if (edit->mCodeFolding.fillIndents) {
PSynHighlighterAttribute attr = edit->mHighlighter->symbolAttribute();
GetBraceColorAttr(indentLevel,attr);
gradientStart=attr->foreground();
attr = edit->mHighlighter->symbolAttribute();
GetBraceColorAttr(indentLevel+1,attr);
gradientStart=attr->foreground();
}
}
if (edit->mCodeFolding.fillIndents) {
int X1;
if (TabSteps>LineIndent)
X1 = LineIndent * edit->mCharWidth + edit->textOffset() - 2;
else
X1 = TabSteps * edit->mCharWidth + edit->textOffset() - 2;
gradientStart.setAlpha(20);
gradientEnd.setAlpha(10);
QLinearGradient gradient(X,Y,X1,Y);
gradient.setColorAt(1,gradientStart);
gradient.setColorAt(0,gradientEnd);
painter->fillRect(X,Y,(X1-X),edit->mTextHeight,gradient);
}
// Move to top of vertical line
if (edit->mCodeFolding.indentGuides) {
QPen dottedPen(Qt::PenStyle::DashLine);
dottedPen.setColor(paintColor);
painter->setPen(dottedPen);
painter->drawLine(X,Y,X,Y+edit->mTextHeight);
}
}
}
painter->setPen(oldPen);
}
if (!edit->mUseCodeFolding)
return;
// Paint collapsed lines using changed pen
if (edit->mCodeFolding.showCollapsedLine) {
painter->setPen(edit->mCodeFolding.collapsedLineColor);
for (int i=0; i< edit->mAllFoldRanges.count();i++) {
PSynEditFoldRange range = edit->mAllFoldRanges[i];
if (range->collapsed && !range->parentCollapsed() &&
(range->fromLine <= vLastLine) && (range->fromLine >= vFirstLine) ) {
// Get starting and end points
Y = (edit->lineToRow(range->fromLine) - edit->mTopLine + 1) * edit->mTextHeight - 1;
painter->drawLine(AClip.left(),Y, AClip.right(),Y);
}
}
}
}
void SynEditTextPainter::GetBraceColorAttr(int level, PSynHighlighterAttribute &attr)
{
if (!edit->mOptions.testFlag(SynEditorOption::eoShowRainbowColor))
return;
if (attr != edit->mHighlighter->symbolAttribute())
return;
PSynHighlighterAttribute oldAttr = attr;
switch(level % 4) {
case 0:
attr = edit->mRainbowAttr0;
break;
case 1:
attr = edit->mRainbowAttr1;
break;
case 2:
attr = edit->mRainbowAttr2;
break;
case 3:
attr = edit->mRainbowAttr3;
break;
}
if (!attr)
attr = oldAttr;
}
void SynEditTextPainter::PaintLines()
{
int cRow; // row index for the loop
int vLine;
QString sLine; // the current line
QString sToken; // highlighter token info
int nTokenColumnsBefore, nTokenColumnLen;
PSynHighlighterAttribute attr;
int vFirstChar;
int vLastChar;
SynEditingAreaList areaList;
PSynEditFoldRange foldRange;
PSynHighlighterAttribute preeditAttr;
int nFold;
QString sFold;
// Initialize rcLine for drawing. Note that Top and Bottom are updated
// inside the loop. Get only the starting point for this.
rcLine = AClip;
rcLine.setBottom((aFirstRow - edit->mTopLine) * edit->mTextHeight);
TokenAccu.Columns = 0;
TokenAccu.ColumnsBefore = 0;
// Now loop through all the lines. The indices are valid for Lines.
BufferCoord selectionBegin = edit->blockBegin();
BufferCoord selectionEnd= edit->blockEnd();
for (cRow = aFirstRow; cRow<=aLastRow; cRow++) {
vLine = edit->rowToLine(cRow);
if (vLine > edit->mDocument->count() && edit->mDocument->count() != 0)
break;
// Get the line.
sLine = edit->mDocument->getString(vLine - 1);
// determine whether will be painted with ActiveLineColor
if (edit->mActiveSelectionMode == SynSelectionMode::Column) {
bCurrentLine = (vLine >= selectionBegin.line && vLine <= selectionEnd.line);
} else {
bCurrentLine = (edit->mCaretY == vLine);
}
if (bCurrentLine && !edit->mInputPreeditString.isEmpty()) {
int col = edit->charToColumn(edit->mCaretY,edit->mCaretX);
int ch = edit->columnToChar(vLine,col);
sLine = sLine.left(ch-1) + edit->mInputPreeditString
+ sLine.mid(ch-1);
}
// Initialize the text and background colors, maybe the line should
// use special values for them.
colFG = edit->mForegroundColor;
colBG = colEditorBG();
colSpFG = QColor();
colSpBG = QColor();
bSpecialLine = edit->onGetSpecialLineColors(vLine, colSpFG, colSpBG);
colSelFG = edit->mSelectedForeground;
colSelBG = edit->mSelectedBackground;
edit->onGetEditingAreas(vLine, areaList);
// Removed word wrap support
vFirstChar = FirstCol;
vLastChar = LastCol;
// Get the information about the line selection. Three different parts
// are possible (unselected before, selected, unselected after), only
// unselected or only selected means bComplexLine will be FALSE. Start
// with no selection, compute based on the visible columns.
bComplexLine = false;
nLineSelStart = 0;
nLineSelEnd = 0;
// Does the selection intersect the visible area?
if (bAnySelection && (cRow >= vSelStart.Row) && (cRow <= vSelEnd.Row)) {
// Default to a fully selected line. This is correct for the smLine
// selection mode and a good start for the smNormal mode.
nLineSelStart = FirstCol;
nLineSelEnd = LastCol + 1;
if ((edit->mActiveSelectionMode == SynSelectionMode::Column) ||
((edit->mActiveSelectionMode == SynSelectionMode::Normal) && (cRow == vSelStart.Row)) ) {
int ch = edit->columnToChar(vLine,vSelStart.Column);
ch = edit->charToColumn(vLine,ch);
if (ch > LastCol) {
nLineSelStart = 0;
nLineSelEnd = 0;
} else if (ch > FirstCol) {
nLineSelStart = ch;
bComplexLine = true;
}
}
if ( (edit->mActiveSelectionMode == SynSelectionMode::Column) ||
((edit->mActiveSelectionMode == SynSelectionMode::Normal) && (cRow == vSelEnd.Row)) ) {
int ch = edit->columnToChar(vLine,vSelEnd.Column);
int col = edit->charToColumn(vLine,ch);
if (col<vSelEnd.Column)
col = edit->charToColumn(vLine,ch+1);
if (col < FirstCol) {
nLineSelStart = 0;
nLineSelEnd = 0;
} else if (col < LastCol) {
nLineSelEnd = col;
bComplexLine = true;
}
}
} //endif bAnySelection
// Update the rcLine rect to this line.
// rcLine.setTop(rcLine.bottom());
// rcLine.setBottom(rcLine.bottom()+edit->mTextHeight);
rcLine.setTop((cRow - edit->mTopLine) * edit->mTextHeight);
rcLine.setHeight(edit->mTextHeight);
bLineSelected = (!bComplexLine) && (nLineSelStart > 0);
rcToken = rcLine;
if (!edit->mHighlighter || !edit->mHighlighter->enabled()) {
sToken = sLine;
if (bCurrentLine) {
nTokenColumnLen = edit->stringColumns(sLine,0);
} else {
nTokenColumnLen = edit->mDocument->lineColumns(vLine-1);
}
if (edit->mOptions.testFlag(eoShowSpecialChars) && (!bLineSelected) && (!bSpecialLine) && (nTokenColumnLen < vLastChar)) {
sToken = sToken + SynLineBreakGlyph;
nTokenColumnLen += edit->charColumns(SynLineBreakGlyph);
}
if (bComplexLine) {
setDrawingColors(true);
rcToken.setLeft(std::max(rcLine.left(), ColumnToXValue(nLineSelStart)));
rcToken.setRight(std::min(rcLine.right(), ColumnToXValue(nLineSelEnd)));
PaintToken(sToken, nTokenColumnLen, 0, nLineSelStart, nLineSelEnd,false,edit->font(),edit->fontForNonAscii());
setDrawingColors(false);
rcToken.setLeft(std::max(rcLine.left(), ColumnToXValue(FirstCol)));
rcToken.setRight(std::min(rcLine.right(), ColumnToXValue(nLineSelStart)));
PaintToken(sToken, nTokenColumnLen, 0, FirstCol, nLineSelStart,false,edit->font(),edit->fontForNonAscii());
rcToken.setLeft(std::max(rcLine.left(), ColumnToXValue(nLineSelEnd)));
rcToken.setRight(std::min(rcLine.right(), ColumnToXValue(LastCol)));
PaintToken(sToken, nTokenColumnLen, 0, nLineSelEnd, LastCol,true,edit->font(),edit->fontForNonAscii());
} else {
setDrawingColors(bLineSelected);
PaintToken(sToken, nTokenColumnLen, 0, FirstCol, LastCol,bLineSelected,edit->font(),edit->fontForNonAscii());
}
//Paint editingAreaBorders
if (bCurrentLine && edit->mInputPreeditString.length()>0) {
PSynEditingArea area = std::make_shared<SynEditingArea>();
int col = edit->charToColumn(edit->mCaretY,edit->mCaretX);
int ch = edit->columnToChar(vLine,col);
area->beginX = edit->charToColumn(sLine,ch);
area->endX = edit->charToColumn(sLine,ch + edit->mInputPreeditString.length());
area->type = SynEditingAreaType::eatUnderLine;
area->color = colFG;
areaList.append(area);
PaintEditAreas(areaList);
}
} else {
// Initialize highlighter with line text and range info. It is
// necessary because we probably did not scan to the end of the last
// line - the internal highlighter range might be wrong.
if (vLine == 1) {
edit->mHighlighter->resetState();
} else {
edit->mHighlighter->setState(
edit->mDocument->ranges(vLine-2));
}
edit->mHighlighter->setLine(sLine, vLine - 1);
// Try to concatenate as many tokens as possible to minimize the count
// of ExtTextOut calls necessary. This depends on the selection state
// or the line having special colors. For spaces the foreground color
// is ignored as well.
TokenAccu.Columns = 0;
nTokenColumnsBefore = 0;
// Test first whether anything of this token is visible.
while (!edit->mHighlighter->eol()) {
sToken = edit->mHighlighter->getToken();
// Work-around buggy highlighters which return empty tokens.
if (sToken.isEmpty()) {
edit->mHighlighter->next();
if (edit->mHighlighter->eol())
break;
sToken = edit->mHighlighter->getToken();
// Maybe should also test whether GetTokenPos changed...
if (sToken.isEmpty()) {
qDebug()<<SynEdit::tr("The highlighter seems to be in an infinite loop");
throw BaseError(SynEdit::tr("The highlighter seems to be in an infinite loop"));
}
}
//nTokenColumnsBefore = edit->charToColumn(sLine,edit->mHighlighter->getTokenPos()+1)-1;
nTokenColumnLen = edit->stringColumns(sToken, nTokenColumnsBefore);
if (nTokenColumnsBefore + nTokenColumnLen >= vFirstChar) {
if (nTokenColumnsBefore + nTokenColumnLen >= vLastChar) {
if (nTokenColumnsBefore >= vLastChar)
break; //*** BREAK ***
nTokenColumnLen = vLastChar - nTokenColumnsBefore - 1;
}
// It's at least partially visible. Get the token attributes now.
attr = edit->mHighlighter->getTokenAttribute();
if (sToken == "["
|| sToken == "("
|| sToken == "{"
) {
SynRangeState rangeState = edit->mHighlighter->getRangeState();
GetBraceColorAttr(rangeState.bracketLevel
+rangeState.braceLevel
+rangeState.parenthesisLevel
,attr);
} else if (sToken == "]"
|| sToken == ")"
|| sToken == "}"
){
SynRangeState rangeState = edit->mHighlighter->getRangeState();
GetBraceColorAttr(rangeState.bracketLevel
+rangeState.braceLevel
+rangeState.parenthesisLevel+1,
attr);
}
if (bCurrentLine && edit->mInputPreeditString.length()>0) {
int startPos = edit->mHighlighter->getTokenPos()+1;
int endPos = edit->mHighlighter->getTokenPos() + sToken.length();
//qDebug()<<startPos<<":"<<endPos<<" - "+sToken+" - "<<edit->mCaretX<<":"<<edit->mCaretX+edit->mInputPreeditString.length();
if (!(endPos < edit->mCaretX
|| startPos >= edit->mCaretX+edit->mInputPreeditString.length())) {
if (!preeditAttr) {
preeditAttr = attr;
} else {
attr = preeditAttr;
}
}
}
AddHighlightToken(sToken, nTokenColumnsBefore - (vFirstChar - FirstCol),
nTokenColumnLen, vLine,attr);
}
nTokenColumnsBefore+=nTokenColumnLen;
// Let the highlighter scan the next token.
edit->mHighlighter->next();
}
// Don't assume HL.GetTokenPos is valid after HL.GetEOL == True.
//nTokenColumnsBefore += edit->stringColumns(sToken,nTokenColumnsBefore);
if (edit->mHighlighter->eol() && (nTokenColumnsBefore < vLastChar)) {
int lineColumns = edit->mDocument->lineColumns(vLine-1);
// Draw text that couldn't be parsed by the highlighter, if any.
if (nTokenColumnsBefore < lineColumns) {
if (nTokenColumnsBefore + 1 < vFirstChar)
nTokenColumnsBefore = vFirstChar - 1;
nTokenColumnLen = std::min(lineColumns, vLastChar) - (nTokenColumnsBefore + 1);
if (nTokenColumnLen > 0) {
sToken = edit->substringByColumns(sLine,nTokenColumnsBefore+1,nTokenColumnLen);
AddHighlightToken(sToken, nTokenColumnsBefore - (vFirstChar - FirstCol),
nTokenColumnLen, vLine, PSynHighlighterAttribute());
}
}
// Draw LineBreak glyph.
if (edit->mOptions.testFlag(eoShowSpecialChars) && (!bLineSelected) &&
(!bSpecialLine) && (edit->mDocument->lineColumns(vLine-1) < vLastChar)) {
AddHighlightToken(SynLineBreakGlyph,
edit->mDocument->lineColumns(vLine-1) - (vFirstChar - FirstCol),
edit->charColumns(SynLineBreakGlyph),vLine, edit->mHighlighter->whitespaceAttribute());
}
}
// Paint folding
foldRange = edit->foldStartAtLine(vLine);
if ((foldRange) && foldRange->collapsed) {
sFold = edit->highlighter()->foldString();
nFold = edit->stringColumns(sFold,edit->mDocument->lineColumns(vLine-1));
attr = edit->mHighlighter->symbolAttribute();
GetBraceColorAttr(edit->mHighlighter->getRangeState().braceLevel,attr);
AddHighlightToken(sFold,edit->mDocument->lineColumns(vLine-1) - (vFirstChar - FirstCol)
, nFold, vLine, attr);
}
// Draw anything that's left in the TokenAccu record. Fill to the end
// of the invalid area with the correct colors.
PaintHighlightToken(true);
//Paint editingAreaBorders
foreach (const PSynEditingArea& area, areaList) {
if (bCurrentLine && edit->mInputPreeditString.length()>0) {
if (area->beginX > edit->mCaretX) {
area->beginX+=edit->mInputPreeditString.length();
}
if (area->endX > edit->mCaretX) {
area->endX+=edit->mInputPreeditString.length();
}
}
area->beginX = edit->charToColumn(sLine, area->beginX);
area->endX = edit->charToColumn(sLine,area->endX);
}
if (bCurrentLine && edit->mInputPreeditString.length()>0) {
PSynEditingArea area = std::make_shared<SynEditingArea>();
int col = edit->charToColumn(edit->mCaretY,edit->mCaretX);
int ch = edit->columnToChar(vLine,col);
area->beginX = edit->charToColumn(sLine,ch);
area->endX = edit->charToColumn(sLine,ch + edit->mInputPreeditString.length());
area->type = SynEditingAreaType::eatUnderLine;
if (preeditAttr) {
area->color = preeditAttr->foreground();
} else {
area->color = colFG;
}
areaList.append(area);
}
PaintEditAreas(areaList);
}
// Now paint the right edge if necessary. We do it line by line to reduce
// the flicker. Should not cost very much anyway, compared to the many
// calls to ExtTextOut.
if (bDoRightEdge) {
painter->setPen(edit->mRightEdgeColor);
painter->drawLine(nRightEdge, rcLine.top(),nRightEdge,rcLine.bottom()+1);
}
bCurrentLine = false;
}
}
void SynEditTextPainter::drawMark(PSynEditMark , int &, int )
{
//todo
}