From 168a7ce2e5614feffc203d769a45954fd81a20fd Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 13 May 2018 18:56:08 +0300 Subject: [PATCH] Add "Suggest emoji replacements" checkbox. Also emoji suggestions insert an instant emoji replacement. --- Telegram/Resources/langs/lang.strings | 1 + .../chat_helpers/emoji_suggestions_widget.cpp | 41 ++++++++++++------- .../chat_helpers/emoji_suggestions_widget.h | 8 ++++ .../chat_helpers/message_field.cpp | 4 ++ Telegram/SourceFiles/facades.cpp | 6 +++ Telegram/SourceFiles/facades.h | 3 ++ .../SourceFiles/history/history_widget.cpp | 6 +++ Telegram/SourceFiles/settings.cpp | 1 - Telegram/SourceFiles/settings.h | 1 - .../settings_chat_settings_widget.cpp | 11 ++++- .../settings/settings_chat_settings_widget.h | 2 + Telegram/SourceFiles/storage/localstorage.cpp | 38 ++++++++++------- .../SourceFiles/ui/widgets/input_fields.cpp | 34 ++++++++++----- .../SourceFiles/ui/widgets/input_fields.h | 9 +++- 14 files changed, 121 insertions(+), 44 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 68d03b67a..c04a26d47 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -296,6 +296,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_section_chat_settings" = "Chat Settings"; "lng_settings_replace_emojis" = "Replace emoji"; +"lng_settings_suggest_emoji" = "Suggest emoji replacements"; "lng_settings_suggest_by_emoji" = "Suggest popular stickers by emoji"; "lng_settings_view_emojis" = "View list"; "lng_settings_send_enter" = "Send by Enter"; diff --git a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp index e7da4d4b5..d12d435bb 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp @@ -346,12 +346,15 @@ void SuggestionsWidget::leaveEventHook(QEvent *e) { return TWidget::leaveEventHook(e); } -SuggestionsController::SuggestionsController(QWidget *parent, not_null field) : QObject(nullptr) +SuggestionsController::SuggestionsController(QWidget *parent, not_null field) +: QObject(nullptr) , _field(field) , _container(parent, st::emojiSuggestionsDropdown) , _suggestions(_container->setOwnedWidget(object_ptr(parent, st::emojiSuggestionsMenu))) { _container->setAutoHiding(false); + setReplaceCallback(nullptr); + _field->installEventFilter(this); connect(_field, &QTextEdit::textChanged, this, [this] { handleTextChange(); }); connect(_field, &QTextEdit::cursorPositionChanged, this, [this] { handleCursorPositionChange(); }); @@ -363,6 +366,23 @@ SuggestionsController::SuggestionsController(QWidget *parent, not_null callback) { + if (callback) { + _replaceCallback = std::move(callback); + } else { + _replaceCallback = [=](int from, int till, const QString &replacement) { + auto cursor = _field->textCursor(); + cursor.setPosition(from); + cursor.setPosition(till, QTextCursor::KeepAnchor); + cursor.insertText(replacement); + }; + } +} + void SuggestionsController::handleTextChange() { _ignoreCursorPositionChange = true; InvokeQueued(this, [this] { _ignoreCursorPositionChange = false; }); @@ -374,7 +394,7 @@ void SuggestionsController::handleTextChange() { } QString SuggestionsController::getEmojiQuery() { - if (!cReplaceEmojis()) { + if (!Global::SuggestEmoji()) { return QString(); } @@ -471,23 +491,14 @@ QString SuggestionsController::getEmojiQuery() { } void SuggestionsController::replaceCurrent(const QString &replacement) { - auto cursor = _field->textCursor(); auto suggestion = getEmojiQuery(); if (suggestion.isEmpty()) { _suggestions->showWithQuery(QString()); } else { - cursor.setPosition(cursor.position() - suggestion.size(), QTextCursor::KeepAnchor); - cursor.insertText(replacement); - } - - if (auto emoji = Find(replacement)) { - if (emoji->hasVariants()) { - auto it = cEmojiVariants().constFind(emoji->nonColoredId()); - if (it != cEmojiVariants().cend()) { - emoji = emoji->variant(it.value()); - } - } - AddRecent(emoji); + const auto cursor = _field->textCursor(); + const auto position = cursor.position(); + const auto from = position - suggestion.size(); + _replaceCallback(from, position, replacement); } } diff --git a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.h b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.h index d316db3b3..4a3c3cc03 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.h +++ b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.h @@ -69,6 +69,10 @@ public: SuggestionsController(QWidget *parent, not_null field); void raise(); + void setReplaceCallback(base::lambda callback); protected: bool eventFilter(QObject *object, QEvent *event) override; @@ -88,6 +92,10 @@ private: bool _ignoreCursorPositionChange = false; bool _textChangeAfterKeyPress = false; QPointer _field; + base::lambda _replaceCallback; object_ptr _container; QPointer _suggestions; diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index a2268935a..99183480b 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -133,6 +133,10 @@ MessageField::MessageField(QWidget *parent, not_null contro Assert(emoji != nullptr); addInstantReplace(what, emoji->text()); } + enableInstantReplaces(Global::ReplaceEmoji()); + subscribe(Global::RefReplaceEmojiChanged(), [=] { + enableInstantReplaces(Global::ReplaceEmoji()); + }); } bool MessageField::hasSendText() const { diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index aaed8f099..f3b4a2698 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -553,7 +553,10 @@ struct Data { QByteArray DownloadPathBookmark; base::Observable DownloadPathChanged; + bool ReplaceEmoji = true; + bool SuggestEmoji = true; bool SuggestStickersByEmoji = true; + base::Observable ReplaceEmojiChanged; bool SoundNotify = true; bool DesktopNotify = true; bool RestoreSoundNotifyFromTray = false; @@ -678,7 +681,10 @@ DefineVar(Global, QString, DownloadPath); DefineVar(Global, QByteArray, DownloadPathBookmark); DefineRefVar(Global, base::Observable, DownloadPathChanged); +DefineVar(Global, bool, ReplaceEmoji); +DefineVar(Global, bool, SuggestEmoji); DefineVar(Global, bool, SuggestStickersByEmoji); +DefineRefVar(Global, base::Observable, ReplaceEmojiChanged); DefineVar(Global, bool, SoundNotify); DefineVar(Global, bool, DesktopNotify); DefineVar(Global, bool, RestoreSoundNotifyFromTray); diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index add37728c..0782651e7 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -361,7 +361,10 @@ DeclareVar(QString, DownloadPath); DeclareVar(QByteArray, DownloadPathBookmark); DeclareRefVar(base::Observable, DownloadPathChanged); +DeclareVar(bool, ReplaceEmoji); +DeclareVar(bool, SuggestEmoji); DeclareVar(bool, SuggestStickersByEmoji); +DeclareRefVar(base::Observable, ReplaceEmojiChanged); DeclareVar(bool, SoundNotify); DeclareVar(bool, DesktopNotify); DeclareVar(bool, RestoreSoundNotifyFromTray); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index e149677f7..8de17d589 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -512,6 +512,12 @@ HistoryWidget::HistoryWidget( data->text()); }); _emojiSuggestions.create(this, _field.data()); + _emojiSuggestions->setReplaceCallback([=]( + int from, + int till, + const QString &replacement) { + _field->commmitInstantReplacement(from, till, replacement); + }); updateFieldSubmitSettings(); _field->hide(); diff --git a/Telegram/SourceFiles/settings.cpp b/Telegram/SourceFiles/settings.cpp index 35e815159..162369818 100644 --- a/Telegram/SourceFiles/settings.cpp +++ b/Telegram/SourceFiles/settings.cpp @@ -40,7 +40,6 @@ bool gRestartingUpdate = false, gRestarting = false, gRestartingToSettings = fal int32 gLastUpdateCheck = 0; bool gNoStartUpdate = false; bool gStartToSettings = false; -bool gReplaceEmojis = true; bool gCtrlEnter = false; diff --git a/Telegram/SourceFiles/settings.h b/Telegram/SourceFiles/settings.h index 7d6b46989..43dc77c1a 100644 --- a/Telegram/SourceFiles/settings.h +++ b/Telegram/SourceFiles/settings.h @@ -100,7 +100,6 @@ DeclareSetting(bool, WriteProtected); DeclareSetting(int32, LastUpdateCheck); DeclareSetting(bool, NoStartUpdate); DeclareSetting(bool, StartToSettings); -DeclareSetting(bool, ReplaceEmojis); DeclareReadSetting(bool, ManyInstance); DeclareSetting(QByteArray, LocalSalt); diff --git a/Telegram/SourceFiles/settings/settings_chat_settings_widget.cpp b/Telegram/SourceFiles/settings/settings_chat_settings_widget.cpp index 8ee2f000b..028cfb3f5 100644 --- a/Telegram/SourceFiles/settings/settings_chat_settings_widget.cpp +++ b/Telegram/SourceFiles/settings/settings_chat_settings_widget.cpp @@ -140,7 +140,8 @@ void ChatSettingsWidget::createControls() { style::margins marginSub(0, 0, 0, st::settingsSubSkip); style::margins slidedPadding(0, marginSub.bottom() / 2, 0, marginSub.bottom() - (marginSub.bottom() / 2)); - createChildRow(_replaceEmoji, marginSmall, lang(lng_settings_replace_emojis), [this](bool) { toggleReplaceEmoji(); }, cReplaceEmojis()); + createChildRow(_replaceEmoji, marginSmall, lang(lng_settings_replace_emojis), [this](bool) { toggleReplaceEmoji(); }, Global::ReplaceEmoji()); + createChildRow(_suggestEmoji, marginSmall, lang(lng_settings_suggest_emoji), [this](bool) { toggleSuggestEmoji(); }, Global::SuggestEmoji()); createChildRow(_suggestByEmoji, marginSkip, lang(lng_settings_suggest_by_emoji), [this](bool) { toggleSuggestStickersByEmoji(); }, Global::SuggestStickersByEmoji()); #ifndef OS_WIN_STORE @@ -170,7 +171,13 @@ void ChatSettingsWidget::createControls() { } void ChatSettingsWidget::toggleReplaceEmoji() { - cSetReplaceEmojis(_replaceEmoji->checked()); + Global::SetReplaceEmoji(_replaceEmoji->checked()); + Global::RefReplaceEmojiChanged().notify(); + Local::writeUserSettings(); +} + +void ChatSettingsWidget::toggleSuggestEmoji() { + Global::SetSuggestEmoji(_suggestEmoji->checked()); Local::writeUserSettings(); } diff --git a/Telegram/SourceFiles/settings/settings_chat_settings_widget.h b/Telegram/SourceFiles/settings/settings_chat_settings_widget.h index fa11ef11a..60316573a 100644 --- a/Telegram/SourceFiles/settings/settings_chat_settings_widget.h +++ b/Telegram/SourceFiles/settings/settings_chat_settings_widget.h @@ -94,9 +94,11 @@ private: void createControls(); void toggleReplaceEmoji(); + void toggleSuggestEmoji(); void toggleSuggestStickersByEmoji(); Ui::Checkbox *_replaceEmoji = nullptr; + Ui::Checkbox *_suggestEmoji = nullptr; Ui::Checkbox *_suggestByEmoji = nullptr; Ui::Checkbox *_dontAskDownloadPath = nullptr; diff --git a/Telegram/SourceFiles/storage/localstorage.cpp b/Telegram/SourceFiles/storage/localstorage.cpp index db0ada9fc..88cf68c83 100644 --- a/Telegram/SourceFiles/storage/localstorage.cpp +++ b/Telegram/SourceFiles/storage/localstorage.cpp @@ -522,7 +522,7 @@ enum { // 0x10 reserved dbiDefaultAttach = 0x11, dbiCatsAndDogs = 0x12, - dbiReplaceEmojis = 0x13, + dbiReplaceEmoji = 0x13, dbiAskDownloadPath = 0x14, dbiDownloadPathOld = 0x15, dbiScale = 0x16, @@ -575,6 +575,7 @@ enum { dbiConnectionType = 0x4f, dbiStickersFavedLimit = 0x50, dbiSuggestStickersByEmoji = 0x51, + dbiSuggestEmoji = 0x52, dbiEncryptedWithSalt = 333, dbiEncrypted = 444, @@ -1019,14 +1020,6 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSetting Global::SetSoundNotify(v == 1); } break; - case dbiSuggestStickersByEmoji: { - qint32 v; - stream >> v; - if (!_checkStreamStatus(stream)) return false; - - Global::SetSuggestStickersByEmoji(v == 1); - } break; - case dbiAutoDownload: { qint32 photo, audio, gif; stream >> photo >> audio >> gif; @@ -1434,12 +1427,28 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSetting Global::RefLocalPasscodeChanged().notify(); } break; - case dbiReplaceEmojis: { + case dbiReplaceEmoji: { qint32 v; stream >> v; if (!_checkStreamStatus(stream)) return false; - cSetReplaceEmojis(v == 1); + Global::SetReplaceEmoji(v == 1); + } break; + + case dbiSuggestEmoji: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + Global::SetSuggestEmoji(v == 1); + } break; + + case dbiSuggestStickersByEmoji: { + qint32 v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + Global::SetSuggestStickersByEmoji(v == 1); } break; case dbiDefaultAttach: { @@ -1852,7 +1861,7 @@ void _writeUserSettings() { ? userDataInstance->serialize() : QByteArray(); - uint32 size = 22 * (sizeof(quint32) + sizeof(qint32)); + uint32 size = 23 * (sizeof(quint32) + sizeof(qint32)); size += sizeof(quint32) + Serialize::stringSize(Global::AskDownloadPath() ? QString() : Global::DownloadPath()) + Serialize::bytearraySize(Global::AskDownloadPath() ? QByteArray() : Global::DownloadPathBookmark()); size += sizeof(quint32) + sizeof(qint32); @@ -1877,7 +1886,9 @@ void _writeUserSettings() { data.stream << quint32(dbiTileBackground) << qint32(Window::Theme::Background()->tileForSave() ? 1 : 0); data.stream << quint32(dbiAdaptiveForWide) << qint32(Global::AdaptiveForWide() ? 1 : 0); data.stream << quint32(dbiAutoLock) << qint32(Global::AutoLock()); - data.stream << quint32(dbiReplaceEmojis) << qint32(cReplaceEmojis() ? 1 : 0); + data.stream << quint32(dbiReplaceEmoji) << qint32(Global::ReplaceEmoji() ? 1 : 0); + data.stream << quint32(dbiSuggestEmoji) << qint32(Global::SuggestEmoji() ? 1 : 0); + data.stream << quint32(dbiSuggestStickersByEmoji) << qint32(Global::SuggestStickersByEmoji() ? 1 : 0); data.stream << quint32(dbiSoundNotify) << qint32(Global::SoundNotify()); data.stream << quint32(dbiIncludeMuted) << qint32(Global::IncludeMuted()); data.stream << quint32(dbiDesktopNotify) << qint32(Global::DesktopNotify()); @@ -1895,7 +1906,6 @@ void _writeUserSettings() { data.stream << quint32(dbiModerateMode) << qint32(Global::ModerateModeEnabled() ? 1 : 0); data.stream << quint32(dbiAutoPlay) << qint32(cAutoPlayGif() ? 1 : 0); data.stream << quint32(dbiUseExternalVideoPlayer) << qint32(cUseExternalVideoPlayer()); - data.stream << quint32(dbiSuggestStickersByEmoji) << qint32(Global::SuggestStickersByEmoji() ? 1 : 0); if (!userData.isEmpty()) { data.stream << quint32(dbiAuthSessionSettings) << userData; } diff --git a/Telegram/SourceFiles/ui/widgets/input_fields.cpp b/Telegram/SourceFiles/ui/widgets/input_fields.cpp index cb0370423..647e14b82 100644 --- a/Telegram/SourceFiles/ui/widgets/input_fields.cpp +++ b/Telegram/SourceFiles/ui/widgets/input_fields.cpp @@ -211,6 +211,10 @@ void FlatTextarea::addInstantReplace( accumulate_max(_instantReplaceMaxLength, int(what.size())); } +void FlatTextarea::enableInstantReplaces(bool enabled) { + _instantReplacesEnabled = enabled; +} + void FlatTextarea::updatePalette() { auto p = palette(); p.setColor(QPalette::Text, _st.textColor->c); @@ -1499,7 +1503,9 @@ void FlatTextarea::keyPressEvent(QKeyEvent *e) { } void FlatTextarea::processInstantReplaces(const QString &text) { - if (text.size() != 1 || !_instantReplaceMaxLength) { + if (text.size() != 1 + || !_instantReplaceMaxLength + || !_instantReplacesEnabled) { return; } const auto it = _reverseInstantReplaces.tail.find(text[0]); @@ -1540,9 +1546,18 @@ void FlatTextarea::applyInstantReplace( } else if (position < length) { return; } + commmitInstantReplacement(position - length, position, with, what); +} + +void FlatTextarea::commmitInstantReplacement( + int from, + int till, + const QString &with, + base::optional checkOriginal) { auto tags = QVector(); - const auto original = getTextPart(position - length, position, &tags); - if (what.compare(original, Qt::CaseInsensitive) != 0) { + const auto original = getTextPart(from, till, &tags); + if (checkOriginal + && checkOriginal->compare(original, Qt::CaseInsensitive) != 0) { return; } @@ -1550,7 +1565,7 @@ void FlatTextarea::applyInstantReplace( auto emojiLength = 0; const auto emoji = Ui::Emoji::Find(with, &emojiLength); if (!emoji || with.size() != emojiLength) { - return cursor.charFormat(); + return _defaultCharFormat; } const auto use = [&] { if (!emoji->hasVariants()) { @@ -1562,6 +1577,7 @@ void FlatTextarea::applyInstantReplace( ? emoji->variant(it.value()) : emoji; }(); + Ui::Emoji::AddRecent(use); return PrepareEmojiFormat(use, _st.font); }(); const auto replacement = format.isImageFormat() @@ -1570,12 +1586,10 @@ void FlatTextarea::applyInstantReplace( format.setProperty(kInstantReplaceWhatId, original); format.setProperty(kInstantReplaceWithId, replacement); format.setProperty(kInstantReplaceRandomId, rand_value()); - auto replaceCursor = cursor; - replaceCursor.setPosition(position - length); - replaceCursor.setPosition(position, QTextCursor::KeepAnchor); - replaceCursor.insertText( - replacement, - format); + auto cursor = textCursor(); + cursor.setPosition(from); + cursor.setPosition(till, QTextCursor::KeepAnchor); + cursor.insertText(replacement, format); } bool FlatTextarea::revertInstantReplace() { diff --git a/Telegram/SourceFiles/ui/widgets/input_fields.h b/Telegram/SourceFiles/ui/widgets/input_fields.h index b31f476b4..ad35691d6 100644 --- a/Telegram/SourceFiles/ui/widgets/input_fields.h +++ b/Telegram/SourceFiles/ui/widgets/input_fields.h @@ -16,7 +16,7 @@ namespace Ui { static UserData * const LookingUpInlineBot = SharedMemoryLocation(); -class FlatTextarea : public TWidgetHelper, private base::Subscriber { +class FlatTextarea : public TWidgetHelper, protected base::Subscriber { Q_OBJECT public: @@ -32,7 +32,13 @@ public: void setMinHeight(int minHeight); void setMaxHeight(int maxHeight); + void enableInstantReplaces(bool enabled); void addInstantReplace(const QString &what, const QString &with); + void commmitInstantReplacement( + int from, + int till, + const QString &with, + base::optional checkOriginal = base::none); void setPlaceholder(base::lambda placeholderFactory, int afterSymbols = 0); void updatePlaceholder(); @@ -230,6 +236,7 @@ private: int _instantReplaceMaxLength = 0; InstantReplaceNode _reverseInstantReplaces; + bool _instantReplacesEnabled = true; };