From 4962fdf5aed3497656bc6998d80d2b62c35cb9ce Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 20 Jun 2017 19:03:18 +0300 Subject: [PATCH] Add phrases and layout for all events in log. --- Telegram/Resources/langs/lang.strings | 21 ++ .../SourceFiles/boxes/edit_participant_box.h | 3 +- .../SourceFiles/codegen/lang/generator.cpp | 21 +- .../history/history_admin_log_item.cpp | 228 ++++++++++++++++-- .../history/history_admin_log_item.h | 2 - .../history/history_media_types.cpp | 47 ++-- .../SourceFiles/history/history_media_types.h | 41 ++-- .../SourceFiles/history/history_message.cpp | 13 +- .../SourceFiles/history/history_message.h | 6 +- Telegram/SourceFiles/lang/lang_tag.cpp | 35 ++- Telegram/SourceFiles/lang/lang_tag.h | 37 ++- Telegram/SourceFiles/structs.h | 10 +- Telegram/SourceFiles/ui/text/text_entity.cpp | 67 ++++- Telegram/SourceFiles/ui/text/text_entity.h | 25 +- .../window/themes/window_theme_editor.cpp | 3 +- 15 files changed, 454 insertions(+), 105 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 7b2180523..f453bde74 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1351,6 +1351,27 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_admin_log_deleted_message" = "{from} deleted message:"; "lng_admin_log_participant_joined" = "{from} joined the group"; "lng_admin_log_participant_left" = "{from} left the group"; +"lng_admin_log_invited" = "invited {user}"; +"lng_admin_log_banned" = "banned {user}"; +"lng_admin_log_restricted" = "changed restrictions for {user} {until}"; +"lng_admin_log_promoted" = "changed privileges for {user}"; +"lng_admin_log_user_with_username" = "{name} ({mention})"; +"lng_admin_log_restricted_forever" = "indefinitely"; +"lng_admin_log_restricted_until" = "until {date}"; +"lng_admin_log_banned_view_messages" = "Read messages"; +"lng_admin_log_banned_send_messages" = "Send messages"; +"lng_admin_log_banned_send_media" = "Send media"; +"lng_admin_log_banned_send_stickers" = "Send stickers & GIFs"; +"lng_admin_log_banned_embed_links" = "Embed links"; +"lng_admin_log_admin_change_info" = "Change info"; +"lng_admin_log_admin_post_messages" = "Post messages"; +"lng_admin_log_admin_edit_messages" = "Edit messages"; +"lng_admin_log_admin_delete_messages" = "Delete messages"; +"lng_admin_log_admin_ban_users" = "Ban users"; +"lng_admin_log_admin_invite_users" = "Add users"; +"lng_admin_log_admin_invite_link" = "Invite users via link"; +"lng_admin_log_admin_pin_messages" = "Pin messages"; +"lng_admin_log_admin_add_admins" = "Add new admins"; // Not used diff --git a/Telegram/SourceFiles/boxes/edit_participant_box.h b/Telegram/SourceFiles/boxes/edit_participant_box.h index 41451ac4a..5469dd6e5 100644 --- a/Telegram/SourceFiles/boxes/edit_participant_box.h +++ b/Telegram/SourceFiles/boxes/edit_participant_box.h @@ -96,7 +96,6 @@ public: EditRestrictedBox(QWidget*, gsl::not_null channel, gsl::not_null user, bool hasAdminRights, const MTPChannelBannedRights &rights, base::lambda callback); static MTPChannelBannedRights DefaultRights(gsl::not_null channel); - static constexpr auto kRestrictUntilForever = TimeId(INT_MAX); protected: void prepare() override; @@ -109,7 +108,7 @@ private: void showRestrictUntil(); void setRestrictUntil(int32 until); bool isUntilForever() { - return (_until <= 0) || (_until == kRestrictUntilForever); + return ChannelData::IsRestrictedForever(_until); } MTPChannelBannedRights _rights; diff --git a/Telegram/SourceFiles/codegen/lang/generator.cpp b/Telegram/SourceFiles/codegen/lang/generator.cpp index e8e4ad391..125f6ef97 100644 --- a/Telegram/SourceFiles/codegen/lang/generator.cpp +++ b/Telegram/SourceFiles/codegen/lang/generator.cpp @@ -142,24 +142,35 @@ QString lang(LangKey key);\n\ for (auto &entry : langpack_.entries) { auto isPlural = !entry.keyBase.isEmpty(); auto &key = entry.key; + auto genericParams = QStringList(); auto params = QStringList(); auto applyTags = QStringList(); + auto plural = QString(); + auto nonPluralTagFound = false; for (auto &tagData : entry.tags) { auto &tag = tagData.tag; auto isPluralTag = isPlural && (tag == kPluralTag); + genericParams.push_back("lngtag_" + tag + ", " + (isPluralTag ? "float64 " : "const ResultString &") + tag + "__val"); params.push_back("lngtag_" + tag + ", " + (isPluralTag ? "float64 " : "const QString &") + tag + "__val"); - if (!isPluralTag) { - applyTags.push_back("\tresult = Lang::Tag(result, lt_" + tag + ", " + tag + "__val);\n"); + if (isPluralTag) { + plural = "\tauto plural = Lang::Plural(" + key + ", " + kPluralTag + "__val);\n"; + applyTags.push_back("\tresult = Lang::ReplaceTag::Call(std::move(result), lt_" + tag + ", Lang::StartReplacements::Call(std::move(plural.replacement)));\n"); + } else { + nonPluralTagFound = true; + applyTags.push_back("\tresult = Lang::ReplaceTag::Call(std::move(result), lt_" + tag + ", " + tag + "__val);\n"); } } if (!entry.tags.empty() && (!isPlural || key == ComputePluralKey(entry.keyBase, 0))) { - auto initialString = isPlural ? ("Lang::Plural(" + key + ", lt_" + kPluralTag + ", " + kPluralTag + "__val)") : ("lang(" + getFullKey(entry) + ")"); + auto initialString = isPlural ? ("std::move(plural.string)") : ("lang(" + getFullKey(entry) + ")"); header_->stream() << "\ -inline QString " << (isPlural ? entry.keyBase : key) << "(" << params.join(QString(", ")) << ") {\n\ - auto result = " << initialString << ";\n\ +template \n\ +inline ResultString " << (isPlural ? entry.keyBase : key) << "__generic(" << genericParams.join(QString(", ")) << ") {\n\ +" << plural << "\ + auto result = Lang::StartReplacements::Call(" << initialString << ");\n\ " << applyTags.join(QString()) << "\ return result;\n\ }\n\ +constexpr auto " << (isPlural ? entry.keyBase : key) << " = &" << (isPlural ? entry.keyBase : key) << "__generic;\n\ \n"; } } diff --git a/Telegram/SourceFiles/history/history_admin_log_item.cpp b/Telegram/SourceFiles/history/history_admin_log_item.cpp index e80e19532..11eb9a5cb 100644 --- a/Telegram/SourceFiles/history/history_admin_log_item.cpp +++ b/Telegram/SourceFiles/history/history_admin_log_item.cpp @@ -26,6 +26,19 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org namespace AdminLog { namespace { +TextWithEntities PrepareText(const QString &value, const QString &emptyValue) { + auto result = TextWithEntities { textClean(value) }; + if (result.text.isEmpty()) { + result.text = emptyValue; + if (!emptyValue.isEmpty()) { + result.entities.push_back(EntityInText(EntityInTextItalic, 0, emptyValue.size())); + } + } else { + textParseEntities(result.text, TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands, &result.entities); + } + return result; +} + MTPMessage PrepareLogMessage(const MTPMessage &message, MsgId newId, int32 newDate) { switch (message.type()) { case mtpc_messageEmpty: return MTP_messageEmpty(MTP_int(newId)); @@ -43,7 +56,7 @@ MTPMessage PrepareLogMessage(const MTPMessage &message, MsgId newId, int32 newDa Unexpected("Type in PrepareLogMessage()"); } -bool MessageHasCaption(const MTPMessage &message) { +bool MediaCanHaveCaption(const MTPMessage &message) { if (message.type() != mtpc_message) { return false; } @@ -52,6 +65,172 @@ bool MessageHasCaption(const MTPMessage &message) { return (mediaType == mtpc_messageMediaDocument || mediaType == mtpc_messageMediaPhoto); } +TextWithEntities ExtractEditedText(const MTPMessage &message) { + if (message.type() != mtpc_message) { + return TextWithEntities(); + } + auto &data = message.c_message(); + auto mediaType = data.has_media() ? data.vmedia.type() : mtpc_messageMediaEmpty; + if (mediaType == mtpc_messageMediaDocument) { + return PrepareText(qs(data.vmedia.c_messageMediaDocument().vcaption), QString()); + } else if (mediaType == mtpc_messageMediaPhoto) { + return PrepareText(qs(data.vmedia.c_messageMediaPhoto().vcaption), QString()); + } + auto text = textClean(qs(data.vmessage)); + auto entities = data.has_entities() ? entitiesFromMTP(data.ventities.v) : EntitiesInText(); + return { text, entities }; +} + +PhotoData *GenerateChatPhoto(ChannelId channelId, uint64 logEntryId, MTPint date, const MTPDchatPhoto &photo) { + // We try to make a unique photoId that will stay the same for each pair (channelId, logEntryId). + static const auto RandomIdPart = rand_value(); + auto mixinIdPart = (static_cast(static_cast(channelId)) << 32) ^ logEntryId; + auto photoId = RandomIdPart ^ mixinIdPart; + + auto photoSizes = QVector(); + photoSizes.reserve(2); + photoSizes.push_back(MTP_photoSize(MTP_string("a"), photo.vphoto_small, MTP_int(160), MTP_int(160), MTP_int(0))); + photoSizes.push_back(MTP_photoSize(MTP_string("c"), photo.vphoto_big, MTP_int(640), MTP_int(640), MTP_int(0))); + return App::feedPhoto(MTP_photo(MTP_flags(0), MTP_long(photoId), MTP_long(0), date, MTP_vector(photoSizes))); +} + +const auto CollectChanges = [](auto &phraseMap, auto plusFlags, auto minusFlags) { + auto withPrefix = [&phraseMap](auto flags, QChar prefix) { + auto result = QString(); + for (auto &phrase : phraseMap) { + if (flags & phrase.first) { + result.append('\n' + (prefix + lang(phrase.second))); + } + } + return result; + }; + const auto kMinus = QChar(0x2212); + return withPrefix(plusFlags & ~minusFlags, '+') + withPrefix(minusFlags & ~plusFlags, kMinus); +}; + +auto GenerateAdminChangeText(gsl::not_null channel, const TextWithEntities &user, const MTPChannelAdminRights *newRights, const MTPChannelAdminRights *prevRights) { + using Flag = MTPDchannelAdminRights::Flag; + using Flags = MTPDchannelAdminRights::Flags; + + Expects(!newRights || newRights->type() == mtpc_channelAdminRights); + Expects(!prevRights || prevRights->type() == mtpc_channelAdminRights); + auto newFlags = newRights ? newRights->c_channelAdminRights().vflags.v : 0; + auto prevFlags = prevRights ? prevRights->c_channelAdminRights().vflags.v : 0; + auto result = lng_admin_log_promoted__generic(lt_user, user); + + auto inviteKey = Flag::f_invite_users | Flag::f_invite_link; + auto useInviteLinkPhrase = channel->isMegagroup() && channel->anyoneCanAddMembers(); + auto invitePhrase = (useInviteLinkPhrase ? lng_admin_log_admin_invite_link : lng_admin_log_admin_invite_users); + static auto phraseMap = std::map { + { Flag::f_change_info, lng_admin_log_admin_change_info }, + { Flag::f_post_messages, lng_admin_log_admin_post_messages }, + { Flag::f_edit_messages, lng_admin_log_admin_edit_messages }, + { Flag::f_delete_messages, lng_admin_log_admin_delete_messages }, + { Flag::f_ban_users, lng_admin_log_admin_ban_users }, + { inviteKey, invitePhrase }, + { Flag::f_pin_messages, lng_admin_log_admin_pin_messages }, + { Flag::f_add_admins, lng_admin_log_admin_add_admins }, + }; + phraseMap[inviteKey] = invitePhrase; + + auto changes = CollectChanges(phraseMap, newFlags, prevFlags); + if (!changes.isEmpty()) { + result.text.append('\n' + changes); + } + + return result; +}; + +auto GenerateBannedChangeText(const TextWithEntities &user, const MTPChannelBannedRights *newRights, const MTPChannelBannedRights *prevRights) { + using Flag = MTPDchannelBannedRights::Flag; + using Flags = MTPDchannelBannedRights::Flags; + + Expects(!newRights || newRights->type() == mtpc_channelBannedRights); + Expects(!prevRights || prevRights->type() == mtpc_channelBannedRights); + auto newFlags = newRights ? newRights->c_channelBannedRights().vflags.v : 0; + auto prevFlags = prevRights ? prevRights->c_channelBannedRights().vflags.v : 0; + auto newUntil = newRights ? newRights->c_channelBannedRights().vuntil_date : MTP_int(0); + auto indefinitely = ChannelData::IsRestrictedForever(newUntil.v); + if (newFlags & Flag::f_view_messages) { + return lng_admin_log_banned__generic(lt_user, user); + } + auto untilText = indefinitely ? lang(lng_admin_log_restricted_forever) : lng_admin_log_restricted_until(lt_date, langDateTime(::date(newUntil))); + auto result = lng_admin_log_restricted__generic(lt_user, user, lt_until, TextWithEntities { untilText }); + + static auto phraseMap = std::map { + { Flag::f_view_messages, lng_admin_log_banned_view_messages }, + { Flag::f_send_messages, lng_admin_log_banned_send_messages }, + { Flag::f_send_media, lng_admin_log_banned_send_media }, + { Flag::f_send_stickers | Flag::f_send_gifs | Flag::f_send_inline | Flag::f_send_games, lng_admin_log_banned_send_stickers }, + { Flag::f_embed_links, lng_admin_log_banned_embed_links }, + }; + auto changes = CollectChanges(phraseMap, prevFlags, newFlags); + if (!changes.isEmpty()) { + result.text.append('\n' + changes); + } + + return result; +}; + +auto GenerateUserString(MTPint userId) { + // User name in "User name (@username)" format with entities. + auto user = App::user(userId.v); + auto name = TextWithEntities { App::peerName(user) }; + name.entities.push_back(EntityInText(EntityInTextMentionName, 0, name.text.size(), QString::number(user->id) + '.' + QString::number(user->access))); + auto username = user->userName(); + if (username.isEmpty()) { + return name; + } + auto mention = TextWithEntities { '@' + username }; + mention.entities.push_back(EntityInText(EntityInTextMention, 0, mention.text.size())); + return lng_admin_log_user_with_username__generic(lt_name, name, lt_mention, mention); +} + +auto GenerateParticipantChangeTextInner(gsl::not_null channel, const MTPChannelParticipant &participant, const MTPChannelParticipant *oldParticipant) { + auto oldType = oldParticipant ? oldParticipant->type() : 0; + + auto resultForParticipant = [channel, oldParticipant, oldType](auto &&data) { + auto user = GenerateUserString(data.vuser_id); + if (oldType == mtpc_channelParticipantAdmin) { + return GenerateAdminChangeText(channel, user, nullptr, &oldParticipant->c_channelParticipantAdmin().vadmin_rights); + } else if (oldType == mtpc_channelParticipantBanned) { + return GenerateBannedChangeText(user, nullptr, &oldParticipant->c_channelParticipantBanned().vbanned_rights); + } + return lng_admin_log_invited__generic(lt_user, user); + }; + + switch (participant.type()) { + case mtpc_channelParticipantCreator: { + // No valid string here :( + auto &data = participant.c_channelParticipantCreator(); + return lng_admin_log_invited__generic(lt_user, GenerateUserString(data.vuser_id)); + } break; + + case mtpc_channelParticipant: return resultForParticipant(participant.c_channelParticipant()); + case mtpc_channelParticipantSelf: return resultForParticipant(participant.c_channelParticipantSelf()); + + case mtpc_channelParticipantAdmin: { + auto &data = participant.c_channelParticipantAdmin(); + auto user = GenerateUserString(data.vuser_id); + return GenerateAdminChangeText(channel, user, &data.vadmin_rights, (oldType == mtpc_channelParticipantAdmin) ? &oldParticipant->c_channelParticipantAdmin().vadmin_rights : nullptr); + } break; + + case mtpc_channelParticipantBanned: { + auto &data = participant.c_channelParticipantBanned(); + auto user = GenerateUserString(data.vuser_id); + return GenerateBannedChangeText(user, &data.vbanned_rights, (oldType == mtpc_channelParticipantBanned) ? &oldParticipant->c_channelParticipantBanned().vbanned_rights : nullptr); + } break; + } + + Unexpected("Participant type in GenerateParticipantChangeTextInner()"); +} + +TextWithEntities GenerateParticipantChangeText(gsl::not_null channel, const MTPChannelParticipant &participant, const MTPChannelParticipant *oldParticipant = nullptr) { + auto result = GenerateParticipantChangeTextInner(channel, participant, oldParticipant); + result.entities.push_front(EntityInText(EntityInTextItalic, 0, result.text.size())); + return result; +} + } // namespace Item::Item(gsl::not_null history, LocalIdManager &idManager, const MTPDchannelAdminLogEvent &event) @@ -67,10 +246,10 @@ Item::Item(gsl::not_null history, LocalIdManager &idManager, const MTP auto fromLink = _from->openLink(); auto fromLinkText = textcmdLink(1, fromName); - auto addSimpleServiceMessage = [this, &idManager, date, fromLink](const QString &text) { + auto addSimpleServiceMessage = [this, &idManager, date, fromLink](const QString &text, PhotoData *photo = nullptr) { auto message = HistoryService::PreparedText { text }; message.links.push_back(fromLink); - addPart(HistoryService::create(_history, idManager.next(), ::date(date), message, 0, peerToUser(_from->id))); + addPart(HistoryService::create(_history, idManager.next(), ::date(date), message, 0, peerToUser(_from->id), photo)); }; auto createChangeTitle = [this, addSimpleServiceMessage, fromLinkText](const MTPDchannelAdminLogEventActionChangeTitle &action) { @@ -120,9 +299,12 @@ Item::Item(gsl::not_null history, LocalIdManager &idManager, const MTP addPart(body); }; - auto createChangePhoto = [this, addSimpleServiceMessage, fromLinkText](const MTPDchannelAdminLogEventActionChangePhoto &action) { + auto createChangePhoto = [this, date, addSimpleServiceMessage, fromLinkText](const MTPDchannelAdminLogEventActionChangePhoto &action) { + t_assert(action.vnew_photo.type() == mtpc_chatPhoto); + auto photo = GenerateChatPhoto(channel()->bareId(), _id, date, action.vnew_photo.c_chatPhoto()); + auto text = (channel()->isMegagroup() ? lng_admin_log_changed_photo_group : lng_admin_log_changed_photo_channel)(lt_from, fromLinkText); - addSimpleServiceMessage(text); + addSimpleServiceMessage(text, photo); }; auto createToggleInvites = [this, addSimpleServiceMessage, fromLinkText](const MTPDchannelAdminLogEventActionToggleInvites &action) { @@ -147,12 +329,22 @@ Item::Item(gsl::not_null history, LocalIdManager &idManager, const MTP }; auto createEditMessage = [this, &idManager, date, addSimpleServiceMessage, fromLinkText](const MTPDchannelAdminLogEventActionEditMessage &action) { - auto text = (MessageHasCaption(action.vnew_message) ? lng_admin_log_edited_caption : lng_admin_log_edited_message)(lt_from, fromLinkText); + auto newValue = ExtractEditedText(action.vnew_message); + auto canHaveCaption = MediaCanHaveCaption(action.vnew_message); + auto text = (canHaveCaption + ? (newValue.text.isEmpty() ? lng_admin_log_removed_caption : lng_admin_log_edited_caption) + : lng_admin_log_edited_message + )(lt_from, fromLinkText); addSimpleServiceMessage(text); + auto oldValue = ExtractEditedText(action.vprev_message); auto applyServiceAction = false; auto detachExistingItem = false; - addPart(_history->createItem(PrepareLogMessage(action.vnew_message, idManager.next(), date.v), applyServiceAction, detachExistingItem)); + auto body = _history->createItem(PrepareLogMessage(action.vnew_message, idManager.next(), date.v), applyServiceAction, detachExistingItem); + if (!oldValue.text.isEmpty()) { + body->addLogEntryOriginal(lang(canHaveCaption ? lng_admin_log_previous_caption : lng_admin_log_previous_description), oldValue); + } + addPart(body); }; auto createDeleteMessage = [this, &idManager, date, addSimpleServiceMessage, fromLinkText](const MTPDchannelAdminLogEventActionDeleteMessage &action) { @@ -178,21 +370,24 @@ Item::Item(gsl::not_null history, LocalIdManager &idManager, const MTP auto bodyFlags = Flag::f_entities | Flag::f_from_id; auto bodyReplyTo = 0; auto bodyViaBotId = 0; - addPart(HistoryMessage::create(_history, idManager.next(), bodyFlags, bodyReplyTo, bodyViaBotId, ::date(date), peerToUser(_from->id), { "participant invite text", EntitiesInText() })); + auto bodyText = GenerateParticipantChangeText(channel(), action.vparticipant); + addPart(HistoryMessage::create(_history, idManager.next(), bodyFlags, bodyReplyTo, bodyViaBotId, ::date(date), peerToUser(_from->id), bodyText)); }; auto createParticipantToggleBan = [this, &idManager, date](const MTPDchannelAdminLogEventActionParticipantToggleBan &action) { auto bodyFlags = Flag::f_entities | Flag::f_from_id; auto bodyReplyTo = 0; auto bodyViaBotId = 0; - addPart(HistoryMessage::create(_history, idManager.next(), bodyFlags, bodyReplyTo, bodyViaBotId, ::date(date), peerToUser(_from->id), { "participant toggle ban text", EntitiesInText() })); + auto bodyText = GenerateParticipantChangeText(channel(), action.vnew_participant, &action.vprev_participant); + addPart(HistoryMessage::create(_history, idManager.next(), bodyFlags, bodyReplyTo, bodyViaBotId, ::date(date), peerToUser(_from->id), bodyText)); }; auto createParticipantToggleAdmin = [this, &idManager, date](const MTPDchannelAdminLogEventActionParticipantToggleAdmin &action) { auto bodyFlags = Flag::f_entities | Flag::f_from_id; auto bodyReplyTo = 0; auto bodyViaBotId = 0; - addPart(HistoryMessage::create(_history, idManager.next(), bodyFlags, bodyReplyTo, bodyViaBotId, ::date(date), peerToUser(_from->id), { "participant toggle admin text", EntitiesInText() })); + auto bodyText = GenerateParticipantChangeText(channel(), action.vnew_participant, &action.vprev_participant); + addPart(HistoryMessage::create(_history, idManager.next(), bodyFlags, bodyReplyTo, bodyViaBotId, ::date(date), peerToUser(_from->id), bodyText)); }; switch (action.type()) { @@ -263,19 +458,6 @@ HistoryTextState Item::getState(QPoint point, HistoryStateRequest request) const return HistoryTextState(); } -TextWithEntities Item::PrepareText(const QString &value, const QString &emptyValue) { - auto result = TextWithEntities { textClean(value) }; - if (result.text.isEmpty()) { - result.text = emptyValue; - if (!emptyValue.isEmpty()) { - result.entities.push_back(EntityInText(EntityInTextItalic, 0, emptyValue.size())); - } - } else { - textParseEntities(result.text, TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands, &result.entities); - } - return result; -} - Item::~Item() { for (auto part : _parts) { part->destroy(); diff --git a/Telegram/SourceFiles/history/history_admin_log_item.h b/Telegram/SourceFiles/history/history_admin_log_item.h index d29666ec1..d2a1de4c4 100644 --- a/Telegram/SourceFiles/history/history_admin_log_item.h +++ b/Telegram/SourceFiles/history/history_admin_log_item.h @@ -54,8 +54,6 @@ private: } void addPart(HistoryItem *item); - static TextWithEntities PrepareText(const QString &value, const QString &emptyValue); - uint64 _id = 0; gsl::not_null _history; gsl::not_null _from; diff --git a/Telegram/SourceFiles/history/history_media_types.cpp b/Telegram/SourceFiles/history/history_media_types.cpp index bc355487e..d4c9d9955 100644 --- a/Telegram/SourceFiles/history/history_media_types.cpp +++ b/Telegram/SourceFiles/history/history_media_types.cpp @@ -222,7 +222,7 @@ void HistoryFileMedia::checkAnimationFinished() const { HistoryFileMedia::~HistoryFileMedia() = default; -HistoryPhoto::HistoryPhoto(HistoryItem *parent, PhotoData *photo, const QString &caption) : HistoryFileMedia(parent) +HistoryPhoto::HistoryPhoto(gsl::not_null parent, gsl::not_null photo, const QString &caption) : HistoryFileMedia(parent) , _data(photo) , _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) { setLinks(MakeShared(_data), MakeShared(_data), MakeShared(_data)); @@ -232,15 +232,18 @@ HistoryPhoto::HistoryPhoto(HistoryItem *parent, PhotoData *photo, const QString init(); } -HistoryPhoto::HistoryPhoto(HistoryItem *parent, PeerData *chat, const MTPDphoto &photo, int32 width) : HistoryFileMedia(parent) -, _data(App::feedPhoto(photo)) { +HistoryPhoto::HistoryPhoto(gsl::not_null parent, gsl::not_null chat, gsl::not_null photo, int32 width) : HistoryFileMedia(parent) +, _data(photo) { setLinks(MakeShared(_data, chat), MakeShared(_data, chat), MakeShared(_data, chat)); _width = width; init(); } -HistoryPhoto::HistoryPhoto(HistoryItem *parent, const HistoryPhoto &other) : HistoryFileMedia(parent) +HistoryPhoto::HistoryPhoto(gsl::not_null parent, gsl::not_null chat, const MTPDphoto &photo, int32 width) : HistoryPhoto(parent, chat, App::feedPhoto(photo), width) { +} + +HistoryPhoto::HistoryPhoto(gsl::not_null parent, const HistoryPhoto &other) : HistoryFileMedia(parent) , _data(other._data) , _pixw(other._pixw) , _pixh(other._pixh) @@ -559,7 +562,7 @@ void HistoryPhoto::updateSentMedia(const MTPMessageMedia &media) { bool HistoryPhoto::needReSetInlineResultMedia(const MTPMessageMedia &media) { if (media.type() == mtpc_messageMediaPhoto) { - if (PhotoData *existing = App::feedPhoto(media.c_messageMediaPhoto().vphoto)) { + if (auto existing = App::feedPhoto(media.c_messageMediaPhoto().vphoto)) { if (existing == _data) { return false; } else { @@ -612,7 +615,7 @@ ImagePtr HistoryPhoto::replyPreview() { return _data->makeReplyPreview(); } -HistoryVideo::HistoryVideo(HistoryItem *parent, DocumentData *document, const QString &caption) : HistoryFileMedia(parent) +HistoryVideo::HistoryVideo(gsl::not_null parent, DocumentData *document, const QString &caption) : HistoryFileMedia(parent) , _data(document) , _thumbw(1) , _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) { @@ -627,7 +630,7 @@ HistoryVideo::HistoryVideo(HistoryItem *parent, DocumentData *document, const QS _data->thumb->load(); } -HistoryVideo::HistoryVideo(HistoryItem *parent, const HistoryVideo &other) : HistoryFileMedia(parent) +HistoryVideo::HistoryVideo(gsl::not_null parent, const HistoryVideo &other) : HistoryFileMedia(parent) , _data(other._data) , _thumbw(other._thumbw) , _caption(other._caption) { @@ -976,7 +979,7 @@ void HistoryDocumentVoice::stopSeeking() { Media::Player::instance()->stopSeeking(AudioMsgId::Type::Voice); } -HistoryDocument::HistoryDocument(HistoryItem *parent, DocumentData *document, const QString &caption) : HistoryFileMedia(parent) +HistoryDocument::HistoryDocument(gsl::not_null parent, DocumentData *document, const QString &caption) : HistoryFileMedia(parent) , _data(document) { createComponents(!caption.isEmpty()); if (auto named = Get()) { @@ -992,7 +995,7 @@ HistoryDocument::HistoryDocument(HistoryItem *parent, DocumentData *document, co } } -HistoryDocument::HistoryDocument(HistoryItem *parent, const HistoryDocument &other) : HistoryFileMedia(parent) +HistoryDocument::HistoryDocument(gsl::not_null parent, const HistoryDocument &other) : HistoryFileMedia(parent) , RuntimeComposer() , _data(other._data) { auto captioned = other.Get(); @@ -1715,7 +1718,7 @@ ImagePtr HistoryDocument::replyPreview() { return _data->makeReplyPreview(); } -HistoryGif::HistoryGif(HistoryItem *parent, DocumentData *document, const QString &caption) : HistoryFileMedia(parent) +HistoryGif::HistoryGif(gsl::not_null parent, DocumentData *document, const QString &caption) : HistoryFileMedia(parent) , _data(document) , _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) { setDocumentLinks(_data, true); @@ -1730,7 +1733,7 @@ HistoryGif::HistoryGif(HistoryItem *parent, DocumentData *document, const QStrin _data->thumb->load(); } -HistoryGif::HistoryGif(HistoryItem *parent, const HistoryGif &other) : HistoryFileMedia(parent) +HistoryGif::HistoryGif(gsl::not_null parent, const HistoryGif &other) : HistoryFileMedia(parent) , _data(other._data) , _thumbw(other._thumbw) , _thumbh(other._thumbh) @@ -2488,7 +2491,7 @@ bool HistoryGif::dataLoaded() const { return (!_parent || _parent->id > 0) ? _data->loaded() : false; } -HistorySticker::HistorySticker(HistoryItem *parent, DocumentData *document) : HistoryMedia(parent) +HistorySticker::HistorySticker(gsl::not_null parent, DocumentData *document) : HistoryMedia(parent) , _data(document) , _emoji(_data->sticker()->alt) { _data->thumb->load(); @@ -2770,7 +2773,7 @@ ClickHandlerPtr addContactClickHandler(HistoryItem *item) { } // namespace -HistoryContact::HistoryContact(HistoryItem *parent, int32 userId, const QString &first, const QString &last, const QString &phone) : HistoryMedia(parent) +HistoryContact::HistoryContact(gsl::not_null parent, int32 userId, const QString &first, const QString &last, const QString &phone) : HistoryMedia(parent) , _userId(userId) , _fname(first) , _lname(last) @@ -2936,7 +2939,7 @@ void HistoryContact::updateSentMedia(const MTPMessageMedia &media) { } } -HistoryCall::HistoryCall(HistoryItem *parent, const MTPDmessageActionPhoneCall &call) : HistoryMedia(parent) +HistoryCall::HistoryCall(gsl::not_null parent, const MTPDmessageActionPhoneCall &call) : HistoryMedia(parent) , _reason(GetReason(call)) { if (_parent->out()) { _text = lang(_reason == FinishReason::Missed ? lng_call_cancelled : lng_call_outgoing); @@ -3087,13 +3090,13 @@ int unitedLineHeight() { } // namespace -HistoryWebPage::HistoryWebPage(HistoryItem *parent, WebPageData *data) : HistoryMedia(parent) +HistoryWebPage::HistoryWebPage(gsl::not_null parent, WebPageData *data) : HistoryMedia(parent) , _data(data) , _title(st::msgMinWidth - st::webPageLeft) , _description(st::msgMinWidth - st::webPageLeft) { } -HistoryWebPage::HistoryWebPage(HistoryItem *parent, const HistoryWebPage &other) : HistoryMedia(parent) +HistoryWebPage::HistoryWebPage(gsl::not_null parent, const HistoryWebPage &other) : HistoryMedia(parent) , _data(other._data) , _attach(other._attach ? other._attach->clone(parent) : nullptr) , _asArticle(other._asArticle) @@ -3613,13 +3616,13 @@ int HistoryWebPage::bottomInfoPadding() const { return result; } -HistoryGame::HistoryGame(HistoryItem *parent, GameData *data) : HistoryMedia(parent) +HistoryGame::HistoryGame(gsl::not_null parent, GameData *data) : HistoryMedia(parent) , _data(data) , _title(st::msgMinWidth - st::webPageLeft) , _description(st::msgMinWidth - st::webPageLeft) { } -HistoryGame::HistoryGame(HistoryItem *parent, const HistoryGame &other) : HistoryMedia(parent) +HistoryGame::HistoryGame(gsl::not_null parent, const HistoryGame &other) : HistoryMedia(parent) , _data(other._data) , _attach(other._attach ? other._attach->clone(parent) : nullptr) , _title(other._title) @@ -3992,14 +3995,14 @@ int HistoryGame::bottomInfoPadding() const { return result; } -HistoryInvoice::HistoryInvoice(HistoryItem *parent, const MTPDmessageMediaInvoice &data) : HistoryMedia(parent) +HistoryInvoice::HistoryInvoice(gsl::not_null parent, const MTPDmessageMediaInvoice &data) : HistoryMedia(parent) , _title(st::msgMinWidth) , _description(st::msgMinWidth) , _status(st::msgMinWidth) { fillFromData(data); } -HistoryInvoice::HistoryInvoice(HistoryItem *parent, const HistoryInvoice &other) : HistoryMedia(parent) +HistoryInvoice::HistoryInvoice(gsl::not_null parent, const HistoryInvoice &other) : HistoryMedia(parent) , _attach(other._attach ? other._attach->clone(parent) : nullptr) , _titleHeight(other._titleHeight) , _descriptionHeight(other._descriptionHeight) @@ -4371,7 +4374,7 @@ int HistoryInvoice::bottomInfoPadding() const { return result; } -HistoryLocation::HistoryLocation(HistoryItem *parent, const LocationCoords &coords, const QString &title, const QString &description) : HistoryMedia(parent) +HistoryLocation::HistoryLocation(gsl::not_null parent, const LocationCoords &coords, const QString &title, const QString &description) : HistoryMedia(parent) , _data(App::location(coords)) , _title(st::msgMinWidth) , _description(st::msgMinWidth) @@ -4384,7 +4387,7 @@ HistoryLocation::HistoryLocation(HistoryItem *parent, const LocationCoords &coor } } -HistoryLocation::HistoryLocation(HistoryItem *parent, const HistoryLocation &other) : HistoryMedia(parent) +HistoryLocation::HistoryLocation(gsl::not_null parent, const HistoryLocation &other) : HistoryMedia(parent) , _data(other._data) , _title(other._title) , _description(other._description) diff --git a/Telegram/SourceFiles/history/history_media_types.h b/Telegram/SourceFiles/history/history_media_types.h index 2f1c94b0a..bd0a55af9 100644 --- a/Telegram/SourceFiles/history/history_media_types.h +++ b/Telegram/SourceFiles/history/history_media_types.h @@ -116,9 +116,10 @@ protected: class HistoryPhoto : public HistoryFileMedia { public: - HistoryPhoto(HistoryItem *parent, PhotoData *photo, const QString &caption); - HistoryPhoto(HistoryItem *parent, PeerData *chat, const MTPDphoto &photo, int width); - HistoryPhoto(HistoryItem *parent, const HistoryPhoto &other); + HistoryPhoto(gsl::not_null parent, gsl::not_null photo, const QString &caption); + HistoryPhoto(gsl::not_null parent, gsl::not_null chat, gsl::not_null photo, int width); + HistoryPhoto(gsl::not_null parent, gsl::not_null chat, const MTPDphoto &photo, int width); + HistoryPhoto(gsl::not_null parent, const HistoryPhoto &other); void init(); HistoryMediaType type() const override { @@ -212,8 +213,8 @@ private: class HistoryVideo : public HistoryFileMedia { public: - HistoryVideo(HistoryItem *parent, DocumentData *document, const QString &caption); - HistoryVideo(HistoryItem *parent, const HistoryVideo &other); + HistoryVideo(gsl::not_null parent, DocumentData *document, const QString &caption); + HistoryVideo(gsl::not_null parent, const HistoryVideo &other); HistoryMediaType type() const override { return MediaTypeVideo; } @@ -370,8 +371,8 @@ private: class HistoryDocument : public HistoryFileMedia, public RuntimeComposer { public: - HistoryDocument(HistoryItem *parent, DocumentData *document, const QString &caption); - HistoryDocument(HistoryItem *parent, const HistoryDocument &other); + HistoryDocument(gsl::not_null parent, DocumentData *document, const QString &caption); + HistoryDocument(gsl::not_null parent, const HistoryDocument &other); HistoryMediaType type() const override { return _data->voice() ? MediaTypeVoiceFile : (_data->song() ? MediaTypeMusicFile : MediaTypeFile); } @@ -477,8 +478,8 @@ private: class HistoryGif : public HistoryFileMedia { public: - HistoryGif(HistoryItem *parent, DocumentData *document, const QString &caption); - HistoryGif(HistoryItem *parent, const HistoryGif &other); + HistoryGif(gsl::not_null parent, DocumentData *document, const QString &caption); + HistoryGif(gsl::not_null parent, const HistoryGif &other); HistoryMediaType type() const override { return MediaTypeGif; } @@ -601,7 +602,7 @@ private: class HistorySticker : public HistoryMedia { public: - HistorySticker(HistoryItem *parent, DocumentData *document); + HistorySticker(gsl::not_null parent, DocumentData *document); HistoryMediaType type() const override { return MediaTypeSticker; } @@ -670,7 +671,7 @@ private: class HistoryContact : public HistoryMedia { public: - HistoryContact(HistoryItem *parent, int32 userId, const QString &first, const QString &last, const QString &phone); + HistoryContact(gsl::not_null parent, int32 userId, const QString &first, const QString &last, const QString &phone); HistoryMediaType type() const override { return MediaTypeContact; } @@ -732,7 +733,7 @@ private: class HistoryCall : public HistoryMedia { public: - HistoryCall(HistoryItem *parent, const MTPDmessageActionPhoneCall &call); + HistoryCall(gsl::not_null parent, const MTPDmessageActionPhoneCall &call); HistoryMediaType type() const override { return MediaTypeCall; } @@ -787,8 +788,8 @@ private: class HistoryWebPage : public HistoryMedia { public: - HistoryWebPage(HistoryItem *parent, WebPageData *data); - HistoryWebPage(HistoryItem *parent, const HistoryWebPage &other); + HistoryWebPage(gsl::not_null parent, WebPageData *data); + HistoryWebPage(gsl::not_null parent, const HistoryWebPage &other); HistoryMediaType type() const override { return MediaTypeWebPage; } @@ -886,8 +887,8 @@ private: class HistoryGame : public HistoryMedia { public: - HistoryGame(HistoryItem *parent, GameData *data); - HistoryGame(HistoryItem *parent, const HistoryGame &other); + HistoryGame(gsl::not_null parent, GameData *data); + HistoryGame(gsl::not_null parent, const HistoryGame &other); HistoryMediaType type() const override { return MediaTypeGame; } @@ -986,8 +987,8 @@ private: class HistoryInvoice : public HistoryMedia { public: - HistoryInvoice(HistoryItem *parent, const MTPDmessageMediaInvoice &data); - HistoryInvoice(HistoryItem *parent, const HistoryInvoice &other); + HistoryInvoice(gsl::not_null parent, const MTPDmessageMediaInvoice &data); + HistoryInvoice(gsl::not_null parent, const HistoryInvoice &other); HistoryMediaType type() const override { return MediaTypeInvoice; } @@ -1077,8 +1078,8 @@ struct LocationData; class HistoryLocation : public HistoryMedia { public: - HistoryLocation(HistoryItem *parent, const LocationCoords &coords, const QString &title = QString(), const QString &description = QString()); - HistoryLocation(HistoryItem *parent, const HistoryLocation &other); + HistoryLocation(gsl::not_null parent, const LocationCoords &coords, const QString &title = QString(), const QString &description = QString()); + HistoryLocation(gsl::not_null parent, const HistoryLocation &other); HistoryMediaType type() const override { return MediaTypeLocation; } diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index 302477b32..dc6af58f6 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -425,11 +425,9 @@ HistoryMessage::HistoryMessage(History *history, const MTPDmessage &msg) initMedia(msg.has_media() ? (&msg.vmedia) : nullptr); - TextWithEntities textWithEntities = { - textClean(qs(msg.vmessage)), - msg.has_entities() ? entitiesFromMTP(msg.ventities.v) : EntitiesInText(), - }; - setText(textWithEntities); + auto text = textClean(qs(msg.vmessage)); + auto entities = msg.has_entities() ? entitiesFromMTP(msg.ventities.v) : EntitiesInText(); + setText({ text, entities }); } HistoryMessage::HistoryMessage(History *history, const MTPDmessageService &msg) @@ -2214,9 +2212,12 @@ HistoryService::HistoryService(History *history, const MTPDmessageService &messa setMessageByAction(message.vaction); } -HistoryService::HistoryService(History *history, MsgId msgId, QDateTime date, const PreparedText &message, MTPDmessage::Flags flags, int32 from) : +HistoryService::HistoryService(History *history, MsgId msgId, QDateTime date, const PreparedText &message, MTPDmessage::Flags flags, int32 from, PhotoData *photo) : HistoryItem(history, msgId, flags, date, from) { setServiceText(message); + if (photo) { + _media = std::make_unique(this, history->peer, photo, st::msgServicePhotoWidth); + } } void HistoryService::initDimensions() { diff --git a/Telegram/SourceFiles/history/history_message.h b/Telegram/SourceFiles/history/history_message.h index e69879418..b7b9b0727 100644 --- a/Telegram/SourceFiles/history/history_message.h +++ b/Telegram/SourceFiles/history/history_message.h @@ -281,8 +281,8 @@ public: static HistoryService *create(History *history, const MTPDmessageService &message) { return _create(history, message); } - static HistoryService *create(History *history, MsgId msgId, QDateTime date, const PreparedText &message, MTPDmessage::Flags flags = 0, int32 from = 0) { - return _create(history, msgId, date, message, flags, from); + static HistoryService *create(History *history, MsgId msgId, QDateTime date, const PreparedText &message, MTPDmessage::Flags flags = 0, UserId from = 0, PhotoData *photo = nullptr) { + return _create(history, msgId, date, message, flags, from, photo); } bool updateDependencyItem() override; @@ -339,7 +339,7 @@ protected: friend class HistoryLayout::ServiceMessagePainter; HistoryService(History *history, const MTPDmessageService &message); - HistoryService(History *history, MsgId msgId, QDateTime date, const PreparedText &message, MTPDmessage::Flags flags = 0, int32 from = 0); + HistoryService(History *history, MsgId msgId, QDateTime date, const PreparedText &message, MTPDmessage::Flags flags = 0, UserId from = 0, PhotoData *photo = 0); friend class HistoryItemInstantiated; void initDimensions() override; diff --git a/Telegram/SourceFiles/lang/lang_tag.cpp b/Telegram/SourceFiles/lang/lang_tag.cpp index 56bdbfb93..83bb0273c 100644 --- a/Telegram/SourceFiles/lang/lang_tag.cpp +++ b/Telegram/SourceFiles/lang/lang_tag.cpp @@ -129,19 +129,14 @@ ChoosePluralMethod ChoosePlural = ChoosePluralEn; } // namespace -QString Tag(const QString &original, ushort tag, const QString &replacement) { +int FindTagReplacementPosition(const QString &original, ushort tag) { for (auto s = original.constData(), ch = s, e = ch + original.size(); ch != e;) { if (*ch == TextCommand) { - if (ch + 3 < e && (ch + 1)->unicode() == TextCommandLangTag && *(ch + 3) == TextCommand) { + if (ch + kTagReplacementSize <= e && (ch + 1)->unicode() == TextCommandLangTag && *(ch + 3) == TextCommand) { if ((ch + 2)->unicode() == 0x0020 + tag) { - auto result = QString(); - result.reserve(original.size() + replacement.size() - 4); - if (ch > s) result.append(original.midRef(0, ch - s)); - result.append(replacement); - if (ch + 4 < e) result.append(original.midRef(ch - s + 4)); - return result; + return ch - s; } else { - ch += 4; + ch += kTagReplacementSize; } } else { auto next = textSkipCommand(ch, e); @@ -155,10 +150,11 @@ QString Tag(const QString &original, ushort tag, const QString &replacement) { ++ch; } } - return original; + return -1; + } -QString Plural(ushort keyBase, ushort tag, float64 value) { +PluralResult Plural(ushort keyBase, float64 value) { // Simplified. auto n = qAbs(value); auto i = qFloor(n); @@ -173,9 +169,9 @@ QString Plural(ushort keyBase, ushort tag, float64 value) { auto shift = (useNonDefaultPlural ? ChoosePlural : ChoosePluralEn)((integer ? i : -1), i, v, w, f, t); auto string = langpack.getValue(LangKey(keyBase + shift)); if (i == qCeil(n)) { - return Tag(string, tag, QString::number(value)); + return { string, QString::number(value) }; } - return Tag(string, tag, QString::number(qRound(value))); + return { string, QString::number(qRound(value)) }; } void UpdatePluralRules(const QString &languageId) { @@ -183,4 +179,17 @@ void UpdatePluralRules(const QString &languageId) { ChoosePlural = kMap.value(languageId.toLower(), ChoosePluralEn); } +QString ReplaceTag::Replace(QString &&original, const QString &replacement, int replacementPosition) { + auto result = QString(); + result.reserve(original.size() + replacement.size() - kTagReplacementSize); + if (replacementPosition > 0) { + result.append(original.midRef(0, replacementPosition)); + } + result.append(replacement); + if (replacementPosition + kTagReplacementSize < original.size()) { + result.append(original.midRef(replacementPosition + kTagReplacementSize)); + } + return result; +} + } // namespace Lang diff --git a/Telegram/SourceFiles/lang/lang_tag.h b/Telegram/SourceFiles/lang/lang_tag.h index 360f67de8..6f11b0f30 100644 --- a/Telegram/SourceFiles/lang/lang_tag.h +++ b/Telegram/SourceFiles/lang/lang_tag.h @@ -22,8 +22,41 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org namespace Lang { -QString Tag(const QString &original, ushort tag, const QString &replacement); -QString Plural(ushort keyBase, ushort tag, float64 value); +constexpr auto kTagReplacementSize = 4; + +int FindTagReplacementPosition(const QString &original, ushort tag); + +struct PluralResult { + QString string; + QString replacement; +}; +PluralResult Plural(ushort keyBase, float64 value); void UpdatePluralRules(const QString &languageId); +template +struct StartReplacements; + +template <> +struct StartReplacements { + static inline QString Call(QString &&langString) { + return std::move(langString); + } +}; + +template +struct ReplaceTag; + +template <> +struct ReplaceTag { + static inline QString Call(QString &&original, ushort tag, const QString &replacement) { + auto replacementPosition = FindTagReplacementPosition(original, tag); + if (replacementPosition < 0) { + return std::move(original); + } + return Replace(std::move(original), replacement, replacementPosition); + } + static QString Replace(QString &&original, const QString &replacement, int start); + +}; + } // namespace Lang diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h index 181ad7968..e386b20a9 100644 --- a/Telegram/SourceFiles/structs.h +++ b/Telegram/SourceFiles/structs.h @@ -801,6 +801,10 @@ public: } static MTPChannelBannedRights KickedRestrictedRights(); + static constexpr auto kRestrictUntilForever = TimeId(INT_MAX); + static bool IsRestrictedForever(TimeId until) { + return !until || (until == kRestrictUntilForever); + } void applyEditAdmin(gsl::not_null user, const MTPChannelAdminRights &rights); void applyEditBanned(gsl::not_null user, const MTPChannelBannedRights &rights); @@ -1117,9 +1121,9 @@ private: class PhotoClickHandler : public LeftButtonClickHandler { public: - PhotoClickHandler(PhotoData *photo, PeerData *peer = 0) : _photo(photo), _peer(peer) { + PhotoClickHandler(gsl::not_null photo, PeerData *peer = nullptr) : _photo(photo), _peer(peer) { } - PhotoData *photo() const { + gsl::not_null photo() const { return _photo; } PeerData *peer() const { @@ -1127,7 +1131,7 @@ public: } private: - PhotoData *_photo; + gsl::not_null _photo; PeerData *_peer; }; diff --git a/Telegram/SourceFiles/ui/text/text_entity.cpp b/Telegram/SourceFiles/ui/text/text_entity.cpp index e314d1b48..f09656506 100644 --- a/Telegram/SourceFiles/ui/text/text_entity.cpp +++ b/Telegram/SourceFiles/ui/text/text_entity.cpp @@ -21,6 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "ui/text/text_entity.h" #include "auth_session.h" +#include "lang/lang_tag.h" namespace { @@ -1366,7 +1367,7 @@ EntitiesInText entitiesFromMTP(const QVector &entities) { case mtpc_messageEntityHashtag: { const auto &d(entity.c_messageEntityHashtag()); result.push_back(EntityInText(EntityInTextHashtag, d.voffset.v, d.vlength.v)); } break; case mtpc_messageEntityMention: { const auto &d(entity.c_messageEntityMention()); result.push_back(EntityInText(EntityInTextMention, d.voffset.v, d.vlength.v)); } break; case mtpc_messageEntityMentionName: { - const auto &d(entity.c_messageEntityMentionName()); + auto &d = entity.c_messageEntityMentionName(); auto data = QString::number(d.vuser_id.v); if (auto user = App::userLoaded(peerFromUser(d.vuser_id))) { data += '.' + QString::number(user->access); @@ -1374,7 +1375,7 @@ EntitiesInText entitiesFromMTP(const QVector &entities) { result.push_back(EntityInText(EntityInTextMentionName, d.voffset.v, d.vlength.v, data)); } break; case mtpc_inputMessageEntityMentionName: { - const auto &d(entity.c_inputMessageEntityMentionName()); + auto &d = entity.c_inputMessageEntityMentionName(); auto data = ([&d]() -> QString { if (d.vuser_id.type() == mtpc_inputUserSelf) { return QString::number(AuthSession::CurrentUserId()); @@ -1994,3 +1995,65 @@ void trimTextWithEntities(QString &result, EntitiesInText *inOutEntities) { } } } + +namespace Lang { + +TextWithEntities ReplaceTag::Call(TextWithEntities &&original, ushort tag, const TextWithEntities &replacement) { + auto replacementPosition = FindTagReplacementPosition(original.text, tag); + if (replacementPosition < 0) { + return std::move(original); + } + + auto result = TextWithEntities(); + result.text = ReplaceTag::Replace(std::move(original.text), replacement.text, replacementPosition); + auto originalEntitiesCount = original.entities.size(); + auto replacementEntitiesCount = replacement.entities.size(); + if (originalEntitiesCount != 0 || replacementEntitiesCount != 0) { + result.entities.reserve(originalEntitiesCount + replacementEntitiesCount); + + auto replacementEnd = replacementPosition + replacement.text.size(); + auto replacementEntity = replacement.entities.cbegin(); + auto addReplacementEntitiesUntil = [&replacementEntity, &replacement, &result, replacementPosition, replacementEnd](int untilPosition) { + while (replacementEntity != replacement.entities.cend()) { + auto newOffset = replacementPosition + replacementEntity->offset(); + if (newOffset >= untilPosition) { + return; + } + auto newEnd = newOffset + replacementEntity->length(); + newOffset = snap(newOffset, replacementPosition, replacementEnd); + newEnd = snap(newEnd, replacementPosition, replacementEnd); + if (auto newLength = newEnd - newOffset) { + result.entities.push_back(EntityInText(replacementEntity->type(), newOffset, newLength, replacementEntity->data())); + } + ++replacementEntity; + } + }; + + for_const (auto &entity, original.entities) { + // Transform the entity by the replacement. + auto offset = entity.offset(); + auto end = offset + entity.length(); + if (offset > replacementPosition) { + offset = offset + replacement.text.size() - kTagReplacementSize; + } + if (end > replacementPosition) { + end = end + replacement.text.size() - kTagReplacementSize; + } + offset = snap(offset, 0, result.text.size()); + end = snap(end, 0, result.text.size()); + + // Add all replacement entities that start before the current original entity. + addReplacementEntitiesUntil(offset); + + // Add a modified original entity. + if (auto length = end - offset) { + result.entities.push_back(EntityInText(entity.type(), offset, length, entity.data())); + } + } + // Add the remaining replacement entities. + addReplacementEntitiesUntil(result.text.size()); + } + return result; +} + +} // namespace Lang diff --git a/Telegram/SourceFiles/ui/text/text_entity.h b/Telegram/SourceFiles/ui/text/text_entity.h index 97bb73e46..84319b6ac 100644 --- a/Telegram/SourceFiles/ui/text/text_entity.h +++ b/Telegram/SourceFiles/ui/text/text_entity.h @@ -180,4 +180,27 @@ inline QString prepareText(QString result, bool checkLinks = false) { // replace bad symbols with space and remove \r void cleanTextWithEntities(QString &result, EntitiesInText *inOutEntities); -void trimTextWithEntities(QString &result, EntitiesInText *inOutEntities); \ No newline at end of file +void trimTextWithEntities(QString &result, EntitiesInText *inOutEntities); + +namespace Lang { + +template +struct StartReplacements; + +template <> +struct StartReplacements { + static inline TextWithEntities Call(QString &&langString) { + return { std::move(langString), EntitiesInText() }; + } +}; + +template +struct ReplaceTag; + +template <> +struct ReplaceTag { + static TextWithEntities Call(TextWithEntities &&original, ushort tag, const TextWithEntities &replacement); + +}; + +} diff --git a/Telegram/SourceFiles/window/themes/window_theme_editor.cpp b/Telegram/SourceFiles/window/themes/window_theme_editor.cpp index 0d0437fbe..433945534 100644 --- a/Telegram/SourceFiles/window/themes/window_theme_editor.cpp +++ b/Telegram/SourceFiles/window/themes/window_theme_editor.cpp @@ -650,7 +650,8 @@ void ThemeExportBox::chooseBackgroundFromFile() { _background = image; _backgroundContent = content; _isPng = (format == "png"); - _imageText = (_isPng ? lng_theme_editor_read_from_png : lng_theme_editor_read_from_jpg)(lt_size, formatSizeText(_backgroundContent.size())); + auto sizeText = formatSizeText(_backgroundContent.size()); + _imageText = _isPng ? lng_theme_editor_read_from_png(lt_size, sizeText) : lng_theme_editor_read_from_jpg(lt_size, sizeText); _tileBackground->setChecked(false); updateThumbnail(); }