RedPanda-CPP/libs/qsynedit/qsynedit/painter.cpp

1263 lines
51 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 "painter.h"
#include "qsynedit.h"
#include "document.h"
#include "constants.h"
#include "syntaxer/syntaxer.h"
#include <cmath>
#include <QDebug>
namespace QSynedit {
QSynEditPainter::QSynEditPainter(QSynEdit *edit, QPainter *painter, int firstRow, int lastRow, int left, int right):
mEdit{edit},
mPainter{painter},
mFirstRow{firstRow},
mLastRow{lastRow},
mLeft{left},
mRight{right}
{
}
void QSynEditPainter::paintEditingArea(const QRect& clip)
{
mPainter->fillRect(clip, mEdit->mBackgroundColor);
mClip = clip;
mFirstLine = mEdit->rowToLine(mFirstRow);
mLastLine = mEdit->rowToLine(mLastRow);
mIsCurrentLine = 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 (mEdit->mRightEdge > 0) { // column value
nRightEdge = mEdit->textOffset()+ mEdit->mRightEdge * mEdit->mCharWidth; // pixel value
if (nRightEdge >= mClip.left() &&nRightEdge <= mClip.right()) {
bDoRightEdge = true;
}
}
// 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 (mLastLine >= mFirstLine) {
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 (mClip.left() <mEdit->mGutterWidth + 2) {
mRcToken = mClip;
mRcToken.setLeft( std::max(mClip.left(), mEdit->mGutterWidth));
mRcToken.setRight(mEdit->mGutterWidth + 2);
// Paint whole left edge of the text with same color.
// (value of WhiteAttribute can vary in e.g. MultiSyn)
mPainter->fillRect(mRcToken,colEditorBG());
// Adjust the invalid area to not include this area.
mClip.setLeft(mRcToken.right());
}
// If there is anything visible below the last line, then fill this as well.
mRcToken = mClip;
mRcToken.setTop((mLastRow - mEdit->yposToRow(0) + 1) * mEdit->mTextHeight);
if (mRcToken.top() < mRcToken.bottom()) {
mPainter->fillRect(mRcToken,colEditorBG());
// Draw the right edge if necessary.
if (bDoRightEdge) {
QPen pen(mEdit->mRightEdgeColor,1);
mPainter->setPen(pen);
mPainter->drawLine(nRightEdge, mRcToken.top(),nRightEdge, mRcToken.bottom() + 1);
}
}
// This messes with pen colors, so draw after right margin has been drawn
paintFoldAttributes();
}
void QSynEditPainter::paintGutter(const QRect& clip)
{
QRectF rcLine, rcFold;
int x;
mClip = clip;
mPainter->fillRect(mClip,mEdit->mGutter.color());
rcLine=mClip;
if (mEdit->mGutter.showLineNumbers()) {
// prepare the rect initially
rcLine = mClip;
rcLine.setRight( std::max(rcLine.right(), mEdit->mGutterWidth - 2.0));
rcLine.setBottom(rcLine.top());
if (mEdit->mGutter.useFontStyle()) {
mPainter->setFont(mEdit->mGutter.font());
} else {
QFont newFont = mEdit->font();
newFont.setBold(false);
newFont.setItalic(false);
newFont.setStrikeOut(false);
newFont.setUnderline(false);
mPainter->setFont(newFont);
}
QColor textColor;
if (mEdit->mGutter.textColor().isValid()) {
textColor = mEdit->mGutter.textColor();
} else {
textColor = mEdit->mForegroundColor;
}
// draw each line if it is not hidden by a fold
BufferCoord selectionStart = mEdit->blockBegin();
BufferCoord selectionEnd = mEdit->blockEnd();
for (int row = mFirstRow; row <= mLastRow; row++) {
int line = mEdit->rowToLine(row);
if ((line > mEdit->mDocument->count()) && (mEdit->mDocument->count() > 0 ))
break;
if (mEdit->mGutter.activeLineTextColor().isValid()) {
if (
(mEdit->mCaretY==line) ||
(mEdit->mActiveSelectionMode == SelectionMode::Column && line >= selectionStart.line && line <= selectionEnd.line)
)
mPainter->setPen(mEdit->mGutter.activeLineTextColor());
else
mPainter->setPen(textColor);
} else {
mPainter->setPen(textColor);
}
int lineTop = (row - 1) * mEdit->mTextHeight - mEdit->mTopPos;
// next line rect
rcLine.setTop(lineTop);
rcLine.setBottom(rcLine.top() + mEdit->mTextHeight);
QString s = mEdit->mGutter.formatLineNumber(line);
mEdit->onGutterGetText(line,s);
QRectF textRect;
textRect = mPainter->boundingRect(textRect, Qt::AlignLeft,s);
mPainter->drawText(
(mEdit->mGutterWidth - mEdit->mGutter.rightOffset() - 2) - textRect.width(),
rcLine.bottom() - (mEdit->mTextHeight - int(textRect.height())) / 2 - mPainter->fontMetrics().descent(),
s
);
}
}
// Draw the folding lines and squares
if (mEdit->useCodeFolding()) {
int lineWidth = std::max(0.0,std::ceil(mEdit->font().pixelSize() / 15));
for (int row = mLastRow; row>= mFirstRow; row--) {
int line = mEdit->rowToLine(row);
if ((line > mEdit->mDocument->count()) && (mEdit->mDocument->count() != 0))
continue;
// Form a rectangle for the square the user can click on
rcFold.setLeft(mEdit->mGutterWidth - mEdit->mGutter.rightOffset());
rcFold.setTop((row - 1) * mEdit->mTextHeight - mEdit->mTopPos);
rcFold.setRight(rcFold.left() + mEdit->mGutter.rightOffset() - 4);
rcFold.setBottom(rcFold.top() + mEdit->mTextHeight);
mPainter->setPen(QPen(mEdit->mCodeFolding.folderBarLinesColor,lineWidth));
// Need to paint a line?
if (mEdit->foldAroundLine(line)) {
x = rcFold.left() + (rcFold.width() / 2);
mPainter->drawLine(x,rcFold.top(), x, rcFold.bottom());
}
// Need to paint a line end?
if (mEdit->foldEndAtLine(line)) {
x = rcFold.left() + (rcFold.width() / 2);
mPainter->drawLine(x,rcFold.top(), x, rcFold.top() + rcFold.height() / 2);
mPainter->drawLine(x,
rcFold.top() + rcFold.height() / 2,
rcFold.right() - 2 ,
rcFold.top() + rcFold.height() / 2);
}
// Any fold ranges beginning on this line?
PCodeFoldingRange foldRange = mEdit->foldStartAtLine(line);
if (foldRange) {
// Draw the bottom part of a line
if (!foldRange->collapsed) {
x = rcFold.left() + (rcFold.width() / 2);
mPainter->drawLine(x, rcFold.top() + rcFold.height() / 2,
x, rcFold.bottom());
}
// make a square rect
int size = std::min(mEdit->font().pixelSize() * 4 / 5, mEdit->mGutter.rightOffset()) - lineWidth;
float centerX = rcFold.left() + rcFold.width() / 2.0;
float centerY = rcFold.top() + rcFold.height() / 2.0;
float halfSize = size / 2.0;
rcFold.setLeft(centerX - halfSize);
rcFold.setRight(centerX + halfSize);
rcFold.setTop(centerY - halfSize);
rcFold.setBottom(centerY + halfSize);
// Paint the square the user can click on
mPainter->setBrush(mEdit->mGutter.color());
//mPainter->setPen(mEdit->mCodeFolding.folderBarLinesColor);
mPainter->drawRect(rcFold);
// Paint minus sign
mPainter->drawLine(
rcFold.left() + lineWidth * 2 + 1 , centerY,
rcFold.right() - lineWidth * 2 , centerY );
// Paint vertical line of plus sign
if (foldRange->collapsed) {
mPainter->drawLine(centerX, rcFold.top() + lineWidth * 2,
centerX, rcFold.bottom() - lineWidth * 2 );
}
}
}
}
for (int row = mFirstRow; row <= mLastRow; row++) {
int line = mEdit->rowToLine(row);
if ((line > mEdit->mDocument->count()) && (mEdit->mDocument->count() != 0))
break;
mEdit->onGutterPaint(*mPainter,line, 0, (row - mEdit->yposToRow(0)) * mEdit->mTextHeight);
}
}
QColor QSynEditPainter::colEditorBG()
{
if (mEdit->mActiveLineColor.isValid() && mIsCurrentLine) {
return mEdit->mActiveLineColor;
} else {
return mEdit->mBackgroundColor;
}
}
void QSynEditPainter::computeSelectionInfo()
{
BufferCoord vStart{0,0};
BufferCoord vEnd{0,0};
bAnySelection = false;
// Only if selection is visible anyway.
bAnySelection = true;
// Get the *real* start of the selected area.
if (mEdit->mBlockBegin.line < mEdit->mBlockEnd.line) {
vStart = mEdit->mBlockBegin;
vEnd = mEdit->mBlockEnd;
} else if (mEdit->mBlockBegin.line > mEdit->mBlockEnd.line) {
vEnd = mEdit->mBlockBegin;
vStart = mEdit->mBlockEnd;
} else if (mEdit->mBlockBegin.ch != mEdit->mBlockEnd.ch) {
// it is only on this line.
vStart.line = mEdit->mBlockBegin.line;
vEnd.line = vStart.line;
if (mEdit->mBlockBegin.ch < mEdit->mBlockEnd.ch) {
vStart.ch = mEdit->mBlockBegin.ch;
vEnd.ch = mEdit->mBlockEnd.ch;
} else {
vStart.ch = mEdit->mBlockEnd.ch;
vEnd.ch = mEdit->mBlockBegin.ch;
}
} else
bAnySelection = false;
if (mEdit->mInputPreeditString.length()>0) {
if (vStart.line == mEdit->mCaretY && vStart.ch >=mEdit->mCaretX) {
vStart.ch+=mEdit->mInputPreeditString.length();
}
if (vEnd.line == mEdit->mCaretY && vEnd.ch >mEdit->mCaretX) {
vEnd.ch+=mEdit->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 >= mFirstLine) && (vStart.line <= mLastLine);
if (bAnySelection) {
// Transform the selection from text space into screen space
mSelStart = mEdit->bufferToDisplayPos(vStart);
mSelEnd = mEdit->bufferToDisplayPos(vEnd);
if (mEdit->mInputPreeditString.length()
&& vStart.line == mEdit->mCaretY) {
QString sLine = mEdit->lineText().left(mEdit->mCaretX-1)
+ mEdit->mInputPreeditString
+ mEdit->lineText().mid(mEdit->mCaretX-1);
mSelStart.x = mEdit->charToGlyphLeft(mEdit->mCaretY, sLine,vStart.ch);
}
if (mEdit->mInputPreeditString.length()
&& vEnd.line == mEdit->mCaretY) {
QString sLine = mEdit->lineText().left(mEdit->mCaretX-1)
+ mEdit->mInputPreeditString
+ mEdit->lineText().mid(mEdit->mCaretX-1);
mSelEnd.x = mEdit->charToGlyphLeft(mEdit->mCaretY, sLine,vEnd.ch);
}
// In the column selection mode sort the begin and end of the selection,
// this makes the painting code simpler.
if (mEdit->mActiveSelectionMode == SelectionMode::Column && mSelStart.x > mSelEnd.x)
std::swap(mSelStart.x, mSelEnd.x);
}
}
}
void QSynEditPainter::getDrawingColors(bool selected, QColor &foreground, QColor &background)
{
if (selected) {
if (colSelFG.isValid())
foreground = colSelFG;
else
foreground = colFG;
if (colSelBG.isValid())
background = colSelBG;
else
background = colBG;
} else {
foreground = colFG;
background = colBG;
}
}
int QSynEditPainter::fixXValue(int xpos)
{
return mEdit->textOffset() + xpos;
}
void QSynEditPainter::paintLine()
{
QRect rect = mRcLine;
for (int i=0;i<mLineTokenBackgrounds.length();i++) {
rect.setX(mLineTokenBackgrounds[i].left);
rect.setWidth(mLineTokenBackgrounds[i].width);
mPainter->fillRect(rect, mLineTokenBackgrounds[i].background);
}
QFont font;
QFontMetrics fm{font};
int lineHeight = mRcLine.height();
for (int i=0;i<mLineTokens.length();i++) {
if (font!=mLineTokens[i].font) {
font = mLineTokens[i].font;
fm = QFontMetrics{font};
}
int fontHeight = fm.descent() + fm.ascent();
int linePadding = (lineHeight - fontHeight) / 2;
int nY = mRcLine.bottom() - linePadding - fm.descent();
if (font!=mPainter->font())
mPainter->setFont(font);
QPen pen(mLineTokens[i].foreground);
if (pen!=mPainter->pen())
mPainter->setPen(pen);
mPainter->drawText(mLineTokens[i].left,nY, mLineTokens[i].token);
}
mLineTokens.clear();
mLineTokenBackgrounds.clear();
}
void QSynEditPainter::paintToken(
const QString& lineText,
const QList<int> &glyphStartCharList,
const QList<int> &glyphStartPositionList,
int startGlyph,
int endGlyph,
int tokenWidth, int tokenLeft,
int first, int last,
QColor foreground,
QColor background,
const QFont& font, bool showGlyphs)
{
bool startPaint;
int tokenRight = tokenWidth+tokenLeft;
if (last >= first && mRcToken.right() > mRcToken.left()) {
int nX = fixXValue(first);
first -= tokenLeft;
last -= tokenLeft;
QRect rcTokenBack = mRcToken;
TokenBackgroundInfo backInfo;
backInfo.left=rcTokenBack.left();
backInfo.width=rcTokenBack.width();
backInfo.background=background;
mLineTokenBackgrounds.append(backInfo);
//mPainter->fillRect(rcTokenBack,mPainter->brush());
if (first > tokenWidth) {
} else {
int tokenWidth=0;
startPaint = false;
for (int i=startGlyph; i<endGlyph;i++) {
int glyphStart = glyphStartCharList[i];
int glyphLen = calcSegmentInterval(glyphStartCharList,lineText.length(),i);
QString glyph = lineText.mid(glyphStart,glyphLen);
int glyphWidth = calcSegmentInterval(glyphStartPositionList, tokenRight, i);
if (tokenWidth+glyphWidth>first) {
if (!startPaint ) {
nX-= (first - tokenWidth - 1) ;
startPaint = true;
}
}
if (startPaint) {
bool drawed = false;
if (mEdit->mOptions.testFlag(EditorOption::LigatureSupport)) {
bool tryLigature = false;
if (glyph.length()==0) {
} else if (glyph.length()==1 && glyph.front().unicode()<=32){
} else if (mEdit->mOptions.testFlag(EditorOption::ForceMonospace)
&& glyphWidth != mPainter->fontMetrics().horizontalAdvance(glyph)) {
} else {
tryLigature = true;
}
if (tryLigature) {
QString textToPaint = glyph;
while(i+1<endGlyph && tokenWidth + glyphWidth < last) {
int glyphStart = glyphStartCharList[i+1];
int glyphLen = calcSegmentInterval(glyphStartCharList,lineText.length(),i+1);
QString glyph2 = lineText.mid(glyphStart,glyphLen);
// if (!OperatorGlyphs.contains(glyph))
// break;
if ( glyph2.length()<1
||
(glyph2.length()==1
&& glyph2.front().unicode()<=32))
break;
int glyph2Width = calcSegmentInterval(glyphStartPositionList, tokenRight, i+1);
if (mEdit->mOptions.testFlag(EditorOption::ForceMonospace)) {
if (glyph2Width != mPainter->fontMetrics().horizontalAdvance(glyph2)) {
break;
}
}
i++;
glyphWidth += glyph2Width;
textToPaint += glyph2;
}
TokenTextInfo tokenInfo;
tokenInfo.left = nX;
tokenInfo.token = textToPaint;
tokenInfo.foreground=foreground;
tokenInfo.font = font;
mLineTokens.append(tokenInfo);
drawed = true;
}
}
if (!drawed) {
if (glyph.length()>0) {
QString textToPaint = glyph;
int padding=0;
if (showGlyphs) {
switch(glyph.front().unicode()) {
case '\t':
textToPaint=TabGlyph;
padding=(glyphWidth-mPainter->fontMetrics().horizontalAdvance(TabGlyph))/2;
break;
case ' ':
textToPaint=SpaceGlyph;
break;
default:
break;
}
}
if (textToPaint!=" " && textToPaint!="\t") {
TokenTextInfo tokenInfo;
tokenInfo.left = nX+padding;
tokenInfo.token = textToPaint;
tokenInfo.foreground=foreground;
tokenInfo.font = font;
mLineTokens.append(tokenInfo);
}
}
drawed = true;
}
nX += glyphWidth;
}
tokenWidth += glyphWidth;
if (tokenWidth >= last)
break;
}
}
mRcToken.setLeft(mRcToken.right()+1);
}
}
void QSynEditPainter::paintEditAreas(const EditingAreaList &areaList)
{
QRect rc;
int x1,x2;
rc=mRcLine;
rc.setBottom(rc.bottom()-1);
for (const PEditingArea& p:areaList) {
int penWidth;
if (p->type == EditingAreaType::eatWaveUnderLine) {
if (mEdit->font().pixelSize()>=16)
penWidth = mEdit->font().pixelSize() / 16;
else
penWidth = 1;
} else {
if (mEdit->font().pixelSize()>=32)
penWidth = mEdit->font().pixelSize() / 16;
else if (mEdit->font().pixelSize()>=14)
penWidth = 2;
else
penWidth = 1;
}
if (p->beginX > mRight)
continue;
if (p->endX < mLeft)
continue;
if (p->beginX < mLeft)
x1 = mLeft;
else
x1 = p->beginX;
if (p->endX > mRight)
x2 = mRight;
else
x2 = p->endX;
rc.setLeft(fixXValue(x1));
rc.setRight(fixXValue(x2));
QPen pen;
pen.setColor(p->color);
pen.setWidthF(penWidth);
mPainter->setPen(pen);
mPainter->setBrush(Qt::NoBrush);
int lineHeight = rc.height();
int fontHeight = mPainter->fontMetrics().descent() + mPainter->fontMetrics().ascent();
int linePadding = (lineHeight - fontHeight) / 2;
switch(p->type) {
case EditingAreaType::eatRectangleBorder:
rc.setTop(rc.top()+penWidth/2);
rc.setRight(rc.right() + penWidth );
rc.setBottom(rc.bottom()-penWidth/2);
mPainter->drawRect(rc);
break;
case EditingAreaType::eatUnderLine:
mPainter->drawLine(rc.left(),rc.bottom()-linePadding-pen.width(),rc.right(),rc.bottom()-linePadding-pen.width());
break;
case EditingAreaType::eatWaveUnderLine: {
if (linePadding>mPainter->fontMetrics().descent())
linePadding -= mPainter->fontMetrics().descent();
else
linePadding = 0;
int maxOffset = std::min(3*penWidth, mPainter->fontMetrics().descent());
maxOffset = std::max(3, maxOffset);
int offset = maxOffset;
int lastX=rc.left();
int lastY=rc.bottom()-offset-linePadding;
int t = rc.left();
while (t<rc.right()) {
t+=maxOffset;
if (t>=rc.right()) {
int diff = t - rc.right();
offset = (offset==0)?(maxOffset-diff):diff;
t = rc.right();
mPainter->drawLine(lastX,lastY,t,rc.bottom()-offset-linePadding);
} else {
offset = maxOffset - offset;
mPainter->drawLine(lastX,lastY,t,rc.bottom()-offset-linePadding);
}
lastX = t;
lastY = rc.bottom()-offset-linePadding;
}
}
break;
}
}
}
void QSynEditPainter::paintHighlightToken(const QString& lineText,
const QList<int> &glyphStartCharList,
const QList<int> &glyphStartPositionsList,
bool bFillToEOL)
{
bool isComplexToken;
int nC1, nC2, nC1Sel, nC2Sel;
bool bU1, bSel, bU2;
// Compute some helper variables.
nC1 = std::max(mLeft, mTokenAccu.left);
nC2 = std::min(mRight, mTokenAccu.left + mTokenAccu.width);
if (mHasSelectionInLine) {
bU1 = (nC1 < mLineSelStart);
bSel = (nC1 < mLineSelEnd) && (nC2 >= mLineSelStart);
bU2 = (nC2 >= mLineSelEnd);
isComplexToken = bSel && (bU1 || bU2);
} else {
bSel = false;
isComplexToken = false;
bU1 = false;
bU2 = false;
}
// Any token chars accumulated?
if (mTokenAccu.width > 0) {
// Initialize the colors and the font style.
colBG = mTokenAccu.background;
colFG = mTokenAccu.foreground;
if (mIsSpecialLine) {
if (colSpFG.isValid())
colFG = colSpFG;
if (colSpBG.isValid())
colBG = colSpBG;
}
// if (bSpecialLine && mEdit->mOptions.testFlag(eoSpecialLineDefaultFg))
// colFG = TokenAccu.FG;
// Paint the chars
if (isComplexToken) {
// first unselected part of the token
if (bU1) {
QColor foreground,background;
getDrawingColors(false,foreground,background);
mRcToken.setRight(fixXValue(mLineSelStart));
paintToken(
lineText,
glyphStartCharList,
glyphStartPositionsList,
mTokenAccu.startGlyph,
mTokenAccu.endGlyph,
mTokenAccu.width,mTokenAccu.left,nC1,mLineSelStart,
foreground,background,
mTokenAccu.font, mTokenAccu.showSpecialGlyphs);
}
// selected part of the token
QColor foreground,background;
getDrawingColors(true,foreground,background);
nC1Sel = std::max(mLineSelStart, nC1);
nC2Sel = std::min(mLineSelEnd, nC2);
mRcToken.setRight(fixXValue(nC2Sel));
paintToken(
lineText,
glyphStartCharList,
glyphStartPositionsList,
mTokenAccu.startGlyph,
mTokenAccu.endGlyph,
mTokenAccu.width, mTokenAccu.left, nC1Sel, nC2Sel,
foreground,background,
mTokenAccu.font, mTokenAccu.showSpecialGlyphs);
// second unselected part of the token
if (bU2) {
QColor foreground,background;
getDrawingColors(false,foreground,background);
mRcToken.setRight(fixXValue(nC2));
paintToken(
lineText,
glyphStartCharList,
glyphStartPositionsList,
mTokenAccu.startGlyph,
mTokenAccu.endGlyph,
mTokenAccu.width, mTokenAccu.left, mLineSelEnd, nC2,
foreground,background,
mTokenAccu.font, mTokenAccu.showSpecialGlyphs);
}
} else {
QColor foreground,background;
getDrawingColors(bSel,foreground,background);
mRcToken.setRight(fixXValue(nC2));
paintToken(
lineText,
glyphStartCharList,
glyphStartPositionsList,
mTokenAccu.startGlyph,
mTokenAccu.endGlyph,
mTokenAccu.width, mTokenAccu.left, nC1, nC2,
foreground,background,
mTokenAccu.font, mTokenAccu.showSpecialGlyphs);
}
}
// Fill the background to the end of this line if necessary.
if (bFillToEOL && mRcToken.left() < mRcLine.right()) {
if (mIsSpecialLine && colSpBG.isValid())
colBG = colSpBG;
else
colBG = colEditorBG();
QColor foreground,background;
if (mHasSelectionInLine) {
getDrawingColors(mIsLineEndSelected,foreground,background);
mRcToken.setRight(mRcLine.right());
//mPainter->fillRect(mRcToken,mPainter->brush());
} else {
getDrawingColors(false,foreground,background);
mRcToken.setRight(mRcLine.right());
//mPainter->fillRect(mRcToken,mPainter->brush());
}
TokenBackgroundInfo backInfo;
backInfo.left=mRcToken.left();
backInfo.width=mRcToken.width();
backInfo.background=background;
mLineTokenBackgrounds.append(backInfo);
}
if (bFillToEOL) {
paintLine();
}
}
// 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 QSynEditPainter::addHighlightToken(
const QString& lineText,
const QString& token, int tokenLeft,
int line, PTokenAttribute attri, bool showGlyphs,
const QList<int> glyphStartCharList,
int tokenStartChar,
int tokenEndChar,
bool calcGlyphPosition,
QList<int> &glyphStartPositionList,
int &tokenWidth)
{
int tokenRight;
int startGlyph, endGlyph;
if (!calcGlyphPosition) {
tokenRight = std::max(0,tokenLeft);
startGlyph = searchForSegmentIdx(glyphStartCharList,0,lineText.length(),tokenStartChar);
endGlyph = searchForSegmentIdx(glyphStartCharList,0,lineText.length(),tokenEndChar);
for (int i=startGlyph;i<endGlyph;i++) {
int gWidth = calcSegmentInterval(glyphStartPositionList, mCurrentLineWidth, i);
tokenRight += gWidth;
}
tokenWidth = tokenRight-tokenLeft;
if (tokenRight<mLeft) {
return;
}
}
QColor foreground, background;
FontStyles style;
if (attri) {
foreground = attri->foreground();
background = attri->background();
style = attri->styles();
} else {
foreground = colFG;
background = colBG;
style = getFontStyles(mEdit->font());
}
// if (!Background.isValid() || (edit->mActiveLineColor.isValid() && bCurrentLine)) {
// Background = colEditorBG();
// }
mEdit->onPreparePaintHighlightToken(line,mEdit->mSyntaxer->getTokenPos()+1,
token,attri,style,foreground,background);
if (!background.isValid() ) {
background = colEditorBG();
}
if (!foreground.isValid()) {
foreground = mEdit->mForegroundColor;
}
// Do we have to paint the old chars first, or can we just append?
bool bCanAppend = false;
bool bInitFont = (mTokenAccu.width==0);
if (mTokenAccu.width > 0 ) {
// font style must be the same or token is only spaces
if (mTokenAccu.style != style) {
bInitFont = true;
} else {
if (
(showGlyphs == mTokenAccu.showSpecialGlyphs) &&
// background color must be the same and
(mTokenAccu.background == background) &&
// foreground color must be the same or token is only spaces
(mTokenAccu.foreground == foreground)) {
bCanAppend = true;
}
}
// If we can't append it, then we have to paint the old token chars first.
if (!bCanAppend)
paintHighlightToken(lineText, glyphStartCharList, glyphStartPositionList, false);
}
if (bInitFont) {
mTokenAccu.style = style;
mTokenAccu.font = mEdit->font();
mTokenAccu.font.setBold(style & FontStyle::fsBold);
mTokenAccu.font.setItalic(style & FontStyle::fsItalic);
mTokenAccu.font.setStrikeOut(style & FontStyle::fsStrikeOut);
mTokenAccu.font.setUnderline(style & FontStyle::fsUnderline);
}
//calculate width of the token ( and update it's glyph start positions )
if (calcGlyphPosition) {
tokenWidth = mEdit->mDocument->updateGlyphStartPositionList(
lineText,
glyphStartCharList,
tokenStartChar,
tokenEndChar,
QFontMetrics(mTokenAccu.font),
glyphStartPositionList,
tokenLeft,
tokenRight,
startGlyph,
endGlyph);
}
// Only accumulate tokens if it's visible.
if (tokenLeft < mRight && tokenRight>mLeft) {
if (bCanAppend) {
mTokenAccu.width += tokenWidth;
Q_ASSERT(startGlyph == mTokenAccu.endGlyph);
mTokenAccu.endGlyph = endGlyph;
} else {
mTokenAccu.width = tokenWidth;
mTokenAccu.left = tokenLeft;
mTokenAccu.startGlyph = startGlyph;
mTokenAccu.endGlyph = endGlyph;
mTokenAccu.foreground = foreground;
mTokenAccu.background = background;
mTokenAccu.showSpecialGlyphs = showGlyphs;
}
}
}
void QSynEditPainter::paintFoldAttributes()
{
int tabSteps, lineIndent, lastNonBlank;
// 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 (mEdit->mCodeFolding.indentGuides || mEdit->mCodeFolding.fillIndents) {
QColor paintColor;
if (mEdit->mCodeFolding.indentGuidesColor.isValid()) {
paintColor = mEdit->mCodeFolding.indentGuidesColor;
} else {
paintColor = mEdit->palette().color(QPalette::Text);
}
QColor gradientStart = paintColor;
QColor gradientEnd = paintColor;
QPen oldPen = mPainter->pen();
// Now loop through all the lines. The indices are valid for Lines.
for (int row = mFirstRow; row<=mLastRow;row++) {
int vLine = mEdit->rowToLine(row);
if (vLine > mEdit->mDocument->count() && mEdit->mDocument->count() > 0)
break;
int X;
// Set vertical coord
int Y = (row-1) * mEdit->mTextHeight - mEdit->mTopPos; // limit inside clip rect
if (mEdit->mTextHeight % 2 == 1 && vLine % 2 == 0) {
Y++;
}
// Get next nonblank line
lastNonBlank = vLine - 1;
while (lastNonBlank + 1 < mEdit->mDocument->count() && mEdit->mDocument->getLine(lastNonBlank).isEmpty())
lastNonBlank++;
if (lastNonBlank>=mEdit->lineCount())
continue;
lineIndent = mEdit->getLineIndent(mEdit->mDocument->getLine(lastNonBlank));
int braceLevel = mEdit->mDocument->getSyntaxState(lastNonBlank).braceLevel;
int indentLevel = braceLevel ;
tabSteps = 0;
indentLevel = 0;
while (tabSteps < lineIndent) {
X = tabSteps * mEdit->mDocument->spaceWidth() + mEdit->textOffset() - 1;
tabSteps+=mEdit->tabSize();
indentLevel++ ;
if (mEdit->mCodeFolding.indentGuides) {
PTokenAttribute attr = mEdit->mSyntaxer->symbolAttribute();
getBraceColorAttr(indentLevel,attr);
paintColor = attr->foreground();
}
if (mEdit->mCodeFolding.fillIndents) {
PTokenAttribute attr = mEdit->mSyntaxer->symbolAttribute();
getBraceColorAttr(indentLevel,attr);
gradientStart=attr->foreground();
attr = mEdit->mSyntaxer->symbolAttribute();
getBraceColorAttr(indentLevel+1,attr);
gradientStart=attr->foreground();
}
if (mEdit->mCodeFolding.fillIndents) {
int X1;
if (tabSteps>lineIndent)
X1 = lineIndent * mEdit->mDocument->spaceWidth() + mEdit->textOffset() - 1;
else
X1 = tabSteps * mEdit->mDocument->spaceWidth() + mEdit->textOffset() - 1;
gradientStart.setAlpha(20);
gradientEnd.setAlpha(10);
QLinearGradient gradient(X,Y,X1,Y);
gradient.setColorAt(1,gradientStart);
gradient.setColorAt(0,gradientEnd);
mPainter->fillRect(X,Y,(X1-X),mEdit->mTextHeight,gradient);
}
// Move to top of vertical line
if (mEdit->mCodeFolding.indentGuides) {
QPen dottedPen(Qt::PenStyle::DashLine);
dottedPen.setColor(paintColor);
mPainter->setPen(dottedPen);
mPainter->drawLine(X,Y,X,Y+mEdit->mTextHeight);
}
}
}
mPainter->setPen(oldPen);
}
if (!mEdit->useCodeFolding())
return;
// Paint collapsed lines using changed pen
if (mEdit->mCodeFolding.showCollapsedLine) {
mPainter->setPen(mEdit->mCodeFolding.collapsedLineColor);
for (int i=0; i< mEdit->mAllFoldRanges->count();i++) {
PCodeFoldingRange range = (*mEdit->mAllFoldRanges)[i];
if (range->collapsed && !range->parentCollapsed() &&
(range->fromLine <= mLastLine) && (range->fromLine >= mFirstLine) ) {
// Get starting and end points
int Y = (mEdit->lineToRow(range->fromLine) - mEdit->yposToRow(0) + 1) * mEdit->mTextHeight - 1;
mPainter->drawLine(mClip.left(),Y, mClip.right(),Y);
}
}
}
}
void QSynEditPainter::getBraceColorAttr(int level, PTokenAttribute &attr)
{
if (!mEdit->mOptions.testFlag(EditorOption::ShowRainbowColor))
return;
if (attr->tokenType() != TokenType::Operator)
return;
PTokenAttribute oldAttr = attr;
switch(level % 4) {
case 0:
attr = mEdit->mRainbowAttr0;
break;
case 1:
attr = mEdit->mRainbowAttr1;
break;
case 2:
attr = mEdit->mRainbowAttr2;
break;
case 3:
attr = mEdit->mRainbowAttr3;
break;
}
if (!attr)
attr = oldAttr;
}
void QSynEditPainter::paintLines()
{
mEdit->mDocument->beginSetLinesWidth();
auto action = finally([this](){
mEdit->mDocument->endSetLinesWidth();
});
QString sLine; // the current line
QString sToken; // token info
int tokenLeft, tokenWidth;
PTokenAttribute attr;
EditingAreaList areaList;
PCodeFoldingRange foldRange;
PTokenAttribute preeditAttr;
// Initialize rcLine for drawing. Note that Top and Bottom are updated
// inside the loop. Get only the starting point for this.
mRcLine = mClip;
mRcLine.setBottom((mFirstRow - 1) * mEdit->mTextHeight - mEdit->mTopPos);
mTokenAccu.width = 0;
mTokenAccu.left = 0;
mTokenAccu.style = FontStyle::fsNone;
// Now loop through all the lines. The indices are valid for Lines.
BufferCoord selectionBegin = mEdit->blockBegin();
BufferCoord selectionEnd= mEdit->blockEnd();
for (int row = mFirstRow; row<=mLastRow; row++) {
int vLine = mEdit->rowToLine(row);
bool lineTextChanged = false;
if (vLine > mEdit->mDocument->count() && mEdit->mDocument->count() != 0)
break;
// Get the line.
sLine = mEdit->lineText(vLine);
// determine whether will be painted with ActiveLineColor
if (mEdit->mActiveSelectionMode == SelectionMode::Column) {
mIsCurrentLine = (vLine >= selectionBegin.line && vLine <= selectionEnd.line);
} else {
mIsCurrentLine = (mEdit->mCaretY == vLine);
}
if (mIsCurrentLine && !mEdit->mInputPreeditString.isEmpty()) {
int ch = mEdit->mDocument->charToGlyphStartChar(mEdit->mCaretY-1,mEdit->mCaretX-1);
sLine = sLine.left(ch) + mEdit->mInputPreeditString
+ sLine.mid(ch);
lineTextChanged = true;
}
// Initialize the text and background colors, maybe the line should
// use special values for them.
colFG = mEdit->mForegroundColor;
colBG = colEditorBG();
colSpFG = QColor();
colSpBG = QColor();
mIsSpecialLine = mEdit->onGetSpecialLineColors(vLine, colSpFG, colSpBG);
colSelFG = mEdit->mSelectedForeground;
colSelBG = mEdit->mSelectedBackground;
mEdit->onGetEditingAreas(vLine, areaList);
// 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.
mHasSelectionInLine = false;
mLineSelStart = 0;
mLineSelEnd = 0;
mIsLineEndSelected = false;
// Does the selection intersect the visible area?
if (bAnySelection && (row >= mSelStart.row) && (row <= mSelEnd.row)) {
// Default to a fully selected line. This is correct for the smLine
// selection mode and a good start for the smNormal mode.
mHasSelectionInLine = true;
mLineSelStart = mLeft;
mLineSelEnd = mRight + 1;
if ((mEdit->mActiveSelectionMode == SelectionMode::Column) ||
((mEdit->mActiveSelectionMode == SelectionMode::Normal) && (row == mSelStart.row)) ) {
int xpos = mSelStart.x;
if (xpos > mRight) {
mLineSelStart = 0;
mLineSelEnd = 0;
mHasSelectionInLine = false;
} else if (xpos > mLeft) {
mLineSelStart = xpos;
}
}
if ( (mEdit->mActiveSelectionMode == SelectionMode::Column) ||
((mEdit->mActiveSelectionMode == SelectionMode::Normal) && (row == mSelEnd.row)) ) {
int xpos = mSelEnd.x;
if (xpos < mLeft) {
mLineSelStart = 0;
mLineSelEnd = 0;
mHasSelectionInLine = false;
} else if (xpos < mRight) {
mLineSelEnd = xpos;
}
}
if (mEdit->mActiveSelectionMode == SelectionMode::Normal) {
mIsLineEndSelected = (row>=mSelStart.row && row < mSelEnd.row);
}
} //endif bAnySelection
// Update the rcLine rect to this line.
// rcLine.setTop(rcLine.bottom());
// rcLine.setBottom(rcLine.bottom()+edit->mTextHeight);
mRcLine.setTop((row - 1) * mEdit->mTextHeight - mEdit->mTopPos);
mRcLine.setHeight(mEdit->mTextHeight);
// if (mIsSpecialLine && colSpBG.isValid())
// colBG = colSpBG;
// else
// colBG = colEditorBG();
// setDrawingColors(selToEnd);
// mPainter->fillRect(rcLine,mPainter->brush());
mRcToken = mRcLine;
QList<int> glyphStartCharList;
if (lineTextChanged) {
glyphStartCharList = mEdit->mDocument->getGlyphStartCharList(vLine-1,sLine);
} else {
glyphStartCharList = mEdit->mDocument->getGlyphStartCharList(vLine-1);
}
// Ensure the list has the right number of elements.
// Values in it doesn't matter, we'll recalculate them.
QList<int> glyphStartPositionsList;
bool lineWidthValid = mEdit->mDocument->lineWidthValid(vLine-1);
bool calculateGlyphPositions = ( mHasSelectionInLine || lineTextChanged || !lineWidthValid);
if (calculateGlyphPositions) {
glyphStartPositionsList = glyphStartCharList;
} else {
glyphStartPositionsList = mEdit->mDocument->getGlyphStartPositionList(vLine-1);
mCurrentLineWidth = mEdit->mDocument->getLineWidth(vLine-1);
}
// 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) {
mEdit->mSyntaxer->resetState();
} else {
mEdit->mSyntaxer->setState(
mEdit->mDocument->getSyntaxState(vLine-2));
}
mEdit->mSyntaxer->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.
mTokenAccu.width = 0;
tokenLeft = 0;
// Test first whether anything of this token is visible.
while (!mEdit->mSyntaxer->eol()) {
sToken = mEdit->mSyntaxer->getToken();
if (sToken.isEmpty()) {
continue;
}
int tokenStartChar = mEdit->mSyntaxer->getTokenPos();
int tokenEndChar = tokenStartChar + sToken.length();
// It's at least partially visible. Get the token attributes now.
attr = mEdit->mSyntaxer->getTokenAttribute();
//rainbow parenthesis
if (sToken == "["
|| sToken == "("
|| sToken == "{"
) {
SyntaxState rangeState = mEdit->mSyntaxer->getState();
getBraceColorAttr(rangeState.bracketLevel
+rangeState.braceLevel
+rangeState.parenthesisLevel
,attr);
} else if (sToken == "]"
|| sToken == ")"
|| sToken == "}"
){
SyntaxState rangeState = mEdit->mSyntaxer->getState();
getBraceColorAttr(rangeState.bracketLevel
+rangeState.braceLevel
+rangeState.parenthesisLevel+1,
attr);
}
//input method
if (mIsCurrentLine && mEdit->mInputPreeditString.length()>0) {
int startPos = mEdit->mSyntaxer->getTokenPos()+1;
int endPos = mEdit->mSyntaxer->getTokenPos() + sToken.length();
//qDebug()<<startPos<<":"<<endPos<<" - "+sToken+" - "<<edit->mCaretX<<":"<<edit->mCaretX+edit->mInputPreeditString.length();
if (!(endPos < mEdit->mCaretX
|| startPos >= mEdit->mCaretX+mEdit->mInputPreeditString.length())) {
if (!preeditAttr) {
preeditAttr = attr;
} else {
attr = preeditAttr;
}
}
}
bool showGlyph=false;
if (attr && attr->tokenType() == TokenType::Space) {
int pos = mEdit->mSyntaxer->getTokenPos();
if (pos==0) {
showGlyph = mEdit->mOptions.testFlag(EditorOption::ShowLeadingSpaces);
} else if (pos+sToken.length()==sLine.length()) {
showGlyph = mEdit->mOptions.testFlag(EditorOption::ShowTrailingSpaces);
} else {
showGlyph = mEdit->mOptions.testFlag(EditorOption::ShowInnerSpaces);
}
}
addHighlightToken(
sLine,
sToken,
tokenLeft,
vLine, attr,showGlyph,
glyphStartCharList,
tokenStartChar,
tokenEndChar,
calculateGlyphPositions,
glyphStartPositionsList,
tokenWidth);
tokenLeft+=tokenWidth;
//We don't need to calculate line width,
//So we just quit if already out of the right edge of the editor
if (lineWidthValid && (tokenLeft>mRight))
break;
// Let the highlighter scan the next token.
mEdit->mSyntaxer->next();
}
if (!lineWidthValid)
mEdit->mDocument->setLineWidth(vLine-1, tokenLeft, glyphStartPositionsList);
if (tokenLeft<mRight) {
QString addOnStr;
// Paint folding
foldRange = mEdit->foldStartAtLine(vLine);
if ((foldRange) && foldRange->collapsed) {
addOnStr = mEdit->mSyntaxer->foldString(sLine);
attr = mEdit->mSyntaxer->symbolAttribute();
getBraceColorAttr(mEdit->mSyntaxer->getState().braceLevel,attr);
} else {
// Draw LineBreak glyph.
if (mEdit->mOptions.testFlag(EditorOption::ShowLineBreaks)
&& (mEdit->mDocument->lineWidth(vLine-1) < mRight)) {
addOnStr = LineBreakGlyph;
attr = mEdit->mSyntaxer->whitespaceAttribute();
}
}
if (!addOnStr.isEmpty()) {
expandGlyphStartCharList(addOnStr, sLine.length(), glyphStartCharList);
int len=glyphStartCharList.length()-glyphStartPositionsList.length();
for (int i=0;i<len;i++) {
glyphStartPositionsList.append(tokenLeft);
}
int oldLen = sLine.length();
sLine += addOnStr;
addHighlightToken(
sLine,
addOnStr,
tokenLeft,
vLine, attr, false,
glyphStartCharList,
oldLen,
sLine.length(),
calculateGlyphPositions,
glyphStartPositionsList,
tokenWidth);
tokenLeft += tokenWidth;
}
}
// Draw anything that's left in the TokenAccu record. Fill to the end
// of the invalid area with the correct colors.
paintHighlightToken(sLine, glyphStartCharList, glyphStartPositionsList, true);
//Paint editingAreaBorders
foreach (const PEditingArea& area, areaList) {
if (mIsCurrentLine && mEdit->mInputPreeditString.length()>0) {
if (area->beginX > mEdit->mCaretX) {
area->beginX += mEdit->mInputPreeditString.length();
}
if (area->endX > mEdit->mCaretX) {
area->endX += mEdit->mInputPreeditString.length();
}
}
int glyphIdx;
glyphIdx = searchForSegmentIdx(glyphStartCharList, 0, sLine.length(), area->beginX-1);
area->beginX = segmentIntervalStart(glyphStartPositionsList, 0, tokenLeft, glyphIdx);
glyphIdx = searchForSegmentIdx(glyphStartCharList, 0, sLine.length(), area->endX-1);
area->endX = segmentIntervalStart(glyphStartPositionsList, 0, tokenLeft, glyphIdx);
}
//input method
if (mIsCurrentLine && mEdit->mInputPreeditString.length()>0) {
PEditingArea area = std::make_shared<EditingArea>();
int glyphIdx;
glyphIdx = searchForSegmentIdx(glyphStartCharList, 0, sLine.length(), mEdit->mCaretX-1);
area->beginX = segmentIntervalStart(glyphStartPositionsList, 0, tokenLeft, glyphIdx);
glyphIdx = searchForSegmentIdx(glyphStartCharList, 0, sLine.length(), mEdit->mCaretX+mEdit->mInputPreeditString.length()-1);
area->endX = segmentIntervalStart(glyphStartPositionsList, 0, tokenLeft, glyphIdx);
area->type = EditingAreaType::eatUnderLine;
if (preeditAttr) {
area->color = preeditAttr->foreground();
} else {
area->color = colFG;
}
areaList.append(area);
mEdit->mGlyphPostionCacheForInputMethod.str = sLine;
mEdit->mGlyphPostionCacheForInputMethod.glyphCharList = glyphStartCharList;
mEdit->mGlyphPostionCacheForInputMethod.glyphPositionList = glyphStartPositionsList;
mEdit->mGlyphPostionCacheForInputMethod.strWidth = tokenLeft;
}
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) {
mPainter->setPen(mEdit->mRightEdgeColor);
mPainter->drawLine(nRightEdge, mRcLine.top(),nRightEdge,mRcLine.bottom()+1);
}
mIsCurrentLine = false;
}
}
}