/* WARNING! All changes made in this file will be lost! Created from 'empty' by 'codegen_emoji' 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 */ #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() { auto scaleForEmoji = cRetina() ? dbisTwo : cScale(); switch (scaleForEmoji) { case dbisOne: WorkingIndex = 0; break; case dbisOneAndQuarter: WorkingIndex = 1; break; case dbisOneAndHalf: WorkingIndex = 2; break; case dbisTwo: WorkingIndex = 3; break; }; internal::Init(); } int Index() { return WorkingIndex; } int One::variantsCount() const { return hasVariants() ? 5 : 0; } int One::variantIndex(EmojiPtr variant) const { return (variant - original()); } EmojiPtr One::variant(int index) const { return (index >= 0 && index <= variantsCount()) ? (original() + index) : this; } 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