Support instant-replacement with a custom emoji.

This commit is contained in:
John Preston 2022-07-18 20:30:09 +03:00
parent 1d34c64da8
commit 0daf3d4ac7
2 changed files with 56 additions and 17 deletions

View file

@ -35,7 +35,6 @@ constexpr auto kInstantReplaceWhatId = QTextFormat::UserProperty + 1;
constexpr auto kInstantReplaceWithId = QTextFormat::UserProperty + 2; constexpr auto kInstantReplaceWithId = QTextFormat::UserProperty + 2;
constexpr auto kReplaceTagId = QTextFormat::UserProperty + 3; constexpr auto kReplaceTagId = QTextFormat::UserProperty + 3;
constexpr auto kTagProperty = QTextFormat::UserProperty + 4; constexpr auto kTagProperty = QTextFormat::UserProperty + 4;
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; constexpr auto kCustomEmojiId = QTextFormat::UserProperty + 7;
@ -50,6 +49,7 @@ const auto &kTagStrikeOut = InputField::kTagStrikeOut;
const auto &kTagCode = InputField::kTagCode; const auto &kTagCode = InputField::kTagCode;
const auto &kTagPre = InputField::kTagPre; const auto &kTagPre = InputField::kTagPre;
const auto &kTagSpoiler = InputField::kTagSpoiler; const auto &kTagSpoiler = InputField::kTagSpoiler;
const auto &kCustomEmojiFormat = InputField::kCustomEmojiFormat;
const auto kTagCheckLinkMeta = u"^:/:/:^"_q; const auto kTagCheckLinkMeta = u"^:/:/:^"_q;
const auto kNewlineChars = QString("\r\n") const auto kNewlineChars = QString("\r\n")
+ QChar(0xfdd0) // QTextBeginningOfFrame + QChar(0xfdd0) // QTextBeginningOfFrame
@ -942,6 +942,8 @@ const QString InputField::kTagCode = QStringLiteral("`");
const QString InputField::kTagPre = QStringLiteral("```"); const QString InputField::kTagPre = QStringLiteral("```");
const QString InputField::kTagSpoiler = QStringLiteral("||"); const QString InputField::kTagSpoiler = QStringLiteral("||");
const QString InputField::kCustomEmojiTagStart = u"custom-emoji://"_q; const QString InputField::kCustomEmojiTagStart = u"custom-emoji://"_q;
const int InputField::kCustomEmojiFormat
= QTextFormat::UserObject + 1;
class InputField::Inner final : public QTextEdit { class InputField::Inner final : public QTextEdit {
public: public:
@ -1012,12 +1014,9 @@ void InsertCustomEmojiAtCursor(
format.setProperty(kCustomEmojiId, CustomEmojiIdFromLink(link)); format.setProperty(kCustomEmojiId, CustomEmojiIdFromLink(link));
format.setVerticalAlignment(QTextCharFormat::AlignBottom); format.setVerticalAlignment(QTextCharFormat::AlignBottom);
ApplyTagFormat(format, currentFormat); ApplyTagFormat(format, currentFormat);
auto existingTag = format.property(kTagProperty).toString(); format.setProperty(kTagProperty, TextUtilities::TagWithAdded(
auto existingTags = existingTag.isEmpty() format.property(kTagProperty).toString(),
? QList<QStringView>() unique));
: TextUtilities::SplitTags(existingTag);
existingTags.push_back(unique);
format.setProperty(kTagProperty, TextUtilities::JoinTag(existingTags));
cursor.insertText(kObjectReplacement, format); cursor.insertText(kObjectReplacement, format);
} }
@ -3352,20 +3351,34 @@ void InputField::applyInstantReplace(
} else if (position < length) { } else if (position < length) {
return; return;
} }
commitInstantReplacement(position - length, position, with, what, true); commitInstantReplacement(
} position - length,
position,
void InputField::commitInstantReplacement( with,
int from, QString(),
int till, what,
const QString &with) { true);
commitInstantReplacement(from, till, with, std::nullopt, false);
} }
void InputField::commitInstantReplacement( void InputField::commitInstantReplacement(
int from, int from,
int till, int till,
const QString &with, const QString &with,
const QString &customEmojiData) {
commitInstantReplacement(
from,
till,
with,
customEmojiData,
std::nullopt,
false);
}
void InputField::commitInstantReplacement(
int from,
int till,
const QString &with,
const QString &customEmojiData,
std::optional<QString> checkOriginal, std::optional<QString> checkOriginal,
bool checkIfInMonospace) { bool checkIfInMonospace) {
const auto original = getTextWithTagsPart(from, till).text; const auto original = getTextWithTagsPart(from, till).text;
@ -3388,17 +3401,32 @@ void InputField::commitInstantReplacement(
cursor.setPosition(from); cursor.setPosition(from);
cursor.setPosition(till, QTextCursor::KeepAnchor); cursor.setPosition(till, QTextCursor::KeepAnchor);
const auto link = customEmojiData.isEmpty()
? QString()
: CustomEmojiLink(customEmojiData);
const auto unique = link.isEmpty()
? QString()
: MakeUniqueCustomEmojiLink(link);
auto format = [&]() -> QTextCharFormat { auto format = [&]() -> QTextCharFormat {
auto emojiLength = 0; auto emojiLength = 0;
const auto emoji = Emoji::Find(with, &emojiLength); const auto emoji = Emoji::Find(with, &emojiLength);
if (!emoji || with.size() != emojiLength) { if (!emoji || with.size() != emojiLength) {
return _defaultCharFormat; return _defaultCharFormat;
} else if (!customEmojiData.isEmpty()) {
auto result = QTextCharFormat();
result.setObjectType(kCustomEmojiFormat);
result.setProperty(kCustomEmojiText, with);
result.setProperty(kCustomEmojiLink, unique);
result.setProperty(kCustomEmojiId, CustomEmojiIdFromLink(link));
result.setVerticalAlignment(QTextCharFormat::AlignBottom);
return result;
} }
const auto use = Integration::Instance().defaultEmojiVariant( const auto use = Integration::Instance().defaultEmojiVariant(
emoji); emoji);
return PrepareEmojiFormat(use, _st.font); return PrepareEmojiFormat(use, _st.font);
}(); }();
const auto replacement = format.isImageFormat() const auto replacement = (format.isImageFormat()
|| format.objectType() == kCustomEmojiFormat)
? kObjectReplacement ? kObjectReplacement
: with; : with;
format.setProperty(kInstantReplaceWhatId, original); format.setProperty(kInstantReplaceWhatId, original);
@ -3407,6 +3435,11 @@ void InputField::commitInstantReplacement(
kInstantReplaceRandomId, kInstantReplaceRandomId,
base::RandomValue<uint32>()); base::RandomValue<uint32>());
ApplyTagFormat(format, cursor.charFormat()); ApplyTagFormat(format, cursor.charFormat());
if (!unique.isEmpty()) {
format.setProperty(kTagProperty, TextUtilities::TagWithAdded(
format.property(kTagProperty).toString(),
unique));
}
cursor.insertText(replacement, format); cursor.insertText(replacement, format);
} }

