Fix editing text with custom emoji and formatting.

This commit is contained in:
John Preston 2022-07-01 12:55:26 +04:00
parent e6b3951b40
commit 6bd7518109
4 changed files with 213 additions and 58 deletions

View file

@ -1527,6 +1527,29 @@ bool CutPart(TextWithEntities &sending, TextWithEntities &left, int32 limit) {
return true; return true;
} }
MentionNameFields MentionNameDataToFields(QStringView data) {
const auto components = data.split('.');
if (components.size() != 2) {
return {};
}
const auto parts = components[1].split(':');
if (parts.size() != 2) {
return {};
}
return {
.selfId = parts[1].toULongLong(),
.userId = components[0].toULongLong(),
.accessHash = parts[0].toULongLong(),
};
}
QString MentionNameDataFromFields(const MentionNameFields &fields) {
return u"%1.%2:%3"_q
.arg(fields.userId)
.arg(fields.accessHash)
.arg(fields.selfId);
}
TextWithEntities ParseEntities(const QString &text, int32 flags) { TextWithEntities ParseEntities(const QString &text, int32 flags) {
auto result = TextWithEntities{ text, EntitiesInText() }; auto result = TextWithEntities{ text, EntitiesInText() };
ParseEntities(result, flags); ParseEntities(result, flags);
@ -1929,7 +1952,14 @@ bool IsMentionLink(QStringView link) {
return link.startsWith(kMentionTagStart); return link.startsWith(kMentionTagStart);
} }
[[nodiscard]] bool IsSeparateTag(QStringView tag) { QString MentionEntityData(QStringView link) {
const auto match = qthelp::regex_match(
"^(\\d+\\.\\d+:\\d+)(/|$)",
base::StringViewMid(link, kMentionTagStart.size()));
return match ? match->captured(1) : QString();
}
bool IsSeparateTag(QStringView tag) {
return (tag == Ui::InputField::kTagCode) return (tag == Ui::InputField::kTagCode)
|| (tag == Ui::InputField::kTagPre); || (tag == Ui::InputField::kTagPre);
} }
@ -1953,8 +1983,8 @@ QString JoinTag(const QList<QStringView> &list) {
return result; return result;
} }
QList<QStringView> SplitTags(const QString &tag) { QList<QStringView> SplitTags(QStringView tag) {
return QStringView(tag).split(kTagSeparator); return tag.split(kTagSeparator);
} }
QString TagWithRemoved(const QString &tag, const QString &removed) { QString TagWithRemoved(const QString &tag, const QString &removed) {
@ -2073,11 +2103,9 @@ EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags) {
openType(EntityType::CustomEmoji, data); openType(EntityType::CustomEmoji, data);
} }
} else if (IsMentionLink(nextState.link)) { } else if (IsMentionLink(nextState.link)) {
const auto match = qthelp::regex_match( const auto data = MentionEntityData(nextState.link);
"^(\\d+\\.\\d+)(/|$)", if (!data.isEmpty()) {
base::StringViewMid(nextState.link, kMentionTagStart.size())); openType(EntityType::MentionName, data);
if (match) {
openType(EntityType::MentionName, match->captured(1));
} }
} else { } else {
openType(EntityType::CustomUrl, nextState.link); openType(EntityType::CustomUrl, nextState.link);
@ -2169,8 +2197,8 @@ TextWithTags::Tags ConvertEntitiesToTextTags(
}; };
switch (entity.type()) { switch (entity.type()) {
case EntityType::MentionName: { case EntityType::MentionName: {
auto match = QRegularExpression( const auto match = QRegularExpression(
R"(^(\d+\.\d+)$)" "^(\\d+\\.\\d+:\\d+)$"
).match(entity.data()); ).match(entity.data());
if (match.hasMatch()) { if (match.hasMatch()) {
push(kMentionTagStart + entity.data()); push(kMentionTagStart + entity.data());
@ -2184,7 +2212,12 @@ TextWithTags::Tags ConvertEntitiesToTextTags(
} }
} break; } break;
case EntityType::CustomEmoji: { case EntityType::CustomEmoji: {
push(Ui::InputField::CustomEmojiLink(entity.data())); const auto match = QRegularExpression(
"^(\\d+\\.\\d+:\\d+/\\d+)$"
).match(entity.data());
if (match.hasMatch()) {
push(Ui::InputField::CustomEmojiLink(entity.data()));
}
} break; } break;
case EntityType::Bold: push(Ui::InputField::kTagBold); break; case EntityType::Bold: push(Ui::InputField::kTagBold); break;
//case EntityType::Semibold: // Semibold is for UI parts only. //case EntityType::Semibold: // Semibold is for UI parts only.

View file

@ -306,31 +306,13 @@ QStringList PrepareSearchWords(const QString &query, const QRegularExpression *S
bool CutPart(TextWithEntities &sending, TextWithEntities &left, int limit); bool CutPart(TextWithEntities &sending, TextWithEntities &left, int limit);
struct MentionNameFields { struct MentionNameFields {
MentionNameFields(uint64 userId = 0, uint64 accessHash = 0) uint64 selfId = 0;
: userId(userId), accessHash(accessHash) {
}
uint64 userId = 0; uint64 userId = 0;
uint64 accessHash = 0; uint64 accessHash = 0;
}; };
[[nodiscard]] MentionNameFields MentionNameDataToFields(QStringView data);
inline MentionNameFields MentionNameDataToFields(const QString &data) { [[nodiscard]] QString MentionNameDataFromFields(
auto components = data.split('.'); const MentionNameFields &fields);
if (!components.isEmpty()) {
return {
components.at(0).toULongLong(),
(components.size() > 1) ? components.at(1).toULongLong() : 0
};
}
return MentionNameFields{};
}
inline QString MentionNameDataFromFields(const MentionNameFields &fields) {
auto result = QString::number(fields.userId);
if (fields.accessHash) {
result += '.' + QString::number(fields.accessHash);
}
return result;
}
// New entities are added to the ones that are already in result. // New entities are added to the ones that are already in result.
// Changes text if (flags & TextParseMarkdown). // Changes text if (flags & TextParseMarkdown).
@ -362,12 +344,13 @@ void ApplyServerCleaning(TextWithEntities &result);
[[nodiscard]] QString TagsMimeType(); [[nodiscard]] QString TagsMimeType();
[[nodiscard]] QString TagsTextMimeType(); [[nodiscard]] QString TagsTextMimeType();
inline const auto kMentionTagStart = qstr("mention://user."); inline const auto kMentionTagStart = qstr("mention://");
[[nodiscard]] bool IsMentionLink(QStringView link); [[nodiscard]] bool IsMentionLink(QStringView link);
[[nodiscard]] QString MentionEntityData(QStringView link);
[[nodiscard]] bool IsSeparateTag(QStringView tag); [[nodiscard]] bool IsSeparateTag(QStringView tag);
[[nodiscard]] QString JoinTag(const QList<QStringView> &list); [[nodiscard]] QString JoinTag(const QList<QStringView> &list);
[[nodiscard]] QList<QStringView> SplitTags(const QString &tag); [[nodiscard]] QList<QStringView> SplitTags(QStringView tag);
[[nodiscard]] QString TagWithRemoved( [[nodiscard]] QString TagWithRemoved(
const QString &tag, const QString &tag,
const QString &removed); const QString &removed);

View file

@ -38,6 +38,7 @@ constexpr auto kTagProperty = QTextFormat::UserProperty + 4;
constexpr auto kCustomEmojiFormat = QTextFormat::UserObject + 1; constexpr auto kCustomEmojiFormat = QTextFormat::UserObject + 1;
constexpr auto kCustomEmojiText = QTextFormat::UserProperty + 5; constexpr auto kCustomEmojiText = QTextFormat::UserProperty + 5;
constexpr auto kCustomEmojiLink = QTextFormat::UserProperty + 6; constexpr auto kCustomEmojiLink = QTextFormat::UserProperty + 6;
constexpr auto kCustomEmojiId = QTextFormat::UserProperty + 7;
const auto kObjectReplacementCh = QChar(QChar::ObjectReplacementCharacter); const auto kObjectReplacementCh = QChar(QChar::ObjectReplacementCharacter);
const auto kObjectReplacement = QString::fromRawData( const auto kObjectReplacement = QString::fromRawData(
&kObjectReplacementCh, &kObjectReplacementCh,
@ -127,6 +128,19 @@ bool IsNewline(QChar ch) {
.arg(++GlobalCustomEmojiCounter); .arg(++GlobalCustomEmojiCounter);
} }
[[nodiscard]] uint64 CustomEmojiIdFromLink(QStringView link) {
const auto skip = Ui::InputField::kCustomEmojiTagStart.size();
if (const auto i = link.indexOf('/', skip + 1); i > 0) {
const auto j = link.indexOf('?', i + 1);
return base::StringViewMid(
link,
i + 1,
(j > i) ? (j - i - 1) : -1
).toULongLong();
}
return 0;
}
[[nodiscard]] QString CheckFullTextTag( [[nodiscard]] QString CheckFullTextTag(
const TextWithTags &textWithTags, const TextWithTags &textWithTags,
const QString &tag) { const QString &tag) {
@ -733,6 +747,9 @@ QTextCharFormat PrepareTagFormat(
replaceWith = MakeUniqueCustomEmojiLink(tag); replaceWith = MakeUniqueCustomEmojiLink(tag);
result.setObjectType(kCustomEmojiFormat); result.setObjectType(kCustomEmojiFormat);
result.setProperty(kCustomEmojiLink, replaceWith); result.setProperty(kCustomEmojiLink, replaceWith);
result.setProperty(
kCustomEmojiId,
CustomEmojiIdFromLink(replaceWith));
} else if (IsValidMarkdownLink(tag)) { } else if (IsValidMarkdownLink(tag)) {
color = st::defaultTextPalette.linkFg; color = st::defaultTextPalette.linkFg;
} else if (tag == kTagBold) { } else if (tag == kTagBold) {
@ -762,10 +779,40 @@ QTextCharFormat PrepareTagFormat(
: std::move(tag).replace(replaceWhat, replaceWith))); : std::move(tag).replace(replaceWhat, replaceWith)));
if (bg) { if (bg) {
result.setBackground(*bg); result.setBackground(*bg);
} else {
result.setBackground(QBrush());
} }
return result; return result;
} }
[[nodiscard]] QString TagWithoutCustomEmoji(QStringView tag) {
auto tags = TextUtilities::SplitTags(tag);
for (auto i = tags.begin(); i != tags.end();) {
if (IsCustomEmojiLink(*i)) {
i = tags.erase(i);
} else {
++i;
}
}
return TextUtilities::JoinTag(tags);
}
void RemoveCustomEmojiTag(
const style::InputField &st,
not_null<QTextDocument*> document,
const QString &existingTags,
int from,
int end) {
auto cursor = QTextCursor(document);
cursor.setPosition(from);
cursor.setPosition(end, QTextCursor::KeepAnchor);
auto format = PrepareTagFormat(st, TagWithoutCustomEmoji(existingTags));
format.setProperty(kCustomEmojiLink, QString());
format.setProperty(kCustomEmojiId, QString());
cursor.mergeCharFormat(format);
}
void ApplyTagFormat(QTextCharFormat &to, const QTextCharFormat &from) { void ApplyTagFormat(QTextCharFormat &to, const QTextCharFormat &from) {
to.setProperty(kTagProperty, from.property(kTagProperty)); to.setProperty(kTagProperty, from.property(kTagProperty));
to.setProperty(kReplaceTagId, from.property(kReplaceTagId)); to.setProperty(kReplaceTagId, from.property(kReplaceTagId));
@ -781,7 +828,7 @@ int ProcessInsertedTags(
int changedPosition, int changedPosition,
int changedEnd, int changedEnd,
const TextWithTags::Tags &tags, const TextWithTags::Tags &tags,
InputField::TagMimeProcessor *processor) { Fn<QString(QStringView)> processor) {
int firstTagStart = changedEnd; int firstTagStart = changedEnd;
int applyNoTagFrom = changedEnd; int applyNoTagFrom = changedEnd;
for (const auto &tag : tags) { for (const auto &tag : tags) {
@ -789,7 +836,7 @@ int ProcessInsertedTags(
int tagTo = tagFrom + tag.length; int tagTo = tagFrom + tag.length;
accumulate_max(tagFrom, changedPosition); accumulate_max(tagFrom, changedPosition);
accumulate_min(tagTo, changedEnd); accumulate_min(tagTo, changedEnd);
auto tagId = processor ? processor->tagFromMimeTag(tag.id) : tag.id; auto tagId = processor ? processor(tag.id) : tag.id;
if (tagTo > tagFrom && !tagId.isEmpty()) { if (tagTo > tagFrom && !tagId.isEmpty()) {
accumulate_min(firstTagStart, tagFrom); accumulate_min(firstTagStart, tagFrom);
@ -833,7 +880,9 @@ bool WasInsertTillTheEndOfTag(
const auto outsideInsertion = (position >= insertionEnd); const auto outsideInsertion = (position >= insertionEnd);
if (outsideInsertion) { if (outsideInsertion) {
const auto format = fragment.charFormat(); const auto format = fragment.charFormat();
return (format.property(kTagProperty) != insertTagName); const auto tag = format.property(kTagProperty).toString();
return TagWithoutCustomEmoji(tag)
!= TagWithoutCustomEmoji(insertTagName.toString());
} }
const auto end = position + fragment.length(); const auto end = position + fragment.length();
const auto notFullFragmentInserted = (end > insertionEnd); const auto notFullFragmentInserted = (end > insertionEnd);
@ -857,6 +906,7 @@ struct FormattingAction {
Invalid, Invalid,
InsertEmoji, InsertEmoji,
InsertCustomEmoji, InsertCustomEmoji,
RemoveCustomEmoji,
TildeFont, TildeFont,
RemoveTag, RemoveTag,
RemoveNewline, RemoveNewline,
@ -867,6 +917,7 @@ struct FormattingAction {
EmojiPtr emoji = nullptr; EmojiPtr emoji = nullptr;
bool isTilde = false; bool isTilde = false;
QString tildeTag; QString tildeTag;
QString existingTags;
QString customEmojiText; QString customEmojiText;
QString customEmojiLink; QString customEmojiLink;
int intervalStart = 0; int intervalStart = 0;
@ -949,6 +1000,7 @@ void InsertCustomEmojiAtCursor(
format.setObjectType(kCustomEmojiFormat); format.setObjectType(kCustomEmojiFormat);
format.setProperty(kCustomEmojiText, text); format.setProperty(kCustomEmojiText, text);
format.setProperty(kCustomEmojiLink, MakeUniqueCustomEmojiLink(link)); format.setProperty(kCustomEmojiLink, MakeUniqueCustomEmojiLink(link));
format.setProperty(kCustomEmojiId, CustomEmojiIdFromLink(link));
format.setVerticalAlignment(QTextCharFormat::AlignBottom); format.setVerticalAlignment(QTextCharFormat::AlignBottom);
ApplyTagFormat(format, currentFormat); ApplyTagFormat(format, currentFormat);
cursor.insertText(kObjectReplacement, format); cursor.insertText(kObjectReplacement, format);
@ -1315,9 +1367,14 @@ void FlatInput::onTextChange(const QString &text) {
Integration::Instance().textActionsUpdated(); Integration::Instance().textActionsUpdated();
} }
CustomEmojiObject::CustomEmojiObject(QObject *parent) : QObject(parent) { CustomEmojiObject::CustomEmojiObject(Factory factory, Fn<bool()> paused)
: _factory(std::move(factory))
, _paused(std::move(paused))
, _now(crl::now()) {
} }
CustomEmojiObject::~CustomEmojiObject() = default;
QSizeF CustomEmojiObject::intrinsicSize( QSizeF CustomEmojiObject::intrinsicSize(
QTextDocument *doc, QTextDocument *doc,
int posInDocument, int posInDocument,
@ -1326,7 +1383,7 @@ QSizeF CustomEmojiObject::intrinsicSize(
const auto size = Emoji::GetSizeNormal() / factor; const auto size = Emoji::GetSizeNormal() / factor;
const auto width = size + st::emojiPadding * 2.; const auto width = size + st::emojiPadding * 2.;
const auto font = format.toCharFormat().font(); const auto font = format.toCharFormat().font();
const auto height = std::max(QFontMetrics(font).height() * 1., size); const auto height = std::min(QFontMetrics(font).height() * 1., size);
return { width, height }; return { width, height };
} }
@ -1336,7 +1393,36 @@ void CustomEmojiObject::drawObject(
QTextDocument *doc, QTextDocument *doc,
int posInDocument, int posInDocument,
const QTextFormat &format) { const QTextFormat &format) {
painter->fillRect(rect, QColor(0, 128, 0, 128)); const auto id = format.property(kCustomEmojiId).toULongLong();
if (!id) {
return;
}
auto i = _emoji.find(id);
if (i == end(_emoji)) {
const auto link = format.property(kCustomEmojiLink).toString();
const auto data = InputField::CustomEmojiEntityData(link);
if (auto emoji = _factory(data)) {
i = _emoji.emplace(id, std::move(emoji)).first;
}
}
if (i == end(_emoji)) {
return;
}
i->second->paint(
*painter,
int(base::SafeRound(rect.x())) + st::emojiPadding,
int(base::SafeRound(rect.y())),
_now,
st::defaultTextPalette.spoilerActiveBg->c,
_paused());
}
void CustomEmojiObject::clear() {
_emoji.clear();
}
void CustomEmojiObject::setNow(crl::time now) {
_now = now;
} }
InputField::InputField( InputField::InputField(
@ -1388,10 +1474,6 @@ InputField::InputField(
setAttribute(Qt::WA_OpaquePaintEvent); setAttribute(Qt::WA_OpaquePaintEvent);
} }
_inner->document()->documentLayout()->registerHandler(
kCustomEmojiFormat,
new CustomEmojiObject(this));
_inner->setFont(_st.font->f); _inner->setFont(_st.font->f);
_inner->setAlignment(_st.textAlign); _inner->setAlignment(_st.textAlign);
if (_mode == Mode::SingleLine) { if (_mode == Mode::SingleLine) {
@ -1473,6 +1555,8 @@ bool InputField::viewportEventInner(QEvent *e) {
if (ev->device()->type() == base::TouchDevice::TouchScreen) { if (ev->device()->type() == base::TouchDevice::TouchScreen) {
handleTouchEvent(ev); handleTouchEvent(ev);
} }
} else if (e->type() == QEvent::Paint && _customEmojiObject) {
_customEmojiObject->setNow(crl::now());
} }
return _inner->QTextEdit::viewportEvent(e); return _inner->QTextEdit::viewportEvent(e);
} }
@ -1566,11 +1650,22 @@ void InputField::setMarkdownReplacesEnabled(rpl::producer<bool> enabled) {
}, lifetime()); }, lifetime());
} }
void InputField::setTagMimeProcessor( void InputField::setTagMimeProcessor(Fn<QString(QStringView)> processor) {
std::unique_ptr<TagMimeProcessor> &&processor) {
_tagMimeProcessor = std::move(processor); _tagMimeProcessor = std::move(processor);
} }
void InputField::setCustomEmojiFactory(
CustomEmojiFactory factory,
Fn<bool()> paused) {
_customEmojiObject = std::make_unique<CustomEmojiObject>([=](
QStringView data) {
return factory(data, [=] { _inner->update(); });
}, std::move(paused));
_inner->document()->documentLayout()->registerHandler(
kCustomEmojiFormat,
_customEmojiObject.get());
}
void InputField::setAdditionalMargin(int margin) { void InputField::setAdditionalMargin(int margin) {
_additionalMargin = margin; _additionalMargin = margin;
QResizeEvent e(size(), size()); QResizeEvent e(size(), size());
@ -2105,8 +2200,8 @@ void InputField::processFormatting(int insertPosition, int insertEnd) {
auto document = _inner->document(); auto document = _inner->document();
// Apply inserted tags. // Apply inserted tags.
auto insertedTagsProcessor = _insertedTagsAreFromMime const auto insertedTagsProcessor = _insertedTagsAreFromMime
? _tagMimeProcessor.get() ? _tagMimeProcessor
: nullptr; : nullptr;
const auto breakTagOnNotLetterTill = ProcessInsertedTags( const auto breakTagOnNotLetterTill = ProcessInsertedTags(
_st, _st,
@ -2176,6 +2271,7 @@ void InputField::processFormatting(int insertPosition, int insertEnd) {
action.customEmojiText = fragmentText; action.customEmojiText = fragmentText;
action.customEmojiLink = format.property( action.customEmojiLink = format.property(
kCustomEmojiLink).toString(); kCustomEmojiLink).toString();
break;
} }
const auto with = format.property(kInstantReplaceWithId); const auto with = format.property(kInstantReplaceWithId);
@ -2193,6 +2289,15 @@ void InputField::processFormatting(int insertPosition, int insertEnd) {
} }
} }
if (format.hasProperty(kCustomEmojiLink)
&& !format.property(kCustomEmojiLink).toString().isEmpty()) {
action.type = ActionType::RemoveCustomEmoji;
action.existingTags = format.property(kTagProperty).toString();
action.intervalStart = fragmentPosition;
action.intervalEnd = fragmentPosition
+ fragmentText.size();
break;
}
if (!startTagFound) { if (!startTagFound) {
startTagFound = true; startTagFound = true;
auto tagName = format.property(kTagProperty).toString(); auto tagName = format.property(kTagProperty).toString();
@ -2319,6 +2424,13 @@ void InputField::processFormatting(int insertPosition, int insertEnd) {
document, document,
action.intervalStart, action.intervalStart,
action.intervalEnd); action.intervalEnd);
} else if (action.type == ActionType::RemoveCustomEmoji) {
RemoveCustomEmojiTag(
_st,
document,
action.existingTags,
action.intervalStart,
action.intervalEnd);
} else if (action.type == ActionType::TildeFont) { } else if (action.type == ActionType::TildeFont) {
auto format = QTextCharFormat(); auto format = QTextCharFormat();
format.setFont(action.isTilde format.setFont(action.isTilde
@ -2476,6 +2588,11 @@ void InputField::handleContentsChanged() {
checkContentHeight(); checkContentHeight();
} }
startPlaceholderAnimation(); startPlaceholderAnimation();
if (_lastTextWithTags.text.isEmpty()) {
if (const auto object = _customEmojiObject.get()) {
object->clear();
}
}
Integration::Instance().textActionsUpdated(); Integration::Instance().textActionsUpdated();
} }
@ -2751,6 +2868,9 @@ TextWithTags InputField::getTextWithAppliedMarkdown() const {
void InputField::clear() { void InputField::clear() {
_inner->clear(); _inner->clear();
startPlaceholderAnimation(); startPlaceholderAnimation();
if (const auto object = _customEmojiObject.get()) {
object->clear();
}
} }
bool InputField::hasFocus() const { bool InputField::hasFocus() const {
@ -3460,7 +3580,7 @@ QString InputField::CustomEmojiLink(QStringView entityData) {
QString InputField::CustomEmojiEntityData(QStringView link) { QString InputField::CustomEmojiEntityData(QStringView link) {
const auto match = qthelp::regex_match( const auto match = qthelp::regex_match(
"^(\\d+\\.\\d+/\\d+)(\\?|$)", "^(\\d+\\.\\d+:\\d+/\\d+)(\\?|$)",
base::StringViewMid(link, kCustomEmojiTagStart.size())); base::StringViewMid(link, kCustomEmojiTagStart.size()));
return match ? match->captured(1) : QString(); return match ? match->captured(1) : QString();
} }

View file

@ -23,6 +23,10 @@
class QTouchEvent; class QTouchEvent;
class Painter; class Painter;
namespace Ui::Text {
class CustomEmoji;
} // namespace Ui::Text
namespace Ui { namespace Ui {
const auto kClearFormatSequence = QKeySequence("ctrl+shift+n"); const auto kClearFormatSequence = QKeySequence("ctrl+shift+n");
@ -31,6 +35,10 @@ const auto kMonospaceSequence = QKeySequence("ctrl+shift+m");
const auto kEditLinkSequence = QKeySequence("ctrl+k"); const auto kEditLinkSequence = QKeySequence("ctrl+k");
const auto kSpoilerSequence = QKeySequence("ctrl+shift+p"); const auto kSpoilerSequence = QKeySequence("ctrl+shift+p");
using CustomEmojiFactory = Fn<std::unique_ptr<Text::CustomEmoji>(
QStringView,
Fn<void()>)>;
class PopupMenu; class PopupMenu;
void InsertEmojiAtCursor(QTextCursor cursor, EmojiPtr emoji); void InsertEmojiAtCursor(QTextCursor cursor, EmojiPtr emoji);
@ -149,7 +157,10 @@ class CustomEmojiObject : public QObject, public QTextObjectInterface {
Q_INTERFACES(QTextObjectInterface) Q_INTERFACES(QTextObjectInterface)
public: public:
explicit CustomEmojiObject(QObject *parent); using Factory = Fn<std::unique_ptr<Text::CustomEmoji>(QStringView)>;
CustomEmojiObject(Factory factory, Fn<bool()> paused);
~CustomEmojiObject();
QSizeF intrinsicSize( QSizeF intrinsicSize(
QTextDocument *doc, QTextDocument *doc,
@ -162,6 +173,15 @@ public:
int posInDocument, int posInDocument,
const QTextFormat &format) override; const QTextFormat &format) override;
void setNow(crl::time now);
void clear();
private:
Factory _factory;
Fn<bool()> _paused;
base::flat_map<uint64, std::unique_ptr<Text::CustomEmoji>> _emoji;
crl::time _now = 0;
}; };
class InputField : public RpWidget { class InputField : public RpWidget {
@ -245,12 +265,10 @@ public:
// If you need to make some preparations of tags before putting them to QMimeData // If you need to make some preparations of tags before putting them to QMimeData
// (and then to clipboard or to drag-n-drop object), here is a strategy for that. // (and then to clipboard or to drag-n-drop object), here is a strategy for that.
class TagMimeProcessor { void setTagMimeProcessor(Fn<QString(QStringView)> processor);
public: void setCustomEmojiFactory(
virtual QString tagFromMimeTag(const QString &mimeTag) = 0; CustomEmojiFactory factory,
virtual ~TagMimeProcessor() = default; Fn<bool()> paused);
};
void setTagMimeProcessor(std::unique_ptr<TagMimeProcessor> &&processor);
struct EditLinkSelection { struct EditLinkSelection {
int from = 0; int from = 0;
@ -528,7 +546,8 @@ private:
// before _documentContentsChanges fire. // before _documentContentsChanges fire.
int _emojiSurrogateAmount = 0; int _emojiSurrogateAmount = 0;
std::unique_ptr<TagMimeProcessor> _tagMimeProcessor; Fn<QString(QStringView)> _tagMimeProcessor;
std::unique_ptr<CustomEmojiObject> _customEmojiObject;
SubmitSettings _submitSettings = SubmitSettings::Enter; SubmitSettings _submitSettings = SubmitSettings::Enter;
bool _markdownEnabled = false; bool _markdownEnabled = false;