diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp index 67deeff07c..7daefb071d 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp @@ -30,7 +30,6 @@ namespace { constexpr auto kEmojiPanelPerRow = Ui::Emoji::kPanelPerRow; constexpr auto kEmojiPanelRowsPerPage = Ui::Emoji::kPanelRowsPerPage; -constexpr auto kSaveRecentEmojiTimeout = 3000; } // namespace @@ -536,41 +535,7 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) { } void EmojiListWidget::selectEmoji(EmojiPtr emoji) { - auto &recent = Ui::Emoji::GetRecent(); - auto i = recent.begin(), e = recent.end(); - for (; i != e; ++i) { - if (i->first == emoji) { - ++i->second; - if (i->second > 0x8000) { - for (auto j = recent.begin(); j != e; ++j) { - if (j->second > 1) { - j->second /= 2; - } else { - j->second = 1; - } - } - } - for (; i != recent.begin(); --i) { - if ((i - 1)->second > i->second) { - break; - } - qSwap(*i, *(i - 1)); - } - break; - } - } - if (i == e) { - while (recent.size() >= kEmojiPanelPerRow * kEmojiPanelRowsPerPage) recent.pop_back(); - recent.push_back(qMakePair(emoji, 1)); - for (i = recent.end() - 1; i != recent.begin(); --i) { - if ((i - 1)->second > i->second) { - break; - } - qSwap(*i, *(i - 1)); - } - } - emit saveConfigDelayed(kSaveRecentEmojiTimeout); - + Ui::Emoji::AddRecent(emoji); emit selected(emoji); } diff --git a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_helper.h b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_helper.h new file mode 100644 index 0000000000..2531eced9b --- /dev/null +++ b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_helper.h @@ -0,0 +1,40 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "emoji_suggestions.h" +#include "emoji_suggestions_data.h" + +namespace Ui { +namespace Emoji { + +inline utf16string QStringToUTF16(const QString &string) { + return utf16string(reinterpret_cast(string.constData()), string.size()); +} + +inline QString QStringFromUTF16(utf16string string) { + return QString::fromRawData(reinterpret_cast(string.data()), string.size()); +} + +constexpr auto kSuggestionMaxLength = internal::kReplacementMaxLength; + +} // namespace Emoji +} // namespace Ui diff --git a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp index 26e3cd770a..c6592c2bfd 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.cpp @@ -20,7 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "chat_helpers/emoji_suggestions_widget.h" -#include "emoji_suggestions.h" +#include "chat_helpers/emoji_suggestions_helper.h" #include "ui/effects/ripple_animation.h" #include "ui/widgets/shadow.h" #include "platform/platform_specific.h" @@ -32,15 +32,6 @@ namespace Emoji { namespace { constexpr auto kRowLimit = 5; -constexpr auto kLargestReplacementLength = 128; - -utf16string QStringToUTF16(const QString &string) { - return utf16string(reinterpret_cast(string.constData()), string.size()); -} - -QString QStringFromUTF16(utf16string string) { - return QString::fromRawData(reinterpret_cast(string.data()), string.size()); -} } // namespace @@ -98,19 +89,8 @@ void SuggestionsWidget::showWithQuery(const QString &query) { if (_query == query) { return; } - auto rows = std::vector(); _query = query; - if (!_query.isEmpty()) { - rows.reserve(kRowLimit); - for (auto &item : GetSuggestions(QStringToUTF16(_query))) { - if (auto emoji = Find(QStringFromUTF16(item.emoji()))) { - rows.emplace_back(emoji, QStringFromUTF16(item.label()), QStringFromUTF16(item.replacement())); - if (rows.size() == kRowLimit) { - break; - } - } - } - } + auto rows = getRowsByQuery(); if (rows.empty()) { toggleAnimated.notify(false, true); } @@ -126,6 +106,63 @@ void SuggestionsWidget::showWithQuery(const QString &query) { } } +std::vector SuggestionsWidget::getRowsByQuery() const { + auto result = std::vector(); + if (_query.isEmpty()) { + return result; + } + auto suggestions = GetSuggestions(QStringToUTF16(_query)); + if (suggestions.empty()) { + return result; + } + auto count = suggestions.size(); + auto suggestionsEmoji = std::vector(count, nullptr); + for (auto i = 0; i != count; ++i) { + suggestionsEmoji[i] = Find(QStringFromUTF16(suggestions[i].emoji())); + } + auto recents = 0; + auto &recent = GetRecent(); + for (auto &item : recent) { + auto emoji = item.first->original(); + if (!emoji) emoji = item.first; + auto it = std::find(suggestionsEmoji.begin(), suggestionsEmoji.end(), emoji); + if (it != suggestionsEmoji.end()) { + auto index = (it - suggestionsEmoji.begin()); + if (index >= recents) { + if (index > recents) { + auto recentEmoji = suggestionsEmoji[index]; + auto recentSuggestion = suggestions[index]; + for (auto i = index; i != recents; --i) { + suggestionsEmoji[i] = suggestionsEmoji[i - 1]; + suggestions[i] = suggestions[i - 1]; + } + suggestionsEmoji[recents] = recentEmoji; + suggestions[recents] = recentSuggestion; + } + ++recents; + } + } + } + + result.reserve(kRowLimit); + auto index = 0; + for (auto &item : suggestions) { + if (auto emoji = suggestionsEmoji[index++]) { + if (emoji->hasVariants()) { + auto it = cEmojiVariants().constFind(emoji->nonColoredId()); + if (it != cEmojiVariants().cend()) { + emoji = emoji->variant(it.value()); + } + } + result.emplace_back(emoji, QStringFromUTF16(item.label()), QStringFromUTF16(item.replacement())); + if (result.size() == kRowLimit) { + break; + } + } + } + return result; +} + void SuggestionsWidget::resizeToRows() { auto newWidth = 0; for (auto &row : _rows) { @@ -342,6 +379,10 @@ void SuggestionsController::handleTextChange() { } QString SuggestionsController::getEmojiQuery() { + if (!cReplaceEmojis()) { + return QString(); + } + auto cursor = _field->textCursor(); auto position = _field->textCursor().position(); if (cursor.anchor() != position) { @@ -396,7 +437,7 @@ QString SuggestionsController::getEmojiQuery() { } return QString(); } - if (position - i > kLargestReplacementLength) { + if (position - i > kSuggestionMaxLength) { return QString(); } if (!isSuggestionChar(ch)) { @@ -415,6 +456,17 @@ void SuggestionsController::replaceCurrent(const QString &replacement) { cursor.setPosition(cursor.position() - suggestion.size(), QTextCursor::KeepAnchor); cursor.insertText(replacement + ' '); } + + auto emojiText = GetSuggestionEmoji(QStringToUTF16(replacement)); + if (auto emoji = Find(QStringFromUTF16(emojiText))) { + if (emoji->hasVariants()) { + auto it = cEmojiVariants().constFind(emoji->nonColoredId()); + if (it != cEmojiVariants().cend()) { + emoji = emoji->variant(it.value()); + } + } + AddRecent(emoji); + } } void SuggestionsController::handleCursorPositionChange() { diff --git a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.h b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.h index 23b82c4371..d83a198686 100644 --- a/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.h +++ b/Telegram/SourceFiles/chat_helpers/emoji_suggestions_widget.h @@ -51,6 +51,7 @@ protected: private: class Row; + std::vector getRowsByQuery() const; void resizeToRows(); int countWidth(const Row &row); void setSelected(int selected); diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp index 615327c62e..5d3f6274f7 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp @@ -323,9 +323,6 @@ TabbedSelector::TabbedSelector(QWidget *parent, gsl::not_nulldisableScroll(disabled); } }); - connect(widget, &Inner::saveConfigDelayed, this, [this](int delay) { - AuthSession::Current().saveDataDelayed(delay); - }); } connect(stickers(), SIGNAL(scrollUpdated()), this, SLOT(onScroll())); diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h index d308893c43..48d9f2538a 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h @@ -234,7 +234,6 @@ public: signals: void scrollToY(int y); void disableScroll(bool disabled); - void saveConfigDelayed(int delay); protected: gsl::not_null controller() const { diff --git a/Telegram/SourceFiles/ui/emoji_config.cpp b/Telegram/SourceFiles/ui/emoji_config.cpp index e3db24680f..a9f93ce6d2 100644 --- a/Telegram/SourceFiles/ui/emoji_config.cpp +++ b/Telegram/SourceFiles/ui/emoji_config.cpp @@ -23,12 +23,58 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "emoji_config.h" +#include "chat_helpers/emoji_suggestions_helper.h" +#include "auth_session.h" + namespace Ui { namespace Emoji { namespace { +constexpr auto kSaveRecentEmojiTimeout = 3000; + auto WorkingIndex = -1; +void AppendPartToResult(TextWithEntities &result, const QChar *start, const QChar *from, const QChar *to) { + if (to <= from) { + return; + } + for (auto &entity : result.entities) { + if (entity.offset() >= to - start) break; + if (entity.offset() + entity.length() < from - start) continue; + if (entity.offset() >= from - start) { + entity.extendToLeft(from - start - result.text.size()); + } + if (entity.offset() + entity.length() <= to - start) { + entity.shrinkFromRight(from - start - result.text.size()); + } + } + result.text.append(from, to - from); +} + +bool IsReplacementPart(ushort ch) { + return (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || (ch == '-') || (ch == '+') || (ch == '_'); +} + +EmojiPtr FindReplacement(const QChar *start, const QChar *end, int *outLength) { + if (start != end && *start == ':') { + auto maxLength = GetSuggestionMaxLength(); + for (auto till = start + 1; till != end; ++till) { + if (*till == ':') { + auto text = QString::fromRawData(start, till + 1 - start); + auto emoji = GetSuggestionEmoji(QStringToUTF16(text)); + auto result = Find(QStringFromUTF16(emoji)); + if (result) { + if (outLength) *outLength = (till + 1 - start); + } + return result; + } else if (!IsReplacementPart(till->unicode()) || (till - start) > maxLength) { + break; + } + } + } + return internal::FindReplace(start, end, outLength); +} + } // namespace void Init() { @@ -64,5 +110,213 @@ int One::index() const { return (this - internal::ByIndex(0)); } +QString IdFromOldKey(uint64 oldKey) { + auto code = uint32(oldKey >> 32); + auto code2 = uint32(oldKey & 0xFFFFFFFFLLU); + if (!code && code2) { + code = base::take(code2); + } + if ((code & 0xFFFF0000U) != 0xFFFF0000U) { // code and code2 contain the whole id + auto result = QString(); + result.reserve(4); + auto addCode = [&result](uint32 code) { + if (auto high = (code >> 16)) { + result.append(QChar(static_cast(high & 0xFFFFU))); + } + result.append(QChar(static_cast(code & 0xFFFFU))); + }; + addCode(code); + if (code2) addCode(code2); + return result; + } + + // old sequence + auto sequenceIndex = int(code & 0xFFFFU); + switch (sequenceIndex) { + case 0: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa7"); + case 1: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa7\xe2\x80\x8d\xf0\x9f\x91\xa6"); + case 2: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa6\xe2\x80\x8d\xf0\x9f\x91\xa6"); + case 3: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa7\xe2\x80\x8d\xf0\x9f\x91\xa7"); + case 4: return QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa6"); + case 5: return QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa7"); + case 6: return QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa7\xe2\x80\x8d\xf0\x9f\x91\xa6"); + case 7: return QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa6\xe2\x80\x8d\xf0\x9f\x91\xa6"); + case 8: return QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa7\xe2\x80\x8d\xf0\x9f\x91\xa7"); + case 9: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa6"); + case 10: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa7"); + case 11: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa7\xe2\x80\x8d\xf0\x9f\x91\xa6"); + case 12: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa6\xe2\x80\x8d\xf0\x9f\x91\xa6"); + case 13: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa7\xe2\x80\x8d\xf0\x9f\x91\xa7"); + case 14: return QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xe2\x9d\xa4\xef\xb8\x8f\xe2\x80\x8d\xf0\x9f\x91\xa9"); + case 15: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xe2\x9d\xa4\xef\xb8\x8f\xe2\x80\x8d\xf0\x9f\x91\xa8"); + case 16: return QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xe2\x9d\xa4\xef\xb8\x8f\xe2\x80\x8d\xf0\x9f\x92\x8b\xe2\x80\x8d\xf0\x9f\x91\xa9"); + case 17: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xe2\x9d\xa4\xef\xb8\x8f\xe2\x80\x8d\xf0\x9f\x92\x8b\xe2\x80\x8d\xf0\x9f\x91\xa8"); + case 18: return QString::fromUtf8("\xf0\x9f\x91\x81\xe2\x80\x8d\xf0\x9f\x97\xa8"); + } + return QString(); +} + +void ReplaceInText(TextWithEntities &result) { + auto newText = TextWithEntities(); + newText.entities = std::move(result.entities); + auto currentEntity = newText.entities.begin(); + auto entitiesEnd = newText.entities.end(); + auto emojiStart = result.text.constData(); + auto emojiEnd = emojiStart; + auto end = emojiStart + result.text.size(); + auto canFindEmoji = true; + for (auto ch = emojiEnd; ch != end;) { + auto emojiLength = 0; + auto emoji = canFindEmoji ? FindReplacement(ch, end, &emojiLength) : nullptr; + auto newEmojiEnd = ch + emojiLength; + + while (currentEntity != entitiesEnd && ch >= emojiStart + currentEntity->offset() + currentEntity->length()) { + ++currentEntity; + } + if (emoji && + (ch == emojiStart || !ch->isLetterOrNumber() || !(ch - 1)->isLetterOrNumber()) && + (newEmojiEnd == end || !newEmojiEnd->isLetterOrNumber() || newEmojiEnd == emojiStart || !(newEmojiEnd - 1)->isLetterOrNumber()) && + (currentEntity == entitiesEnd || (ch < emojiStart + currentEntity->offset() && newEmojiEnd <= emojiStart + currentEntity->offset()) || (ch >= emojiStart + currentEntity->offset() + currentEntity->length() && newEmojiEnd > emojiStart + currentEntity->offset() + currentEntity->length())) + ) { + if (newText.text.isEmpty()) newText.text.reserve(result.text.size()); + + AppendPartToResult(newText, emojiStart, emojiEnd, ch); + + if (emoji->hasVariants()) { + auto it = cEmojiVariants().constFind(emoji->nonColoredId()); + if (it != cEmojiVariants().cend()) { + emoji = emoji->variant(it.value()); + } + } + newText.text.append(emoji->text()); + + ch = emojiEnd = newEmojiEnd; + canFindEmoji = true; + } else { + if (internal::IsReplaceEdge(ch)) { + canFindEmoji = true; + } else { + canFindEmoji = false; + } + ++ch; + } + } + if (newText.text.isEmpty()) { + result.entities = std::move(newText.entities); + } else { + AppendPartToResult(newText, emojiStart, emojiEnd, end); + result = std::move(newText); + } +} + +RecentEmojiPack &GetRecent() { + if (cRecentEmoji().isEmpty()) { + RecentEmojiPack result; + auto haveAlready = [&result](EmojiPtr emoji) { + for (auto &row : result) { + if (row.first->id() == emoji->id()) { + return true; + } + } + return false; + }; + if (!cRecentEmojiPreload().isEmpty()) { + auto preload = cRecentEmojiPreload(); + cSetRecentEmojiPreload(RecentEmojiPreload()); + result.reserve(preload.size()); + for (auto i = preload.cbegin(), e = preload.cend(); i != e; ++i) { + if (auto emoji = Ui::Emoji::Find(i->first)) { + if (!haveAlready(emoji)) { + result.push_back(qMakePair(emoji, i->second)); + } + } + } + } + auto defaultRecent = { + 0xD83DDE02LLU, + 0xD83DDE18LLU, + 0x2764LLU, + 0xD83DDE0DLLU, + 0xD83DDE0ALLU, + 0xD83DDE01LLU, + 0xD83DDC4DLLU, + 0x263ALLU, + 0xD83DDE14LLU, + 0xD83DDE04LLU, + 0xD83DDE2DLLU, + 0xD83DDC8BLLU, + 0xD83DDE12LLU, + 0xD83DDE33LLU, + 0xD83DDE1CLLU, + 0xD83DDE48LLU, + 0xD83DDE09LLU, + 0xD83DDE03LLU, + 0xD83DDE22LLU, + 0xD83DDE1DLLU, + 0xD83DDE31LLU, + 0xD83DDE21LLU, + 0xD83DDE0FLLU, + 0xD83DDE1ELLU, + 0xD83DDE05LLU, + 0xD83DDE1ALLU, + 0xD83DDE4ALLU, + 0xD83DDE0CLLU, + 0xD83DDE00LLU, + 0xD83DDE0BLLU, + 0xD83DDE06LLU, + 0xD83DDC4CLLU, + 0xD83DDE10LLU, + 0xD83DDE15LLU, + }; + for (auto oldKey : defaultRecent) { + if (result.size() >= kPanelPerRow * kPanelRowsPerPage) break; + + if (auto emoji = Ui::Emoji::FromOldKey(oldKey)) { + if (!haveAlready(emoji)) { + result.push_back(qMakePair(emoji, 1)); + } + } + } + cSetRecentEmoji(result); + } + return cRefRecentEmoji(); +} + +void AddRecent(EmojiPtr emoji) { + auto &recent = GetRecent(); + auto i = recent.begin(), e = recent.end(); + for (; i != e; ++i) { + if (i->first == emoji) { + ++i->second; + if (i->second > 0x8000) { + for (auto j = recent.begin(); j != e; ++j) { + if (j->second > 1) { + j->second /= 2; + } else { + j->second = 1; + } + } + } + for (; i != recent.begin(); --i) { + if ((i - 1)->second > i->second) { + break; + } + qSwap(*i, *(i - 1)); + } + break; + } + } + if (i == e) { + while (recent.size() >= kPanelPerRow * kPanelRowsPerPage) recent.pop_back(); + recent.push_back(qMakePair(emoji, 1)); + for (i = recent.end() - 1; i != recent.begin(); --i) { + if ((i - 1)->second > i->second) { + break; + } + qSwap(*i, *(i - 1)); + } + } +} + } // namespace Emoji } // namespace Ui diff --git a/Telegram/SourceFiles/ui/emoji_config.h b/Telegram/SourceFiles/ui/emoji_config.h index 7e2029d57f..d580ba7ab1 100644 --- a/Telegram/SourceFiles/ui/emoji_config.h +++ b/Telegram/SourceFiles/ui/emoji_config.h @@ -116,51 +116,7 @@ inline EmojiPtr Find(const QString &text, int *outLength = nullptr) { return Find(text.constBegin(), text.constEnd(), outLength); } -inline QString IdFromOldKey(uint64 oldKey) { - auto code = uint32(oldKey >> 32); - auto code2 = uint32(oldKey & 0xFFFFFFFFLLU); - if (!code && code2) { - code = base::take(code2); - } - if ((code & 0xFFFF0000U) != 0xFFFF0000U) { // code and code2 contain the whole id - auto result = QString(); - result.reserve(4); - auto addCode = [&result](uint32 code) { - if (auto high = (code >> 16)) { - result.append(QChar(static_cast(high & 0xFFFFU))); - } - result.append(QChar(static_cast(code & 0xFFFFU))); - }; - addCode(code); - if (code2) addCode(code2); - return result; - } - - // old sequence - auto sequenceIndex = int(code & 0xFFFFU); - switch (sequenceIndex) { - case 0: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa7"); - case 1: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa7\xe2\x80\x8d\xf0\x9f\x91\xa6"); - case 2: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa6\xe2\x80\x8d\xf0\x9f\x91\xa6"); - case 3: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa7\xe2\x80\x8d\xf0\x9f\x91\xa7"); - case 4: return QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa6"); - case 5: return QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa7"); - case 6: return QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa7\xe2\x80\x8d\xf0\x9f\x91\xa6"); - case 7: return QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa6\xe2\x80\x8d\xf0\x9f\x91\xa6"); - case 8: return QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa7\xe2\x80\x8d\xf0\x9f\x91\xa7"); - case 9: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa6"); - case 10: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa7"); - case 11: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa7\xe2\x80\x8d\xf0\x9f\x91\xa6"); - case 12: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa6\xe2\x80\x8d\xf0\x9f\x91\xa6"); - case 13: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa7\xe2\x80\x8d\xf0\x9f\x91\xa7"); - case 14: return QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xe2\x9d\xa4\xef\xb8\x8f\xe2\x80\x8d\xf0\x9f\x91\xa9"); - case 15: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xe2\x9d\xa4\xef\xb8\x8f\xe2\x80\x8d\xf0\x9f\x91\xa8"); - case 16: return QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xe2\x9d\xa4\xef\xb8\x8f\xe2\x80\x8d\xf0\x9f\x92\x8b\xe2\x80\x8d\xf0\x9f\x91\xa9"); - case 17: return QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xe2\x9d\xa4\xef\xb8\x8f\xe2\x80\x8d\xf0\x9f\x92\x8b\xe2\x80\x8d\xf0\x9f\x91\xa8"); - case 18: return QString::fromUtf8("\xf0\x9f\x91\x81\xe2\x80\x8d\xf0\x9f\x97\xa8"); - } - return QString(); -} +QString IdFromOldKey(uint64 oldKey); inline EmojiPtr FromOldKey(uint64 oldKey) { return Find(IdFromOldKey(oldKey)); @@ -168,11 +124,11 @@ inline EmojiPtr FromOldKey(uint64 oldKey) { inline int ColorIndexFromCode(uint32 code) { switch (code) { - case 0xD83CDFFB: return 1; - case 0xD83CDFFC: return 2; - case 0xD83CDFFD: return 3; - case 0xD83CDFFE: return 4; - case 0xD83CDFFF: return 5; + case 0xD83CDFFBU: return 1; + case 0xD83CDFFCU: return 2; + case 0xD83CDFFDU: return 3; + case 0xD83CDFFEU: return 4; + case 0xD83CDFFFU: return 5; } return 0; } @@ -197,147 +153,9 @@ inline QString Filename(int index = Index()) { return QString::fromLatin1(EmojiNames[index]); } -inline void AppendPartToResult(TextWithEntities &result, const QChar *start, const QChar *from, const QChar *to) { - if (to > from) { - for (auto &entity : result.entities) { - if (entity.offset() >= to - start) break; - if (entity.offset() + entity.length() < from - start) continue; - if (entity.offset() >= from - start) { - entity.extendToLeft(from - start - result.text.size()); - } - if (entity.offset() + entity.length() <= to - start) { - entity.shrinkFromRight(from - start - result.text.size()); - } - } - result.text.append(from, to - from); - } -} - -inline void ReplaceInText(TextWithEntities &result) { - auto newText = TextWithEntities(); - newText.entities = std::move(result.entities); - auto currentEntity = newText.entities.begin(); - auto entitiesEnd = newText.entities.end(); - auto emojiStart = result.text.constData(); - auto emojiEnd = emojiStart; - auto end = emojiStart + result.text.size(); - auto canFindEmoji = true; - for (auto ch = emojiEnd; ch != end;) { - auto emojiLength = 0; - auto emoji = canFindEmoji ? internal::FindReplace(ch, end, &emojiLength) : nullptr; - auto newEmojiEnd = ch + emojiLength; - - while (currentEntity != entitiesEnd && ch >= emojiStart + currentEntity->offset() + currentEntity->length()) { - ++currentEntity; - } - if (emoji && - (ch == emojiStart || !ch->isLetterOrNumber() || !(ch - 1)->isLetterOrNumber()) && - (newEmojiEnd == end || !newEmojiEnd->isLetterOrNumber() || newEmojiEnd == emojiStart || !(newEmojiEnd - 1)->isLetterOrNumber()) && - (currentEntity == entitiesEnd || (ch < emojiStart + currentEntity->offset() && newEmojiEnd <= emojiStart + currentEntity->offset()) || (ch >= emojiStart + currentEntity->offset() + currentEntity->length() && newEmojiEnd > emojiStart + currentEntity->offset() + currentEntity->length())) - ) { - if (newText.text.isEmpty()) newText.text.reserve(result.text.size()); - - AppendPartToResult(newText, emojiStart, emojiEnd, ch); - - if (emoji->hasVariants()) { - auto it = cEmojiVariants().constFind(emoji->nonColoredId()); - if (it != cEmojiVariants().cend()) { - emoji = emoji->variant(it.value()); - } - } - newText.text.append(emoji->text()); - - ch = emojiEnd = newEmojiEnd; - canFindEmoji = true; - } else { - if (internal::IsReplaceEdge(ch)) { - canFindEmoji = true; - } else { - canFindEmoji = false; - } - ++ch; - } - } - if (newText.text.isEmpty()) { - result.entities = std::move(newText.entities); - } else { - AppendPartToResult(newText, emojiStart, emojiEnd, end); - result = std::move(newText); - } -} - -inline RecentEmojiPack &GetRecent() { - if (cRecentEmoji().isEmpty()) { - RecentEmojiPack result; - auto haveAlready = [&result](EmojiPtr emoji) { - for (auto &row : result) { - if (row.first->id() == emoji->id()) { - return true; - } - } - return false; - }; - if (!cRecentEmojiPreload().isEmpty()) { - auto preload = cRecentEmojiPreload(); - cSetRecentEmojiPreload(RecentEmojiPreload()); - result.reserve(preload.size()); - for (auto i = preload.cbegin(), e = preload.cend(); i != e; ++i) { - if (auto emoji = Ui::Emoji::Find(i->first)) { - if (!haveAlready(emoji)) { - result.push_back(qMakePair(emoji, i->second)); - } - } - } - } - auto defaultRecent = { - 0xD83DDE02LLU, - 0xD83DDE18LLU, - 0x2764LLU, - 0xD83DDE0DLLU, - 0xD83DDE0ALLU, - 0xD83DDE01LLU, - 0xD83DDC4DLLU, - 0x263ALLU, - 0xD83DDE14LLU, - 0xD83DDE04LLU, - 0xD83DDE2DLLU, - 0xD83DDC8BLLU, - 0xD83DDE12LLU, - 0xD83DDE33LLU, - 0xD83DDE1CLLU, - 0xD83DDE48LLU, - 0xD83DDE09LLU, - 0xD83DDE03LLU, - 0xD83DDE22LLU, - 0xD83DDE1DLLU, - 0xD83DDE31LLU, - 0xD83DDE21LLU, - 0xD83DDE0FLLU, - 0xD83DDE1ELLU, - 0xD83DDE05LLU, - 0xD83DDE1ALLU, - 0xD83DDE4ALLU, - 0xD83DDE0CLLU, - 0xD83DDE00LLU, - 0xD83DDE0BLLU, - 0xD83DDE06LLU, - 0xD83DDC4CLLU, - 0xD83DDE10LLU, - 0xD83DDE15LLU, - }; - for (auto oldKey : defaultRecent) { - if (result.size() >= kPanelPerRow * kPanelRowsPerPage) break; - - if (auto emoji = Ui::Emoji::FromOldKey(oldKey)) { - if (!haveAlready(emoji)) { - result.push_back(qMakePair(emoji, 1)); - } - } - } - cSetRecentEmoji(result); - } - return cRefRecentEmoji(); -} +void ReplaceInText(TextWithEntities &result); +RecentEmojiPack &GetRecent(); +void AddRecent(EmojiPtr emoji); } // namespace Emoji } // namespace Ui diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index 86dbbc666d..d528bcc278 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -98,6 +98,7 @@ <(src_loc)/chat_helpers/bot_keyboard.h <(src_loc)/chat_helpers/emoji_list_widget.cpp <(src_loc)/chat_helpers/emoji_list_widget.h +<(src_loc)/chat_helpers/emoji_suggestions_helper.h <(src_loc)/chat_helpers/emoji_suggestions_widget.cpp <(src_loc)/chat_helpers/emoji_suggestions_widget.h <(src_loc)/chat_helpers/field_autocomplete.cpp