View file

@ -220,6 +220,7 @@ public:
static const QString kTagPre; static const QString kTagPre;
static const QString kTagSpoiler; static const QString kTagSpoiler;
static const QString kCustomEmojiTagStart; static const QString kCustomEmojiTagStart;
static const int kCustomEmojiFormat;
InputField( InputField(
QWidget *parent, QWidget *parent,
@ -301,7 +302,11 @@ public:
void setInstantReplacesEnabled(rpl::producer<bool> enabled); void setInstantReplacesEnabled(rpl::producer<bool> enabled);
void setMarkdownReplacesEnabled(rpl::producer<bool> enabled); void setMarkdownReplacesEnabled(rpl::producer<bool> enabled);
void setExtendedContextMenu(rpl::producer<ExtendedContextMenu> value); void setExtendedContextMenu(rpl::producer<ExtendedContextMenu> value);
void commitInstantReplacement(int from, int till, const QString &with); void commitInstantReplacement(
int from,
int till,
const QString &with,
const QString &customEmojiData);
void commitMarkdownLinkEdit( void commitMarkdownLinkEdit(
EditLinkSelection selection, EditLinkSelection selection,
const QString &text, const QString &text,
@ -497,6 +502,7 @@ private:
int from, int from,
int till, int till,
const QString &with, const QString &with,
const QString &customEmojiData,
std::optional<QString> checkOriginal, std::optional<QString> checkOriginal,
bool checkIfInMonospace); bool checkIfInMonospace);
bool commitMarkdownReplacement( bool commitMarkdownReplacement(