2087 lines
62 KiB
C++
2087 lines
62 KiB
C++
#include "SynEdit.h"
|
|
#include <QApplication>
|
|
#include <QFontMetrics>
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <QScrollBar>
|
|
#include <QPaintEvent>
|
|
#include <QPainter>
|
|
#include <QTimerEvent>
|
|
#include "highlighter/base.h"
|
|
#include "Constants.h"
|
|
#include "TextPainter.h"
|
|
#include <QDebug>
|
|
|
|
SynEdit::SynEdit(QWidget *parent) : QAbstractScrollArea(parent)
|
|
{
|
|
qDebug()<<"init SynEdit:";
|
|
mPaintLock = 0;
|
|
mPainterLock = 0;
|
|
mPainting = false;
|
|
mLines = std::make_shared<SynEditStringList>(this);
|
|
qDebug()<<"init SynEdit: 1";
|
|
mOrigLines = mLines;
|
|
//fPlugins := TList.Create;
|
|
mMouseMoved = false;
|
|
mUndoing = false;
|
|
mLines->connect(mLines.get(), &SynEditStringList::changed, this, &SynEdit::linesChanged);
|
|
mLines->connect(mLines.get(), &SynEditStringList::changing, this, &SynEdit::linesChanging);
|
|
mLines->connect(mLines.get(), &SynEditStringList::cleared, this, &SynEdit::linesCleared);
|
|
mLines->connect(mLines.get(), &SynEditStringList::deleted, this, &SynEdit::linesDeleted);
|
|
mLines->connect(mLines.get(), &SynEditStringList::inserted, this, &SynEdit::linesInserted);
|
|
mLines->connect(mLines.get(), &SynEditStringList::putted, this, &SynEdit::linesPutted);
|
|
qDebug()<<"init SynEdit: 2";
|
|
|
|
#ifdef Q_OS_WIN
|
|
mFontDummy = QFont("Consolas",10);
|
|
#elif Q_OS_LINUX
|
|
mFontDummy = QFont("terminal",14);
|
|
#else
|
|
#error "Not supported!"
|
|
#endif
|
|
setFont(mFontDummy);
|
|
qDebug()<<"init SynEdit:3";
|
|
|
|
mUndoList = std::make_shared<SynEditUndoList>();
|
|
mUndoList->connect(mUndoList.get(), &SynEditUndoList::addedUndo, this, &SynEdit::undoAdded);
|
|
mOrigUndoList = mUndoList;
|
|
mRedoList = std::make_shared<SynEditUndoList>();
|
|
mRedoList->connect(mRedoList.get(), &SynEditUndoList::addedUndo, this, &SynEdit::redoAdded);
|
|
mOrigRedoList = mRedoList;
|
|
qDebug()<<"init SynEdit: 4";
|
|
|
|
//DoubleBuffered = false;
|
|
mActiveLineColor = QColor();
|
|
mSelectedBackground = QColor();
|
|
mSelectedForeground = QColor();
|
|
|
|
mBookMarkOpt.connect(&mBookMarkOpt, &SynBookMarkOpt::changed, this, &SynEdit::bookMarkOptionsChanged);
|
|
// fRightEdge has to be set before FontChanged is called for the first time
|
|
mRightEdge = 80;
|
|
qDebug()<<"init SynEdit: 5";
|
|
|
|
mGutter.setRightOffset(21);
|
|
mGutter.connect(&mGutter, &SynGutter::changed, this, &SynEdit::gutterChanged);
|
|
mGutterWidth = mGutter.width();
|
|
mTextOffset = mGutterWidth + 2;
|
|
//ControlStyle := ControlStyle + [csOpaque, csSetCaption, csNeedsBorderPaint];
|
|
//Height := 150;
|
|
//Width := 200;
|
|
this->setCursor(Qt::CursorShape::IBeamCursor);
|
|
//TabStop := True;
|
|
mInserting = true;
|
|
mMaxScrollWidth = 1024;
|
|
mScrollBars = SynScrollStyle::ssBoth;
|
|
mExtraLineSpacing = 0;
|
|
qDebug()<<"init SynEdit: 6";
|
|
|
|
this->setFrameShape(QFrame::Panel);
|
|
this->setFrameShadow(QFrame::Sunken);
|
|
this->setLineWidth(1);
|
|
mInsertCaret = SynEditCaretType::ctVerticalLine;
|
|
mOverwriteCaret = SynEditCaretType::ctBlock;
|
|
mSelectionMode = SynSelectionMode::smNormal;
|
|
mActiveSelectionMode = SynSelectionMode::smNormal;
|
|
qDebug()<<"init SynEdit: 7";
|
|
|
|
//stop qt to auto fill background
|
|
setAutoFillBackground(false);
|
|
//fFocusList := TList.Create;
|
|
//fKbdHandler := TSynEditKbdHandler.Create;
|
|
//fMarkList.OnChange := MarkListChange;
|
|
qDebug()<<"init SynEdit: 7-1";
|
|
setDefaultKeystrokes();
|
|
qDebug()<<"init SynEdit: 7-2";
|
|
mRightEdgeColor = QColorConstants::Svg::silver;
|
|
qDebug()<<"init SynEdit: 8";
|
|
|
|
/* IME input */
|
|
mImeCount = 0;
|
|
mMBCSStepAside = false;
|
|
/* end of IME input */
|
|
mWantReturns = true;
|
|
mWantTabs = false;
|
|
mTabWidth = 4;
|
|
mLeftChar = 1;
|
|
mTopLine = 1;
|
|
mCaretX = 1;
|
|
mLastCaretX = 1;
|
|
mCaretY = 1;
|
|
mBlockBegin.Char = 1;
|
|
mBlockBegin.Line = 1;
|
|
mBlockEnd = mBlockBegin;
|
|
mOptions = eoAutoIndent | eoDragDropEditing | eoEnhanceEndKey |
|
|
eoScrollPastEol | eoShowScrollHint | eoSmartTabs | eoTabsToSpaces |
|
|
eoSmartTabDelete| eoGroupUndo;
|
|
qDebug()<<"init SynEdit: 9";
|
|
|
|
mScrollTimer = new QTimer(this);
|
|
mScrollTimer->setInterval(100);
|
|
connect(mScrollTimer, &QTimer::timeout,this, &SynEdit::scrollTimerHandler);
|
|
|
|
mScrollHintColor = QColorConstants::Yellow;
|
|
mScrollHintFormat = SynScrollHintFormat::shfTopLineOnly;
|
|
|
|
mContentImage = std::make_shared<QImage>(clientWidth(),clientHeight(),QImage::Format_ARGB32);
|
|
|
|
m_blinkTimerId = 0;
|
|
m_bliknStatus = 0;
|
|
qDebug()<<"init SynEdit: 10";
|
|
|
|
synFontChanged();
|
|
qDebug()<<"init SynEdit: done";
|
|
|
|
}
|
|
|
|
int SynEdit::displayLineCount()
|
|
{
|
|
if (mLines->empty()) {
|
|
return 0;
|
|
}
|
|
return lineToRow(mLines->count());
|
|
}
|
|
|
|
DisplayCoord SynEdit::displayXY()
|
|
{
|
|
qDebug()<<"displayXY"<<caretXY().Char<<caretXY().Line;
|
|
return bufferToDisplayPos(caretXY());
|
|
}
|
|
|
|
int SynEdit::displayX()
|
|
{
|
|
return displayXY().Column;
|
|
}
|
|
|
|
int SynEdit::displayY()
|
|
{
|
|
return displayXY().Row;
|
|
}
|
|
|
|
BufferCoord SynEdit::caretXY()
|
|
{
|
|
BufferCoord result;
|
|
result.Char = caretX();
|
|
result.Line = caretY();
|
|
return result;
|
|
}
|
|
|
|
int SynEdit::caretX()
|
|
{
|
|
return mCaretX;
|
|
}
|
|
|
|
int SynEdit::caretY()
|
|
{
|
|
return mCaretY;
|
|
}
|
|
|
|
void SynEdit::setCaretX(int value)
|
|
{
|
|
setCaretXY({value,mCaretY});
|
|
}
|
|
|
|
void SynEdit::setCaretY(int value)
|
|
{
|
|
setCaretXY({mCaretX,value});
|
|
}
|
|
|
|
void SynEdit::setCaretXY(const BufferCoord &value)
|
|
{
|
|
setCaretXYCentered(false,value);
|
|
}
|
|
|
|
void SynEdit::setCaretXYEx(bool CallEnsureCursorPos, BufferCoord value)
|
|
{
|
|
bool vTriggerPaint=true; //how to test it?
|
|
|
|
if (vTriggerPaint)
|
|
doOnPaintTransient(SynTransientType::ttBefore);
|
|
int nMaxX = mMaxScrollWidth + 1;
|
|
if (value.Line > mLines->count())
|
|
value.Line = mLines->count();
|
|
if (value.Line < 1) {
|
|
// this is just to make sure if Lines stringlist should be empty
|
|
value.Line = 1;
|
|
if (!mOptions.testFlag(SynEditorOption::eoScrollPastEol)) {
|
|
nMaxX = 1;
|
|
}
|
|
} else {
|
|
if (!mOptions.testFlag(SynEditorOption::eoScrollPastEol))
|
|
nMaxX = mLines->getString(value.Line-1).length();
|
|
}
|
|
if ((value.Char > nMaxX) && (! (mOptions.testFlag(SynEditorOption::eoScrollPastEol)) ||
|
|
!(mOptions.testFlag(SynEditorOption::eoAutoSizeMaxScrollWidth))) )
|
|
value.Char = nMaxX;
|
|
if (value.Char < 1)
|
|
value.Char = 1;
|
|
if ((value.Char != mCaretX) || (value.Line != mCaretY)) {
|
|
incPaintLock();
|
|
auto action = finally([this]{
|
|
decPaintLock();
|
|
});
|
|
// simply include the flags, fPaintLock is > 0
|
|
if (mCaretX != value.Char) {
|
|
mCaretX = value.Char;
|
|
mStatusChanges.setFlag(SynStatusChange::scCaretX);
|
|
}
|
|
if (mCaretY != value.Line) {
|
|
if (!mActiveLineColor.isValid()) {
|
|
invalidateLine(value.Line);
|
|
invalidateLine(mCaretY);
|
|
}
|
|
mCaretY = value.Line;
|
|
mStatusChanges.setFlag(SynStatusChange::scCaretY);
|
|
}
|
|
// Call UpdateLastCaretX before DecPaintLock because the event handler it
|
|
// calls could raise an exception, and we don't want fLastCaretX to be
|
|
// left in an undefined state if that happens.
|
|
updateLastCaretX();
|
|
if (CallEnsureCursorPos)
|
|
ensureCursorPosVisible();
|
|
mStateFlags.setFlag(SynStateFlag::sfCaretChanged);
|
|
mStateFlags.setFlag(SynStateFlag::sfScrollbarChanged);
|
|
} else {
|
|
// Also call UpdateLastCaretX if the caret didn't move. Apps don't know
|
|
// anything about fLastCaretX and they shouldn't need to. So, to avoid any
|
|
// unwanted surprises, always update fLastCaretX whenever CaretXY is
|
|
// assigned to.
|
|
// Note to SynEdit developers: If this is undesirable in some obscure
|
|
// case, just save the value of fLastCaretX before assigning to CaretXY and
|
|
// restore it afterward as appropriate.
|
|
updateLastCaretX();
|
|
}
|
|
if (vTriggerPaint)
|
|
doOnPaintTransient(SynTransientType::ttAfter);
|
|
|
|
}
|
|
|
|
void SynEdit::setCaretXYCentered(bool ForceToMiddle, const BufferCoord &value)
|
|
{
|
|
incPaintLock();
|
|
auto action = finally([this] {
|
|
decPaintLock();
|
|
});
|
|
mStatusChanges.setFlag(SynStatusChange::scSelection);
|
|
setCaretXYEx(ForceToMiddle,value);
|
|
if (selAvail())
|
|
invalidateSelection();
|
|
mBlockBegin.Char = mCaretX;
|
|
mBlockBegin.Line = mCaretY;
|
|
mBlockEnd = mBlockBegin;
|
|
if (ForceToMiddle)
|
|
ensureCursorPosVisibleEx(true); // but here after block has been set
|
|
|
|
}
|
|
|
|
void SynEdit::invalidateGutter()
|
|
{
|
|
invalidateGutterLines(-1, -1);
|
|
}
|
|
|
|
void SynEdit::invalidateGutterLine(int aLine)
|
|
{
|
|
if ((aLine < 1) || (aLine > mLines->count()))
|
|
return;
|
|
|
|
invalidateGutterLines(aLine, aLine);
|
|
}
|
|
|
|
void SynEdit::invalidateGutterLines(int FirstLine, int LastLine)
|
|
{
|
|
QRect rcInval;
|
|
if (!isVisible())
|
|
return;
|
|
if (FirstLine == -1 && LastLine == -1) {
|
|
rcInval = QRect(0, 0, mGutterWidth, clientHeight());
|
|
if (mStateFlags.testFlag(SynStateFlag::sfLinesChanging))
|
|
mInvalidateRect = mInvalidateRect.united(rcInval);
|
|
else
|
|
invalidateRect(rcInval);
|
|
} else {
|
|
// find the visible lines first
|
|
if (LastLine < FirstLine)
|
|
std::swap(LastLine, FirstLine);
|
|
if (mUseCodeFolding) {
|
|
FirstLine = lineToRow(FirstLine);
|
|
if (LastLine <= mLines->count())
|
|
LastLine = lineToRow(LastLine);
|
|
else
|
|
LastLine = INT_MAX;
|
|
}
|
|
FirstLine = std::max(FirstLine, mTopLine);
|
|
LastLine = std::min(LastLine, mTopLine + mLinesInWindow);
|
|
// any line visible?
|
|
if (LastLine >= FirstLine) {
|
|
rcInval = {0, mTextHeight * (FirstLine - mTopLine),
|
|
mGutterWidth, mTextHeight * (LastLine - mTopLine + 1)};
|
|
if (mStateFlags.testFlag(SynStateFlag::sfLinesChanging)) {
|
|
mInvalidateRect = mInvalidateRect.united(rcInval);
|
|
} else {
|
|
invalidateRect(rcInval);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Convert point on the edit (x,y) to (row,column)
|
|
* @param aX
|
|
* @param aY
|
|
* @return
|
|
*/
|
|
DisplayCoord SynEdit::pixelsToNearestRowColumn(int aX, int aY)
|
|
{
|
|
// Result is in display coordinates
|
|
float f;
|
|
f = (aX - mGutterWidth - 2.0) / mCharWidth;
|
|
// don't return a partially visible last line
|
|
if (aY >= mLinesInWindow * mTextHeight) {
|
|
aY = mLinesInWindow * mTextHeight - 1;
|
|
if (aY < 0)
|
|
aY = 0;
|
|
}
|
|
return {
|
|
.Column = std::max(1, (int)(leftChar() + round(f))),
|
|
.Row = std::max(1, mTopLine + (aY / mTextHeight))
|
|
};
|
|
}
|
|
|
|
DisplayCoord SynEdit::pixelsToRowColumn(int aX, int aY)
|
|
{
|
|
return {
|
|
.Column = std::max(1, mLeftChar + ((aX - mGutterWidth - 2) / mCharWidth)),
|
|
.Row = std::max(1, mTopLine + (aY / mTextHeight))
|
|
};
|
|
}
|
|
|
|
QPoint SynEdit::RowColumnToPixels(const DisplayCoord &coord)
|
|
{
|
|
QPoint result;
|
|
result.setX((coord.Column - 1) * mCharWidth + mTextOffset);
|
|
result.setY((coord.Row - mTopLine) * mTextHeight);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* @brief takes a position in the text and transforms it into
|
|
* the row and column it appears to be on the screen
|
|
* @param p
|
|
* @return
|
|
*/
|
|
DisplayCoord SynEdit::bufferToDisplayPos(const BufferCoord &p)
|
|
{
|
|
qDebug()<<"bufferTodisplayPos"<<p.Char<<p.Line;
|
|
DisplayCoord result {p.Char,p.Line};
|
|
// Account for tabs and charColumns
|
|
result.Column = charToColumn(p.Line,p.Char);
|
|
// Account for code folding
|
|
if (mUseCodeFolding)
|
|
result.Row = foldLineToRow(result.Row);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* @brief takes a position on screen and transfrom it into position of text
|
|
* @param p
|
|
* @return
|
|
*/
|
|
BufferCoord SynEdit::displayToBufferPos(const DisplayCoord &p)
|
|
{
|
|
BufferCoord Result{p.Column,p.Row};
|
|
// Account for code folding
|
|
if (mUseCodeFolding)
|
|
Result.Line = foldRowToLine(Result.Line);
|
|
// Account for tabs
|
|
if (Result.Line <= mLines->count() ) {
|
|
QString s = mLines->getString(Result.Line - 1);
|
|
int l = s.length();
|
|
int x = 0;
|
|
int i = 0;
|
|
|
|
while (x < p.Column) {
|
|
if (i < l && s[i] == '\t')
|
|
x += mTabWidth - (x % mTabWidth);
|
|
else
|
|
x += charColumns(s[i]);
|
|
i++;
|
|
}
|
|
Result.Char = i;
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
int SynEdit::charToColumn(int aLine, int aChar)
|
|
{
|
|
if (aLine <= mLines->count()) {
|
|
QString s = mLines->getString(aLine - 1);
|
|
int l = s.length();
|
|
int x = 0;
|
|
for (int i=0;i<aChar-1;i++) {
|
|
if (i<=l && s[i] == '\t')
|
|
x+=mTabWidth - (x % mTabWidth);
|
|
else
|
|
x+=charColumns(s[i]);
|
|
}
|
|
return x+1;
|
|
}
|
|
qDebug()<<"Line outof range"<<aLine<<aChar;
|
|
throw BaseError(SynEdit::tr("Line %1 is out of range").arg(aLine));
|
|
}
|
|
|
|
int SynEdit::stringColumns(const QString &line, int colsBefore)
|
|
{
|
|
int columns = colsBefore;
|
|
int charCols;
|
|
for (int i=0;i<line.length();i++) {
|
|
QChar ch = line[i];
|
|
if (ch == '\t') {
|
|
charCols = mTabWidth - columns % mTabWidth;
|
|
} else {
|
|
charCols = charColumns(ch);
|
|
}
|
|
columns+=charCols;
|
|
}
|
|
return columns-colsBefore;
|
|
}
|
|
|
|
int SynEdit::getLineIndent(const QString &line)
|
|
{
|
|
int indents = 0;
|
|
for (QChar ch:line) {
|
|
switch(ch.digitValue()) {
|
|
case '\t':
|
|
indents+=mTabWidth;
|
|
break;
|
|
case ' ':
|
|
indents+=1;
|
|
break;
|
|
default:
|
|
return indents;
|
|
}
|
|
}
|
|
return indents;
|
|
}
|
|
|
|
int SynEdit::rowToLine(int aRow)
|
|
{
|
|
return displayToBufferPos({1, aRow}).Line;
|
|
}
|
|
|
|
int SynEdit::lineToRow(int aLine)
|
|
{
|
|
qDebug()<<"line to row"<<aLine;
|
|
return bufferToDisplayPos({1, aLine}).Row;
|
|
}
|
|
|
|
int SynEdit::foldRowToLine(int Row)
|
|
{
|
|
int result = Row;
|
|
for (int i=0;i<mAllFoldRanges.count();i++) {
|
|
PSynEditFoldRange range = mAllFoldRanges[i];
|
|
if (range->collapsed && !range->parentCollapsed() && range->fromLine < result) {
|
|
result += range->linesCollapsed;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int SynEdit::foldLineToRow(int Line)
|
|
{
|
|
int result = Line;
|
|
for (int i=mAllFoldRanges.count()-1;i>=0;i--) {
|
|
PSynEditFoldRange range =mAllFoldRanges[i];
|
|
if (range->collapsed && !range->parentCollapsed()) {
|
|
// Line is found after fold
|
|
if (range->toLine < Line)
|
|
result -= range->linesCollapsed;
|
|
// Inside fold
|
|
else if (range->fromLine < Line && Line <= range->toLine)
|
|
result -= Line - range->fromLine;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void SynEdit::setDefaultKeystrokes()
|
|
{
|
|
mKeyStrokes.resetDefaults();
|
|
}
|
|
|
|
void SynEdit::invalidateLine(int Line)
|
|
{
|
|
QRect rcInval;
|
|
if (mPainterLock >0)
|
|
return;
|
|
if (Line<1 || Line>mLines->count() || !isVisible())
|
|
return;
|
|
|
|
// invalidate text area of this line
|
|
if (mUseCodeFolding)
|
|
Line = foldLineToRow(Line);
|
|
if (Line >= mTopLine && Line <= mTopLine + mLinesInWindow) {
|
|
rcInval = { clientLeft() + mGutterWidth,
|
|
clientTop() + mTextHeight * (Line - mTopLine),
|
|
clientWidth(),
|
|
mTextHeight};
|
|
if (mStateFlags.testFlag(SynStateFlag::sfLinesChanging))
|
|
mInvalidateRect = mInvalidateRect.united(rcInval);
|
|
else
|
|
invalidateRect(rcInval);
|
|
}
|
|
}
|
|
|
|
void SynEdit::invalidateLines(int FirstLine, int LastLine)
|
|
{
|
|
if (mPainterLock>0)
|
|
return;
|
|
|
|
if (!isVisible())
|
|
return;
|
|
if (FirstLine == -1 && LastLine == -1) {
|
|
QRect rcInval = clientRect();
|
|
rcInval.setLeft(rcInval.left()+mGutterWidth);
|
|
if (mStateFlags.testFlag(SynStateFlag::sfLinesChanging)) {
|
|
mInvalidateRect = mInvalidateRect.united(rcInval);
|
|
} else {
|
|
invalidateRect(rcInval);
|
|
}
|
|
} else {
|
|
FirstLine = std::max(FirstLine, 1);
|
|
LastLine = std::max(LastLine, 1);
|
|
// find the visible lines first
|
|
if (LastLine < FirstLine)
|
|
std::swap(LastLine, FirstLine);
|
|
|
|
if (LastLine >= mLines->count())
|
|
LastLine = INT_MAX; // paint empty space beyond last line
|
|
|
|
if (mUseCodeFolding) {
|
|
FirstLine = lineToRow(FirstLine);
|
|
// Could avoid this conversion if (First = Last) and
|
|
// (Length < CharsInWindow) but the dependency isn't worth IMO.
|
|
if (LastLine < mLines->count())
|
|
LastLine = lineToRow(LastLine + 1) - 1;
|
|
}
|
|
|
|
// mTopLine is in display coordinates, so FirstLine and LastLine must be
|
|
// converted previously.
|
|
FirstLine = std::max(FirstLine, mTopLine);
|
|
LastLine = std::min(LastLine, mTopLine + mLinesInWindow);
|
|
|
|
// any line visible?
|
|
if (LastLine >= FirstLine) {
|
|
QRect rcInval = {
|
|
clientLeft()+mGutterWidth,
|
|
mTextHeight * (FirstLine - mTopLine),
|
|
clientWidth(), mTextHeight * (LastLine - mTopLine + 1)
|
|
};
|
|
if (mStateFlags.testFlag(SynStateFlag::sfLinesChanging))
|
|
mInvalidateRect = mInvalidateRect.united(rcInval);
|
|
else
|
|
invalidateRect(rcInval);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SynEdit::invalidateSelection()
|
|
{
|
|
if (mPainterLock>0)
|
|
return;
|
|
invalidateLines(mBlockBegin.Line, mBlockEnd.Line);
|
|
}
|
|
|
|
void SynEdit::invalidateRect(const QRect &rect)
|
|
{
|
|
if (mPainterLock>0)
|
|
return;
|
|
viewport()->update(rect);
|
|
}
|
|
|
|
void SynEdit::invalidate()
|
|
{
|
|
if (mPainterLock>0)
|
|
return;
|
|
viewport()->update();
|
|
}
|
|
|
|
void SynEdit::lockPainter()
|
|
{
|
|
mPainterLock++;
|
|
}
|
|
|
|
void SynEdit::unlockPainter()
|
|
{
|
|
Q_ASSERT(mPainterLock>0);
|
|
mPainterLock--;
|
|
}
|
|
|
|
bool SynEdit::selAvail()
|
|
{
|
|
return (mBlockBegin.Char != mBlockEnd.Char) ||
|
|
((mBlockBegin.Line != mBlockEnd.Line) && (mActiveSelectionMode != SynSelectionMode::smColumn));
|
|
}
|
|
|
|
void SynEdit::setCaretAndSelection(const BufferCoord &ptCaret, const BufferCoord &ptBefore, const BufferCoord &ptAfter)
|
|
{
|
|
SynSelectionMode vOldMode = mActiveSelectionMode;
|
|
incPaintLock();
|
|
auto action = finally([this,vOldMode]{
|
|
mActiveSelectionMode = vOldMode;
|
|
decPaintLock();
|
|
});
|
|
internalSetCaretXY(ptCaret);
|
|
setBlockBegin(ptBefore);
|
|
setBlockEnd(ptAfter);
|
|
}
|
|
|
|
void SynEdit::clearUndo()
|
|
{
|
|
mUndoList->Clear();
|
|
mRedoList->Clear();
|
|
}
|
|
|
|
int SynEdit::charColumns(QChar ch)
|
|
{
|
|
if (ch == ' ')
|
|
return 1;
|
|
return std::ceil((int)(fontMetrics().horizontalAdvance(ch) * dpiFactor()) / (double)mCharWidth);
|
|
}
|
|
|
|
double SynEdit::dpiFactor()
|
|
{
|
|
return fontMetrics().fontDpi() / 96.0;
|
|
}
|
|
|
|
void SynEdit::showCaret()
|
|
{
|
|
m_blinkTimerId = startTimer(100);
|
|
}
|
|
|
|
void SynEdit::hideCaret()
|
|
{
|
|
if (m_blinkTimerId!=0) {
|
|
killTimer(m_blinkTimerId);
|
|
m_blinkTimerId = 0;
|
|
}
|
|
}
|
|
|
|
void SynEdit::clearAreaList(SynEditingAreaList areaList)
|
|
{
|
|
areaList.clear();
|
|
}
|
|
|
|
void SynEdit::computeCaret(int X, int Y)
|
|
{
|
|
DisplayCoord vCaretNearestPos = pixelsToNearestRowColumn(X, Y);
|
|
vCaretNearestPos.Row = MinMax(vCaretNearestPos.Row, 1, displayLineCount());
|
|
setInternalDisplayXY(vCaretNearestPos);
|
|
}
|
|
|
|
void SynEdit::computeScroll(int X, int Y)
|
|
{
|
|
QRect iScrollBounds; // relative to the client area
|
|
// don't scroll if dragging text from other control
|
|
// if (not MouseCapture) and (not Dragging) then begin
|
|
// fScrollTimer.Enabled := False;
|
|
// Exit;
|
|
// end;
|
|
|
|
iScrollBounds = QRect(mGutterWidth+this->frameWidth(), this->frameWidth(), mCharsInWindow * mCharWidth,
|
|
mLinesInWindow * mTextHeight);
|
|
|
|
if (X < iScrollBounds.left())
|
|
mScrollDeltaX = (X - iScrollBounds.left()) / mCharWidth - 1;
|
|
else if (X >= iScrollBounds.right())
|
|
mScrollDeltaX = (X - iScrollBounds.right()) / mCharWidth + 1;
|
|
else
|
|
mScrollDeltaX = 0;
|
|
|
|
if (Y < iScrollBounds.top())
|
|
mScrollDeltaY = (Y - iScrollBounds.top()) / mTextHeight - 1;
|
|
else if (Y >= iScrollBounds.bottom())
|
|
mScrollDeltaY = (Y - iScrollBounds.bottom()) / mTextHeight + 1;
|
|
else
|
|
mScrollDeltaY = 0;
|
|
|
|
if (mScrollDeltaX!=0 || mScrollDeltaY!=0)
|
|
mScrollTimer->start();
|
|
}
|
|
|
|
void SynEdit::doBlockIndent()
|
|
{
|
|
BufferCoord OrgCaretPos;
|
|
BufferCoord BB, BE;
|
|
QString StrToInsert;
|
|
int Run;
|
|
int e,x,i,InsertStrLen;
|
|
QString Spaces;
|
|
SynSelectionMode OrgSelectionMode;
|
|
BufferCoord InsertionPos;
|
|
|
|
OrgSelectionMode = mActiveSelectionMode;
|
|
OrgCaretPos = caretXY();
|
|
StrToInsert = nullptr;
|
|
if (selAvail()) {
|
|
auto action = finally([&,this]{
|
|
if (BE.Char > 1)
|
|
BE.Char+=Spaces.length();
|
|
setCaretAndSelection(OrgCaretPos,
|
|
{BB.Char + Spaces.length(), BB.Line}, BE);
|
|
setActiveSelectionMode(OrgSelectionMode);
|
|
});
|
|
// keep current selection detail
|
|
BB = mBlockBegin;
|
|
BE = mBlockEnd;
|
|
// build text to insert
|
|
if (BE.Char == 1) {
|
|
e = BE.Line - 1;
|
|
x = 1;
|
|
} else {
|
|
e = BE.Line;
|
|
if (mOptions.testFlag(SynEditorOption::eoTabsToSpaces))
|
|
x = caretX() + mTabWidth;
|
|
else
|
|
x = caretX() + 1;
|
|
}
|
|
if (mOptions.testFlag(eoTabsToSpaces)) {
|
|
InsertStrLen = (mTabWidth + 2) * (e - BB.Line) + mTabWidth + 1;
|
|
// chars per line * lines-1 + last line + null char
|
|
StrToInsert.resize(InsertStrLen);
|
|
Run = 0;
|
|
Spaces = QString(mTabWidth,' ') ;
|
|
} else {
|
|
InsertStrLen = 3 * (e - BB.Line) + 2;
|
|
// #9#13#10 * lines-1 + (last line's #9 + null char)
|
|
StrToInsert.resize(InsertStrLen);
|
|
Run = 0;
|
|
Spaces = "\t";
|
|
}
|
|
for (i = BB.Line; i<e;i++) {
|
|
StrToInsert.replace(Run,Spaces.length()+2,Spaces+"\r\n");
|
|
Run+=Spaces.length()+2;
|
|
}
|
|
StrToInsert.replace(Run,Spaces.length(),Spaces);
|
|
|
|
{
|
|
mUndoList->BeginBlock();
|
|
auto action2=finally([this]{
|
|
mUndoList->EndBlock();
|
|
});
|
|
InsertionPos.Line = BB.Line;
|
|
if (mActiveSelectionMode == SynSelectionMode::smColumn)
|
|
InsertionPos.Char = std::min(BB.Char, BE.Char);
|
|
else
|
|
InsertionPos.Char = 1;
|
|
insertBlock(InsertionPos, InsertionPos, StrToInsert, true);
|
|
mUndoList->AddChange(SynChangeReason::crIndent, BB, BE, "", SynSelectionMode::smColumn);
|
|
//We need to save the position of the end block for redo
|
|
mUndoList->AddChange(SynChangeReason::crIndent,
|
|
{BB.Char + Spaces.length(), BB.Line},
|
|
{BE.Char + Spaces.length(), BE.Line},
|
|
"", SynSelectionMode::smColumn);
|
|
//adjust the x position of orgcaretpos appropriately
|
|
OrgCaretPos.Char = x;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void SynEdit::incPaintLock()
|
|
{
|
|
mPaintLock ++ ;
|
|
}
|
|
|
|
void SynEdit::decPaintLock()
|
|
{
|
|
Q_ASSERT(mPaintLock > 0);
|
|
mPaintLock--;
|
|
if (mPaintLock == 0 ) {
|
|
if (mStateFlags.testFlag(SynStateFlag::sfScrollbarChanged))
|
|
updateScrollbars();
|
|
if (mStateFlags.testFlag(SynStateFlag::sfCaretChanged))
|
|
updateCaret();
|
|
if (mStatusChanges!=0)
|
|
doOnStatusChange(mStatusChanges);
|
|
}
|
|
}
|
|
|
|
bool SynEdit::mouseCapture()
|
|
{
|
|
return hasMouseTracking();
|
|
}
|
|
|
|
int SynEdit::clientWidth()
|
|
{
|
|
return viewport()->size().width();
|
|
}
|
|
|
|
int SynEdit::clientHeight()
|
|
{
|
|
return viewport()->size().height();
|
|
}
|
|
|
|
int SynEdit::clientTop()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int SynEdit::clientLeft()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
QRect SynEdit::clientRect()
|
|
{
|
|
return QRect(0,0, clientWidth(), clientHeight());
|
|
}
|
|
|
|
void SynEdit::synFontChanged()
|
|
{
|
|
recalcCharExtent();
|
|
sizeOrFontChanged(true);
|
|
}
|
|
|
|
void SynEdit::doOnPaintTransient(SynTransientType TransientType)
|
|
{
|
|
doOnPaintTransientEx(TransientType, false);
|
|
}
|
|
|
|
void SynEdit::updateLastCaretX()
|
|
{
|
|
mMBCSStepAside = false;
|
|
mLastCaretX = displayX();
|
|
}
|
|
|
|
void SynEdit::ensureCursorPosVisible()
|
|
{
|
|
ensureCursorPosVisibleEx(false);
|
|
}
|
|
|
|
void SynEdit::ensureCursorPosVisibleEx(bool ForceToMiddle)
|
|
{
|
|
incPaintLock();
|
|
auto action = finally([this]{
|
|
decPaintLock();
|
|
});
|
|
// Make sure X is visible
|
|
int VisibleX = displayX();
|
|
if (VisibleX < leftChar())
|
|
setLeftChar(VisibleX);
|
|
else if (VisibleX >= mCharsInWindow + leftChar() && mCharsInWindow > 0)
|
|
setLeftChar(VisibleX - mCharsInWindow + 1);
|
|
else
|
|
setLeftChar(leftChar());
|
|
// Make sure Y is visible
|
|
int vCaretRow = displayY();
|
|
if (ForceToMiddle) {
|
|
if (vCaretRow < mTopLine || vCaretRow>(mTopLine + (mLinesInWindow - 1)))
|
|
setTopLine( vCaretRow - (mLinesInWindow - 1) / 2);
|
|
} else {
|
|
if (vCaretRow < mTopLine)
|
|
setTopLine(vCaretRow);
|
|
else if (vCaretRow > mTopLine + (mLinesInWindow - 1) && mLinesInWindow > 0)
|
|
setTopLine(vCaretRow - (mLinesInWindow - 1));
|
|
else
|
|
setTopLine(mTopLine);
|
|
}
|
|
}
|
|
|
|
void SynEdit::scrollWindow(int dx, int dy)
|
|
{
|
|
int nx = horizontalScrollBar()->value()+dx;
|
|
int ny = verticalScrollBar()->value()+dy;
|
|
nx = std::min(std::max(horizontalScrollBar()->minimum(),nx),horizontalScrollBar()->maximum());
|
|
ny = std::min(std::max(verticalScrollBar()->minimum(),ny),verticalScrollBar()->maximum());
|
|
horizontalScrollBar()->setValue(nx);
|
|
verticalScrollBar()->setValue(ny);
|
|
|
|
}
|
|
|
|
void SynEdit::setInternalDisplayXY(const DisplayCoord &aPos)
|
|
{
|
|
incPaintLock();
|
|
internalSetCaretXY(displayToBufferPos(aPos));
|
|
decPaintLock();
|
|
updateLastCaretX();
|
|
}
|
|
|
|
void SynEdit::internalSetCaretXY(const BufferCoord &Value)
|
|
{
|
|
setCaretXYEx(true, Value);
|
|
}
|
|
|
|
void SynEdit::setStatusChanged(SynStatusChanges changes)
|
|
{
|
|
mStatusChanges = mStatusChanges | changes;
|
|
if (mPaintLock == 0)
|
|
doOnStatusChange(mStatusChanges);
|
|
}
|
|
|
|
void SynEdit::doOnStatusChange(SynStatusChanges)
|
|
{
|
|
emit statusChanged(mStatusChanges);
|
|
mStatusChanges = SynStatusChange::scNone;
|
|
}
|
|
|
|
void SynEdit::insertBlock(const BufferCoord &BB, const BufferCoord &BE, const QString &ChangeStr, bool AddToUndoList)
|
|
{
|
|
setCaretAndSelection(BB, BB, BE);
|
|
setActiveSelectionMode(SynSelectionMode::smColumn);
|
|
setSelTextPrimitiveEx(SynSelectionMode::smColumn, ChangeStr, AddToUndoList);
|
|
setStatusChanged(SynStatusChange::scSelection);
|
|
}
|
|
|
|
void SynEdit::updateScrollbars()
|
|
{
|
|
int nMaxScroll;
|
|
int nMin,nMax,nPage,nPos;
|
|
if (mPaintLock!=0) {
|
|
mStateFlags.setFlag(SynStateFlag::sfScrollbarChanged);
|
|
} else {
|
|
mStateFlags.setFlag(SynStateFlag::sfScrollbarChanged,false);
|
|
if (mScrollBars != SynScrollStyle::ssNone) {
|
|
if (mOptions.testFlag(eoHideShowScrollbars)) {
|
|
setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded);
|
|
setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded);
|
|
} else {
|
|
setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOn);
|
|
setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOn);
|
|
}
|
|
if (mScrollBars == SynScrollStyle::ssBoth || mScrollBars == SynScrollStyle::ssHorizontal) {
|
|
if (mOptions.testFlag(eoScrollPastEol))
|
|
nMaxScroll = mMaxScrollWidth;
|
|
else
|
|
nMaxScroll = std::max(mLines->lengthOfLongestLine(), 1);
|
|
if (nMaxScroll <= MAX_SCROLL) {
|
|
nMin = 1;
|
|
nMax = nMaxScroll;
|
|
nPage = mCharsInWindow;
|
|
nPos = mLeftChar;
|
|
} else {
|
|
nMin = 0;
|
|
nMax = MAX_SCROLL;
|
|
nPage = MulDiv(MAX_SCROLL, mCharsInWindow, nMaxScroll);
|
|
nPos = MulDiv(MAX_SCROLL, mLeftChar, nMaxScroll);
|
|
}
|
|
horizontalScrollBar()->setMinimum(nMin);
|
|
horizontalScrollBar()->setMaximum(nMax);
|
|
horizontalScrollBar()->setPageStep(nPage);
|
|
horizontalScrollBar()->setValue(nPos);
|
|
horizontalScrollBar()->setSingleStep(1);
|
|
} else
|
|
setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOn);
|
|
|
|
if (mScrollBars == SynScrollStyle::ssBoth || mScrollBars == SynScrollStyle::ssVertical) {
|
|
nMaxScroll = displayLineCount();
|
|
if (mOptions.testFlag(eoScrollPastEof))
|
|
nMaxScroll+=mLinesInWindow - 1;
|
|
if (nMaxScroll <= MAX_SCROLL) {
|
|
nMin = 1;
|
|
nMax = std::max(1, nMaxScroll);
|
|
nPage = mLinesInWindow;
|
|
nPos = mTopLine;
|
|
} else {
|
|
nMin = 0;
|
|
nMax = MAX_SCROLL;
|
|
nPage = MulDiv(MAX_SCROLL, mLinesInWindow, nMaxScroll);
|
|
nPos = MulDiv(MAX_SCROLL, mTopLine, nMaxScroll);
|
|
}
|
|
verticalScrollBar()->setMinimum(nMin);
|
|
verticalScrollBar()->setMaximum(nMax);
|
|
verticalScrollBar()->setPageStep(nPage);
|
|
verticalScrollBar()->setValue(nPos);
|
|
verticalScrollBar()->setSingleStep(1);
|
|
} else
|
|
setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff);
|
|
} else {
|
|
setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff);
|
|
setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SynEdit::updateCaret()
|
|
{
|
|
mStateFlags.setFlag(SynStateFlag::sfCaretChanged,false);
|
|
//do nothing. Because our caret timer will update the caret periodically
|
|
// invalidateRect(QRect(0,0,1,1)); //we use a rect of 1-width & 1-height to signal the paintEvent that we only want to redraw the caret
|
|
}
|
|
|
|
void SynEdit::recalcCharExtent()
|
|
{
|
|
SynFontStyle styles[] = {SynFontStyle::fsBold, SynFontStyle::fsItalic, SynFontStyle::fsStrikeOut, SynFontStyle::fsUnderline};
|
|
bool hasStyles[] = {false,false,false,false};
|
|
int size = 4;
|
|
if (mHighlighter && mHighlighter->attributes().count()>0) {
|
|
for (PSynHighlighterAttribute attribute: mHighlighter->attributes().values()) {
|
|
for (int i=0;i<size;i++) {
|
|
if (attribute->styles().testFlag(styles[i]))
|
|
hasStyles[i] = true;
|
|
}
|
|
}
|
|
} else {
|
|
hasStyles[0] = font().bold();
|
|
hasStyles[1] = font().italic();
|
|
hasStyles[2] = font().strikeOut();
|
|
hasStyles[3] = font().underline();
|
|
}
|
|
|
|
mTextHeight = 0;
|
|
mCharWidth = 0;
|
|
mTextHeight = fontMetrics().lineSpacing();
|
|
mCharWidth = fontMetrics().horizontalAdvance("M");
|
|
if (hasStyles[0]) { // has bold font
|
|
QFont f = font();
|
|
f.setBold(true);
|
|
QFontMetrics fm(f);
|
|
if (fm.lineSpacing()>mTextHeight)
|
|
mTextHeight=fm.lineSpacing();
|
|
if (fm.horizontalAdvance("M")>mCharWidth)
|
|
mCharWidth = fm.horizontalAdvance("M");
|
|
}
|
|
if (hasStyles[1]) { // has strike out font
|
|
QFont f = font();
|
|
f.setItalic(true);
|
|
QFontMetrics fm(f);
|
|
if (fm.lineSpacing()>mTextHeight)
|
|
mTextHeight=fm.lineSpacing();
|
|
if (fm.horizontalAdvance("M")>mCharWidth)
|
|
mCharWidth = fm.horizontalAdvance("M");
|
|
}
|
|
if (hasStyles[2]) { // has strikeout
|
|
QFont f = font();
|
|
f.setStrikeOut(true);
|
|
QFontMetrics fm(f);
|
|
if (fm.lineSpacing()>mTextHeight)
|
|
mTextHeight=fm.lineSpacing();
|
|
if (fm.horizontalAdvance("M")>mCharWidth)
|
|
mCharWidth = fm.horizontalAdvance("M");
|
|
}
|
|
if (hasStyles[3]) { // has underline
|
|
QFont f = font();
|
|
f.setUnderline(true);
|
|
QFontMetrics fm(f);
|
|
if (fm.lineSpacing()>mTextHeight)
|
|
mTextHeight=fm.lineSpacing();
|
|
if (fm.horizontalAdvance("M")>mCharWidth)
|
|
mCharWidth = fm.horizontalAdvance("M");
|
|
}
|
|
mTextHeight += mExtraLineSpacing;
|
|
mCharWidth = mCharWidth * dpiFactor();
|
|
mTextHeight = mTextHeight * dpiFactor();
|
|
}
|
|
|
|
QString SynEdit::expandAtWideGlyphs(const QString &S)
|
|
{
|
|
QString Result(S.length()*2); // speed improvement
|
|
int j = 0;
|
|
for (int i=0;i<S.length();i++) {
|
|
int CountOfAvgGlyphs = ceil(fontMetrics().horizontalAdvance(S[i])/(double)mCharWidth);
|
|
if (j+CountOfAvgGlyphs>=Result.length())
|
|
Result.resize(Result.length()+128);
|
|
// insert CountOfAvgGlyphs filling chars
|
|
while (CountOfAvgGlyphs>1) {
|
|
Result[j]=QChar(0xE000);
|
|
j++;
|
|
CountOfAvgGlyphs--;
|
|
}
|
|
Result[j]=S[i];
|
|
j++;
|
|
}
|
|
Result.resize(j);
|
|
}
|
|
|
|
void SynEdit::updateModifiedStatus()
|
|
{
|
|
setModified(!mUndoList->initialState());
|
|
}
|
|
|
|
int SynEdit::scanFrom(int Index)
|
|
{
|
|
SynRangeState iRange;
|
|
int Result = Index;
|
|
if (Result >= mLines->count())
|
|
return Result;
|
|
|
|
if (Result == 0) {
|
|
mHighlighter->resetState();
|
|
} else {
|
|
mHighlighter->setState(mLines->ranges(Result-1),
|
|
mLines->braceLevels(Result-1),
|
|
mLines->bracketLevels(Result-1),
|
|
mLines->parenthesisLevels(Result-1));
|
|
}
|
|
do {
|
|
mHighlighter->setLine(mLines->getString(Result), Result);
|
|
mHighlighter->nextToEol();
|
|
iRange = mHighlighter->getRangeState();
|
|
{
|
|
if (mLines->ranges(Result).state == iRange.state)
|
|
return Result;// avoid the final Decrement
|
|
}
|
|
mLines->setRange(Result,iRange);
|
|
mLines->setParenthesisLevel(Result,mHighlighter->getParenthesisLevel());
|
|
mLines->setBraceLevel(Result,mHighlighter->getBraceLevel());
|
|
mLines->setBracketLevel(Result,mHighlighter->getBracketLevel());
|
|
Result ++ ;
|
|
} while (Result < mLines->count());
|
|
Result--;
|
|
return Result;
|
|
}
|
|
|
|
void SynEdit::uncollapse(PSynEditFoldRange FoldRange)
|
|
{
|
|
FoldRange->linesCollapsed = 0;
|
|
FoldRange->collapsed = false;
|
|
|
|
// Redraw the collapsed line
|
|
invalidateLines(FoldRange->fromLine, INT_MAX);
|
|
|
|
// Redraw fold mark
|
|
invalidateGutterLines(FoldRange->fromLine, INT_MAX);
|
|
}
|
|
|
|
void SynEdit::foldOnListInserted(int Line, int Count)
|
|
{
|
|
// Delete collapsed inside selection
|
|
for (int i = mAllFoldRanges.count()-1;i>=0;i--) {
|
|
PSynEditFoldRange range = mAllFoldRanges[i];
|
|
if (range->collapsed || range->parentCollapsed()){
|
|
if (range->fromLine == Line - 1) // insertion starts at fold line
|
|
uncollapse(range);
|
|
else if (range->fromLine >= Line) // insertion of count lines above FromLine
|
|
range->move(Count);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SynEdit::foldOnListDeleted(int Line, int Count)
|
|
{
|
|
// Delete collapsed inside selection
|
|
for (int i = mAllFoldRanges.count()-1;i>=0;i--) {
|
|
PSynEditFoldRange range = mAllFoldRanges[i];
|
|
if (range->collapsed || range->parentCollapsed()){
|
|
if (range->fromLine == Line && Count == 1) // open up because we are messing with the starting line
|
|
uncollapse(range);
|
|
else if (range->fromLine >= Line - 1 && range->fromLine < Line + Count) // delete inside affectec area
|
|
mAllFoldRanges.remove(i);
|
|
else if (range->fromLine >= Line + Count) // Move after affected area
|
|
range->move(-Count);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void SynEdit::foldOnListCleared()
|
|
{
|
|
mAllFoldRanges.clear();
|
|
}
|
|
|
|
void SynEdit::rescan()
|
|
{
|
|
if (!mUseCodeFolding)
|
|
return;
|
|
rescanForFoldRanges();
|
|
invalidateGutter();
|
|
}
|
|
|
|
static void null_deleter(SynEditFoldRanges *) {}
|
|
|
|
void SynEdit::rescanForFoldRanges()
|
|
{
|
|
// Delete all uncollapsed folds
|
|
for (int i=mAllFoldRanges.count()-1;i>=0;i--) {
|
|
PSynEditFoldRange range =mAllFoldRanges[i];
|
|
if (!range->collapsed && !range->parentCollapsed())
|
|
mAllFoldRanges.remove(i);
|
|
}
|
|
|
|
// Did we leave any collapsed folds and are we viewing a code file?
|
|
if (mAllFoldRanges.count() > 0) {
|
|
// Add folds to a separate list
|
|
PSynEditFoldRanges TemporaryAllFoldRanges = std::make_shared<SynEditFoldRanges>();
|
|
scanForFoldRanges(TemporaryAllFoldRanges);
|
|
|
|
// Combine new with old folds, preserve parent order
|
|
for (int i = 0; i< TemporaryAllFoldRanges->count();i++) {
|
|
for (int j = 0; j< mAllFoldRanges.count() - 1;j++) {
|
|
if (TemporaryAllFoldRanges->range(i)->fromLine < mAllFoldRanges[j]->fromLine) {
|
|
mAllFoldRanges.insert(j, TemporaryAllFoldRanges->range(i));
|
|
break;
|
|
}
|
|
// If we can't prepend #i anywhere, just dump it at the end
|
|
if (j == mAllFoldRanges.count() - 1)
|
|
mAllFoldRanges.add(TemporaryAllFoldRanges->range(i));
|
|
}
|
|
}
|
|
|
|
} else {
|
|
// We ended up with no folds after deleting, just pass standard data...
|
|
PSynEditFoldRanges temp(&mAllFoldRanges, null_deleter);
|
|
scanForFoldRanges(temp);
|
|
}
|
|
}
|
|
|
|
void SynEdit::scanForFoldRanges(PSynEditFoldRanges TopFoldRanges)
|
|
{
|
|
// Recursively scan for folds (all types)
|
|
for (int i= 0 ; i< mCodeFolding.foldRegions.count() ; i++ ) {
|
|
findSubFoldRange(TopFoldRanges, i,PSynEditFoldRange());
|
|
}
|
|
}
|
|
|
|
//this func should only be used in findSubFoldRange
|
|
int SynEdit::lineHasChar(int Line, int startChar, QChar character, const QString& highlighterAttrName) {
|
|
QString CurLine = mLines->getString(Line);
|
|
if (!mHighlighter){
|
|
for (int i=startChar; i<CurLine.length();i++) {
|
|
if (CurLine[i]==character) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
/*
|
|
mHighlighter->setState(mLines->ranges(Line),
|
|
mLines->braceLevels(Line),
|
|
mLines->bracketLevels(Line),
|
|
mLines->parenthesisLevels(Line));
|
|
mHighlighter->setLine(CurLine,Line);
|
|
*/
|
|
QString token;
|
|
while (!mHighlighter->eol()) {
|
|
token = mHighlighter->getToken();
|
|
PSynHighlighterAttribute attr = mHighlighter->getTokenAttribute();
|
|
if (token == character && attr->name()==highlighterAttrName)
|
|
return mHighlighter->getTokenPos();
|
|
mHighlighter->next();
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void SynEdit::findSubFoldRange(PSynEditFoldRanges TopFoldRanges, int FoldIndex, PSynEditFoldRange Parent)
|
|
{
|
|
PSynEditFoldRange CollapsedFold;
|
|
PSynEditFoldRanges ParentFoldRanges;
|
|
ParentFoldRanges = TopFoldRanges;
|
|
int Line = 0;
|
|
QString CurLine;
|
|
|
|
if (!mHighlighter)
|
|
return;
|
|
while (Line < mLines->count()) { // index is valid for LinesToScan and fLines
|
|
// If there is a collapsed fold over here, skip it
|
|
CollapsedFold = collapsedFoldStartAtLine(Line + 1); // only collapsed folds remain
|
|
if (CollapsedFold) {
|
|
Line = CollapsedFold->toLine;
|
|
continue;
|
|
}
|
|
|
|
// Find an opening character on this line
|
|
CurLine = mLines->getString(Line);
|
|
|
|
mHighlighter->setState(mLines->ranges(Line),
|
|
mLines->braceLevels(Line),
|
|
mLines->bracketLevels(Line),
|
|
mLines->parenthesisLevels(Line));
|
|
mHighlighter->setLine(CurLine,Line);
|
|
|
|
QString token;
|
|
int pos;
|
|
while (!mHighlighter->eol()) {
|
|
token = mHighlighter->getToken();
|
|
pos = mHighlighter->getTokenPos()+token.length();
|
|
PSynHighlighterAttribute attr = mHighlighter->getTokenAttribute();
|
|
// We've found a starting character and it have proper highlighting (ignore stuff inside comments...)
|
|
if (token == mCodeFolding.foldRegions.get(FoldIndex)->openSymbol && attr->name()==mCodeFolding.foldRegions.get(FoldIndex)->highlight) {
|
|
// And ignore lines with both opening and closing chars in them
|
|
if (lineHasChar(Line,pos,mCodeFolding.foldRegions.get(FoldIndex)->closeSymbol,
|
|
mCodeFolding.foldRegions.get(FoldIndex)->highlight)<0) {
|
|
// Add it to the top list of folds
|
|
Parent = ParentFoldRanges->addByParts(
|
|
Parent,
|
|
TopFoldRanges,
|
|
Line + 1,
|
|
mCodeFolding.foldRegions.get(FoldIndex),
|
|
Line + 1);
|
|
ParentFoldRanges = Parent->subFoldRanges;
|
|
|
|
// Skip until a newline
|
|
break;
|
|
}
|
|
|
|
} else if (token == mCodeFolding.foldRegions.get(FoldIndex)->closeSymbol && attr->name()==mCodeFolding.foldRegions.get(FoldIndex)->highlight) {
|
|
// And ignore lines with both opening and closing chars in them
|
|
if (lineHasChar(Line,pos,mCodeFolding.foldRegions.get(FoldIndex)->openSymbol,
|
|
mCodeFolding.foldRegions.get(FoldIndex)->highlight)<0) {
|
|
// Stop the recursion if we find a closing char, and return to our parent
|
|
if (Parent) {
|
|
Parent->toLine = Line + 1;
|
|
Parent = Parent->parent;
|
|
}
|
|
|
|
// Skip until a newline
|
|
break;
|
|
}
|
|
}
|
|
mHighlighter->next();
|
|
}
|
|
Line++;
|
|
}
|
|
}
|
|
|
|
PSynEditFoldRange SynEdit::collapsedFoldStartAtLine(int Line)
|
|
{
|
|
for (int i = 0; i< mAllFoldRanges.count() - 1; i++ ) {
|
|
if (mAllFoldRanges[i]->fromLine == Line && mAllFoldRanges[i]->collapsed) {
|
|
return mAllFoldRanges[i];
|
|
} else if (mAllFoldRanges[i]->fromLine > Line) {
|
|
break; // sorted by line. don't bother scanning further
|
|
}
|
|
}
|
|
return PSynEditFoldRange();
|
|
}
|
|
|
|
void SynEdit::setSelTextPrimitiveEx(SynSelectionMode PasteMode, const QString &Value, bool AddToUndoList)
|
|
{
|
|
//todo
|
|
}
|
|
|
|
void SynEdit::doOnPaintTransientEx(SynTransientType TransientType, bool Lock)
|
|
{
|
|
//todo: we can't draw to canvas outside paintEvent
|
|
}
|
|
|
|
void SynEdit::initializeCaret()
|
|
{
|
|
//todo:
|
|
}
|
|
|
|
PSynEditFoldRange SynEdit::foldStartAtLine(int Line)
|
|
{
|
|
for (int i = 0; i<mAllFoldRanges.count();i++) {
|
|
PSynEditFoldRange range = mAllFoldRanges[i];
|
|
if (range->fromLine == Line ){
|
|
return range;
|
|
} else if (range->fromLine>Line)
|
|
break; // sorted by line. don't bother scanning further
|
|
}
|
|
}
|
|
|
|
QString SynEdit::substringByColumns(const QString &s, int startColumn, int &colLen)
|
|
{
|
|
|
|
int len = s.length();
|
|
int columns = 0;
|
|
int i = 0;
|
|
int oldColumns;
|
|
while (columns < startColumn) {
|
|
oldColumns = columns;
|
|
if (i < len && s[i] == '\t')
|
|
columns += mTabWidth - (columns % mTabWidth);
|
|
else
|
|
columns += charColumns(s[i]);
|
|
i++;
|
|
}
|
|
QString result;
|
|
if (i>=len) {
|
|
colLen = 0;
|
|
return result;
|
|
}
|
|
if (colLen>result.capacity()) {
|
|
result.resize(colLen);
|
|
}
|
|
int j=0;
|
|
if (i>0) {
|
|
result[0]=s[i-1];
|
|
j++;
|
|
}
|
|
while (i<len && columns<startColumn+colLen) {
|
|
result[j]=s[i];
|
|
if (i < len && s[i] == '\t')
|
|
columns += mTabWidth - (columns % mTabWidth);
|
|
else
|
|
columns += charColumns(s[i]);
|
|
i++;
|
|
j++;
|
|
}
|
|
result.resize(j);
|
|
colLen = columns-oldColumns;
|
|
return result;
|
|
}
|
|
|
|
PSynEditFoldRange SynEdit::foldAroundLine(int Line)
|
|
{
|
|
return foldAroundLineEx(Line,false,false,false);
|
|
}
|
|
|
|
PSynEditFoldRange SynEdit::foldAroundLineEx(int Line, bool WantCollapsed, bool AcceptFromLine, bool AcceptToLine)
|
|
{
|
|
// Check global list
|
|
PSynEditFoldRange Result = checkFoldRange(&mAllFoldRanges, Line, WantCollapsed, AcceptFromLine, AcceptToLine);
|
|
|
|
// Found an item in the top level list?
|
|
if (Result) {
|
|
while (true) {
|
|
PSynEditFoldRange ResultChild = checkFoldRange(Result->subFoldRanges.get(), Line, WantCollapsed, AcceptFromLine, AcceptToLine);
|
|
if (!ResultChild)
|
|
break;
|
|
Result = ResultChild; // repeat for this one
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
PSynEditFoldRange SynEdit::checkFoldRange(SynEditFoldRanges *FoldRangeToCheck, int Line, bool WantCollapsed, bool AcceptFromLine, bool AcceptToLine)
|
|
{
|
|
for (int i = 0; i< FoldRangeToCheck->count(); i++) {
|
|
PSynEditFoldRange range = (*FoldRangeToCheck)[i];
|
|
if (((range->fromLine < Line) || ((range->fromLine <= Line) && AcceptFromLine)) &&
|
|
((range->toLine > Line) || ((range->toLine >= Line) && AcceptToLine))) {
|
|
if (range->collapsed == WantCollapsed) {
|
|
return range;
|
|
}
|
|
}
|
|
}
|
|
return PSynEditFoldRange();
|
|
}
|
|
|
|
PSynEditFoldRange SynEdit::foldEndAtLine(int Line)
|
|
{
|
|
for (int i = 0; i<mAllFoldRanges.count();i++) {
|
|
PSynEditFoldRange range = mAllFoldRanges[i];
|
|
if (range->toLine == Line ){
|
|
return range;
|
|
} else if (range->fromLine>Line)
|
|
break; // sorted by line. don't bother scanning further
|
|
}
|
|
return PSynEditFoldRange();
|
|
}
|
|
|
|
void SynEdit::paintCaret(QPainter &painter, const QRect rcClip)
|
|
{
|
|
if (m_bliknStatus!=1)
|
|
return;
|
|
SynEditCaretType ct;
|
|
if (this->mInserting) {
|
|
ct = mInsertCaret;
|
|
} else {
|
|
ct =mOverwriteCaret;
|
|
}
|
|
painter.setPen(QColorConstants::Svg::red);
|
|
painter.setBrush(QColorConstants::Svg::red);
|
|
switch(ct) {
|
|
case SynEditCaretType::ctVerticalLine:
|
|
painter.drawLine(rcClip.left()+1,rcClip.top(),rcClip.left()+1,rcClip.bottom());
|
|
break;
|
|
case SynEditCaretType::ctHorizontalLine:
|
|
painter.drawLine(rcClip.left(),rcClip.bottom()-1,rcClip.right(),rcClip.bottom()-1);
|
|
break;
|
|
case SynEditCaretType::ctBlock:
|
|
painter.drawRect(rcClip);
|
|
break;
|
|
case SynEditCaretType::ctHalfBlock:
|
|
QRect rc=rcClip;
|
|
rc.setTop(rcClip.top()+rcClip.height() / 2);
|
|
|
|
}
|
|
}
|
|
|
|
void SynEdit::sizeOrFontChanged(bool bFont)
|
|
{
|
|
|
|
if (mCharWidth != 0) {
|
|
mCharsInWindow = std::max(clientWidth() - mGutterWidth - 2, 0) / mCharWidth;
|
|
mLinesInWindow = clientHeight() / mTextHeight;
|
|
if (bFont) {
|
|
if (mGutter.showLineNumbers())
|
|
gutterChanged();
|
|
else
|
|
updateScrollbars();
|
|
initializeCaret();
|
|
mStateFlags.setFlag(SynStateFlag::sfCaretChanged,false);
|
|
invalidate();
|
|
} else
|
|
updateScrollbars();
|
|
mStateFlags.setFlag(SynStateFlag::sfScrollbarChanged,false);
|
|
if (!mOptions.testFlag(SynEditorOption::eoScrollPastEol))
|
|
setLeftChar(mLeftChar);
|
|
if (!mOptions.testFlag(SynEditorOption::eoScrollPastEof))
|
|
setTopLine(mTopLine);
|
|
}
|
|
}
|
|
|
|
void SynEdit::doChange()
|
|
{
|
|
emit Changed();
|
|
}
|
|
|
|
PSynEditStringList SynEdit::lines() const
|
|
{
|
|
return mLines;
|
|
}
|
|
|
|
int SynEdit::tabWidth() const
|
|
{
|
|
return mTabWidth;
|
|
}
|
|
|
|
bool SynEdit::empty()
|
|
{
|
|
return mLines->empty();
|
|
}
|
|
|
|
bool SynEdit::onGetSpecialLineColors(int, QColor &, QColor &)
|
|
{
|
|
|
|
}
|
|
|
|
void SynEdit::onGetEditingAreas(int, SynEditingAreaList &)
|
|
{
|
|
|
|
}
|
|
|
|
void SynEdit::onGutterGetText(int aLine, QString &aText)
|
|
{
|
|
|
|
}
|
|
|
|
void SynEdit::onGutterPaint(QPainter &painter, int aLine, int X, int Y)
|
|
{
|
|
|
|
}
|
|
|
|
void SynEdit::onPaint(QPainter &)
|
|
{
|
|
|
|
}
|
|
|
|
void SynEdit::paintEvent(QPaintEvent *event)
|
|
{
|
|
if (mPainterLock>0)
|
|
return;
|
|
if (mPainting)
|
|
return;
|
|
mPainting = true;
|
|
|
|
// Now paint everything while the caret is hidden.
|
|
QPainter painter(viewport());
|
|
hideCaret();
|
|
//Get the invalidated rect.
|
|
QRect rcClip = event->rect();
|
|
DisplayCoord coord = displayXY();
|
|
QPoint caretPos = RowColumnToPixels(coord);
|
|
int caretWidth=mCharWidth;
|
|
if (mCaretY <= mLines->count() && mCaretX <= mLines->getString(mCaretY-1).length()) {
|
|
caretWidth = charColumns(mLines->getString(mCaretY-1)[mCaretX-1])*mCharWidth;
|
|
}
|
|
QRect rcCaret(caretPos.x(),caretPos.y(),caretWidth,
|
|
mTextHeight);
|
|
|
|
qDebug()<<"Painting";
|
|
qDebug()<<"Caret rect:"<<rcCaret;
|
|
qDebug()<<"Clip rect:"<<rcClip;
|
|
|
|
auto action = finally([&,this] {
|
|
//updateCaret();
|
|
paintCaret(painter, rcCaret);
|
|
mPainting = false;
|
|
});
|
|
|
|
if (rcClip.width()==1 && rcClip.height()==1) {
|
|
// only update caret
|
|
// calculate the needed invalid area for caret
|
|
painter.drawImage(rcCaret,*mContentImage);
|
|
} else {
|
|
QRect rcDraw;
|
|
int nL1, nL2, nC1, nC2;
|
|
// Compute the invalid area in lines / columns.
|
|
// columns
|
|
nC1 = mLeftChar;
|
|
if (rcClip.left() > mGutterWidth + 2 )
|
|
nC1 += (rcClip.left() - mGutterWidth - 2 ) / mCharWidth;
|
|
nC2 = mLeftChar +
|
|
(rcClip.right() - mGutterWidth - 2 + mCharWidth - 1) / mCharWidth;
|
|
// lines
|
|
nL1 = MinMax(mTopLine + rcClip.top() / mTextHeight, mTopLine, displayLineCount());
|
|
nL2 = MinMax(mTopLine + (rcClip.bottom() + mTextHeight - 1) / mTextHeight, 1, displayLineCount());
|
|
|
|
|
|
|
|
// QPainter cachePainter(mContentImage.get());
|
|
// SynEditTextPainter textPainter(this, &cachePainter,
|
|
// nL1,nL2,nC1,nC2);
|
|
SynEditTextPainter textPainter(this, &painter,
|
|
nL1,nL2,nC1,nC2);
|
|
// First paint paint the text area if it was (partly) invalidated.
|
|
if (rcClip.right() > mGutterWidth ) {
|
|
rcDraw = rcClip;
|
|
rcDraw.setLeft( std::max(rcDraw.left(), mGutterWidth));
|
|
textPainter.paintTextLines(rcDraw);
|
|
//paintTextLines(rcDraw, nL1, nL2, nC1, nC2);
|
|
}
|
|
|
|
// Then the gutter area if it was (partly) invalidated.
|
|
if (rcClip.left() < mGutterWidth) {
|
|
rcDraw = rcClip;
|
|
rcDraw.setRight(mGutterWidth);
|
|
//textPainter.paintGutter(rcDraw);
|
|
//paintGutter(rcDraw, nL1, nL2);
|
|
}
|
|
|
|
//PluginsAfterPaint(Canvas, rcClip, nL1, nL2);
|
|
// If there is a custom paint handler call it.
|
|
//onPaint(cachePainter);
|
|
doOnPaintTransient(SynTransientType::ttAfter);
|
|
painter.drawImage(rcClip,*mContentImage);
|
|
}
|
|
|
|
}
|
|
|
|
void SynEdit::resizeEvent(QResizeEvent *event)
|
|
{
|
|
//resize the cache image
|
|
std::shared_ptr<QImage> image = std::make_shared<QImage>(clientWidth(),clientHeight(),
|
|
QImage::Format_ARGB32);
|
|
|
|
QRect newRect = image->rect().intersected(mContentImage->rect());
|
|
|
|
QPainter painter(image.get());
|
|
|
|
painter.drawImage(newRect,*mContentImage);
|
|
|
|
mContentImage = image;
|
|
|
|
sizeOrFontChanged(false);
|
|
}
|
|
|
|
void SynEdit::timerEvent(QTimerEvent *event)
|
|
{
|
|
if (event->timerId() == m_blinkTimerId) {
|
|
m_bliknStatus = 1- m_bliknStatus;
|
|
invalidateRect(QRect(0,0,1,1));
|
|
}
|
|
}
|
|
|
|
bool SynEdit::event(QEvent *event)
|
|
{
|
|
switch(event->type()) {
|
|
case QEvent::FontChange:
|
|
synFontChanged();
|
|
break;
|
|
}
|
|
QAbstractScrollArea::event(event);
|
|
}
|
|
|
|
int SynEdit::maxScrollWidth() const
|
|
{
|
|
return mMaxScrollWidth;
|
|
}
|
|
|
|
void SynEdit::setMaxScrollWidth(int Value)
|
|
{
|
|
Value = MinMax(Value, 1, INT_MAX - 1);
|
|
if (mMaxScrollWidth != Value) {
|
|
mMaxScrollWidth = Value;
|
|
if (mOptions.testFlag(SynEditorOption::eoScrollPastEol))
|
|
updateScrollbars();
|
|
}
|
|
}
|
|
|
|
bool SynEdit::modified() const
|
|
{
|
|
return mModified;
|
|
}
|
|
|
|
void SynEdit::setModified(bool Value)
|
|
{
|
|
if (Value)
|
|
mLastModifyTime = QDateTime::currentDateTime();
|
|
if (Value != mModified) {
|
|
mModified = Value;
|
|
if (mOptions.testFlag(SynEditorOption::eoGroupUndo) && (!Value) && mUndoList->CanUndo())
|
|
mUndoList->AddGroupBreak();
|
|
mUndoList->setInitialState(!Value);
|
|
statusChanged(SynStatusChange::scModified);
|
|
}
|
|
}
|
|
|
|
int SynEdit::gutterWidth() const
|
|
{
|
|
return mGutterWidth;
|
|
}
|
|
|
|
void SynEdit::setGutterWidth(int Value)
|
|
{
|
|
Value = std::max(Value, 0);
|
|
if (mGutterWidth != Value) {
|
|
mGutterWidth = Value;
|
|
mTextOffset = mGutterWidth + 2 - (mLeftChar - 1) * mCharWidth;
|
|
sizeOrFontChanged(false);
|
|
}
|
|
}
|
|
|
|
int SynEdit::charWidth() const
|
|
{
|
|
return mCharWidth;
|
|
}
|
|
|
|
int SynEdit::charsInWindow() const
|
|
{
|
|
return mCharsInWindow;
|
|
}
|
|
|
|
void SynEdit::bookMarkOptionsChanged()
|
|
{
|
|
invalidateGutter();
|
|
}
|
|
|
|
void SynEdit::linesChanged()
|
|
{
|
|
SynSelectionMode vOldMode;
|
|
mStateFlags.setFlag(SynStateFlag::sfLinesChanging, false);
|
|
if (mUseCodeFolding)
|
|
rescan();
|
|
|
|
updateScrollbars();
|
|
vOldMode = mActiveSelectionMode;
|
|
setBlockBegin(caretXY());
|
|
mActiveSelectionMode = vOldMode;
|
|
invalidateRect(mInvalidateRect);
|
|
mInvalidateRect = {0,0,0,0};
|
|
|
|
if (mGutter.showLineNumbers() && (mGutter.autoSize()))
|
|
mGutter.autoSizeDigitCount(mLines->count());
|
|
if (!mOptions.testFlag(SynEditorOption::eoScrollPastEof))
|
|
setTopLine(mTopLine);
|
|
}
|
|
|
|
void SynEdit::linesChanging()
|
|
{
|
|
mStateFlags.setFlag(SynStateFlag::sfLinesChanging);
|
|
}
|
|
|
|
void SynEdit::linesCleared()
|
|
{
|
|
if (mUseCodeFolding)
|
|
foldOnListCleared();
|
|
clearUndo();
|
|
// invalidate the *whole* client area
|
|
mInvalidateRect={0,0,0,0};
|
|
invalidate();
|
|
// set caret and selected block to start of text
|
|
setCaretXY({1,1});
|
|
// scroll to start of text
|
|
setTopLine(1);
|
|
setLeftChar(1);
|
|
mStatusChanges.setFlag(SynStatusChange::scAll);
|
|
}
|
|
|
|
void SynEdit::linesDeleted(int index, int count)
|
|
{
|
|
if (mUseCodeFolding)
|
|
foldOnListDeleted(index + 1, count);
|
|
if (mHighlighter && mLines->count() > 0)
|
|
scanFrom(index);
|
|
invalidateLines(index + 1, INT_MAX);
|
|
invalidateGutterLines(index + 1, INT_MAX);
|
|
}
|
|
|
|
void SynEdit::linesInserted(int index, int count)
|
|
{
|
|
if (mUseCodeFolding)
|
|
foldOnListInserted(index + 1, count);
|
|
if (mHighlighter && mLines->count() > 0) {
|
|
int vLastScan = index;
|
|
do {
|
|
vLastScan = scanFrom(vLastScan);
|
|
vLastScan++;
|
|
} while (vLastScan < index + count) ;
|
|
}
|
|
invalidateLines(index + 1, INT_MAX);
|
|
invalidateGutterLines(index + 1, INT_MAX);
|
|
if (mOptions.setFlag(SynEditorOption::eoAutoSizeMaxScrollWidth)) {
|
|
int L = mLines->lineColumns(index);
|
|
if (L > mMaxScrollWidth)
|
|
setMaxScrollWidth(L);
|
|
}
|
|
}
|
|
|
|
void SynEdit::linesPutted(int index, int)
|
|
{
|
|
int vEndLine = index + 1;
|
|
if (mHighlighter) {
|
|
vEndLine = std::max(vEndLine, scanFrom(index) + 1);
|
|
// If this editor is chained then the real owner of text buffer will probably
|
|
// have already parsed the changes, so ScanFrom will return immediately.
|
|
if (mLines != mOrigLines)
|
|
vEndLine = INT_MAX;
|
|
}
|
|
invalidateLines(index + 1, vEndLine);
|
|
|
|
if (mOptions.setFlag(SynEditorOption::eoAutoSizeMaxScrollWidth)) {
|
|
int L = mLines->lineColumns(index);
|
|
if (L > mMaxScrollWidth)
|
|
setMaxScrollWidth(L);
|
|
}
|
|
}
|
|
|
|
void SynEdit::undoAdded()
|
|
{
|
|
updateModifiedStatus();
|
|
|
|
// we have to clear the redo information, since adding undo info removes
|
|
// the necessary context to undo earlier edit actions
|
|
if (! mUndoList->insideRedo() &&
|
|
mUndoList->PeekItem() && (mUndoList->PeekItem()->changeReason()!=SynChangeReason::crGroupBreak))
|
|
mRedoList->Clear();
|
|
if (mUndoList->blockCount() == 0 )
|
|
doChange();
|
|
}
|
|
|
|
SynSelectionMode SynEdit::activeSelectionMode() const
|
|
{
|
|
return mActiveSelectionMode;
|
|
}
|
|
|
|
void SynEdit::setActiveSelectionMode(const SynSelectionMode &Value)
|
|
{
|
|
if (mActiveSelectionMode != Value) {
|
|
if (selAvail())
|
|
invalidateSelection();
|
|
mActiveSelectionMode = Value;
|
|
if (selAvail())
|
|
invalidateSelection();
|
|
setStatusChanged(SynStatusChange::scSelection);
|
|
}
|
|
}
|
|
|
|
BufferCoord SynEdit::blockEnd() const
|
|
{
|
|
return mBlockEnd;
|
|
}
|
|
|
|
void SynEdit::setBlockEnd(BufferCoord Value)
|
|
{
|
|
setActiveSelectionMode(mSelectionMode);
|
|
if (!mOptions.testFlag(eoNoSelection)) {
|
|
if (mOptions.testFlag(eoScrollPastEol))
|
|
Value.Char = MinMax(Value.Char, 1, mMaxScrollWidth + 1);
|
|
else
|
|
Value.Char = std::max(Value.Char, 1);
|
|
Value.Line = MinMax(Value.Line, 1, mLines->count());
|
|
if (mActiveSelectionMode == SynSelectionMode::smNormal) {
|
|
if (Value.Line >= 1 && Value.Line <= mLines->count())
|
|
Value.Char = std::min(Value.Char, mLines->getString(Value.Line - 1).length() + 1);
|
|
else
|
|
Value.Char = 1;
|
|
}
|
|
if (Value.Char != mBlockEnd.Char || Value.Line != mBlockEnd.Line) {
|
|
if (Value.Char != mBlockEnd.Char || Value.Line != mBlockEnd.Line) {
|
|
if (mActiveSelectionMode == SynSelectionMode::smColumn && Value.Char != mBlockEnd.Char) {
|
|
invalidateLines(
|
|
std::min(mBlockBegin.Line, std::min(mBlockEnd.Line, Value.Line)),
|
|
std::max(mBlockBegin.Line, std::max(mBlockEnd.Line, Value.Line)));
|
|
mBlockEnd = Value;
|
|
} else {
|
|
int nLine = mBlockEnd.Line;
|
|
mBlockEnd = Value;
|
|
if (mActiveSelectionMode != SynSelectionMode::smColumn || mBlockBegin.Char != mBlockEnd.Char)
|
|
invalidateLines(nLine, mBlockEnd.Line);
|
|
}
|
|
setStatusChanged(SynStatusChange::scSelection);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
BufferCoord SynEdit::blockBegin() const
|
|
{
|
|
return mBlockBegin;
|
|
}
|
|
|
|
void SynEdit::setBlockBegin(BufferCoord value)
|
|
{
|
|
int nInval1, nInval2;
|
|
bool SelChanged;
|
|
setActiveSelectionMode(mSelectionMode);
|
|
if (mOptions.testFlag(SynEditorOption::eoScrollPastEol))
|
|
value.Char = MinMax(value.Char, 1, mMaxScrollWidth + 1);
|
|
else
|
|
value.Char = std::max(value.Char, 1);
|
|
value.Line = MinMax(value.Line, 1, mLines->count());
|
|
if (mActiveSelectionMode == SynSelectionMode::smNormal) {
|
|
if (value.Line >= 1 && value.Line <= mLines->count())
|
|
value.Char = std::min(value.Char, mLines->getString(value.Line - 1).length() + 1);
|
|
else
|
|
value.Char = 1;
|
|
}
|
|
if (selAvail()) {
|
|
if (mBlockBegin.Line < mBlockEnd.Line) {
|
|
nInval1 = std::min(value.Line, mBlockBegin.Line);
|
|
nInval2 = std::max(value.Line, mBlockEnd.Line);
|
|
} else {
|
|
nInval1 = std::min(value.Line, mBlockEnd.Line);
|
|
nInval2 = std::max(value.Line, mBlockBegin.Line);
|
|
};
|
|
mBlockBegin = value;
|
|
mBlockEnd = value;
|
|
invalidateLines(nInval1, nInval2);
|
|
SelChanged = true;
|
|
} else {
|
|
SelChanged =
|
|
(mBlockBegin.Char != value.Char) || (mBlockBegin.Line != value.Line) ||
|
|
(mBlockEnd.Char != value.Char) || (mBlockEnd.Line != value.Line);
|
|
mBlockBegin = value;
|
|
mBlockEnd = value;
|
|
}
|
|
if (SelChanged)
|
|
setStatusChanged(SynStatusChange::scSelection);
|
|
}
|
|
|
|
int SynEdit::leftChar() const
|
|
{
|
|
return mLeftChar;
|
|
}
|
|
|
|
void SynEdit::setLeftChar(int Value)
|
|
{
|
|
int MaxVal;
|
|
int iDelta;
|
|
//QRect iTextArea;
|
|
if (mOptions.testFlag(SynEditorOption::eoScrollPastEol)) {
|
|
if (mOptions.testFlag(SynEditorOption::eoAutoSizeMaxScrollWidth))
|
|
MaxVal = INT_MAX - mCharsInWindow;
|
|
else
|
|
MaxVal = mMaxScrollWidth - mCharsInWindow + 1;
|
|
} else {
|
|
MaxVal = mLines->lengthOfLongestLine();
|
|
if (MaxVal > mCharsInWindow)
|
|
MaxVal = MaxVal - mCharsInWindow + 1;
|
|
else
|
|
MaxVal = 1;
|
|
}
|
|
Value = MinMax(Value, 1, MaxVal);
|
|
if (Value != mLeftChar) {
|
|
iDelta = mLeftChar - Value;
|
|
mLeftChar = Value;
|
|
mTextOffset = mGutterWidth + 2 - (mLeftChar - 1) * mCharWidth;
|
|
if (std::abs(iDelta) < mCharsInWindow) {
|
|
// iTextArea = clientRect();
|
|
// iTextArea.setLeft(iTextArea.left() + mGutterWidth + 2);
|
|
scrollWindow(iDelta * mCharWidth, 0);
|
|
} else {
|
|
invalidateLines(-1, -1);
|
|
}
|
|
if ( (mOptions & (SynEditorOption::eoAutoSizeMaxScrollWidth | SynEditorOption::eoScrollPastEol))
|
|
&&
|
|
(mMaxScrollWidth < mLeftChar + mCharsInWindow)) {
|
|
setMaxScrollWidth(mLeftChar + mCharsInWindow);
|
|
} else
|
|
updateScrollbars();
|
|
setStatusChanged(SynStatusChange::scLeftChar);
|
|
}
|
|
|
|
}
|
|
|
|
int SynEdit::linesInWindow() const
|
|
{
|
|
return mLinesInWindow;
|
|
}
|
|
|
|
int SynEdit::topLine() const
|
|
{
|
|
return mTopLine;
|
|
}
|
|
|
|
void SynEdit::setTopLine(int Value)
|
|
{
|
|
if (mOptions.testFlag(SynEditorOption::eoScrollPastEof))
|
|
Value = std::min(Value, displayLineCount());
|
|
else
|
|
Value = std::min(Value, displayLineCount() - mLinesInWindow + 1);
|
|
Value = std::max(Value, 1);
|
|
if (Value != mTopLine) {
|
|
int Delta = mTopLine - Value;
|
|
mTopLine = Value;
|
|
if (mPainterLock == 0) {
|
|
if (std::abs(Delta) < mLinesInWindow) {
|
|
scrollWindow(0, mTextHeight * Delta);
|
|
} else {
|
|
invalidate();
|
|
}
|
|
}
|
|
updateScrollbars();
|
|
setStatusChanged(SynStatusChange::scTopLine);
|
|
}
|
|
}
|
|
|
|
void SynEdit::redoAdded()
|
|
{
|
|
updateModifiedStatus();
|
|
|
|
if (mRedoList->blockCount() == 0 )
|
|
doChange();
|
|
}
|
|
|
|
void SynEdit::gutterChanged()
|
|
{
|
|
if (mGutter.showLineNumbers() && mGutter.autoSize())
|
|
mGutter.autoSizeDigitCount(mLines->count());
|
|
int nW;
|
|
if (mGutter.useFontStyle()) {
|
|
QFontMetrics fm=QFontMetrics(mGutter.font());
|
|
nW = mGutter.realGutterWidth(fm.averageCharWidth());
|
|
} else {
|
|
nW = mGutter.realGutterWidth(mCharWidth);
|
|
}
|
|
if (nW == mGutterWidth)
|
|
invalidateGutter();
|
|
else
|
|
setGutterWidth(nW);
|
|
}
|
|
|
|
void SynEdit::scrollTimerHandler()
|
|
{
|
|
QPoint iMousePos;
|
|
DisplayCoord C;
|
|
int X, Y;
|
|
|
|
iMousePos = QCursor::pos();
|
|
iMousePos = mapFromGlobal(iMousePos);
|
|
C = pixelsToRowColumn(iMousePos.x(), iMousePos.y());
|
|
C.Row = MinMax(C.Row, 1, displayLineCount());
|
|
if (mScrollDeltaX != 0) {
|
|
setLeftChar(leftChar() + mScrollDeltaX);
|
|
X = leftChar();
|
|
if (mScrollDeltaX > 0) // scrolling right?
|
|
X+=charsInWindow();
|
|
C.Column = X;
|
|
}
|
|
if (mScrollDeltaY != 0) {
|
|
if (QApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier))
|
|
setTopLine(mTopLine + mScrollDeltaY * mLinesInWindow);
|
|
else
|
|
setTopLine(mTopLine + mScrollDeltaY);
|
|
Y = mTopLine;
|
|
if (mScrollDeltaY > 0) // scrolling down?
|
|
Y+=mLinesInWindow - 1;
|
|
C.Row = MinMax(Y, 1, displayLineCount());
|
|
}
|
|
BufferCoord vCaret = displayToBufferPos(C);
|
|
if ((caretX() != vCaret.Char) || (caretY() != vCaret.Line)) {
|
|
// changes to line / column in one go
|
|
incPaintLock();
|
|
auto action = finally([this]{
|
|
decPaintLock();
|
|
});
|
|
internalSetCaretXY(vCaret);
|
|
|
|
// if MouseCapture is True we're changing selection. otherwise we're dragging
|
|
if (mouseCapture())
|
|
setBlockEnd(caretXY());
|
|
}
|
|
computeScroll(iMousePos.x(), iMousePos.y());
|
|
}
|