Fix editing text with custom emoji and formatting.
This commit is contained in:
parent
e6b3951b40
commit
6bd7518109
4 changed files with 213 additions and 58 deletions
|
|
@ -1527,6 +1527,29 @@ bool CutPart(TextWithEntities &sending, TextWithEntities &left, int32 limit) {
|
|||
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) {
|
||||
auto result = TextWithEntities{ text, EntitiesInText() };
|
||||
ParseEntities(result, flags);
|
||||
|
|
@ -1929,7 +1952,14 @@ bool IsMentionLink(QStringView link) {
|
|||
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)
|
||||
|| (tag == Ui::InputField::kTagPre);
|
||||
}
|
||||
|
|
@ -1953,8 +1983,8 @@ QString JoinTag(const QList<QStringView> &list) {
|
|||
return result;
|
||||
}
|
||||
|
||||
QList<QStringView> SplitTags(const QString &tag) {
|
||||
return QStringView(tag).split(kTagSeparator);
|
||||
QList<QStringView> SplitTags(QStringView tag) {
|
||||
return tag.split(kTagSeparator);
|
||||
}
|
||||
|
||||
QString TagWithRemoved(const QString &tag, const QString &removed) {
|
||||
|
|
@ -2073,11 +2103,9 @@ EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags) {
|
|||
openType(EntityType::CustomEmoji, data);
|
||||
}
|
||||
} else if (IsMentionLink(nextState.link)) {
|
||||
const auto match = qthelp::regex_match(
|
||||
"^(\\d+\\.\\d+)(/|$)",
|
||||
base::StringViewMid(nextState.link, kMentionTagStart.size()));
|
||||
if (match) {
|
||||
openType(EntityType::MentionName, match->captured(1));
|
||||
const auto data = MentionEntityData(nextState.link);
|
||||
if (!data.isEmpty()) {
|
||||
openType(EntityType::MentionName, data);
|
||||
}
|
||||
} else {
|
||||
openType(EntityType::CustomUrl, nextState.link);
|
||||
|
|
@ -2169,8 +2197,8 @@ TextWithTags::Tags ConvertEntitiesToTextTags(
|
|||
};
|
||||
switch (entity.type()) {
|
||||
case EntityType::MentionName: {
|
||||
auto match = QRegularExpression(
|
||||
R"(^(\d+\.\d+)$)"
|
||||
const auto match = QRegularExpression(
|
||||
"^(\\d+\\.\\d+:\\d+)$"
|
||||
).match(entity.data());
|
||||
if (match.hasMatch()) {
|
||||
push(kMentionTagStart + entity.data());
|
||||
|
|
@ -2184,7 +2212,12 @@ TextWithTags::Tags ConvertEntitiesToTextTags(
|
|||
}
|
||||
} break;
|
||||
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;
|
||||
case EntityType::Bold: push(Ui::InputField::kTagBold); break;
|
||||
//case EntityType::Semibold: // Semibold is for UI parts only.
|
||||
|
|
|
|||
|
|
@ -306,31 +306,13 @@ QStringList PrepareSearchWords(const QString &query, const QRegularExpression *S
|
|||
bool CutPart(TextWithEntities &sending, TextWithEntities &left, int limit);
|
||||
|
||||
struct MentionNameFields {
|
||||
MentionNameFields(uint64 userId = 0, uint64 accessHash = 0)
|
||||
: userId(userId), accessHash(accessHash) {
|
||||
}
|
||||
uint64 selfId = 0;
|
||||
uint64 userId = 0;
|
||||
uint64 accessHash = 0;
|
||||
};
|
||||
|
||||
inline MentionNameFields MentionNameDataToFields(const QString &data) {
|
||||
auto components = data.split('.');
|
||||
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;
|
||||
}
|
||||
[[nodiscard]] MentionNameFields MentionNameDataToFields(QStringView data);
|
||||
[[nodiscard]] QString MentionNameDataFromFields(
|
||||
const MentionNameFields &fields);
|
||||
|
||||
// New entities are added to the ones that are already in result.
|
||||
// Changes text if (flags & TextParseMarkdown).
|
||||
|
|
@ -362,12 +344,13 @@ void ApplyServerCleaning(TextWithEntities &result);
|
|||
[[nodiscard]] QString TagsMimeType();
|
||||
[[nodiscard]] QString TagsTextMimeType();
|
||||
|
||||
inline const auto kMentionTagStart = qstr("mention://user.");
|
||||
inline const auto kMentionTagStart = qstr("mention://");
|
||||
|
||||
[[nodiscard]] bool IsMentionLink(QStringView link);
|
||||
[[nodiscard]] QString MentionEntityData(QStringView link);
|
||||
[[nodiscard]] bool IsSeparateTag(QStringView tag);
|
||||
[[nodiscard]] QString JoinTag(const QList<QStringView> &list);
|
||||
[[nodiscard]] QList<QStringView> SplitTags(const QString &tag);
|
||||
[[nodiscard]] QList<QStringView> SplitTags(QStringView tag);
|
||||
[[nodiscard]] QString TagWithRemoved(
|
||||
const QString &tag,
|
||||
const QString &removed);
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ constexpr auto kTagProperty = QTextFormat::UserProperty + 4;
|
|||
constexpr auto kCustomEmojiFormat = QTextFormat::UserObject + 1;
|
||||
constexpr auto kCustomEmojiText = QTextFormat::UserProperty + 5;
|
||||
constexpr auto kCustomEmojiLink = QTextFormat::UserProperty + 6;
|
||||
constexpr auto kCustomEmojiId = QTextFormat::UserProperty + 7;
|
||||
const auto kObjectReplacementCh = QChar(QChar::ObjectReplacementCharacter);
|
||||
const auto kObjectReplacement = QString::fromRawData(
|
||||
&kObjectReplacementCh,
|
||||
|
|
@ -127,6 +128,19 @@ bool IsNewline(QChar ch) {
|
|||
.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(
|
||||
const TextWithTags &textWithTags,
|
||||
const QString &tag) {
|
||||
|
|
@ -733,6 +747,9 @@ QTextCharFormat PrepareTagFormat(
|
|||
replaceWith = MakeUniqueCustomEmojiLink(tag);
|
||||
result.setObjectType(kCustomEmojiFormat);
|
||||
result.setProperty(kCustomEmojiLink, replaceWith);
|
||||
result.setProperty(
|
||||
kCustomEmojiId,
|
||||
CustomEmojiIdFromLink(replaceWith));
|
||||
} else if (IsValidMarkdownLink(tag)) {
|
||||
color = st::defaultTextPalette.linkFg;
|
||||
} else if (tag == kTagBold) {
|
||||
|
|
@ -762,10 +779,40 @@ QTextCharFormat PrepareTagFormat(
|
|||
: std::move(tag).replace(replaceWhat, replaceWith)));
|
||||
if (bg) {
|
||||
result.setBackground(*bg);
|
||||
} else {
|
||||
result.setBackground(QBrush());
|
||||
}
|
||||
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) {
|
||||
to.setProperty(kTagProperty, from.property(kTagProperty));
|
||||
to.setProperty(kReplaceTagId, from.property(kReplaceTagId));
|
||||
|
|
@ -781,7 +828,7 @@ int ProcessInsertedTags(
|
|||
int changedPosition,
|
||||
int changedEnd,
|
||||
const TextWithTags::Tags &tags,
|
||||
InputField::TagMimeProcessor *processor) {
|
||||
Fn<QString(QStringView)> processor) {
|
||||
int firstTagStart = changedEnd;
|
||||
int applyNoTagFrom = changedEnd;
|
||||
for (const auto &tag : tags) {
|
||||
|
|
@ -789,7 +836,7 @@ int ProcessInsertedTags(
|
|||
int tagTo = tagFrom + tag.length;
|
||||
accumulate_max(tagFrom, changedPosition);
|
||||
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()) {
|
||||
accumulate_min(firstTagStart, tagFrom);
|
||||
|
||||
|
|
@ -833,7 +880,9 @@ bool WasInsertTillTheEndOfTag(
|
|||
const auto outsideInsertion = (position >= insertionEnd);
|
||||
if (outsideInsertion) {
|
||||
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 notFullFragmentInserted = (end > insertionEnd);
|
||||
|
|
@ -857,6 +906,7 @@ struct FormattingAction {
|
|||
Invalid,
|
||||
InsertEmoji,
|
||||
InsertCustomEmoji,
|
||||
RemoveCustomEmoji,
|
||||
TildeFont,
|
||||
RemoveTag,
|
||||
RemoveNewline,
|
||||
|
|
@ -867,6 +917,7 @@ struct FormattingAction {
|
|||
EmojiPtr emoji = nullptr;
|
||||
bool isTilde = false;
|
||||
QString tildeTag;
|
||||
QString existingTags;
|
||||
QString customEmojiText;
|
||||
QString customEmojiLink;
|
||||
int intervalStart = 0;
|
||||
|
|
@ -949,6 +1000,7 @@ void InsertCustomEmojiAtCursor(
|
|||
format.setObjectType(kCustomEmojiFormat);
|
||||
format.setProperty(kCustomEmojiText, text);
|
||||
format.setProperty(kCustomEmojiLink, MakeUniqueCustomEmojiLink(link));
|
||||
format.setProperty(kCustomEmojiId, CustomEmojiIdFromLink(link));
|
||||
format.setVerticalAlignment(QTextCharFormat::AlignBottom);
|
||||
ApplyTagFormat(format, currentFormat);
|
||||
cursor.insertText(kObjectReplacement, format);
|
||||
|
|
@ -1315,9 +1367,14 @@ void FlatInput::onTextChange(const QString &text) {
|
|||
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(
|
||||
QTextDocument *doc,
|
||||
int posInDocument,
|
||||
|
|
@ -1326,7 +1383,7 @@ QSizeF CustomEmojiObject::intrinsicSize(
|
|||
const auto size = Emoji::GetSizeNormal() / factor;
|
||||
const auto width = size + st::emojiPadding * 2.;
|
||||
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 };
|
||||
}
|
||||
|
||||
|
|
@ -1336,7 +1393,36 @@ void CustomEmojiObject::drawObject(
|
|||
QTextDocument *doc,
|
||||
int posInDocument,
|
||||
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(
|
||||
|
|
@ -1388,10 +1474,6 @@ InputField::InputField(
|
|||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
}
|
||||
|
||||
_inner->document()->documentLayout()->registerHandler(
|
||||
kCustomEmojiFormat,
|
||||
new CustomEmojiObject(this));
|
||||
|
||||
_inner->setFont(_st.font->f);
|
||||
_inner->setAlignment(_st.textAlign);
|
||||
if (_mode == Mode::SingleLine) {
|
||||
|
|
@ -1473,6 +1555,8 @@ bool InputField::viewportEventInner(QEvent *e) {
|
|||
if (ev->device()->type() == base::TouchDevice::TouchScreen) {
|
||||
handleTouchEvent(ev);
|
||||
}
|
||||
} else if (e->type() == QEvent::Paint && _customEmojiObject) {
|
||||
_customEmojiObject->setNow(crl::now());
|
||||
}
|
||||
return _inner->QTextEdit::viewportEvent(e);
|
||||
}
|
||||
|
|
@ -1566,11 +1650,22 @@ void InputField::setMarkdownReplacesEnabled(rpl::producer<bool> enabled) {
|
|||
}, lifetime());
|
||||
}
|
||||
|
||||
void InputField::setTagMimeProcessor(
|
||||
std::unique_ptr<TagMimeProcessor> &&processor) {
|
||||
void InputField::setTagMimeProcessor(Fn<QString(QStringView)> 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) {
|
||||
_additionalMargin = margin;
|
||||
QResizeEvent e(size(), size());
|
||||
|
|
@ -2105,8 +2200,8 @@ void InputField::processFormatting(int insertPosition, int insertEnd) {
|
|||
auto document = _inner->document();
|
||||
|
||||
// Apply inserted tags.
|
||||
auto insertedTagsProcessor = _insertedTagsAreFromMime
|
||||
? _tagMimeProcessor.get()
|
||||
const auto insertedTagsProcessor = _insertedTagsAreFromMime
|
||||
? _tagMimeProcessor
|
||||
: nullptr;
|
||||
const auto breakTagOnNotLetterTill = ProcessInsertedTags(
|
||||
_st,
|
||||
|
|
@ -2176,6 +2271,7 @@ void InputField::processFormatting(int insertPosition, int insertEnd) {
|
|||
action.customEmojiText = fragmentText;
|
||||
action.customEmojiLink = format.property(
|
||||
kCustomEmojiLink).toString();
|
||||
break;
|
||||
}
|
||||
|
||||
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) {
|
||||
startTagFound = true;
|
||||
auto tagName = format.property(kTagProperty).toString();
|
||||
|
|
@ -2319,6 +2424,13 @@ void InputField::processFormatting(int insertPosition, int insertEnd) {
|
|||
document,
|
||||
action.intervalStart,
|
||||
action.intervalEnd);
|
||||
} else if (action.type == ActionType::RemoveCustomEmoji) {
|
||||
RemoveCustomEmojiTag(
|
||||
_st,
|
||||
document,
|
||||
action.existingTags,
|
||||
action.intervalStart,
|
||||
action.intervalEnd);
|
||||
} else if (action.type == ActionType::TildeFont) {
|
||||
auto format = QTextCharFormat();
|
||||
format.setFont(action.isTilde
|
||||
|
|
@ -2476,6 +2588,11 @@ void InputField::handleContentsChanged() {
|
|||
checkContentHeight();
|
||||
}
|
||||
startPlaceholderAnimation();
|
||||
if (_lastTextWithTags.text.isEmpty()) {
|
||||
if (const auto object = _customEmojiObject.get()) {
|
||||
object->clear();
|
||||
}
|
||||
}
|
||||
Integration::Instance().textActionsUpdated();
|
||||
}
|
||||
|
||||
|
|
@ -2751,6 +2868,9 @@ TextWithTags InputField::getTextWithAppliedMarkdown() const {
|
|||
void InputField::clear() {
|
||||
_inner->clear();
|
||||
startPlaceholderAnimation();
|
||||
if (const auto object = _customEmojiObject.get()) {
|
||||
object->clear();
|
||||
}
|
||||
}
|
||||
|
||||
bool InputField::hasFocus() const {
|
||||
|
|
@ -3460,7 +3580,7 @@ QString InputField::CustomEmojiLink(QStringView entityData) {
|
|||
|
||||
QString InputField::CustomEmojiEntityData(QStringView link) {
|
||||
const auto match = qthelp::regex_match(
|
||||
"^(\\d+\\.\\d+/\\d+)(\\?|$)",
|
||||
"^(\\d+\\.\\d+:\\d+/\\d+)(\\?|$)",
|
||||
base::StringViewMid(link, kCustomEmojiTagStart.size()));
|
||||
return match ? match->captured(1) : QString();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@
|
|||
class QTouchEvent;
|
||||
class Painter;
|
||||
|
||||
namespace Ui::Text {
|
||||
class CustomEmoji;
|
||||
} // namespace Ui::Text
|
||||
|
||||
namespace Ui {
|
||||
|
||||
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 kSpoilerSequence = QKeySequence("ctrl+shift+p");
|
||||
|
||||
using CustomEmojiFactory = Fn<std::unique_ptr<Text::CustomEmoji>(
|
||||
QStringView,
|
||||
Fn<void()>)>;
|
||||
|
||||
class PopupMenu;
|
||||
|
||||
void InsertEmojiAtCursor(QTextCursor cursor, EmojiPtr emoji);
|
||||
|
|
@ -149,7 +157,10 @@ class CustomEmojiObject : public QObject, public QTextObjectInterface {
|
|||
Q_INTERFACES(QTextObjectInterface)
|
||||
|
||||
public:
|
||||
explicit CustomEmojiObject(QObject *parent);
|
||||
using Factory = Fn<std::unique_ptr<Text::CustomEmoji>(QStringView)>;
|
||||
|
||||
CustomEmojiObject(Factory factory, Fn<bool()> paused);
|
||||
~CustomEmojiObject();
|
||||
|
||||
QSizeF intrinsicSize(
|
||||
QTextDocument *doc,
|
||||
|
|
@ -162,6 +173,15 @@ public:
|
|||
int posInDocument,
|
||||
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 {
|
||||
|
|
@ -245,12 +265,10 @@ public:
|
|||
|
||||
// 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.
|
||||
class TagMimeProcessor {
|
||||
public:
|
||||
virtual QString tagFromMimeTag(const QString &mimeTag) = 0;
|
||||
virtual ~TagMimeProcessor() = default;
|
||||
};
|
||||
void setTagMimeProcessor(std::unique_ptr<TagMimeProcessor> &&processor);
|
||||
void setTagMimeProcessor(Fn<QString(QStringView)> processor);
|
||||
void setCustomEmojiFactory(
|
||||
CustomEmojiFactory factory,
|
||||
Fn<bool()> paused);
|
||||
|
||||
struct EditLinkSelection {
|
||||
int from = 0;
|
||||
|
|
@ -528,7 +546,8 @@ private:
|
|||
// before _documentContentsChanges fire.
|
||||
int _emojiSurrogateAmount = 0;
|
||||
|
||||
std::unique_ptr<TagMimeProcessor> _tagMimeProcessor;
|
||||
Fn<QString(QStringView)> _tagMimeProcessor;
|
||||
std::unique_ptr<CustomEmojiObject> _customEmojiObject;
|
||||
|
||||
SubmitSettings _submitSettings = SubmitSettings::Enter;
|
||||
bool _markdownEnabled = false;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue