Use quote instead of paragraph for quotes.
This commit is contained in:
parent
48012d7f4a
commit
9eb9fcf043
9 changed files with 275 additions and 240 deletions
|
|
@ -19,7 +19,7 @@ TextPalette {
|
||||||
linkAlwaysActive: bool;
|
linkAlwaysActive: bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
ParagraphStyle {
|
QuoteStyle {
|
||||||
padding: margins;
|
padding: margins;
|
||||||
verticalSkip: pixels;
|
verticalSkip: pixels;
|
||||||
header: pixels;
|
header: pixels;
|
||||||
|
|
@ -35,8 +35,8 @@ TextStyle {
|
||||||
font: font;
|
font: font;
|
||||||
linkUnderline: int;
|
linkUnderline: int;
|
||||||
lineHeight: pixels;
|
lineHeight: pixels;
|
||||||
blockquote: ParagraphStyle;
|
blockquote: QuoteStyle;
|
||||||
pre: ParagraphStyle;
|
pre: QuoteStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
kLinkUnderlineNever: 0;
|
kLinkUnderlineNever: 0;
|
||||||
|
|
@ -65,14 +65,14 @@ defaultTextPalette: TextPalette {
|
||||||
selectSpoilerFg: msgInDateFgSelected;
|
selectSpoilerFg: msgInDateFgSelected;
|
||||||
selectOverlay: msgSelectOverlay;
|
selectOverlay: msgSelectOverlay;
|
||||||
}
|
}
|
||||||
defaultParagraphStyle: ParagraphStyle {
|
defaultQuoteStyle: QuoteStyle {
|
||||||
}
|
}
|
||||||
defaultTextStyle: TextStyle {
|
defaultTextStyle: TextStyle {
|
||||||
font: normalFont;
|
font: normalFont;
|
||||||
linkUnderline: kLinkUnderlineActive;
|
linkUnderline: kLinkUnderlineActive;
|
||||||
lineHeight: 0px;
|
lineHeight: 0px;
|
||||||
blockquote: defaultParagraphStyle;
|
blockquote: defaultQuoteStyle;
|
||||||
pre: defaultParagraphStyle;
|
pre: defaultQuoteStyle;
|
||||||
}
|
}
|
||||||
semiboldTextStyle: TextStyle(defaultTextStyle) {
|
semiboldTextStyle: TextStyle(defaultTextStyle) {
|
||||||
font: semiboldFont;
|
font: semiboldFont;
|
||||||
|
|
|
||||||
179
ui/text/text.cpp
179
ui/text/text.cpp
|
|
@ -185,9 +185,9 @@ GeometryDescriptor SimpleGeometry(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void ValidateBlockPaintCache(
|
void ValidateQuotePaintCache(
|
||||||
BlockPaintCache &cache,
|
QuotePaintCache &cache,
|
||||||
const style::ParagraphStyle &st) {
|
const style::QuoteStyle &st) {
|
||||||
const auto icon = st.icon.empty() ? nullptr : &st.icon;
|
const auto icon = st.icon.empty() ? nullptr : &st.icon;
|
||||||
if (!cache.corners.isNull()
|
if (!cache.corners.isNull()
|
||||||
&& cache.bgCached == cache.bg
|
&& cache.bgCached == cache.bg
|
||||||
|
|
@ -248,11 +248,11 @@ void ValidateBlockPaintCache(
|
||||||
cache.corners = std::move(image);
|
cache.corners = std::move(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FillBlockPaint(
|
void FillQuotePaint(
|
||||||
QPainter &p,
|
QPainter &p,
|
||||||
QRect rect,
|
QRect rect,
|
||||||
const BlockPaintCache &cache,
|
const QuotePaintCache &cache,
|
||||||
const style::ParagraphStyle &st,
|
const style::QuoteStyle &st,
|
||||||
SkipBlockPaintParts parts) {
|
SkipBlockPaintParts parts) {
|
||||||
const auto &image = cache.corners;
|
const auto &image = cache.corners;
|
||||||
const auto ratio = int(image.devicePixelRatio());
|
const auto ratio = int(image.devicePixelRatio());
|
||||||
|
|
@ -431,18 +431,18 @@ void String::recountNaturalSize(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
auto pindex = paragraphIndex(nullptr);
|
auto qindex = quoteIndex(nullptr);
|
||||||
auto paragraph = paragraphByIndex(pindex);
|
auto quote = quoteByIndex(qindex);
|
||||||
auto ppadding = paragraphPadding(paragraph);
|
auto qpadding = quotePadding(quote);
|
||||||
auto pminwidth = paragraphMinWidth(paragraph);
|
auto qminwidth = quoteMinWidth(quote);
|
||||||
auto pmaxwidth = QFixed(pminwidth);
|
auto qmaxwidth = QFixed(qminwidth);
|
||||||
auto poldheight = 0;
|
auto qoldheight = 0;
|
||||||
|
|
||||||
_maxWidth = 0;
|
_maxWidth = 0;
|
||||||
_minHeight = ppadding.top();
|
_minHeight = qpadding.top();
|
||||||
auto lineHeight = 0;
|
auto lineHeight = 0;
|
||||||
auto maxWidth = QFixed();
|
auto maxWidth = QFixed();
|
||||||
auto width = QFixed(pminwidth);
|
auto width = QFixed(qminwidth);
|
||||||
auto last_rBearing = QFixed();
|
auto last_rBearing = QFixed();
|
||||||
auto last_rPadding = QFixed();
|
auto last_rPadding = QFixed();
|
||||||
for (auto &block : _blocks) {
|
for (auto &block : _blocks) {
|
||||||
|
|
@ -453,21 +453,24 @@ void String::recountNaturalSize(
|
||||||
if (!lineHeight) {
|
if (!lineHeight) {
|
||||||
lineHeight = blockHeight;
|
lineHeight = blockHeight;
|
||||||
}
|
}
|
||||||
const auto index = paragraphIndex(b);
|
accumulate_max(maxWidth, width);
|
||||||
if (pindex != index) {
|
accumulate_max(qmaxwidth, width);
|
||||||
_minHeight += ppadding.bottom();
|
|
||||||
if (paragraph) {
|
const auto index = quoteIndex(b);
|
||||||
paragraph->maxWidth = pmaxwidth.ceil().toInt();
|
if (qindex != index) {
|
||||||
paragraph->minHeight = _minHeight - poldheight;
|
_minHeight += qpadding.bottom();
|
||||||
|
if (quote) {
|
||||||
|
quote->maxWidth = qmaxwidth.ceil().toInt();
|
||||||
|
quote->minHeight = _minHeight - qoldheight;
|
||||||
}
|
}
|
||||||
poldheight = _minHeight;
|
qoldheight = _minHeight;
|
||||||
pindex = index;
|
qindex = index;
|
||||||
paragraph = paragraphByIndex(pindex);
|
quote = quoteByIndex(qindex);
|
||||||
ppadding = paragraphPadding(paragraph);
|
qpadding = quotePadding(quote);
|
||||||
pminwidth = paragraphMinWidth(paragraph);
|
qminwidth = quoteMinWidth(quote);
|
||||||
pmaxwidth = pminwidth;
|
qmaxwidth = qminwidth;
|
||||||
_minHeight += ppadding.top();
|
_minHeight += qpadding.top();
|
||||||
ppadding.setTop(0);
|
qpadding.setTop(0);
|
||||||
}
|
}
|
||||||
if (initial) {
|
if (initial) {
|
||||||
computeParagraphDirection(b->position());
|
computeParagraphDirection(b->position());
|
||||||
|
|
@ -480,9 +483,7 @@ void String::recountNaturalSize(
|
||||||
last_rBearing = 0;// b->f_rbearing(); (0 for newline)
|
last_rBearing = 0;// b->f_rbearing(); (0 for newline)
|
||||||
last_rPadding = 0;// b->f_rpadding(); (0 for newline)
|
last_rPadding = 0;// b->f_rpadding(); (0 for newline)
|
||||||
|
|
||||||
accumulate_max(maxWidth, width);
|
width = qminwidth;
|
||||||
accumulate_max(pmaxwidth, width);
|
|
||||||
width = pminwidth;
|
|
||||||
// + (b->f_width() - last_rBearing); (0 for newline)
|
// + (b->f_width() - last_rBearing); (0 for newline)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -497,7 +498,7 @@ void String::recountNaturalSize(
|
||||||
// for all the blocks to fit on their line we check each block, even the
|
// for all the blocks to fit on their line we check each block, even the
|
||||||
// intermediate one with a large negative right bearing.
|
// intermediate one with a large negative right bearing.
|
||||||
accumulate_max(maxWidth, width);
|
accumulate_max(maxWidth, width);
|
||||||
accumulate_max(pmaxwidth, width);
|
accumulate_max(qmaxwidth, width);
|
||||||
|
|
||||||
width += last_rBearing + (last_rPadding + b->f_width() - b__f_rbearing);
|
width += last_rBearing + (last_rPadding + b->f_width() - b__f_rbearing);
|
||||||
lineHeight = qMax(lineHeight, blockHeight);
|
lineHeight = qMax(lineHeight, blockHeight);
|
||||||
|
|
@ -513,17 +514,17 @@ void String::recountNaturalSize(
|
||||||
if (!lineHeight) {
|
if (!lineHeight) {
|
||||||
lineHeight = CountBlockHeight(_blocks.back().get(), _st);
|
lineHeight = CountBlockHeight(_blocks.back().get(), _st);
|
||||||
}
|
}
|
||||||
_minHeight += ppadding.top() + lineHeight + ppadding.bottom();
|
_minHeight += qpadding.top() + lineHeight + qpadding.bottom();
|
||||||
accumulate_max(maxWidth, width);
|
accumulate_max(maxWidth, width);
|
||||||
accumulate_max(pmaxwidth, width);
|
accumulate_max(qmaxwidth, width);
|
||||||
}
|
}
|
||||||
_maxWidth = maxWidth.ceil().toInt();
|
_maxWidth = maxWidth.ceil().toInt();
|
||||||
if (paragraph) {
|
if (quote) {
|
||||||
paragraph->maxWidth = pmaxwidth.ceil().toInt();
|
quote->maxWidth = qmaxwidth.ceil().toInt();
|
||||||
paragraph->minHeight = _minHeight - poldheight;
|
quote->minHeight = _minHeight - qoldheight;
|
||||||
_endsWithParagraphDetails = true;
|
_endsWithQuote = true;
|
||||||
} else {
|
} else {
|
||||||
_endsWithParagraphDetails = false;
|
_endsWithQuote = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -674,7 +675,7 @@ bool String::updateSkipBlock(int width, int height) {
|
||||||
}
|
}
|
||||||
_text.resize(block->position());
|
_text.resize(block->position());
|
||||||
_blocks.pop_back();
|
_blocks.pop_back();
|
||||||
} else if (_endsWithParagraphDetails) {
|
} else if (_endsWithQuote) {
|
||||||
_text.push_back(QChar::LineFeed);
|
_text.push_back(QChar::LineFeed);
|
||||||
_blocks.push_back(Block::Newline(
|
_blocks.push_back(Block::Newline(
|
||||||
_st->font,
|
_st->font,
|
||||||
|
|
@ -764,10 +765,10 @@ void String::enumerateLines(
|
||||||
Callback callback) const {
|
Callback callback) const {
|
||||||
const auto width = QFixed(std::max(w, _minResizeWidth));
|
const auto width = QFixed(std::max(w, _minResizeWidth));
|
||||||
|
|
||||||
auto pindex = paragraphIndex(nullptr);
|
auto qindex = quoteIndex(nullptr);
|
||||||
auto paragraph = paragraphByIndex(pindex);
|
auto quote = quoteByIndex(qindex);
|
||||||
auto ppadding = paragraphPadding(paragraph);
|
auto qpadding = quotePadding(quote);
|
||||||
auto widthLeft = width - ppadding.left() - ppadding.right();
|
auto widthLeft = width - qpadding.left() - qpadding.right();
|
||||||
auto lineHeight = 0;
|
auto lineHeight = 0;
|
||||||
auto last_rBearing = QFixed();
|
auto last_rBearing = QFixed();
|
||||||
auto last_rPadding = QFixed();
|
auto last_rPadding = QFixed();
|
||||||
|
|
@ -780,15 +781,15 @@ void String::enumerateLines(
|
||||||
if (!lineHeight) {
|
if (!lineHeight) {
|
||||||
lineHeight = blockHeight;
|
lineHeight = blockHeight;
|
||||||
}
|
}
|
||||||
lineHeight += ppadding.top();
|
lineHeight += qpadding.top();
|
||||||
const auto index = paragraphIndex(b.get());
|
const auto index = quoteIndex(b.get());
|
||||||
if (pindex != index) {
|
if (qindex != index) {
|
||||||
lineHeight += ppadding.bottom();
|
lineHeight += qpadding.bottom();
|
||||||
pindex = index;
|
qindex = index;
|
||||||
paragraph = paragraphByIndex(pindex);
|
quote = quoteByIndex(qindex);
|
||||||
ppadding = paragraphPadding(paragraph);
|
qpadding = quotePadding(quote);
|
||||||
} else {
|
} else {
|
||||||
ppadding.setTop(0);
|
qpadding.setTop(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
callback(width - widthLeft, lineHeight);
|
callback(width - widthLeft, lineHeight);
|
||||||
|
|
@ -796,7 +797,7 @@ void String::enumerateLines(
|
||||||
lineHeight = 0;
|
lineHeight = 0;
|
||||||
last_rBearing = 0;// b->f_rbearing(); (0 for newline)
|
last_rBearing = 0;// b->f_rbearing(); (0 for newline)
|
||||||
last_rPadding = 0;// b->f_rpadding(); (0 for newline)
|
last_rPadding = 0;// b->f_rpadding(); (0 for newline)
|
||||||
widthLeft = width - ppadding.left() - ppadding.right();
|
widthLeft = width - qpadding.left() - qpadding.right();
|
||||||
// - (b->f_width() - last_rBearing); (0 for newline)
|
// - (b->f_width() - last_rBearing); (0 for newline)
|
||||||
|
|
||||||
longWordLine = true;
|
longWordLine = true;
|
||||||
|
|
@ -858,15 +859,15 @@ void String::enumerateLines(
|
||||||
j_width = (j->f_width() >= 0) ? j->f_width() : -j->f_width();
|
j_width = (j->f_width() >= 0) ? j->f_width() : -j->f_width();
|
||||||
}
|
}
|
||||||
|
|
||||||
callback(width - widthLeft, lineHeight + ppadding.top());
|
callback(width - widthLeft, lineHeight + qpadding.top());
|
||||||
ppadding.setTop(0);
|
qpadding.setTop(0);
|
||||||
|
|
||||||
lineHeight = qMax(0, blockHeight);
|
lineHeight = qMax(0, blockHeight);
|
||||||
last_rBearing = j->f_rbearing();
|
last_rBearing = j->f_rbearing();
|
||||||
last_rPadding = j->f_rpadding();
|
last_rPadding = j->f_rpadding();
|
||||||
widthLeft = width
|
widthLeft = width
|
||||||
- ppadding.left()
|
- qpadding.left()
|
||||||
- ppadding.right()
|
- qpadding.right()
|
||||||
- (j_width - last_rBearing);
|
- (j_width - last_rBearing);
|
||||||
|
|
||||||
longWordLine = !wordEndsHere;
|
longWordLine = !wordEndsHere;
|
||||||
|
|
@ -877,15 +878,15 @@ void String::enumerateLines(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
callback(width - widthLeft, lineHeight + ppadding.top());
|
callback(width - widthLeft, lineHeight + qpadding.top());
|
||||||
ppadding.setTop(0);
|
qpadding.setTop(0);
|
||||||
|
|
||||||
lineHeight = qMax(0, blockHeight);
|
lineHeight = qMax(0, blockHeight);
|
||||||
last_rBearing = b__f_rbearing;
|
last_rBearing = b__f_rbearing;
|
||||||
last_rPadding = b->f_rpadding();
|
last_rPadding = b->f_rpadding();
|
||||||
widthLeft = width
|
widthLeft = width
|
||||||
- ppadding.left()
|
- qpadding.left()
|
||||||
- ppadding.right()
|
- qpadding.right()
|
||||||
- (b->f_width() - last_rBearing);
|
- (b->f_width() - last_rBearing);
|
||||||
|
|
||||||
longWordLine = true;
|
longWordLine = true;
|
||||||
|
|
@ -894,7 +895,7 @@ void String::enumerateLines(
|
||||||
if (widthLeft < width) {
|
if (widthLeft < width) {
|
||||||
callback(
|
callback(
|
||||||
width - widthLeft,
|
width - widthLeft,
|
||||||
lineHeight + ppadding.top() + ppadding.bottom());
|
lineHeight + qpadding.top() + qpadding.bottom());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1096,60 +1097,60 @@ uint16 String::countBlockLength(
|
||||||
return countBlockEnd(i, e) - (*i)->position();
|
return countBlockEnd(i, e) - (*i)->position();
|
||||||
}
|
}
|
||||||
|
|
||||||
ParagraphDetails *String::paragraphByIndex(int index) const {
|
QuoteDetails *String::quoteByIndex(int index) const {
|
||||||
Expects(!index
|
Expects(!index
|
||||||
|| (_extended && index <= _extended->paragraphs.size()));
|
|| (_extended && index <= _extended->quotes.size()));
|
||||||
|
|
||||||
return index ? &_extended->paragraphs[index - 1] : nullptr;
|
return index ? &_extended->quotes[index - 1] : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
int String::paragraphIndex(const AbstractBlock *block) const {
|
int String::quoteIndex(const AbstractBlock *block) const {
|
||||||
Expects(!block || block->type() == TextBlockType::Newline);
|
Expects(!block || block->type() == TextBlockType::Newline);
|
||||||
|
|
||||||
return block
|
return block
|
||||||
? static_cast<const NewlineBlock*>(block)->paragraphIndex()
|
? static_cast<const NewlineBlock*>(block)->quoteIndex()
|
||||||
: _startParagraphIndex;
|
: _startQuoteIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
const style::ParagraphStyle &String::paragraphStyle(
|
const style::QuoteStyle &String::quoteStyle(
|
||||||
not_null<ParagraphDetails*> info) const {
|
not_null<QuoteDetails*> quote) const {
|
||||||
return info->pre ? _st->pre : _st->blockquote;
|
return quote->pre ? _st->pre : _st->blockquote;
|
||||||
}
|
}
|
||||||
|
|
||||||
QMargins String::paragraphPadding(ParagraphDetails *info) const {
|
QMargins String::quotePadding(QuoteDetails *quote) const {
|
||||||
if (!info) {
|
if (!quote) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
const auto &st = paragraphStyle(info);
|
const auto &st = quoteStyle(quote);
|
||||||
const auto skip = st.verticalSkip;
|
const auto skip = st.verticalSkip;
|
||||||
const auto top = st.header;
|
const auto top = st.header;
|
||||||
return st.padding + QMargins(0, top + skip, 0, skip);
|
return st.padding + QMargins(0, top + skip, 0, skip);
|
||||||
}
|
}
|
||||||
|
|
||||||
int String::paragraphMinWidth(ParagraphDetails *info) const {
|
int String::quoteMinWidth(QuoteDetails *quote) const {
|
||||||
if (!info) {
|
if (!quote) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
const auto ppadding = paragraphPadding(info);
|
const auto qpadding = quotePadding(quote);
|
||||||
const auto &pheader = paragraphHeaderText(info);
|
const auto &qheader = quoteHeaderText(quote);
|
||||||
const auto pst = info ? ¶graphStyle(info) : nullptr;
|
const auto qst = quote ? "eStyle(quote) : nullptr;
|
||||||
return ppadding.left()
|
return qpadding.left()
|
||||||
+ (pheader.isEmpty() ? 0 : _st->font->monospace()->width(pheader))
|
+ (qheader.isEmpty() ? 0 : _st->font->monospace()->width(qheader))
|
||||||
+ std::max(
|
+ std::max(
|
||||||
ppadding.right(),
|
qpadding.right(),
|
||||||
((pst && !pst->icon.empty())
|
((qst && !qst->icon.empty())
|
||||||
? (pst->iconPosition.x() + pst->icon.width())
|
? (qst->iconPosition.x() + qst->icon.width())
|
||||||
: 0));
|
: 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString &String::paragraphHeaderText(ParagraphDetails *info) const {
|
const QString &String::quoteHeaderText(QuoteDetails *quote) const {
|
||||||
static const auto kEmptyHeader = QString();
|
static const auto kEmptyHeader = QString();
|
||||||
static const auto kDefaultHeader = u"code"_q;
|
static const auto kDefaultHeader = u"code"_q;
|
||||||
return (!info || !info->pre)
|
return (!quote || !quote->pre)
|
||||||
? kEmptyHeader
|
? kEmptyHeader
|
||||||
: info->language.isEmpty()
|
: quote->language.isEmpty()
|
||||||
? kDefaultHeader
|
? kDefaultHeader
|
||||||
: info->language;
|
: quote->language;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <
|
template <
|
||||||
|
|
@ -1478,7 +1479,7 @@ void String::clear() {
|
||||||
_blocks.clear();
|
_blocks.clear();
|
||||||
_extended = nullptr;
|
_extended = nullptr;
|
||||||
_maxWidth = _minHeight = 0;
|
_maxWidth = _minHeight = 0;
|
||||||
_startParagraphIndex = 0;
|
_startQuoteIndex = 0;
|
||||||
_startParagraphLTR = false;
|
_startParagraphLTR = false;
|
||||||
_startParagraphRTL = false;
|
_startParagraphRTL = false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ enum class type : uchar;
|
||||||
namespace style {
|
namespace style {
|
||||||
struct TextStyle;
|
struct TextStyle;
|
||||||
struct TextPalette;
|
struct TextPalette;
|
||||||
struct ParagraphStyle;
|
struct QuoteStyle;
|
||||||
} // namespace style
|
} // namespace style
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
|
|
@ -81,7 +81,7 @@ class AbstractBlock;
|
||||||
struct IsolatedEmoji;
|
struct IsolatedEmoji;
|
||||||
struct OnlyCustomEmoji;
|
struct OnlyCustomEmoji;
|
||||||
struct SpoilerData;
|
struct SpoilerData;
|
||||||
struct ParagraphDetails;
|
struct QuoteDetails;
|
||||||
struct ExtendedData;
|
struct ExtendedData;
|
||||||
|
|
||||||
struct Modification {
|
struct Modification {
|
||||||
|
|
@ -165,7 +165,7 @@ struct GeometryDescriptor {
|
||||||
bool elisionOneLine,
|
bool elisionOneLine,
|
||||||
bool elisionBreakEverywhere);
|
bool elisionBreakEverywhere);
|
||||||
|
|
||||||
struct BlockPaintCache {
|
struct QuotePaintCache {
|
||||||
QImage corners;
|
QImage corners;
|
||||||
QColor headerCached;
|
QColor headerCached;
|
||||||
QColor bgCached;
|
QColor bgCached;
|
||||||
|
|
@ -178,19 +178,19 @@ struct BlockPaintCache {
|
||||||
QColor icon;
|
QColor icon;
|
||||||
};
|
};
|
||||||
|
|
||||||
void ValidateBlockPaintCache(
|
void ValidateQuotePaintCache(
|
||||||
BlockPaintCache &cache,
|
QuotePaintCache &cache,
|
||||||
const style::ParagraphStyle &st);
|
const style::QuoteStyle &st);
|
||||||
|
|
||||||
struct SkipBlockPaintParts {
|
struct SkipBlockPaintParts {
|
||||||
bool skipTop : 1 = false;
|
bool skipTop : 1 = false;
|
||||||
bool skipBottom : 1 = false;
|
bool skipBottom : 1 = false;
|
||||||
};
|
};
|
||||||
void FillBlockPaint(
|
void FillQuotePaint(
|
||||||
QPainter &p,
|
QPainter &p,
|
||||||
QRect rect,
|
QRect rect,
|
||||||
const BlockPaintCache &cache,
|
const QuotePaintCache &cache,
|
||||||
const style::ParagraphStyle &st,
|
const style::QuoteStyle &st,
|
||||||
SkipBlockPaintParts parts = {});
|
SkipBlockPaintParts parts = {});
|
||||||
|
|
||||||
struct PaintContext {
|
struct PaintContext {
|
||||||
|
|
@ -202,8 +202,8 @@ struct PaintContext {
|
||||||
QRect clip;
|
QRect clip;
|
||||||
|
|
||||||
const style::TextPalette *palette = nullptr;
|
const style::TextPalette *palette = nullptr;
|
||||||
BlockPaintCache *pre = nullptr;
|
QuotePaintCache *pre = nullptr;
|
||||||
BlockPaintCache *blockquote = nullptr;
|
QuotePaintCache *blockquote = nullptr;
|
||||||
std::span<SpecialColor> colors;
|
std::span<SpecialColor> colors;
|
||||||
SpoilerMessCache *spoiler = nullptr;
|
SpoilerMessCache *spoiler = nullptr;
|
||||||
crl::time now = 0;
|
crl::time now = 0;
|
||||||
|
|
@ -375,16 +375,15 @@ private:
|
||||||
[[nodiscard]] uint16 countBlockLength(
|
[[nodiscard]] uint16 countBlockLength(
|
||||||
const TextBlocks::const_iterator &i,
|
const TextBlocks::const_iterator &i,
|
||||||
const TextBlocks::const_iterator &e) const;
|
const TextBlocks::const_iterator &e) const;
|
||||||
[[nodiscard]] ParagraphDetails *paragraphByIndex(int index) const;
|
[[nodiscard]] QuoteDetails *quoteByIndex(int index) const;
|
||||||
[[nodiscard]] const style::ParagraphStyle ¶graphStyle(
|
[[nodiscard]] const style::QuoteStyle "eStyle(
|
||||||
not_null<ParagraphDetails*> info) const;
|
not_null<QuoteDetails*> quote) const;
|
||||||
[[nodiscard]] QMargins paragraphPadding(ParagraphDetails *info) const;
|
[[nodiscard]] QMargins quotePadding(QuoteDetails *quote) const;
|
||||||
[[nodiscard]] int paragraphMinWidth(ParagraphDetails *info) const;
|
[[nodiscard]] int quoteMinWidth(QuoteDetails *quote) const;
|
||||||
[[nodiscard]] const QString ¶graphHeaderText(
|
[[nodiscard]] const QString "eHeaderText(QuoteDetails *quote) const;
|
||||||
ParagraphDetails *info) const;
|
|
||||||
|
|
||||||
// block must be either nullptr or a pointer to a NewlineBlock.
|
// block must be either nullptr or a pointer to a NewlineBlock.
|
||||||
[[nodiscard]] int paragraphIndex(const AbstractBlock *block) const;
|
[[nodiscard]] int quoteIndex(const AbstractBlock *block) const;
|
||||||
|
|
||||||
// Template method for originalText(), originalTextWithEntities().
|
// Template method for originalText(), originalTextWithEntities().
|
||||||
template <
|
template <
|
||||||
|
|
@ -425,7 +424,7 @@ private:
|
||||||
int _minResizeWidth = 0;
|
int _minResizeWidth = 0;
|
||||||
int _maxWidth = 0;
|
int _maxWidth = 0;
|
||||||
int _minHeight = 0;
|
int _minHeight = 0;
|
||||||
uint16 _startParagraphIndex = 0;
|
uint16 _startQuoteIndex = 0;
|
||||||
bool _startParagraphLTR : 1 = false;
|
bool _startParagraphLTR : 1 = false;
|
||||||
bool _startParagraphRTL : 1 = false;
|
bool _startParagraphRTL : 1 = false;
|
||||||
bool _hasCustomEmoji : 1 = false;
|
bool _hasCustomEmoji : 1 = false;
|
||||||
|
|
@ -433,7 +432,7 @@ private:
|
||||||
bool _isOnlyCustomEmoji : 1 = false;
|
bool _isOnlyCustomEmoji : 1 = false;
|
||||||
bool _hasNotEmojiAndSpaces : 1 = false;
|
bool _hasNotEmojiAndSpaces : 1 = false;
|
||||||
bool _skipBlockAddedNewline : 1 = false;
|
bool _skipBlockAddedNewline : 1 = false;
|
||||||
bool _endsWithParagraphDetails : 1 = false;
|
bool _endsWithQuote : 1 = false;
|
||||||
|
|
||||||
friend class Parser;
|
friend class Parser;
|
||||||
friend class Renderer;
|
friend class Renderer;
|
||||||
|
|
|
||||||
|
|
@ -107,15 +107,15 @@ public:
|
||||||
uint16 linkIndex,
|
uint16 linkIndex,
|
||||||
uint16 colorIndex);
|
uint16 colorIndex);
|
||||||
|
|
||||||
[[nodiscard]] uint16 paragraphIndex() const {
|
[[nodiscard]] uint16 quoteIndex() const {
|
||||||
return _paragraphIndex;
|
return _quoteIndex;
|
||||||
}
|
}
|
||||||
[[nodiscard]] Qt::LayoutDirection paragraphDirection() const {
|
[[nodiscard]] Qt::LayoutDirection paragraphDirection() const {
|
||||||
return UnpackParagraphDirection(_paragraphLTR, _paragraphRTL);
|
return UnpackParagraphDirection(_paragraphLTR, _paragraphRTL);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint16 _paragraphIndex = 0;
|
uint16 _quoteIndex = 0;
|
||||||
bool _paragraphLTR : 1 = false;
|
bool _paragraphLTR : 1 = false;
|
||||||
bool _paragraphRTL : 1 = false;
|
bool _paragraphRTL : 1 = false;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ struct SpoilerData {
|
||||||
bool revealed = false;
|
bool revealed = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ParagraphDetails {
|
struct QuoteDetails {
|
||||||
QString language;
|
QString language;
|
||||||
ClickHandlerPtr copy;
|
ClickHandlerPtr copy;
|
||||||
int copyWidth = 0;
|
int copyWidth = 0;
|
||||||
|
|
@ -56,7 +56,7 @@ struct ParagraphDetails {
|
||||||
|
|
||||||
struct ExtendedData {
|
struct ExtendedData {
|
||||||
std::vector<ClickHandlerPtr> links;
|
std::vector<ClickHandlerPtr> links;
|
||||||
std::vector<ParagraphDetails> paragraphs;
|
std::vector<QuoteDetails> quotes;
|
||||||
std::unique_ptr<SpoilerData> spoiler;
|
std::unique_ptr<SpoilerData> spoiler;
|
||||||
std::vector<Modification> modifications;
|
std::vector<Modification> modifications;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -212,7 +212,7 @@ void Parser::createBlock(int32 skipBack) {
|
||||||
} else if (newline) {
|
} else if (newline) {
|
||||||
push(&Block::Newline);
|
push(&Block::Newline);
|
||||||
auto &newline = _t->_blocks.back().unsafe<NewlineBlock>();
|
auto &newline = _t->_blocks.back().unsafe<NewlineBlock>();
|
||||||
newline._paragraphIndex = _paragraphIndex;
|
newline._quoteIndex = _quoteIndex;
|
||||||
} else {
|
} else {
|
||||||
push(&Block::Text, _t->_minResizeWidth);
|
push(&Block::Text, _t->_minResizeWidth);
|
||||||
}
|
}
|
||||||
|
|
@ -233,7 +233,7 @@ void Parser::createNewlineBlock(bool fromOriginalText) {
|
||||||
createBlock();
|
createBlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Parser::ensureAtNewline(ParagraphDetails details) {
|
void Parser::ensureAtNewline(QuoteDetails quote) {
|
||||||
createBlock();
|
createBlock();
|
||||||
const auto lastType = _t->_blocks.empty()
|
const auto lastType = _t->_blocks.empty()
|
||||||
? TextBlockType::Newline
|
? TextBlockType::Newline
|
||||||
|
|
@ -243,15 +243,15 @@ void Parser::ensureAtNewline(ParagraphDetails details) {
|
||||||
createNewlineBlock(false);
|
createNewlineBlock(false);
|
||||||
_customEmojiData = base::take(saved);
|
_customEmojiData = base::take(saved);
|
||||||
}
|
}
|
||||||
auto ¶graphs = _t->ensureExtended()->paragraphs;
|
auto "es = _t->ensureExtended()->quotes;
|
||||||
paragraphs.push_back(std::move(details));
|
quotes.push_back(std::move(quote));
|
||||||
const auto index = _paragraphIndex = int(paragraphs.size());
|
const auto index = _quoteIndex = int(quotes.size());
|
||||||
if (_t->_blocks.empty()) {
|
if (_t->_blocks.empty()) {
|
||||||
_t->_startParagraphIndex = index;
|
_t->_startQuoteIndex = index;
|
||||||
} else {
|
} else {
|
||||||
auto &last = _t->_blocks.back();
|
auto &last = _t->_blocks.back();
|
||||||
Assert(last->type() == TextBlockType::Newline);
|
Assert(last->type() == TextBlockType::Newline);
|
||||||
last.unsafe<NewlineBlock>()._paragraphIndex = index;
|
last.unsafe<NewlineBlock>()._quoteIndex = index;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -274,14 +274,14 @@ void Parser::finishEntities() {
|
||||||
if ((*flags)
|
if ((*flags)
|
||||||
& (TextBlockFlag::Pre
|
& (TextBlockFlag::Pre
|
||||||
| TextBlockFlag::Blockquote)) {
|
| TextBlockFlag::Blockquote)) {
|
||||||
_paragraphIndex = 0;
|
_quoteIndex = 0;
|
||||||
if (lastType != TextBlockType::Newline) {
|
if (lastType != TextBlockType::Newline) {
|
||||||
_newlineAwaited = true;
|
_newlineAwaited = true;
|
||||||
} else if (_t->_blocks.empty()) {
|
} else if (_t->_blocks.empty()) {
|
||||||
_t->_startParagraphIndex = 0;
|
_t->_startQuoteIndex = 0;
|
||||||
} else {
|
} else {
|
||||||
auto &last = _t->_blocks.back();
|
auto &last = _t->_blocks.back();
|
||||||
last.unsafe<NewlineBlock>()._paragraphIndex = 0;
|
last.unsafe<NewlineBlock>()._quoteIndex = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (IsMono(*flags)) {
|
if (IsMono(*flags)) {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
namespace Ui::Text {
|
namespace Ui::Text {
|
||||||
|
|
||||||
struct ParagraphDetails;
|
struct QuoteDetails;
|
||||||
|
|
||||||
class Parser {
|
class Parser {
|
||||||
public:
|
public:
|
||||||
|
|
@ -60,7 +60,7 @@ private:
|
||||||
void blockCreated();
|
void blockCreated();
|
||||||
void createBlock(int32 skipBack = 0);
|
void createBlock(int32 skipBack = 0);
|
||||||
void createNewlineBlock(bool fromOriginalText);
|
void createNewlineBlock(bool fromOriginalText);
|
||||||
void ensureAtNewline(ParagraphDetails details);
|
void ensureAtNewline(QuoteDetails quote);
|
||||||
|
|
||||||
// Returns true if at least one entity was parsed in the current position.
|
// Returns true if at least one entity was parsed in the current position.
|
||||||
bool checkEntities();
|
bool checkEntities();
|
||||||
|
|
@ -115,7 +115,7 @@ private:
|
||||||
uint16 _linkIndex = 0;
|
uint16 _linkIndex = 0;
|
||||||
uint16 _colorIndex = 0;
|
uint16 _colorIndex = 0;
|
||||||
uint16 _monoIndex = 0;
|
uint16 _monoIndex = 0;
|
||||||
uint16 _paragraphIndex = 0;
|
uint16 _quoteIndex = 0;
|
||||||
EmojiPtr _emoji = nullptr; // current emoji, if current word is an emoji, or zero
|
EmojiPtr _emoji = nullptr; // current emoji, if current word is an emoji, or zero
|
||||||
int32 _blockStart = 0; // offset in result, from which current parsed block is started
|
int32 _blockStart = 0; // offset in result, from which current parsed block is started
|
||||||
int32 _diacritics = 0; // diacritic chars skipped without good char
|
int32 _diacritics = 0; // diacritic chars skipped without good char
|
||||||
|
|
|
||||||
|
|
@ -179,8 +179,8 @@ void Renderer::draw(QPainter &p, const PaintContext &context) {
|
||||||
? _originalPen
|
? _originalPen
|
||||||
: _palette->selectFg->p;
|
: _palette->selectFg->p;
|
||||||
|
|
||||||
_x = context.position.x();
|
_x = _startLeft = context.position.x();
|
||||||
_y = context.position.y();
|
_y = _startTop = context.position.y();
|
||||||
_yFrom = context.clip.isNull() ? 0 : context.clip.y();
|
_yFrom = context.clip.isNull() ? 0 : context.clip.y();
|
||||||
_yTo = context.clip.isNull()
|
_yTo = context.clip.isNull()
|
||||||
? -1
|
? -1
|
||||||
|
|
@ -206,8 +206,8 @@ void Renderer::draw(QPainter &p, const PaintContext &context) {
|
||||||
? (1. - _spoiler->revealAnimation.value(
|
? (1. - _spoiler->revealAnimation.value(
|
||||||
_spoiler->revealed ? 1. : 0.))
|
_spoiler->revealed ? 1. : 0.))
|
||||||
: 0.;
|
: 0.;
|
||||||
_preBlockCache = context.pre;
|
_quotePreCache = context.pre;
|
||||||
_blockquoteBlockCache = context.blockquote;
|
_quoteBlockquoteCache = context.blockquote;
|
||||||
enumerate();
|
enumerate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -227,13 +227,10 @@ void Renderer::enumerate() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_startLeft = _x.toInt();
|
|
||||||
_startTop = _y;
|
|
||||||
|
|
||||||
if ((*_t->_blocks.cbegin())->type() != TextBlockType::Newline) {
|
if ((*_t->_blocks.cbegin())->type() != TextBlockType::Newline) {
|
||||||
initNextParagraph(
|
initNextParagraph(
|
||||||
_t->_blocks.cbegin(),
|
_t->_blocks.cbegin(),
|
||||||
_t->_startParagraphIndex,
|
_t->_startQuoteIndex,
|
||||||
UnpackParagraphDirection(
|
UnpackParagraphDirection(
|
||||||
_t->_startParagraphLTR,
|
_t->_startParagraphLTR,
|
||||||
_t->_startParagraphRTL));
|
_t->_startParagraphRTL));
|
||||||
|
|
@ -262,9 +259,9 @@ void Renderer::enumerate() {
|
||||||
if (!_lineHeight) {
|
if (!_lineHeight) {
|
||||||
_lineHeight = blockHeight;
|
_lineHeight = blockHeight;
|
||||||
}
|
}
|
||||||
const auto pindex = static_cast<const NewlineBlock*>(b)->paragraphIndex();
|
const auto qindex = static_cast<const NewlineBlock*>(b)->quoteIndex();
|
||||||
const auto changed = (_pindex != pindex);
|
const auto changed = (_quoteIndex != qindex);
|
||||||
fillParagraphBg(changed ? _ppadding.bottom() : 0);
|
fillParagraphBg(changed ? _quotePadding.bottom() : 0);
|
||||||
if (!drawLine((*i)->position(), i, e)) {
|
if (!drawLine((*i)->position(), i, e)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -277,7 +274,7 @@ void Renderer::enumerate() {
|
||||||
|
|
||||||
initNextParagraph(
|
initNextParagraph(
|
||||||
i + 1,
|
i + 1,
|
||||||
pindex,
|
qindex,
|
||||||
static_cast<const NewlineBlock*>(b)->paragraphDirection());
|
static_cast<const NewlineBlock*>(b)->paragraphDirection());
|
||||||
|
|
||||||
longWordLine = true;
|
longWordLine = true;
|
||||||
|
|
@ -395,7 +392,7 @@ void Renderer::enumerate() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (_lineStart < _t->_text.size()) {
|
if (_lineStart < _t->_text.size()) {
|
||||||
fillParagraphBg(_ppadding.bottom());
|
fillParagraphBg(_quotePadding.bottom());
|
||||||
if (!drawLine(_t->_text.size(), e, e)) {
|
if (!drawLine(_t->_text.size(), e, e)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -407,30 +404,31 @@ void Renderer::enumerate() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::fillParagraphBg(int paddingBottom) {
|
void Renderer::fillParagraphBg(int paddingBottom) {
|
||||||
const auto cache = (!_p || !_paragraph)
|
const auto cache = (!_p || !_quote)
|
||||||
? nullptr
|
? nullptr
|
||||||
: _paragraph->pre
|
: _quote->pre
|
||||||
? _preBlockCache
|
? _quotePreCache
|
||||||
: _paragraph->blockquote
|
: _quote->blockquote
|
||||||
? _blockquoteBlockCache
|
? _quoteBlockquoteCache
|
||||||
: nullptr;
|
: nullptr;
|
||||||
if (cache) {
|
if (cache) {
|
||||||
const auto &st = _t->paragraphStyle(_paragraph);
|
const auto &st = _t->quoteStyle(_quote);
|
||||||
auto &valid = _paragraph->pre
|
auto &valid = _quote->pre
|
||||||
? _preBlockCacheValid
|
? _quotePreValid
|
||||||
: _blockquoteBlockCacheValid;
|
: _quoteBlockquoteValid;
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
valid = true;
|
valid = true;
|
||||||
ValidateBlockPaintCache(*cache, st);
|
ValidateQuotePaintCache(*cache, st);
|
||||||
}
|
}
|
||||||
const auto skip = st.verticalSkip;
|
const auto skip = st.verticalSkip;
|
||||||
const auto isTop = (_y != _blockLineTop);
|
const auto isTop = (_y != _quoteLineTop);
|
||||||
const auto isBottom = (paddingBottom != 0);
|
const auto isBottom = (paddingBottom != 0);
|
||||||
const auto top = _blockLineTop + (isTop ? skip : 0);
|
const auto left = _startLeft + _quoteShift;
|
||||||
|
const auto top = _quoteLineTop + (isTop ? skip : 0);
|
||||||
const auto fill = _y + _lineHeight + paddingBottom - top
|
const auto fill = _y + _lineHeight + paddingBottom - top
|
||||||
- (isBottom ? skip : 0);
|
- (isBottom ? skip : 0);
|
||||||
const auto rect = QRect(_startLeft, top, _startLineWidth, fill);
|
const auto rect = QRect(left, top, _startLineWidth, fill);
|
||||||
FillBlockPaint(*_p, rect, *cache, st, {
|
FillQuotePaint(*_p, rect, *cache, st, {
|
||||||
.skipTop = !isTop,
|
.skipTop = !isTop,
|
||||||
.skipBottom = !isBottom,
|
.skipBottom = !isBottom,
|
||||||
});
|
});
|
||||||
|
|
@ -442,10 +440,10 @@ void Renderer::fillParagraphBg(int paddingBottom) {
|
||||||
const auto baseline = position + QPoint(0, font->ascent);
|
const auto baseline = position + QPoint(0, font->ascent);
|
||||||
_p->setFont(font);
|
_p->setFont(font);
|
||||||
_p->setPen(_palette->monoFg->p);
|
_p->setPen(_palette->monoFg->p);
|
||||||
_p->drawText(baseline, _t->paragraphHeaderText(_paragraph));
|
_p->drawText(baseline, _t->quoteHeaderText(_quote));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_blockLineTop = _y + _lineHeight + paddingBottom;
|
_quoteLineTop = _y + _lineHeight + paddingBottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
StateResult Renderer::getState(
|
StateResult Renderer::getState(
|
||||||
|
|
@ -484,27 +482,28 @@ void Renderer::initNextParagraph(
|
||||||
String::TextBlocks::const_iterator i,
|
String::TextBlocks::const_iterator i,
|
||||||
int16 paragraphIndex,
|
int16 paragraphIndex,
|
||||||
Qt::LayoutDirection direction) {
|
Qt::LayoutDirection direction) {
|
||||||
_parDirection = (direction == Qt::LayoutDirectionAuto)
|
_paragraphDirection = (direction == Qt::LayoutDirectionAuto)
|
||||||
? style::LayoutDirection()
|
? style::LayoutDirection()
|
||||||
: direction;
|
: direction;
|
||||||
_parStartBlock = i;
|
_paragraphStartBlock = i;
|
||||||
_paragraphWidthRemaining = 0;
|
_paragraphWidthRemaining = 0;
|
||||||
if (_pindex != paragraphIndex) {
|
if (_quoteIndex != paragraphIndex) {
|
||||||
_y += _ppadding.bottom();
|
_y += _quotePadding.bottom();
|
||||||
_pindex = paragraphIndex;
|
_quoteIndex = paragraphIndex;
|
||||||
_paragraph = _t->paragraphByIndex(paragraphIndex);
|
_quote = _t->quoteByIndex(paragraphIndex);
|
||||||
_ppadding = _t->paragraphPadding(_paragraph);
|
_quotePadding = _t->quotePadding(_quote);
|
||||||
_blockLineTop = _y;
|
_quoteLineTop = _y;
|
||||||
_y += _ppadding.top();
|
_y += _quotePadding.top();
|
||||||
_ppadding.setTop(0);
|
_quotePadding.setTop(0);
|
||||||
|
_quoteDirection = _paragraphDirection;
|
||||||
}
|
}
|
||||||
const auto e = _t->_blocks.cend();
|
const auto e = _t->_blocks.cend();
|
||||||
if (i == e) {
|
if (i == e) {
|
||||||
_lineStart = _parStart = _t->_text.size();
|
_lineStart = _paragraphStart = _t->_text.size();
|
||||||
_lineStartBlock = _t->_blocks.size();
|
_lineStartBlock = _t->_blocks.size();
|
||||||
_parLength = 0;
|
_paragraphLength = 0;
|
||||||
} else {
|
} else {
|
||||||
_lineStart = _parStart = (*i)->position();
|
_lineStart = _paragraphStart = (*i)->position();
|
||||||
_lineStartBlock = i - _t->_blocks.cbegin();
|
_lineStartBlock = i - _t->_blocks.cbegin();
|
||||||
|
|
||||||
auto last_rPadding = QFixed(0);
|
auto last_rPadding = QFixed(0);
|
||||||
|
|
@ -520,10 +519,13 @@ void Renderer::initNextParagraph(
|
||||||
- rBearing;
|
- rBearing;
|
||||||
last_rBearing = rBearing;
|
last_rBearing = rBearing;
|
||||||
}
|
}
|
||||||
_parLength = ((i == e) ? _t->_text.size() : (*i)->position()) - _parStart;
|
_paragraphLength = ((i == e)
|
||||||
|
? _t->_text.size()
|
||||||
|
: (*i)->position())
|
||||||
|
- _paragraphStart;
|
||||||
}
|
}
|
||||||
_parAnalysis.resize(0);
|
_paragraphAnalysis.resize(0);
|
||||||
_paragraphWidthRemaining += _ppadding.left() + _ppadding.right();
|
_paragraphWidthRemaining += _quotePadding.left() + _quotePadding.right();
|
||||||
initNextLine();
|
initNextLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -533,31 +535,48 @@ void Renderer::initNextLine() {
|
||||||
.top = (_y - _startTop),
|
.top = (_y - _startTop),
|
||||||
.width = _paragraphWidthRemaining.ceil().toInt(),
|
.width = _paragraphWidthRemaining.ceil().toInt(),
|
||||||
});
|
});
|
||||||
_blockLineTop += _startTop + line.top - _y;
|
_quoteLineTop += _startTop + line.top - _y;
|
||||||
_x = _startLeft + line.left + _ppadding.left();
|
_x = _startLeft + line.left + _quotePadding.left();
|
||||||
_y = _startTop + line.top;
|
_y = _startTop + line.top;
|
||||||
_startLineWidth = line.width;
|
_startLineWidth = line.width;
|
||||||
_lineWidth = _startLineWidth - _ppadding.left() - _ppadding.right();
|
_quoteShift = 0;
|
||||||
|
if (_quote && _quote->maxWidth < _startLineWidth) {
|
||||||
|
const auto delta = _startLineWidth - _quote->maxWidth;
|
||||||
|
_startLineWidth = _quote->maxWidth;
|
||||||
|
|
||||||
|
if (_align & Qt::AlignHCenter) {
|
||||||
|
_quoteShift = delta / 2;
|
||||||
|
} else if (((_align & Qt::AlignLeft)
|
||||||
|
&& (_quoteDirection == Qt::RightToLeft))
|
||||||
|
|| ((_align & Qt::AlignRight)
|
||||||
|
&& (_quoteDirection == Qt::LeftToRight))) {
|
||||||
|
_quoteShift = delta;
|
||||||
|
}
|
||||||
|
_x += _quoteShift;
|
||||||
|
}
|
||||||
|
_lineWidth = _startLineWidth
|
||||||
|
- _quotePadding.left()
|
||||||
|
- _quotePadding.right();
|
||||||
_wLeft = _lineWidth;
|
_wLeft = _lineWidth;
|
||||||
_elidedLine = line.elided;
|
_elidedLine = line.elided;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::initParagraphBidi() {
|
void Renderer::initParagraphBidi() {
|
||||||
if (!_parLength || !_parAnalysis.isEmpty()) {
|
if (!_paragraphLength || !_paragraphAnalysis.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String::TextBlocks::const_iterator i = _parStartBlock, e = _t->_blocks.cend(), n = i + 1;
|
String::TextBlocks::const_iterator i = _paragraphStartBlock, e = _t->_blocks.cend(), n = i + 1;
|
||||||
|
|
||||||
bool ignore = false;
|
bool ignore = false;
|
||||||
bool rtl = (_parDirection == Qt::RightToLeft);
|
bool rtl = (_paragraphDirection == Qt::RightToLeft);
|
||||||
if (!ignore && !rtl) {
|
if (!ignore && !rtl) {
|
||||||
ignore = true;
|
ignore = true;
|
||||||
const ushort *start = reinterpret_cast<const ushort*>(_str) + _parStart;
|
const ushort *start = reinterpret_cast<const ushort*>(_str) + _paragraphStart;
|
||||||
const ushort *curr = start;
|
const ushort *curr = start;
|
||||||
const ushort *end = start + _parLength;
|
const ushort *end = start + _paragraphLength;
|
||||||
while (curr < end) {
|
while (curr < end) {
|
||||||
while (n != e && (*n)->position() <= _parStart + (curr - start)) {
|
while (n != e && (*n)->position() <= _paragraphStart + (curr - start)) {
|
||||||
i = n;
|
i = n;
|
||||||
++n;
|
++n;
|
||||||
}
|
}
|
||||||
|
|
@ -572,21 +591,21 @@ void Renderer::initParagraphBidi() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_parAnalysis.resize(_parLength);
|
_paragraphAnalysis.resize(_paragraphLength);
|
||||||
QScriptAnalysis *analysis = _parAnalysis.data();
|
QScriptAnalysis *analysis = _paragraphAnalysis.data();
|
||||||
|
|
||||||
BidiControl control(rtl);
|
BidiControl control(rtl);
|
||||||
|
|
||||||
_parHasBidi = false;
|
_paragraphHasBidi = false;
|
||||||
if (ignore) {
|
if (ignore) {
|
||||||
memset(analysis, 0, _parLength * sizeof(QScriptAnalysis));
|
memset(analysis, 0, _paragraphLength * sizeof(QScriptAnalysis));
|
||||||
if (rtl) {
|
if (rtl) {
|
||||||
for (int i = 0; i < _parLength; ++i)
|
for (int i = 0; i < _paragraphLength; ++i)
|
||||||
analysis[i].bidiLevel = 1;
|
analysis[i].bidiLevel = 1;
|
||||||
_parHasBidi = true;
|
_paragraphHasBidi = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_parHasBidi = eBidiItemize(analysis, control);
|
_paragraphHasBidi = eBidiItemize(analysis, control);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -651,14 +670,17 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
|
||||||
auto x = _x;
|
auto x = _x;
|
||||||
if (_align & Qt::AlignHCenter) {
|
if (_align & Qt::AlignHCenter) {
|
||||||
x += (_wLeft / 2).toInt();
|
x += (_wLeft / 2).toInt();
|
||||||
} else if (((_align & Qt::AlignLeft) && _parDirection == Qt::RightToLeft) || ((_align & Qt::AlignRight) && _parDirection == Qt::LeftToRight)) {
|
} else if (((_align & Qt::AlignLeft)
|
||||||
|
&& (_paragraphDirection == Qt::RightToLeft))
|
||||||
|
|| ((_align & Qt::AlignRight)
|
||||||
|
&& (_paragraphDirection == Qt::LeftToRight))) {
|
||||||
x += _wLeft;
|
x += _wLeft;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_p) {
|
if (!_p) {
|
||||||
if (_lookupX < x) {
|
if (_lookupX < x) {
|
||||||
if (_lookupSymbol) {
|
if (_lookupSymbol) {
|
||||||
if (_parDirection == Qt::RightToLeft) {
|
if (_paragraphDirection == Qt::RightToLeft) {
|
||||||
_lookupResult.symbol = (_lineEnd > _lineStart) ? (_lineEnd - 1) : _lineStart;
|
_lookupResult.symbol = (_lineEnd > _lineStart) ? (_lineEnd - 1) : _lineStart;
|
||||||
_lookupResult.afterSymbol = (_lineEnd > _lineStart) ? true : false;
|
_lookupResult.afterSymbol = (_lineEnd > _lineStart) ? true : false;
|
||||||
// _lookupResult.uponSymbol = ((_lookupX >= _x) && (_lineEnd < _t->_text.size()) && (!_endBlock || _endBlock->type() != TextBlockType::Skip)) ? true : false;
|
// _lookupResult.uponSymbol = ((_lookupX >= _x) && (_lineEnd < _t->_text.size()) && (!_endBlock || _endBlock->type() != TextBlockType::Skip)) ? true : false;
|
||||||
|
|
@ -674,7 +696,7 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
|
||||||
_lookupResult.uponSymbol = false;
|
_lookupResult.uponSymbol = false;
|
||||||
return false;
|
return false;
|
||||||
} else if (_lookupX >= x + (_lineWidth - _wLeft)) {
|
} else if (_lookupX >= x + (_lineWidth - _wLeft)) {
|
||||||
if (_parDirection == Qt::RightToLeft) {
|
if (_paragraphDirection == Qt::RightToLeft) {
|
||||||
_lookupResult.symbol = _lineStart;
|
_lookupResult.symbol = _lineStart;
|
||||||
_lookupResult.afterSymbol = false;
|
_lookupResult.afterSymbol = false;
|
||||||
// _lookupResult.uponSymbol = ((_lookupX < _x + _w) && (_lineStart > 0)) ? true : false;
|
// _lookupResult.uponSymbol = ((_lookupX < _x + _w) && (_lineStart > 0)) ? true : false;
|
||||||
|
|
@ -700,14 +722,14 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
|
||||||
&& (_selection.from <= trimmedLineEnd)
|
&& (_selection.from <= trimmedLineEnd)
|
||||||
&& (!_endBlock || _endBlock->type() != TextBlockType::Skip);
|
&& (!_endBlock || _endBlock->type() != TextBlockType::Skip);
|
||||||
|
|
||||||
if ((selectFromStart && _parDirection == Qt::LeftToRight)
|
if ((selectFromStart && _paragraphDirection == Qt::LeftToRight)
|
||||||
|| (selectTillEnd && _parDirection == Qt::RightToLeft)) {
|
|| (selectTillEnd && _paragraphDirection == Qt::RightToLeft)) {
|
||||||
if (x > _x) {
|
if (x > _x) {
|
||||||
fillSelectRange({ _x, x });
|
fillSelectRange({ _x, x });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((selectTillEnd && _parDirection == Qt::LeftToRight)
|
if ((selectTillEnd && _paragraphDirection == Qt::LeftToRight)
|
||||||
|| (selectFromStart && _parDirection == Qt::RightToLeft)) {
|
|| (selectFromStart && _paragraphDirection == Qt::RightToLeft)) {
|
||||||
if (x < _x + _wLeft) {
|
if (x < _x + _wLeft) {
|
||||||
fillSelectRange({ x + _lineWidth - _wLeft, _x + _lineWidth });
|
fillSelectRange({ x + _lineWidth - _wLeft, _x + _lineWidth });
|
||||||
}
|
}
|
||||||
|
|
@ -723,7 +745,7 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
|
||||||
|
|
||||||
_f = _t->_st->font;
|
_f = _t->_st->font;
|
||||||
QStackTextEngine engine(lineText, _f->f);
|
QStackTextEngine engine(lineText, _f->f);
|
||||||
engine.option.setTextDirection(_parDirection);
|
engine.option.setTextDirection(_paragraphDirection);
|
||||||
_e = &engine;
|
_e = &engine;
|
||||||
|
|
||||||
eItemize();
|
eItemize();
|
||||||
|
|
@ -813,7 +835,7 @@ bool Renderer::drawLine(uint16 _lineEnd, const String::TextBlocks::const_iterato
|
||||||
}
|
}
|
||||||
if (_lookupSymbol) {
|
if (_lookupSymbol) {
|
||||||
if (_type == TextBlockType::Skip) {
|
if (_type == TextBlockType::Skip) {
|
||||||
if (_parDirection == Qt::RightToLeft) {
|
if (_paragraphDirection == Qt::RightToLeft) {
|
||||||
_lookupResult.symbol = _lineStart;
|
_lookupResult.symbol = _lineStart;
|
||||||
_lookupResult.afterSymbol = false;
|
_lookupResult.afterSymbol = false;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1291,19 +1313,25 @@ void Renderer::elideSaveBlock(int32 blockIndex, const AbstractBlock *&_endBlock,
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::setElideBidi(int32 elideStart, int32 elideLen) {
|
void Renderer::setElideBidi(int32 elideStart, int32 elideLen) {
|
||||||
int32 newParLength = elideStart + elideLen - _parStart;
|
int32 newParLength = elideStart + elideLen - _paragraphStart;
|
||||||
if (newParLength > _parAnalysis.size()) {
|
if (newParLength > _paragraphAnalysis.size()) {
|
||||||
_parAnalysis.resize(newParLength);
|
_paragraphAnalysis.resize(newParLength);
|
||||||
}
|
}
|
||||||
for (int32 i = elideLen; i > 0; --i) {
|
for (int32 i = elideLen; i > 0; --i) {
|
||||||
_parAnalysis[newParLength - i].bidiLevel = (_parDirection == Qt::RightToLeft) ? 1 : 0;
|
_paragraphAnalysis[newParLength - i].bidiLevel
|
||||||
|
= (_paragraphDirection == Qt::RightToLeft) ? 1 : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::prepareElidedLine(QString &lineText, int32 lineStart, int32 &lineLength, const AbstractBlock *&_endBlock, int repeat) {
|
void Renderer::prepareElidedLine(
|
||||||
|
QString &lineText,
|
||||||
|
int32 lineStart,
|
||||||
|
int32 &lineLength,
|
||||||
|
const AbstractBlock *&_endBlock,
|
||||||
|
int repeat) {
|
||||||
_f = _t->_st->font;
|
_f = _t->_st->font;
|
||||||
QStackTextEngine engine(lineText, _f->f);
|
QStackTextEngine engine(lineText, _f->f);
|
||||||
engine.option.setTextDirection(_parDirection);
|
engine.option.setTextDirection(_paragraphDirection);
|
||||||
_e = &engine;
|
_e = &engine;
|
||||||
|
|
||||||
eItemize();
|
eItemize();
|
||||||
|
|
@ -1318,7 +1346,10 @@ void Renderer::prepareElidedLine(QString &lineText, int32 lineStart, int32 &line
|
||||||
eShapeLine(line);
|
eShapeLine(line);
|
||||||
|
|
||||||
auto elideWidth = _f->elidew;
|
auto elideWidth = _f->elidew;
|
||||||
_wLeft = _lineWidth - _ppadding.left() - _ppadding.right() - elideWidth;
|
_wLeft = _lineWidth
|
||||||
|
- _quotePadding.left()
|
||||||
|
- _quotePadding.right()
|
||||||
|
- elideWidth;
|
||||||
|
|
||||||
int firstItem = engine.findItem(line.from), lastItem = engine.findItem(line.from + line.length - 1);
|
int firstItem = engine.findItem(line.from), lastItem = engine.findItem(line.from + line.length - 1);
|
||||||
int nItems = (firstItem >= 0 && lastItem >= firstItem) ? (lastItem - firstItem + 1) : 0, i;
|
int nItems = (firstItem >= 0 && lastItem >= firstItem) ? (lastItem - firstItem + 1) : 0, i;
|
||||||
|
|
@ -1518,8 +1549,8 @@ void Renderer::eItemize() {
|
||||||
auto currentBlock = _t->_blocks[blockIndex].get();
|
auto currentBlock = _t->_blocks[blockIndex].get();
|
||||||
auto nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr;
|
auto nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex].get() : nullptr;
|
||||||
|
|
||||||
_e->layoutData->hasBidi = _parHasBidi;
|
_e->layoutData->hasBidi = _paragraphHasBidi;
|
||||||
auto analysis = _parAnalysis.data() + (_localFrom - _parStart);
|
auto analysis = _paragraphAnalysis.data() + (_localFrom - _paragraphStart);
|
||||||
|
|
||||||
{
|
{
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
|
|
@ -1568,7 +1599,7 @@ void Renderer::eItemize() {
|
||||||
|
|
||||||
{
|
{
|
||||||
auto i_string = &_e->layoutData->string;
|
auto i_string = &_e->layoutData->string;
|
||||||
auto i_analysis = _parAnalysis.data() + (_localFrom - _parStart);
|
auto i_analysis = _paragraphAnalysis.data() + (_localFrom - _paragraphStart);
|
||||||
auto i_items = &_e->layoutData->items;
|
auto i_items = &_e->layoutData->items;
|
||||||
|
|
||||||
blockIndex = _lineStartBlock;
|
blockIndex = _lineStartBlock;
|
||||||
|
|
@ -1623,18 +1654,18 @@ QChar::Direction Renderer::eSkipBoundryNeutrals(
|
||||||
|
|
||||||
QChar::Direction dir = control.basicDirection();
|
QChar::Direction dir = control.basicDirection();
|
||||||
int level = sor > 0 ? analysis[sor - 1].bidiLevel : control.level;
|
int level = sor > 0 ? analysis[sor - 1].bidiLevel : control.level;
|
||||||
while (sor <= _parLength) {
|
while (sor <= _paragraphLength) {
|
||||||
while (i != _parStartBlock && (*i)->position() > _parStart + sor) {
|
while (i != _paragraphStartBlock && (*i)->position() > _paragraphStart + sor) {
|
||||||
n = i;
|
n = i;
|
||||||
--i;
|
--i;
|
||||||
}
|
}
|
||||||
while (n != e && (*n)->position() <= _parStart + sor) {
|
while (n != e && (*n)->position() <= _paragraphStart + sor) {
|
||||||
i = n;
|
i = n;
|
||||||
++n;
|
++n;
|
||||||
}
|
}
|
||||||
|
|
||||||
TextBlockType _itype = (*i)->type();
|
TextBlockType _itype = (*i)->type();
|
||||||
if (eor == _parLength)
|
if (eor == _paragraphLength)
|
||||||
dir = control.basicDirection();
|
dir = control.basicDirection();
|
||||||
else if (_itype == TextBlockType::Emoji
|
else if (_itype == TextBlockType::Emoji
|
||||||
|| _itype == TextBlockType::CustomEmoji)
|
|| _itype == TextBlockType::CustomEmoji)
|
||||||
|
|
@ -1662,16 +1693,16 @@ bool Renderer::eBidiItemize(QScriptAnalysis *analysis, BidiControl &control) {
|
||||||
int sor = 0;
|
int sor = 0;
|
||||||
int eor = -1;
|
int eor = -1;
|
||||||
|
|
||||||
const ushort *unicode = reinterpret_cast<const ushort*>(_t->_text.unicode()) + _parStart;
|
const ushort *unicode = reinterpret_cast<const ushort*>(_t->_text.unicode()) + _paragraphStart;
|
||||||
int current = 0;
|
int current = 0;
|
||||||
|
|
||||||
QChar::Direction dir = rightToLeft ? QChar::DirR : QChar::DirL;
|
QChar::Direction dir = rightToLeft ? QChar::DirR : QChar::DirL;
|
||||||
BidiStatus status;
|
BidiStatus status;
|
||||||
|
|
||||||
String::TextBlocks::const_iterator i = _parStartBlock, e = _t->_blocks.cend(), n = i + 1;
|
String::TextBlocks::const_iterator i = _paragraphStartBlock, e = _t->_blocks.cend(), n = i + 1;
|
||||||
|
|
||||||
QChar::Direction sdir;
|
QChar::Direction sdir;
|
||||||
TextBlockType _stype = (*_parStartBlock)->type();
|
TextBlockType _stype = (*_paragraphStartBlock)->type();
|
||||||
if (_stype == TextBlockType::Emoji || _stype == TextBlockType::CustomEmoji)
|
if (_stype == TextBlockType::Emoji || _stype == TextBlockType::CustomEmoji)
|
||||||
sdir = QChar::DirCS;
|
sdir = QChar::DirCS;
|
||||||
else if (_stype == TextBlockType::Skip)
|
else if (_stype == TextBlockType::Skip)
|
||||||
|
|
@ -1688,15 +1719,15 @@ bool Renderer::eBidiItemize(QScriptAnalysis *analysis, BidiControl &control) {
|
||||||
status.last = status.lastStrong;
|
status.last = status.lastStrong;
|
||||||
status.dir = sdir;
|
status.dir = sdir;
|
||||||
|
|
||||||
while (current <= _parLength) {
|
while (current <= _paragraphLength) {
|
||||||
while (n != e && (*n)->position() <= _parStart + current) {
|
while (n != e && (*n)->position() <= _paragraphStart + current) {
|
||||||
i = n;
|
i = n;
|
||||||
++n;
|
++n;
|
||||||
}
|
}
|
||||||
|
|
||||||
QChar::Direction dirCurrent;
|
QChar::Direction dirCurrent;
|
||||||
TextBlockType _itype = (*i)->type();
|
TextBlockType _itype = (*i)->type();
|
||||||
if (current == (int)_parLength)
|
if (current == (int)_paragraphLength)
|
||||||
dirCurrent = control.basicDirection();
|
dirCurrent = control.basicDirection();
|
||||||
else if (_itype == TextBlockType::Emoji
|
else if (_itype == TextBlockType::Emoji
|
||||||
|| _itype == TextBlockType::CustomEmoji)
|
|| _itype == TextBlockType::CustomEmoji)
|
||||||
|
|
@ -2020,7 +2051,7 @@ bool Renderer::eBidiItemize(QScriptAnalysis *analysis, BidiControl &control) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (current >= (int)_parLength) break;
|
if (current >= (int)_paragraphLength) break;
|
||||||
|
|
||||||
// set status.last as needed.
|
// set status.last as needed.
|
||||||
switch (dirCurrent) {
|
switch (dirCurrent) {
|
||||||
|
|
|
||||||
|
|
@ -151,20 +151,25 @@ private:
|
||||||
int _indexOfElidedBlock = -1; // For spoilers.
|
int _indexOfElidedBlock = -1; // For spoilers.
|
||||||
|
|
||||||
// current paragraph data
|
// current paragraph data
|
||||||
String::TextBlocks::const_iterator _parStartBlock;
|
String::TextBlocks::const_iterator _paragraphStartBlock;
|
||||||
Qt::LayoutDirection _parDirection = Qt::LayoutDirectionAuto;
|
Qt::LayoutDirection _paragraphDirection = Qt::LayoutDirectionAuto;
|
||||||
int _parStart = 0;
|
int _paragraphStart = 0;
|
||||||
int _parLength = 0;
|
int _paragraphLength = 0;
|
||||||
bool _parHasBidi = false;
|
bool _paragraphHasBidi = false;
|
||||||
QVarLengthArray<QScriptAnalysis, 4096> _parAnalysis;
|
QVarLengthArray<QScriptAnalysis, 4096> _paragraphAnalysis;
|
||||||
ParagraphDetails *_paragraph = nullptr;
|
QFixed _paragraphWidthRemaining = 0;
|
||||||
int _pindex = 0;
|
|
||||||
QMargins _ppadding;
|
// current quote data
|
||||||
int _blockLineTop = 0;
|
QuoteDetails *_quote = nullptr;
|
||||||
BlockPaintCache *_preBlockCache = nullptr;
|
Qt::LayoutDirection _quoteDirection = Qt::LayoutDirectionAuto;
|
||||||
BlockPaintCache *_blockquoteBlockCache = nullptr;
|
int _quoteShift = 0;
|
||||||
bool _preBlockCacheValid = false;
|
int _quoteIndex = 0;
|
||||||
bool _blockquoteBlockCacheValid = false;
|
QMargins _quotePadding;
|
||||||
|
int _quoteLineTop = 0;
|
||||||
|
QuotePaintCache *_quotePreCache = nullptr;
|
||||||
|
QuotePaintCache *_quoteBlockquoteCache = nullptr;
|
||||||
|
bool _quotePreValid = false;
|
||||||
|
bool _quoteBlockquoteValid = false;
|
||||||
|
|
||||||
// current line data
|
// current line data
|
||||||
QTextEngine *_e = nullptr;
|
QTextEngine *_e = nullptr;
|
||||||
|
|
@ -189,7 +194,6 @@ private:
|
||||||
int _localFrom = 0;
|
int _localFrom = 0;
|
||||||
int _lineStartBlock = 0;
|
int _lineStartBlock = 0;
|
||||||
QFixed _lineWidth = 0;
|
QFixed _lineWidth = 0;
|
||||||
QFixed _paragraphWidthRemaining = 0;
|
|
||||||
|
|
||||||
// link and symbol resolve
|
// link and symbol resolve
|
||||||
QFixed _lookupX = 0;
|
QFixed _lookupX = 0;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue