Add copy code blocks on header click.
This commit is contained in:
parent
5ffbb90fd6
commit
17d73a5c0c
8 changed files with 196 additions and 48 deletions
|
|
@ -9,6 +9,7 @@
|
||||||
#include "ui/gl/gl_detection.h"
|
#include "ui/gl/gl_detection.h"
|
||||||
#include "ui/text/text_entity.h"
|
#include "ui/text/text_entity.h"
|
||||||
#include "ui/text/text_block.h"
|
#include "ui/text/text_block.h"
|
||||||
|
#include "ui/toast/toast.h"
|
||||||
#include "ui/basic_click_handlers.h"
|
#include "ui/basic_click_handlers.h"
|
||||||
#include "base/platform/base_platform_info.h"
|
#include "base/platform/base_platform_info.h"
|
||||||
|
|
||||||
|
|
@ -88,6 +89,11 @@ bool Integration::handleUrlClick(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Integration::copyPreOnClick(const QVariant &context) {
|
||||||
|
Toast::Show(u"Code copied to clipboard."_q);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
QString Integration::convertTagToMimeTag(const QString &tagId) {
|
QString Integration::convertTagToMimeTag(const QString &tagId) {
|
||||||
return tagId;
|
return tagId;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ public:
|
||||||
[[nodiscard]] virtual bool handleUrlClick(
|
[[nodiscard]] virtual bool handleUrlClick(
|
||||||
const QString &url,
|
const QString &url,
|
||||||
const QVariant &context);
|
const QVariant &context);
|
||||||
|
[[nodiscard]] virtual bool copyPreOnClick(const QVariant &context);
|
||||||
[[nodiscard]] virtual QString convertTagToMimeTag(const QString &tagId);
|
[[nodiscard]] virtual QString convertTagToMimeTag(const QString &tagId);
|
||||||
[[nodiscard]] virtual const Emoji::One *defaultEmojiVariant(
|
[[nodiscard]] virtual const Emoji::One *defaultEmojiVariant(
|
||||||
const Emoji::One *emoji);
|
const Emoji::One *emoji);
|
||||||
|
|
|
||||||
101
ui/text/text.cpp
101
ui/text/text.cpp
|
|
@ -239,6 +239,7 @@ void ValidateQuotePaintCache(
|
||||||
p.setClipRect(outline, header, side - outline, side - header);
|
p.setClipRect(outline, header, side - outline, side - header);
|
||||||
p.drawRoundedRect(0, 0, side, side, radius, radius);
|
p.drawRoundedRect(0, 0, side, side, radius, radius);
|
||||||
if (icon) {
|
if (icon) {
|
||||||
|
p.setClipping(false);
|
||||||
const auto left = side - icon->width() - st.iconPosition.x();
|
const auto left = side - icon->width() - st.iconPosition.x();
|
||||||
const auto top = st.iconPosition.y();
|
const auto top = st.iconPosition.y();
|
||||||
icon->paint(p, left, top, side, cache.icon);
|
icon->paint(p, left, top, side, cache.icon);
|
||||||
|
|
@ -364,13 +365,23 @@ String::ExtendedWrap::~ExtendedWrap() = default;
|
||||||
|
|
||||||
void String::ExtendedWrap::adjustFrom(const ExtendedWrap *other) {
|
void String::ExtendedWrap::adjustFrom(const ExtendedWrap *other) {
|
||||||
const auto data = get();
|
const auto data = get();
|
||||||
if (data && data->spoiler) {
|
const auto raw = [](auto pointer) {
|
||||||
const auto raw = [](auto pointer) {
|
return reinterpret_cast<quintptr>(pointer);
|
||||||
return reinterpret_cast<quintptr>(pointer);
|
};
|
||||||
};
|
const auto adjust = [&](auto &link) {
|
||||||
const auto otherText = raw(data->spoiler->link->text().get());
|
const auto otherText = raw(link->text().get());
|
||||||
data->spoiler->link->setText(
|
link->setText(
|
||||||
reinterpret_cast<String*>(otherText + raw(this) - raw(other)));
|
reinterpret_cast<String*>(otherText + raw(this) - raw(other)));
|
||||||
|
};
|
||||||
|
if (data) {
|
||||||
|
if (data->spoiler) {
|
||||||
|
adjust(data->spoiler->link);
|
||||||
|
}
|
||||||
|
for (auto "e : data->quotes) {
|
||||||
|
if (quote.copy) {
|
||||||
|
adjust(quote.copy);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1184,14 +1195,27 @@ int String::quoteMinWidth(QuoteDetails *quote) const {
|
||||||
}
|
}
|
||||||
const auto qpadding = quotePadding(quote);
|
const auto qpadding = quotePadding(quote);
|
||||||
const auto &qheader = quoteHeaderText(quote);
|
const auto &qheader = quoteHeaderText(quote);
|
||||||
const auto qst = quote ? "eStyle(quote) : nullptr;
|
const auto &qst = quoteStyle(quote);
|
||||||
return qpadding.left()
|
const auto radius = qst.radius;
|
||||||
+ (qheader.isEmpty() ? 0 : _st->font->monospace()->width(qheader))
|
const auto header = qst.header;
|
||||||
|
const auto outline = qst.outline;
|
||||||
|
const auto iconsize = (!qst.icon.empty())
|
||||||
|
? std::max(
|
||||||
|
qst.icon.width() + qst.iconPosition.x(),
|
||||||
|
qst.icon.height() + qst.iconPosition.y())
|
||||||
|
: 0;
|
||||||
|
const auto corner = std::max({ header, radius, outline, iconsize });
|
||||||
|
const auto top = qpadding.left()
|
||||||
|
+ (qheader.isEmpty()
|
||||||
|
? 0
|
||||||
|
: (_st->font->monospace()->width(qheader)
|
||||||
|
+ _st->pre.headerPosition.x()))
|
||||||
+ std::max(
|
+ std::max(
|
||||||
qpadding.right(),
|
qpadding.right(),
|
||||||
((qst && !qst->icon.empty())
|
(!qst.icon.empty()
|
||||||
? (qst->iconPosition.x() + qst->icon.width())
|
? (qst.iconPosition.x() + qst.icon.width())
|
||||||
: 0));
|
: 0));
|
||||||
|
return std::max(top, 2 * corner);
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString &String::quoteHeaderText(QuoteDetails *quote) const {
|
const QString &String::quoteHeaderText(QuoteDetails *quote) const {
|
||||||
|
|
@ -1221,11 +1245,19 @@ void String::enumerateText(
|
||||||
|
|
||||||
int linkIndex = 0;
|
int linkIndex = 0;
|
||||||
uint16 linkPosition = 0;
|
uint16 linkPosition = 0;
|
||||||
|
int quoteIndex = _startQuoteIndex;
|
||||||
|
|
||||||
int32 flags = 0;
|
TextBlockFlags flags = {};
|
||||||
for (auto i = _blocks.cbegin(), e = _blocks.cend(); true; ++i) {
|
for (auto i = _blocks.cbegin(), e = _blocks.cend(); true; ++i) {
|
||||||
const auto blockPosition = (i == e) ? uint16(_text.size()) : (*i)->position();
|
const auto blockPosition = (i == e)
|
||||||
|
? uint16(_text.size())
|
||||||
|
: (*i)->position();
|
||||||
const auto blockFlags = (i == e) ? TextBlockFlags() : (*i)->flags();
|
const auto blockFlags = (i == e) ? TextBlockFlags() : (*i)->flags();
|
||||||
|
const auto blockQuoteIndex = (i == e)
|
||||||
|
? 0
|
||||||
|
: ((*i)->type() != TextBlockType::Newline)
|
||||||
|
? quoteIndex
|
||||||
|
: static_cast<const NewlineBlock*>(i->get())->quoteIndex();
|
||||||
const auto blockLinkIndex = [&] {
|
const auto blockLinkIndex = [&] {
|
||||||
if (IsMono(blockFlags) || (i == e)) {
|
if (IsMono(blockFlags) || (i == e)) {
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -1240,7 +1272,10 @@ void String::enumerateText(
|
||||||
auto rangeFrom = qMax(selection.from, linkPosition);
|
auto rangeFrom = qMax(selection.from, linkPosition);
|
||||||
auto rangeTo = qMin(selection.to, blockPosition);
|
auto rangeTo = qMin(selection.to, blockPosition);
|
||||||
if (rangeTo > rangeFrom) { // handle click handler
|
if (rangeTo > rangeFrom) { // handle click handler
|
||||||
const auto r = base::StringViewMid(_text, rangeFrom, rangeTo - rangeFrom);
|
const auto r = base::StringViewMid(
|
||||||
|
_text,
|
||||||
|
rangeFrom,
|
||||||
|
rangeTo - rangeFrom);
|
||||||
// Ignore links that are partially copied.
|
// Ignore links that are partially copied.
|
||||||
const auto handler = (linkPosition != rangeFrom
|
const auto handler = (linkPosition != rangeFrom
|
||||||
|| blockPosition != rangeTo
|
|| blockPosition != rangeTo
|
||||||
|
|
@ -1267,11 +1302,20 @@ void String::enumerateText(
|
||||||
|
|
||||||
const auto checkBlockFlags = (blockPosition >= selection.from)
|
const auto checkBlockFlags = (blockPosition >= selection.from)
|
||||||
&& (blockPosition <= selection.to);
|
&& (blockPosition <= selection.to);
|
||||||
if (checkBlockFlags && blockFlags != flags) {
|
if (checkBlockFlags
|
||||||
flagsChangeCallback(flags, blockFlags);
|
&& (blockFlags != flags
|
||||||
|
|| ((flags & TextBlockFlag::Pre)
|
||||||
|
&& blockQuoteIndex != quoteIndex))) {
|
||||||
|
flagsChangeCallback(
|
||||||
|
flags,
|
||||||
|
quoteIndex,
|
||||||
|
blockFlags,
|
||||||
|
blockQuoteIndex);
|
||||||
flags = blockFlags;
|
flags = blockFlags;
|
||||||
}
|
}
|
||||||
if (i == e || (linkIndex ? linkPosition : blockPosition) >= selection.to) {
|
quoteIndex = blockQuoteIndex;
|
||||||
|
if (i == e
|
||||||
|
|| (linkIndex ? linkPosition : blockPosition) >= selection.to) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1393,18 +1437,33 @@ TextForMimeData String::toText(
|
||||||
{ TextBlockFlag::Pre, EntityType::Pre },
|
{ TextBlockFlag::Pre, EntityType::Pre },
|
||||||
{ TextBlockFlag::Blockquote, EntityType::Blockquote },
|
{ TextBlockFlag::Blockquote, EntityType::Blockquote },
|
||||||
} : std::vector<MarkdownTagTracker>();
|
} : std::vector<MarkdownTagTracker>();
|
||||||
const auto flagsChangeCallback = [&](int32 oldFlags, int32 newFlags) {
|
const auto flagsChangeCallback = [&](
|
||||||
|
TextBlockFlags oldFlags,
|
||||||
|
int oldQuoteIndex,
|
||||||
|
TextBlockFlags newFlags,
|
||||||
|
int newQuoteIndex) {
|
||||||
if (!composeEntities) {
|
if (!composeEntities) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (auto &tracker : markdownTrackers) {
|
for (auto &tracker : markdownTrackers) {
|
||||||
const auto flag = tracker.flag;
|
const auto flag = tracker.flag;
|
||||||
if ((oldFlags & flag) && !(newFlags & flag)) {
|
const auto quoteWithLanguage = (flag == TextBlockFlag::Pre);
|
||||||
|
const auto quoteWithLanguageChanged = quoteWithLanguage
|
||||||
|
&& (oldQuoteIndex != newQuoteIndex);
|
||||||
|
const auto data = (quoteWithLanguage && oldQuoteIndex)
|
||||||
|
? _extended->quotes[oldQuoteIndex - 1].language
|
||||||
|
: QString();
|
||||||
|
if (((oldFlags & flag) && !(newFlags & flag))
|
||||||
|
|| quoteWithLanguageChanged) {
|
||||||
insertEntity({
|
insertEntity({
|
||||||
tracker.type,
|
tracker.type,
|
||||||
tracker.start,
|
tracker.start,
|
||||||
int(result.rich.text.size()) - tracker.start });
|
int(result.rich.text.size()) - tracker.start,
|
||||||
} else if ((newFlags & flag) && !(oldFlags & flag)) {
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (((newFlags & flag) && !(oldFlags & flag))
|
||||||
|
|| quoteWithLanguageChanged) {
|
||||||
tracker.start = result.rich.text.size();
|
tracker.start = result.rich.text.size();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
#include "ui/text/text_extended_data.h"
|
#include "ui/text/text_extended_data.h"
|
||||||
|
|
||||||
#include "ui/text/text.h"
|
#include "ui/text/text.h"
|
||||||
|
#include "ui/integration.h"
|
||||||
|
|
||||||
namespace Ui::Text {
|
namespace Ui::Text {
|
||||||
|
|
||||||
|
|
@ -32,5 +33,41 @@ void SpoilerClickHandler::onClick(ClickContext context) const {
|
||||||
_text->setSpoilerRevealed(true, anim::type::normal);
|
_text->setSpoilerRevealed(true, anim::type::normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PreClickHandler::PreClickHandler(
|
||||||
|
not_null<String*> text,
|
||||||
|
uint16 offset,
|
||||||
|
uint16 length)
|
||||||
|
: _text(text)
|
||||||
|
, _offset(offset)
|
||||||
|
, _length(length) {
|
||||||
|
}
|
||||||
|
|
||||||
|
not_null<String*> PreClickHandler::text() const {
|
||||||
|
return _text;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PreClickHandler::setText(not_null<String*> text) {
|
||||||
|
_text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PreClickHandler::onClick(ClickContext context) const {
|
||||||
|
if (context.button != Qt::LeftButton) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto till = uint16(_offset + _length);
|
||||||
|
auto text = _text->toTextForMimeData({ _offset, till });
|
||||||
|
if (text.empty()) {
|
||||||
|
return;
|
||||||
|
} else if (!text.rich.text.endsWith('\n')) {
|
||||||
|
text.rich.text.append('\n');
|
||||||
|
}
|
||||||
|
if (!text.expanded.endsWith('\n')) {
|
||||||
|
text.expanded.append('\n');
|
||||||
|
}
|
||||||
|
if (Integration::Instance().copyPreOnClick(context.other)) {
|
||||||
|
TextUtilities::SetClipboardText(std::move(text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Ui::Text
|
} // namespace Ui::Text
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,22 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class PreClickHandler final : public ClickHandler {
|
||||||
|
public:
|
||||||
|
PreClickHandler(not_null<String*> text, uint16 offset, uint16 length);
|
||||||
|
|
||||||
|
[[nodiscard]] not_null<String*> text() const;
|
||||||
|
void setText(not_null<String*> text);
|
||||||
|
|
||||||
|
void onClick(ClickContext context) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
not_null<String*> _text;
|
||||||
|
uint16 _offset = 0;
|
||||||
|
uint16 _length = 0;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
struct SpoilerData {
|
struct SpoilerData {
|
||||||
explicit SpoilerData(Fn<void()> repaint)
|
explicit SpoilerData(Fn<void()> repaint)
|
||||||
: animation(std::move(repaint)) {
|
: animation(std::move(repaint)) {
|
||||||
|
|
@ -45,7 +61,7 @@ struct SpoilerData {
|
||||||
|
|
||||||
struct QuoteDetails {
|
struct QuoteDetails {
|
||||||
QString language;
|
QString language;
|
||||||
ClickHandlerPtr copy;
|
std::shared_ptr<PreClickHandler> copy;
|
||||||
int copyWidth = 0;
|
int copyWidth = 0;
|
||||||
int maxWidth = 0;
|
int maxWidth = 0;
|
||||||
int minHeight = 0;
|
int minHeight = 0;
|
||||||
|
|
|
||||||
|
|
@ -243,6 +243,7 @@ void Parser::ensureAtNewline(QuoteDetails quote) {
|
||||||
createNewlineBlock(false);
|
createNewlineBlock(false);
|
||||||
_customEmojiData = base::take(saved);
|
_customEmojiData = base::take(saved);
|
||||||
}
|
}
|
||||||
|
_quoteStartPosition = _t->_text.size();
|
||||||
auto "es = _t->ensureExtended()->quotes;
|
auto "es = _t->ensureExtended()->quotes;
|
||||||
quotes.push_back(std::move(quote));
|
quotes.push_back(std::move(quote));
|
||||||
const auto index = _quoteIndex = int(quotes.size());
|
const auto index = _quoteIndex = int(quotes.size());
|
||||||
|
|
@ -274,6 +275,18 @@ void Parser::finishEntities() {
|
||||||
if ((*flags)
|
if ((*flags)
|
||||||
& (TextBlockFlag::Pre
|
& (TextBlockFlag::Pre
|
||||||
| TextBlockFlag::Blockquote)) {
|
| TextBlockFlag::Blockquote)) {
|
||||||
|
if (_quoteIndex) {
|
||||||
|
auto "es = _t->ensureExtended()->quotes;
|
||||||
|
auto "e = quotes[_quoteIndex - 1];
|
||||||
|
const auto from = _quoteStartPosition;
|
||||||
|
const auto till = _t->_text.size();
|
||||||
|
if (quote.pre && till > from) {
|
||||||
|
quote.copy = std::make_shared<PreClickHandler>(
|
||||||
|
_t,
|
||||||
|
from,
|
||||||
|
till - from);
|
||||||
|
}
|
||||||
|
}
|
||||||
_quoteIndex = 0;
|
_quoteIndex = 0;
|
||||||
if (lastType != TextBlockType::Newline) {
|
if (lastType != TextBlockType::Newline) {
|
||||||
_newlineAwaited = true;
|
_newlineAwaited = true;
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,7 @@ private:
|
||||||
uint16 _colorIndex = 0;
|
uint16 _colorIndex = 0;
|
||||||
uint16 _monoIndex = 0;
|
uint16 _monoIndex = 0;
|
||||||
uint16 _quoteIndex = 0;
|
uint16 _quoteIndex = 0;
|
||||||
|
int _quoteStartPosition = 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
|
||||||
|
|
|
||||||
|
|
@ -404,22 +404,8 @@ void Renderer::enumerate() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::fillParagraphBg(int paddingBottom) {
|
void Renderer::fillParagraphBg(int paddingBottom) {
|
||||||
const auto cache = (!_p || !_quote)
|
if (_quote) {
|
||||||
? nullptr
|
|
||||||
: _quote->pre
|
|
||||||
? _quotePreCache
|
|
||||||
: _quote->blockquote
|
|
||||||
? _quoteBlockquoteCache
|
|
||||||
: nullptr;
|
|
||||||
if (cache) {
|
|
||||||
const auto &st = _t->quoteStyle(_quote);
|
const auto &st = _t->quoteStyle(_quote);
|
||||||
auto &valid = _quote->pre
|
|
||||||
? _quotePreValid
|
|
||||||
: _quoteBlockquoteValid;
|
|
||||||
if (!valid) {
|
|
||||||
valid = true;
|
|
||||||
ValidateQuotePaintCache(*cache, st);
|
|
||||||
}
|
|
||||||
const auto skip = st.verticalSkip;
|
const auto skip = st.verticalSkip;
|
||||||
const auto isTop = (_y != _quoteLineTop);
|
const auto isTop = (_y != _quoteLineTop);
|
||||||
const auto isBottom = (paddingBottom != 0);
|
const auto isBottom = (paddingBottom != 0);
|
||||||
|
|
@ -428,19 +414,48 @@ void Renderer::fillParagraphBg(int paddingBottom) {
|
||||||
const auto fill = _y + _lineHeight + paddingBottom - top
|
const auto fill = _y + _lineHeight + paddingBottom - top
|
||||||
- (isBottom ? skip : 0);
|
- (isBottom ? skip : 0);
|
||||||
const auto rect = QRect(left, top, _startLineWidth, fill);
|
const auto rect = QRect(left, top, _startLineWidth, fill);
|
||||||
FillQuotePaint(*_p, rect, *cache, st, {
|
|
||||||
.skipTop = !isTop,
|
|
||||||
.skipBottom = !isBottom,
|
|
||||||
});
|
|
||||||
|
|
||||||
|
const auto cache = (!_p || !_quote)
|
||||||
|
? nullptr
|
||||||
|
: _quote->pre
|
||||||
|
? _quotePreCache
|
||||||
|
: _quote->blockquote
|
||||||
|
? _quoteBlockquoteCache
|
||||||
|
: nullptr;
|
||||||
|
if (cache) {
|
||||||
|
auto &valid = _quote->pre
|
||||||
|
? _quotePreValid
|
||||||
|
: _quoteBlockquoteValid;
|
||||||
|
if (!valid) {
|
||||||
|
valid = true;
|
||||||
|
ValidateQuotePaintCache(*cache, st);
|
||||||
|
}
|
||||||
|
FillQuotePaint(*_p, rect, *cache, st, {
|
||||||
|
.skipTop = !isTop,
|
||||||
|
.skipBottom = !isBottom,
|
||||||
|
});
|
||||||
|
}
|
||||||
if (isTop && st.header > 0) {
|
if (isTop && st.header > 0) {
|
||||||
const auto font = _t->_st->font->monospace();
|
if (_p) {
|
||||||
const auto topleft = rect.topLeft();
|
const auto font = _t->_st->font->monospace();
|
||||||
const auto position = topleft + st.headerPosition;
|
const auto topleft = rect.topLeft();
|
||||||
const auto baseline = position + QPoint(0, font->ascent);
|
const auto position = topleft + st.headerPosition;
|
||||||
_p->setFont(font);
|
const auto lbaseline = position + QPoint(0, font->ascent);
|
||||||
_p->setPen(_palette->monoFg->p);
|
_p->setFont(font);
|
||||||
_p->drawText(baseline, _t->quoteHeaderText(_quote));
|
_p->setPen(_palette->monoFg->p);
|
||||||
|
_p->drawText(lbaseline, _t->quoteHeaderText(_quote));
|
||||||
|
} else if (_lookupX >= left
|
||||||
|
&& _lookupX < left + _startLineWidth
|
||||||
|
&& _lookupY >= top
|
||||||
|
&& _lookupY < top + st.header) {
|
||||||
|
if (_lookupLink) {
|
||||||
|
_lookupResult.link = _quote->copy;
|
||||||
|
}
|
||||||
|
if (_lookupSymbol) {
|
||||||
|
_lookupResult.symbol = _lineStart;
|
||||||
|
_lookupResult.afterSymbol = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_quoteLineTop = _y + _lineHeight + paddingBottom;
|
_quoteLineTop = _y + _lineHeight + paddingBottom;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue