From b55ed7214a406abfd9b19ee7828828fcbda00cbb Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sun, 26 Dec 2021 19:29:25 +0300 Subject: [PATCH] Removed text commands from history item components. --- Telegram/SourceFiles/apiwrap.cpp | 10 +- .../SourceFiles/boxes/add_contact_box.cpp | 40 +- Telegram/SourceFiles/boxes/add_contact_box.h | 2 +- .../SourceFiles/data/data_media_types.cpp | 137 ++--- Telegram/SourceFiles/data/data_media_types.h | 22 +- .../SourceFiles/data/data_replies_list.cpp | 4 +- .../dialogs/ui/dialogs_message_view.cpp | 35 +- .../dialogs/ui/dialogs_message_view.h | 2 +- .../admin_log/history_admin_log_item.cpp | 225 +++++--- Telegram/SourceFiles/history/history_item.cpp | 41 +- Telegram/SourceFiles/history/history_item.h | 4 +- .../history/history_item_components.cpp | 46 +- .../SourceFiles/history/history_service.cpp | 492 +++++++++++++----- .../SourceFiles/history/history_service.h | 6 +- .../SourceFiles/history/history_widget.cpp | 10 +- .../history_view_compose_controls.cpp | 2 +- .../history/view/history_view_item_preview.h | 2 +- .../history/view/history_view_pinned_bar.cpp | 2 +- .../window/notifications_manager.cpp | 32 +- .../window/notifications_manager_default.cpp | 9 +- 20 files changed, 717 insertions(+), 406 deletions(-) diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index ae5f07c42..41a7cf2c7 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -73,6 +73,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "inline_bots/inline_bot_result.h" #include "chat_helpers/message_field.h" #include "ui/item_text_options.h" +#include "ui/text/text_utilities.h" #include "ui/emoji_config.h" #include "ui/chat/attach/attach_prepare.h" #include "ui/toasts/common_toasts.h" @@ -470,13 +471,14 @@ void ApiWrap::sendMessageFail( Ui::show(Box( PeerFloodErrorText(&session(), PeerFloodType::Send))); } else if (error.type() == qstr("USER_BANNED_IN_CHANNEL")) { - const auto link = textcmdLink( - session().createInternalLinkFull(qsl("spambot")), - tr::lng_cant_more_info(tr::now)); + const auto link = Ui::Text::Link( + tr::lng_cant_more_info(tr::now), + session().createInternalLinkFull(qsl("spambot"))); Ui::show(Box(tr::lng_error_public_groups_denied( tr::now, lt_more_info, - link))); + link, + Ui::Text::WithEntities))); } else if (error.type().startsWith(qstr("SLOWMODE_WAIT_"))) { const auto chop = qstr("SLOWMODE_WAIT_").size(); const auto left = base::StringViewMid(error.type(), chop).toInt(); diff --git a/Telegram/SourceFiles/boxes/add_contact_box.cpp b/Telegram/SourceFiles/boxes/add_contact_box.cpp index da5444c8c..a8a8ee92b 100644 --- a/Telegram/SourceFiles/boxes/add_contact_box.cpp +++ b/Telegram/SourceFiles/boxes/add_contact_box.cpp @@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/special_buttons.h" #include "ui/special_fields.h" #include "ui/text/text_options.h" +#include "ui/text/text_utilities.h" #include "ui/unread_badge.h" #include "ui/ui_utility.h" #include "data/data_channel.h" @@ -112,16 +113,19 @@ style::InputField CreateBioFieldStyle() { return result; } -QString PeerFloodErrorText( +TextWithEntities PeerFloodErrorText( not_null session, PeerFloodType type) { - const auto link = textcmdLink( - session->createInternalLinkFull(qsl("spambot")), - tr::lng_cant_more_info(tr::now)); - if (type == PeerFloodType::InviteGroup) { - return tr::lng_cant_invite_not_contact(tr::now, lt_more_info, link); - } - return tr::lng_cant_send_to_not_contact(tr::now, lt_more_info, link); + const auto link = Ui::Text::Link( + tr::lng_cant_more_info(tr::now), + session->createInternalLinkFull(qsl("spambot"))); + return ((type == PeerFloodType::InviteGroup) + ? tr::lng_cant_invite_not_contact + : tr::lng_cant_send_to_not_contact)( + tr::now, + lt_more_info, + link, + Ui::Text::WithEntities); } void ShowAddParticipantsError( @@ -167,6 +171,14 @@ void ShowAddParticipantsError( } } const auto hasBot = ranges::any_of(users, &UserData::isBot); + if (error == u"PEER_FLOOD"_q) { + const auto type = (chat->isChat() || chat->isMegagroup()) + ? PeerFloodType::InviteGroup + : PeerFloodType::InviteChannel; + const auto text = PeerFloodErrorText(&chat->session(), type); + Ui::show(Box(text), Ui::LayerOption::KeepOther); + return; + } const auto text = [&] { if (error == u"USER_BOT"_q) { return tr::lng_cant_invite_bot_to_channel(tr::now); @@ -184,11 +196,6 @@ void ShowAddParticipantsError( return tr::lng_bot_already_in_group(tr::now); } else if (error == u"BOT_GROUPS_BLOCKED"_q) { return tr::lng_error_cant_add_bot(tr::now); - } else if (error == u"PEER_FLOOD"_q) { - const auto type = (chat->isChat() || chat->isMegagroup()) - ? PeerFloodType::InviteGroup - : PeerFloodType::InviteChannel; - return PeerFloodErrorText(&chat->session(), type); } else if (error == u"ADMINS_TOO_MUCH"_q) { return ((chat->isChat() || chat->isMegagroup()) ? tr::lng_error_admin_limit @@ -1521,10 +1528,11 @@ RevokePublicLinkBox::Inner::Inner( st::contactsNameStyle, peer->name, Ui::NameTextOptions()); - row.status.setText( + row.status.setMarkedText( st::defaultTextStyle, - _session->createInternalLink( - textcmdLink(1, peer->userName())), + Ui::Text::Link( + peer->userName(), + _session->createInternalLink(peer->userName())), Ui::DialogTextOptions()); _rows.push_back(std::move(row)); } diff --git a/Telegram/SourceFiles/boxes/add_contact_box.h b/Telegram/SourceFiles/boxes/add_contact_box.h index 885ff9d12..92ff1ab46 100644 --- a/Telegram/SourceFiles/boxes/add_contact_box.h +++ b/Telegram/SourceFiles/boxes/add_contact_box.h @@ -46,7 +46,7 @@ enum class PeerFloodType { [[nodiscard]] style::InputField CreateBioFieldStyle(); -[[nodiscard]] QString PeerFloodErrorText( +[[nodiscard]] TextWithEntities PeerFloodErrorText( not_null session, PeerFloodType type); void ShowAddParticipantsError( diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index a3569bcfb..a8bf37bec 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -66,44 +66,27 @@ constexpr auto kMaxPreviewImages = 3; using ItemPreview = HistoryView::ItemPreview; using ItemPreviewImage = HistoryView::ItemPreviewImage; -[[nodiscard]] QString WithCaptionDialogsText( +[[nodiscard]] TextWithEntities WithCaptionNotificationText( const QString &attachType, - const QString &caption, - bool hasMiniImages, - const HistoryView::ToPreviewOptions &options) { - if (caption.isEmpty()) { - return textcmdLink(1, TextUtilities::Clean(attachType)); + const TextWithEntities &caption, + bool hasMiniImages = false) { + if (caption.text.isEmpty()) { + return Ui::Text::PlainLink(attachType); } return hasMiniImages - ? TextUtilities::Clean(caption, !options.ignoreSpoilers) + ? caption : tr::lng_dialogs_text_media( tr::now, lt_media_part, - textcmdLink(1, tr::lng_dialogs_text_media_wrapped( + tr::lng_dialogs_text_media_wrapped( tr::now, lt_media, - TextUtilities::Clean(attachType))), + Ui::Text::PlainLink(attachType), + Ui::Text::WithEntities), lt_caption, - TextUtilities::Clean(caption, !options.ignoreSpoilers)); -} - -[[nodiscard]] QString WithCaptionNotificationText( - const QString &attachType, - const QString &caption) { - if (caption.isEmpty()) { - return attachType; - } - - return tr::lng_dialogs_text_media( - tr::now, - lt_media_part, - tr::lng_dialogs_text_media_wrapped( - tr::now, - lt_media, - attachType), - lt_caption, - caption); + caption, + Ui::Text::WithEntities); } [[nodiscard]] QImage PreparePreviewImage( @@ -352,15 +335,7 @@ bool Media::canBeGrouped() const { } ItemPreview Media::toPreview(ToPreviewOptions options) const { - auto result = notificationText(); - auto text = result.isEmpty() - ? QString() - : textcmdLink( - 1, - TextUtilities::Clean( - std::move(result), - !options.ignoreSpoilers)); - return { .text = std::move(text) }; + return { .text = notificationText() }; } bool Media::hasReplyPreview() const { @@ -428,9 +403,8 @@ std::unique_ptr Media::createView( ItemPreview Media::toGroupPreview( const HistoryItemsList &items, ToPreviewOptions options) const { - const auto genericText = textcmdLink( - 1, - TextUtilities::Clean(tr::lng_in_dlg_album(tr::now))); + const auto genericText = Ui::Text::PlainLink( + tr::lng_in_dlg_album(tr::now)); auto result = ItemPreview(); auto loadingContext = std::vector(); for (const auto &item : items) { @@ -452,17 +426,17 @@ ItemPreview Media::toGroupPreview( if (single.loadingContext.has_value()) { loadingContext.push_back(std::move(single.loadingContext)); } - const auto original = item->originalText().text; - if (!original.isEmpty()) { - if (result.text.isEmpty()) { - result.text = TextUtilities::Clean(original); + const auto original = item->originalText(); + if (!original.text.isEmpty()) { + if (result.text.text.isEmpty()) { + result.text = original; } else { result.text = genericText; } } } } - if (result.text.isEmpty()) { + if (result.text.text.isEmpty()) { result.text = genericText; } if (!loadingContext.empty()) { @@ -536,10 +510,10 @@ bool MediaPhoto::replyPreviewLoaded() const { return _photo->replyPreviewLoaded(); } -QString MediaPhoto::notificationText() const { +TextWithEntities MediaPhoto::notificationText() const { return WithCaptionNotificationText( tr::lng_in_dlg_photo(tr::now), - TextUtilities::TextWithSpoilerCommands(parent()->originalText())); + parent()->originalText()); } ItemPreview MediaPhoto::toPreview(ToPreviewOptions options) const { @@ -569,13 +543,11 @@ ItemPreview MediaPhoto::toPreview(ToPreviewOptions options) const { } const auto type = tr::lng_in_dlg_photo(tr::now); const auto caption = options.hideCaption - ? QString() - : options.ignoreSpoilers - ? parent()->originalText().text - : TextUtilities::TextWithSpoilerCommands(parent()->originalText()); + ? TextWithEntities() + : parent()->originalText(); const auto hasMiniImages = !images.empty(); return { - .text = WithCaptionDialogsText(type, caption, hasMiniImages, options), + .text = WithCaptionNotificationText(type, caption, hasMiniImages), .images = std::move(images), .loadingContext = std::move(context), }; @@ -791,23 +763,22 @@ ItemPreview MediaFile::toPreview(ToPreviewOptions options) const { return tr::lng_in_dlg_file(tr::now); }(); const auto caption = options.hideCaption - ? QString() - : options.ignoreSpoilers - ? parent()->originalText().text - : TextUtilities::TextWithSpoilerCommands(parent()->originalText()); + ? TextWithEntities() + : parent()->originalText(); const auto hasMiniImages = !images.empty(); return { - .text = WithCaptionDialogsText(type, caption, hasMiniImages, options), + .text = WithCaptionNotificationText(type, caption, hasMiniImages), .images = std::move(images), .loadingContext = std::move(context), }; } -QString MediaFile::notificationText() const { +TextWithEntities MediaFile::notificationText() const { if (const auto sticker = _document->sticker()) { - return _emoji.isEmpty() + const auto text = _emoji.isEmpty() ? tr::lng_in_dlg_sticker(tr::now) : tr::lng_in_dlg_sticker_emoji(tr::now, lt_emoji, _emoji); + return Ui::Text::PlainLink(text); } const auto type = [&] { if (_document->isVideoMessage()) { @@ -825,9 +796,7 @@ QString MediaFile::notificationText() const { } return tr::lng_in_dlg_file(tr::now); }(); - return WithCaptionNotificationText( - type, - TextUtilities::TextWithSpoilerCommands(parent()->originalText())); + return WithCaptionNotificationText(type, parent()->originalText()); } QString MediaFile::pinnedTextSubstring() const { @@ -1035,8 +1004,8 @@ const SharedContact *MediaContact::sharedContact() const { return &_contact; } -QString MediaContact::notificationText() const { - return tr::lng_in_dlg_contact(tr::now); +TextWithEntities MediaContact::notificationText() const { + return tr::lng_in_dlg_contact(tr::now, Ui::Text::WithEntities); } QString MediaContact::pinnedTextSubstring() const { @@ -1124,13 +1093,16 @@ Data::CloudImage *MediaLocation::location() const { ItemPreview MediaLocation::toPreview(ToPreviewOptions options) const { const auto type = tr::lng_maps_point(tr::now); const auto hasMiniImages = false; + const auto text = TextWithEntities{ .text = _title }; return { - .text = WithCaptionDialogsText(type, _title, hasMiniImages, options), + .text = WithCaptionNotificationText(type, text, hasMiniImages), }; } -QString MediaLocation::notificationText() const { - return WithCaptionNotificationText(tr::lng_maps_point(tr::now), _title); +TextWithEntities MediaLocation::notificationText() const { + return WithCaptionNotificationText( + tr::lng_maps_point(tr::now), + { .text = _title}); } QString MediaLocation::pinnedTextSubstring() const { @@ -1194,7 +1166,7 @@ const Call *MediaCall::call() const { return &_call; } -QString MediaCall::notificationText() const { +TextWithEntities MediaCall::notificationText() const { auto result = Text(parent(), _call.finishReason, _call.video); if (_call.duration > 0) { result = tr::lng_call_type_and_duration( @@ -1204,7 +1176,7 @@ QString MediaCall::notificationText() const { lt_duration, Ui::FormatDurationWords(_call.duration)); } - return result; + return { .text = result }; } QString MediaCall::pinnedTextSubstring() const { @@ -1212,8 +1184,7 @@ QString MediaCall::pinnedTextSubstring() const { } TextForMimeData MediaCall::clipboardText() const { - return TextForMimeData::Simple( - qstr("[ ") + notificationText() + qstr(" ]")); + return { .rich = notificationText() }; } bool MediaCall::allowsForward() const { @@ -1321,8 +1292,8 @@ ItemPreview MediaWebPage::toPreview(ToPreviewOptions options) const { return { .text = notificationText() }; } -QString MediaWebPage::notificationText() const { - return TextUtilities::TextWithSpoilerCommands(parent()->originalText()); +TextWithEntities MediaWebPage::notificationText() const { + return parent()->originalText(); } QString MediaWebPage::pinnedTextSubstring() const { @@ -1390,7 +1361,7 @@ bool MediaGame::replyPreviewLoaded() const { return true; } -QString MediaGame::notificationText() const { +TextWithEntities MediaGame::notificationText() const { // Add a game controller emoji before game title. auto result = QString(); result.reserve(_game->title.size() + 3); @@ -1401,7 +1372,7 @@ QString MediaGame::notificationText() const { ).append( QChar(' ') ).append(_game->title); - return result; + return { .text = result }; } GameData *MediaGame::game() const { @@ -1496,8 +1467,8 @@ bool MediaInvoice::replyPreviewLoaded() const { return true; } -QString MediaInvoice::notificationText() const { - return _invoice.title; +TextWithEntities MediaInvoice::notificationText() const { + return { .text = _invoice.title }; } QString MediaInvoice::pinnedTextSubstring() const { @@ -1541,8 +1512,8 @@ PollData *MediaPoll::poll() const { return _poll; } -QString MediaPoll::notificationText() const { - return _poll->question; +TextWithEntities MediaPoll::notificationText() const { + return Ui::Text::PlainLink(_poll->question); } QString MediaPoll::pinnedTextSubstring() const { @@ -1616,16 +1587,16 @@ bool MediaDice::allowsRevoke(TimeId now) const { return (now >= parent()->date() + kFastRevokeRestriction); } -QString MediaDice::notificationText() const { - return _emoji; +TextWithEntities MediaDice::notificationText() const { + return { .text = _emoji }; } QString MediaDice::pinnedTextSubstring() const { - return QChar(171) + notificationText() + QChar(187); + return QChar(171) + notificationText().text + QChar(187); } TextForMimeData MediaDice::clipboardText() const { - return { notificationText() }; + return { .rich = notificationText() }; } bool MediaDice::forceForwardedInfo() const { diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index fdf42e07e..34110473f 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -100,7 +100,7 @@ public: // Returns text with link-start and link-end commands for service-color highlighting. // Example: "[link1-start]You:[link1-end] [link1-start]Photo,[link1-end] caption text" virtual ItemPreview toPreview(ToPreviewOptions way) const; - virtual QString notificationText() const = 0; + virtual TextWithEntities notificationText() const = 0; virtual QString pinnedTextSubstring() const = 0; virtual TextForMimeData clipboardText() const = 0; virtual bool allowsForward() const; @@ -161,7 +161,7 @@ public: Image *replyPreview() const override; bool replyPreviewLoaded() const override; ItemPreview toPreview(ToPreviewOptions options) const override; - QString notificationText() const override; + TextWithEntities notificationText() const override; QString pinnedTextSubstring() const override; TextForMimeData clipboardText() const override; bool allowsEditCaption() const override; @@ -199,7 +199,7 @@ public: Image *replyPreview() const override; bool replyPreviewLoaded() const override; ItemPreview toPreview(ToPreviewOptions options) const override; - QString notificationText() const override; + TextWithEntities notificationText() const override; QString pinnedTextSubstring() const override; TextForMimeData clipboardText() const override; bool allowsEditCaption() const override; @@ -234,7 +234,7 @@ public: std::unique_ptr clone(not_null parent) override; const SharedContact *sharedContact() const override; - QString notificationText() const override; + TextWithEntities notificationText() const override; QString pinnedTextSubstring() const override; TextForMimeData clipboardText() const override; @@ -265,7 +265,7 @@ public: Data::CloudImage *location() const override; ItemPreview toPreview(ToPreviewOptions options) const override; - QString notificationText() const override; + TextWithEntities notificationText() const override; QString pinnedTextSubstring() const override; TextForMimeData clipboardText() const override; @@ -292,7 +292,7 @@ public: std::unique_ptr clone(not_null parent) override; const Call *call() const override; - QString notificationText() const override; + TextWithEntities notificationText() const override; QString pinnedTextSubstring() const override; TextForMimeData clipboardText() const override; bool allowsForward() const override; @@ -331,7 +331,7 @@ public: Image *replyPreview() const override; bool replyPreviewLoaded() const override; ItemPreview toPreview(ToPreviewOptions options) const override; - QString notificationText() const override; + TextWithEntities notificationText() const override; QString pinnedTextSubstring() const override; TextForMimeData clipboardText() const override; bool allowsEdit() const override; @@ -361,7 +361,7 @@ public: bool hasReplyPreview() const override; Image *replyPreview() const override; bool replyPreviewLoaded() const override; - QString notificationText() const override; + TextWithEntities notificationText() const override; QString pinnedTextSubstring() const override; TextForMimeData clipboardText() const override; QString errorTextForForward(not_null peer) const override; @@ -396,7 +396,7 @@ public: bool hasReplyPreview() const override; Image *replyPreview() const override; bool replyPreviewLoaded() const override; - QString notificationText() const override; + TextWithEntities notificationText() const override; QString pinnedTextSubstring() const override; TextForMimeData clipboardText() const override; @@ -423,7 +423,7 @@ public: PollData *poll() const override; - QString notificationText() const override; + TextWithEntities notificationText() const override; QString pinnedTextSubstring() const override; TextForMimeData clipboardText() const override; QString errorTextForForward(not_null peer) const override; @@ -450,7 +450,7 @@ public: [[nodiscard]] int value() const; bool allowsRevoke(TimeId now) const override; - QString notificationText() const override; + TextWithEntities notificationText() const override; QString pinnedTextSubstring() const override; TextForMimeData clipboardText() const override; bool forceForwardedInfo() const override; diff --git a/Telegram/SourceFiles/data/data_replies_list.cpp b/Telegram/SourceFiles/data/data_replies_list.cpp index b1e3e9467..b0391e0e6 100644 --- a/Telegram/SourceFiles/data/data_replies_list.cpp +++ b/Telegram/SourceFiles/data/data_replies_list.cpp @@ -32,7 +32,7 @@ constexpr auto kMessagesPerPage = 50; history->nextNonHistoryEntryId(), MessageFlag::FakeHistoryItem, date, - HistoryService::PreparedText{ text }); + HistoryService::PreparedText{ { .text = text } }); } } // namespace @@ -287,7 +287,7 @@ void RepliesList::injectRootDivider( text()); } else if (_dividerWithComments != withComments) { _dividerWithComments = withComments; - _divider->setServiceText(HistoryService::PreparedText{ text() }); + _divider->setServiceText(HistoryService::PreparedText{ { text() } }); } slice->ids.push_back(_divider->fullId()); } diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp index 06c9998c4..0f814de79 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_item_preview.h" #include "main/main_session.h" #include "ui/text/text_options.h" +#include "ui/text/text_utilities.h" #include "ui/image/image.h" #include "lang/lang_keys.h" #include "styles/style_dialogs.h" @@ -109,17 +110,25 @@ void MessageView::paint( options.existing = &_imagesCache; auto preview = item->toPreview(options); if (!preview.images.empty() && preview.imagesInTextPosition > 0) { - _senderCache.setText( + auto sender = ::Ui::Text::Mid( + preview.text, + 0, + preview.imagesInTextPosition); + TextUtilities::Trim(sender); + _senderCache.setMarkedText( st::dialogsTextStyle, - preview.text.mid(0, preview.imagesInTextPosition).trimmed(), + std::move(sender), DialogTextOptions()); - preview.text = preview.text.mid(preview.imagesInTextPosition); + preview.text = ::Ui::Text::Mid( + preview.text, + preview.imagesInTextPosition); } else { _senderCache = { st::dialogsTextWidthMin }; } - _textCache.setText( + TextUtilities::Trim(preview.text); + _textCache.setMarkedText( st::dialogsTextStyle, - preview.text.trimmed(), + preview.text, DialogTextOptions()); _textCachedFor = item; _imagesCache = std::move(preview.images); @@ -191,18 +200,24 @@ void MessageView::paint( HistoryView::ItemPreview PreviewWithSender( HistoryView::ItemPreview &&preview, - const QString &sender) { - auto textWithOffset = tr::lng_dialogs_text_with_from( + const TextWithEntities &sender) { + const auto textWithOffset = tr::lng_dialogs_text_with_from( + tr::now, + lt_from_part, + sender.text, + lt_message, + preview.text.text, + TextWithTagOffset::FromString); + preview.text = tr::lng_dialogs_text_with_from( tr::now, lt_from_part, sender, lt_message, std::move(preview.text), - TextWithTagOffset::FromString); - preview.text = std::move(textWithOffset.text); + Ui::Text::WithEntities); preview.imagesInTextPosition = (textWithOffset.offset < 0) ? 0 - : textWithOffset.offset + sender.size(); + : textWithOffset.offset + sender.text.size(); return std::move(preview); } diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h index 54328ec83..a926c5e6f 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h @@ -59,6 +59,6 @@ private: [[nodiscard]] HistoryView::ItemPreview PreviewWithSender( HistoryView::ItemPreview &&preview, - const QString &sender); + const TextWithEntities &sender); } // namespace Dialogs::Ui diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp index cf96d1eb9..457b465d5 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp @@ -319,11 +319,11 @@ QString GenerateInviteLinkText(const MTPExportedChatInvite &data) { ) : label; } -QString GenerateInviteLinkLink(const MTPExportedChatInvite &data) { +TextWithEntities GenerateInviteLinkLink(const MTPExportedChatInvite &data) { const auto text = GenerateInviteLinkText(data); return text.endsWith(Ui::kQEllipsis) - ? text - : textcmdLink(InternalInviteLinkUrl(data), text); + ? TextWithEntities{ .text = text } + : Ui::Text::Link(text, InternalInviteLinkUrl(data)); } TextWithEntities GenerateInviteLinkChangeText( @@ -669,12 +669,12 @@ void GenerateItems( const auto fromName = from->name; const auto fromLink = from->createOpenLink(); - const auto fromLinkText = textcmdLink(1, fromName); + const auto fromLinkText = Ui::Text::Link(fromName, {}); const auto addSimpleServiceMessage = [&]( - const QString &text, + const TextWithEntities &text, PhotoData *photo = nullptr) { - auto message = HistoryService::PreparedText { text }; + auto message = HistoryService::PreparedText{ text }; message.links.push_back(fromLink); addPart(history->makeServiceMessage( history->nextNonHistoryEntryId(), @@ -693,8 +693,9 @@ void GenerateItems( lt_from, fromLinkText, lt_title, - qs(action.vnew_value())); - addSimpleServiceMessage(text); + { .text = qs(action.vnew_value()) }, + Ui::Text::WithEntities); + addSimpleServiceMessage(std::move(text)); }; const auto makeSimpleTextMessage = [&](TextWithEntities &&text) { @@ -731,7 +732,7 @@ void GenerateItems( : (newValue.isEmpty() ? tr::lng_admin_log_removed_description_channel : tr::lng_admin_log_changed_description_channel) - )(tr::now, lt_from, fromLinkText); + )(tr::now, lt_from, fromLinkText, Ui::Text::WithEntities); addSimpleServiceMessage(text); const auto body = makeSimpleTextMessage( @@ -756,7 +757,7 @@ void GenerateItems( : (newValue.isEmpty() ? tr::lng_admin_log_removed_link_channel : tr::lng_admin_log_changed_link_channel) - )(tr::now, lt_from, fromLinkText); + )(tr::now, lt_from, fromLinkText, Ui::Text::WithEntities); addSimpleServiceMessage(text); const auto body = makeSimpleTextMessage(newValue.isEmpty() @@ -784,7 +785,8 @@ void GenerateItems( : tr::lng_admin_log_changed_photo_channel)( tr::now, lt_from, - fromLinkText); + fromLinkText, + Ui::Text::WithEntities); addSimpleServiceMessage(text, photo); }, [&](const MTPDphotoEmpty &data) { const auto text = (channel->isMegagroup() @@ -792,7 +794,8 @@ void GenerateItems( : tr::lng_admin_log_removed_photo_channel)( tr::now, lt_from, - fromLinkText); + fromLinkText, + Ui::Text::WithEntities); addSimpleServiceMessage(text); }); }; @@ -801,16 +804,24 @@ void GenerateItems( const auto enabled = (action.vnew_value().type() == mtpc_boolTrue); const auto text = (enabled ? tr::lng_admin_log_invites_enabled - : tr::lng_admin_log_invites_disabled); - addSimpleServiceMessage(text(tr::now, lt_from, fromLinkText)); + : tr::lng_admin_log_invites_disabled)( + tr::now, + lt_from, + fromLinkText, + Ui::Text::WithEntities); + addSimpleServiceMessage(text); }; const auto createToggleSignatures = [&](const LogSign &action) { const auto enabled = (action.vnew_value().type() == mtpc_boolTrue); const auto text = (enabled ? tr::lng_admin_log_signatures_enabled - : tr::lng_admin_log_signatures_disabled); - addSimpleServiceMessage(text(tr::now, lt_from, fromLinkText)); + : tr::lng_admin_log_signatures_disabled)( + tr::now, + lt_from, + fromLinkText, + Ui::Text::WithEntities); + addSimpleServiceMessage(text); }; const auto createUpdatePinned = [&](const LogPin &action) { @@ -821,7 +832,8 @@ void GenerateItems( : tr::lng_admin_log_unpinned_message)( tr::now, lt_from, - fromLinkText); + fromLinkText, + Ui::Text::WithEntities); addSimpleServiceMessage(text); const auto detachExistingItem = false; @@ -836,7 +848,8 @@ void GenerateItems( const auto text = tr::lng_admin_log_unpinned_message( tr::now, lt_from, - fromLinkText); + fromLinkText, + Ui::Text::WithEntities); addSimpleServiceMessage(text); }); }; @@ -854,7 +867,8 @@ void GenerateItems( : tr::lng_admin_log_edited_caption)( tr::now, lt_from, - fromLinkText); + fromLinkText, + Ui::Text::WithEntities); addSimpleServiceMessage(text); auto oldValue = ExtractEditedText( @@ -885,7 +899,8 @@ void GenerateItems( const auto text = tr::lng_admin_log_deleted_message( tr::now, lt_from, - fromLinkText); + fromLinkText, + Ui::Text::WithEntities); addSimpleServiceMessage(text); const auto detachExistingItem = false; @@ -901,15 +916,23 @@ void GenerateItems( const auto createParticipantJoin = [&]() { const auto text = (channel->isMegagroup() ? tr::lng_admin_log_participant_joined - : tr::lng_admin_log_participant_joined_channel); - addSimpleServiceMessage(text(tr::now, lt_from, fromLinkText)); + : tr::lng_admin_log_participant_joined_channel)( + tr::now, + lt_from, + fromLinkText, + Ui::Text::WithEntities); + addSimpleServiceMessage(text); }; const auto createParticipantLeave = [&]() { const auto text = (channel->isMegagroup() ? tr::lng_admin_log_participant_left - : tr::lng_admin_log_participant_left_channel); - addSimpleServiceMessage(text(tr::now, lt_from, fromLinkText)); + : tr::lng_admin_log_participant_left_channel)( + tr::now, + lt_from, + fromLinkText, + Ui::Text::WithEntities); + addSimpleServiceMessage(text); }; const auto createParticipantInvite = [&](const LogInvite &action) { @@ -947,7 +970,8 @@ void GenerateItems( const auto text = tr::lng_admin_log_removed_stickers_group( tr::now, lt_from, - fromLinkText); + fromLinkText, + Ui::Text::WithEntities); addSimpleServiceMessage(text); } else { const auto text = tr::lng_admin_log_changed_stickers_group( @@ -955,9 +979,10 @@ void GenerateItems( lt_from, fromLinkText, lt_sticker_set, - textcmdLink( - 2, - tr::lng_admin_log_changed_stickers_set(tr::now))); + Ui::Text::Link( + tr::lng_admin_log_changed_stickers_set(tr::now), + {}), + Ui::Text::WithEntities); const auto setLink = std::make_shared([=]( ClickContext context) { const auto my = context.other.value(); @@ -986,8 +1011,12 @@ void GenerateItems( const auto hidden = (action.vnew_value().type() == mtpc_boolTrue); const auto text = (hidden ? tr::lng_admin_log_history_made_hidden - : tr::lng_admin_log_history_made_visible); - addSimpleServiceMessage(text(tr::now, lt_from, fromLinkText)); + : tr::lng_admin_log_history_made_visible)( + tr::now, + lt_from, + fromLinkText, + Ui::Text::WithEntities); + addSimpleServiceMessage(text); }; const auto createDefaultBannedRights = [&]( @@ -1003,7 +1032,8 @@ void GenerateItems( const auto text = tr::lng_admin_log_stopped_poll( tr::now, lt_from, - fromLinkText); + fromLinkText, + Ui::Text::WithEntities); addSimpleServiceMessage(text); const auto detachExistingItem = false; @@ -1025,7 +1055,8 @@ void GenerateItems( : tr::lng_admin_log_removed_linked_channel)( tr::now, lt_from, - fromLinkText); + fromLinkText, + Ui::Text::WithEntities); addSimpleServiceMessage(text); } else { const auto text = (broadcast @@ -1035,7 +1066,8 @@ void GenerateItems( lt_from, fromLinkText, lt_chat, - textcmdLink(2, now->name)); + Ui::Text::Link(now->name, {}), + Ui::Text::WithEntities); const auto chatLink = std::make_shared([=] { Ui::showPeerHistory(now, ShowAtUnreadMsgId); }); @@ -1056,24 +1088,26 @@ void GenerateItems( const auto address = qs(data.vaddress()); const auto link = data.vgeo_point().match([&]( const MTPDgeoPoint &data) { - return textcmdLink( - LocationClickHandler::Url(Data::LocationPoint(data)), - address); + return Ui::Text::Link( + address, + LocationClickHandler::Url(Data::LocationPoint(data))); }, [&](const MTPDgeoPointEmpty &) { - return address; + return TextWithEntities{ .text = address }; }); const auto text = tr::lng_admin_log_changed_location_chat( tr::now, lt_from, fromLinkText, lt_address, - link); + link, + Ui::Text::WithEntities); addSimpleServiceMessage(text); }, [&](const MTPDchannelLocationEmpty &) { const auto text = tr::lng_admin_log_removed_location_chat( tr::now, lt_from, - fromLinkText); + fromLinkText, + Ui::Text::WithEntities); addSimpleServiceMessage(text); }); }; @@ -1094,13 +1128,15 @@ void GenerateItems( lt_from, fromLinkText, lt_duration, - duration); + { .text = duration }, + Ui::Text::WithEntities); addSimpleServiceMessage(text); } else { const auto text = tr::lng_admin_log_removed_slow_mode( tr::now, lt_from, - fromLinkText); + fromLinkText, + Ui::Text::WithEntities); addSimpleServiceMessage(text); } }; @@ -1111,7 +1147,8 @@ void GenerateItems( : tr::lng_admin_log_started_group_call)( tr::now, lt_from, - fromLinkText); + fromLinkText, + Ui::Text::WithEntities); addSimpleServiceMessage(text); }; @@ -1121,7 +1158,8 @@ void GenerateItems( : tr::lng_admin_log_discarded_group_call)( tr::now, lt_from, - fromLinkText); + fromLinkText, + Ui::Text::WithEntities); addSimpleServiceMessage(text); }; @@ -1133,7 +1171,7 @@ void GenerateItems( }; const auto addServiceMessageWithLink = [&]( - const QString &text, + const TextWithEntities &text, const ClickHandlerPtr &link) { auto message = HistoryService::PreparedText{ text }; message.links.push_back(fromLink); @@ -1150,9 +1188,9 @@ void GenerateItems( const auto participantPeer = groupCallParticipantPeer( data.vparticipant()); const auto participantPeerLink = participantPeer->createOpenLink(); - const auto participantPeerLinkText = textcmdLink( - 2, - participantPeer->name); + const auto participantPeerLinkText = Ui::Text::Link( + participantPeer->name, + {}); const auto text = (broadcast ? tr::lng_admin_log_muted_participant_channel : tr::lng_admin_log_muted_participant)( @@ -1160,7 +1198,8 @@ void GenerateItems( lt_from, fromLinkText, lt_user, - participantPeerLinkText); + participantPeerLinkText, + Ui::Text::WithEntities); addServiceMessageWithLink(text, participantPeerLink); }; @@ -1168,9 +1207,9 @@ void GenerateItems( const auto participantPeer = groupCallParticipantPeer( data.vparticipant()); const auto participantPeerLink = participantPeer->createOpenLink(); - const auto participantPeerLinkText = textcmdLink( - 2, - participantPeer->name); + const auto participantPeerLinkText = Ui::Text::Link( + participantPeer->name, + {}); const auto text = (broadcast ? tr::lng_admin_log_unmuted_participant_channel : tr::lng_admin_log_unmuted_participant)( @@ -1178,7 +1217,8 @@ void GenerateItems( lt_from, fromLinkText, lt_user, - participantPeerLinkText); + participantPeerLinkText, + Ui::Text::WithEntities); addServiceMessageWithLink(text, participantPeerLink); }; @@ -1193,12 +1233,13 @@ void GenerateItems( : tr::lng_admin_log_allowed_unmute_self))( tr::now, lt_from, - fromLinkText); + fromLinkText, + Ui::Text::WithEntities); addSimpleServiceMessage(text); }; const auto addInviteLinkServiceMessage = [&]( - const QString &text, + const TextWithEntities &text, const MTPExportedChatInvite &data, ClickHandlerPtr additional = nullptr) { auto message = HistoryService::PreparedText{ text }; @@ -1230,7 +1271,8 @@ void GenerateItems( lt_from, fromLinkText, lt_link, - GenerateInviteLinkLink(data.vinvite())), + GenerateInviteLinkLink(data.vinvite()), + Ui::Text::WithEntities), data.vinvite()); }; @@ -1241,7 +1283,8 @@ void GenerateItems( lt_from, fromLinkText, lt_link, - GenerateInviteLinkLink(data.vinvite())), + GenerateInviteLinkLink(data.vinvite()), + Ui::Text::WithEntities), data.vinvite()); }; @@ -1252,7 +1295,8 @@ void GenerateItems( lt_from, fromLinkText, lt_link, - GenerateInviteLinkLink(data.vinvite())), + GenerateInviteLinkLink(data.vinvite()), + Ui::Text::WithEntities), data.vinvite()); }; @@ -1267,9 +1311,9 @@ void GenerateItems( const auto participantPeer = groupCallParticipantPeer( data.vparticipant()); const auto participantPeerLink = participantPeer->createOpenLink(); - const auto participantPeerLinkText = textcmdLink( - 2, - participantPeer->name); + const auto participantPeerLinkText = Ui::Text::Link( + participantPeer->name, + {}); const auto volume = data.vparticipant().match([&]( const MTPDgroupCallParticipant &data) { return data.vvolume().value_or(10000); @@ -1278,27 +1322,29 @@ void GenerateItems( auto text = (broadcast ? tr::lng_admin_log_participant_volume_channel : tr::lng_admin_log_participant_volume)( - tr::now, - lt_from, - fromLinkText, - lt_user, - participantPeerLinkText, - lt_percent, - volumeText); + tr::now, + lt_from, + fromLinkText, + lt_user, + participantPeerLinkText, + lt_percent, + { .text = volumeText }, + Ui::Text::WithEntities); addServiceMessageWithLink(text, participantPeerLink); }; const auto createChangeHistoryTTL = [&](const LogTTL &data) { const auto was = data.vprev_value().v; const auto now = data.vnew_value().v; - const auto wrap = [](int duration) { - return (duration == 5) + const auto wrap = [](int duration) -> TextWithEntities { + const auto text = (duration == 5) ? u"5 seconds"_q : (duration < 2 * 86400) ? tr::lng_manage_messages_ttl_after1(tr::now) : (duration < 8 * 86400) ? tr::lng_manage_messages_ttl_after2(tr::now) : tr::lng_manage_messages_ttl_after3(tr::now); + return { .text = text }; }; const auto text = !was ? tr::lng_admin_log_messages_ttl_set( @@ -1306,14 +1352,16 @@ void GenerateItems( lt_from, fromLinkText, lt_duration, - wrap(now)) + wrap(now), + Ui::Text::WithEntities) : !now ? tr::lng_admin_log_messages_ttl_removed( tr::now, lt_from, fromLinkText, lt_duration, - wrap(was)) + wrap(was), + Ui::Text::WithEntities) : tr::lng_admin_log_messages_ttl_changed( tr::now, lt_from, @@ -1321,7 +1369,8 @@ void GenerateItems( lt_previous, wrap(was), lt_duration, - wrap(now)); + wrap(now), + Ui::Text::WithEntities); addSimpleServiceMessage(text); }; @@ -1332,7 +1381,6 @@ void GenerateItems( ? tr::lng_admin_log_participant_approved_by_link : tr::lng_admin_log_participant_approved_by_link_channel); const auto linkText = GenerateInviteLinkLink(data.vinvite()); - const auto adminIndex = linkText.endsWith(Ui::kQEllipsis) ? 2 : 3; addInviteLinkServiceMessage( text( tr::now, @@ -1341,7 +1389,8 @@ void GenerateItems( lt_link, linkText, lt_user, - textcmdLink(adminIndex, user->name)), + Ui::Text::Link(user->name, {}), + Ui::Text::WithEntities), data.vinvite(), user->createOpenLink()); }; @@ -1350,15 +1399,20 @@ void GenerateItems( const auto disabled = (data.vnew_value().type() == mtpc_boolTrue); const auto text = (disabled ? tr::lng_admin_log_forwards_disabled - : tr::lng_admin_log_forwards_enabled); - addSimpleServiceMessage(text(tr::now, lt_from, fromLinkText)); + : tr::lng_admin_log_forwards_enabled)( + tr::now, + lt_from, + fromLinkText, + Ui::Text::WithEntities); + addSimpleServiceMessage(text); }; const auto createSendMessage = [&](const LogActionSendMessage &data) { const auto text = tr::lng_admin_log_sent_message( tr::now, lt_from, - fromLinkText); + fromLinkText, + Ui::Text::WithEntities); addSimpleServiceMessage(text); const auto detachExistingItem = false; @@ -1371,14 +1425,25 @@ void GenerateItems( ExtractSentDate(data.vmessage())); }; - const auto createChangeAvailableReactions = [&](const LogEventActionChangeAvailableReactions &data) { + const auto createChangeAvailableReactions = [&]( + const LogEventActionChangeAvailableReactions &data) { auto list = QStringList(); for (const auto &emoji : data.vnew_value().v) { list.append(qs(emoji)); } const auto text = list.isEmpty() - ? tr::lng_admin_log_reactions_disabled(tr::now, lt_from, fromLinkText) - : tr::lng_admin_log_reactions_updated(tr::now, lt_from, fromLinkText, lt_emoji, list.join(", ")); + ? tr::lng_admin_log_reactions_disabled( + tr::now, + lt_from, + fromLinkText, + Ui::Text::WithEntities) + : tr::lng_admin_log_reactions_updated( + tr::now, + lt_from, + fromLinkText, + lt_emoji, + { .text = list.join(", ") }, + Ui::Text::WithEntities); addSimpleServiceMessage(text); }; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index c2dd5b6e7..141501212 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/ripple_animation.h" #include "ui/text/text_isolated_emoji.h" #include "ui/text/text_options.h" +#include "ui/text/text_utilities.h" #include "storage/file_upload.h" #include "storage/storage_facade.h" #include "storage/storage_shared_media.h" @@ -1074,23 +1075,20 @@ bool HistoryItem::isEmpty() const { && !Has(); } -QString HistoryItem::notificationText() const { +TextWithEntities HistoryItem::notificationText() const { const auto result = [&] { if (_media && !isService()) { return _media->notificationText(); } else if (!emptyText()) { - return TextUtilities::TextWithSpoilerCommands( - _text.toTextWithEntities()); + return _text.toTextWithEntities(); } - return QString(); + return TextWithEntities(); }(); - return (result.size() <= kNotificationTextLimit) - ? result - : TextUtilities::CutTextWithCommands( - result, - kNotificationTextLimit, - textcmdStartSpoiler(), - textcmdStopSpoiler()); + if (result.text.size() <= kNotificationTextLimit) { + return result; + } + return Ui::Text::Mid(result, 0, kNotificationTextLimit).append( + Ui::kQEllipsis); } ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const { @@ -1099,12 +1097,7 @@ ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const { return _media->toPreview(options); } else if (!emptyText()) { return { - .text = TextUtilities::Clean( - options.ignoreSpoilers - ? _text.toString() - : TextUtilities::TextWithSpoilerCommands( - _text.toTextWithEntities()), - !options.ignoreSpoilers), + .text = _text.toTextWithEntities() }; } return {}; @@ -1140,16 +1133,12 @@ ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const { if (!sender) { return result; } - const auto fromWrapped = textcmdLink( - 1, - tr::lng_dialogs_text_from_wrapped( - tr::now, - lt_from, - TextUtilities::Clean(*sender))); + const auto fromWrapped = Ui::Text::PlainLink( + tr::lng_dialogs_text_from_wrapped(tr::now, lt_from, *sender)); return Dialogs::Ui::PreviewWithSender(std::move(result), fromWrapped); } -QString HistoryItem::inReplyText() const { +TextWithEntities HistoryItem::inReplyText() const { return toPreview({ .hideSender = true, .generateImages = false, @@ -1274,7 +1263,7 @@ not_null HistoryItem::Create( data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0)); } else if (checked == MediaCheckResult::Empty) { const auto text = HistoryService::PreparedText{ - tr::lng_message_empty(tr::now) + tr::lng_message_empty(tr::now, Ui::Text::WithEntities) }; return history->makeServiceMessage( id, @@ -1293,7 +1282,7 @@ not_null HistoryItem::Create( return history->makeServiceMessage(id, data, localFlags); }, [&](const MTPDmessageEmpty &data) -> HistoryItem* { const auto text = HistoryService::PreparedText{ - tr::lng_message_empty(tr::now) + tr::lng_message_empty(tr::now, Ui::Text::WithEntities) }; return history->makeServiceMessage(id, localFlags, TimeId(0), text); }); diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index dae4f898d..c5d6cabcb 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -290,7 +290,7 @@ public: [[nodiscard]] virtual QString notificationHeader() const { return QString(); } - [[nodiscard]] virtual QString notificationText() const; + [[nodiscard]] virtual TextWithEntities notificationText() const; using ToPreviewOptions = HistoryView::ToPreviewOptions; using ItemPreview = HistoryView::ItemPreview; @@ -299,7 +299,7 @@ public: // Example: "[link1-start]You:[link1-end] [link1-start]Photo,[link1-end] caption text" [[nodiscard]] virtual ItemPreview toPreview( ToPreviewOptions options) const; - [[nodiscard]] virtual QString inReplyText() const; + [[nodiscard]] virtual TextWithEntities inReplyText() const; [[nodiscard]] virtual Ui::Text::IsolatedEmoji isolatedEmoji() const; [[nodiscard]] virtual TextWithEntities originalText() const { return TextWithEntities(); diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 9382813b6..c930b45cc 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/image/image.h" #include "ui/toast/toast.h" #include "ui/text/text_options.h" +#include "ui/text/text_utilities.h" #include "ui/chat/chat_style.h" #include "ui/chat/chat_theme.h" #include "history/history.h" @@ -106,20 +107,21 @@ HiddenSenderInfo::HiddenSenderInfo(const QString &name, bool external) } void HistoryMessageForwarded::create(const HistoryMessageVia *via) const { - auto phrase = QString(); + auto phrase = TextWithEntities(); const auto fromChannel = originalSender && originalSender->isChannel() && !originalSender->isMegagroup(); - const auto name = originalSender - ? originalSender->name - : hiddenSenderInfo->name; + const auto name = TextWithEntities{ + .text = originalSender ? originalSender->name : hiddenSenderInfo->name + }; if (!originalAuthor.isEmpty()) { phrase = tr::lng_forwarded_signed( tr::now, lt_channel, name, lt_user, - originalAuthor); + { .text = originalAuthor }, + Ui::Text::WithEntities); } else { phrase = name; } @@ -128,16 +130,18 @@ void HistoryMessageForwarded::create(const HistoryMessageVia *via) const { phrase = tr::lng_forwarded_channel_via( tr::now, lt_channel, - textcmdLink(1, phrase), + Ui::Text::Link(phrase.text, QString()), // Link 1. lt_inline_bot, - textcmdLink(2, '@' + via->bot->username)); + Ui::Text::Link('@' + via->bot->username, {}), // Link 2. + Ui::Text::WithEntities); } else { phrase = tr::lng_forwarded_via( tr::now, lt_user, - textcmdLink(1, phrase), + Ui::Text::Link(phrase.text, QString()), // Link 1. lt_inline_bot, - textcmdLink(2, '@' + via->bot->username)); + Ui::Text::Link('@' + via->bot->username, {}), // Link 2. + Ui::Text::WithEntities); } } else { if (fromChannel || !psaType.isEmpty()) { @@ -145,19 +149,29 @@ void HistoryMessageForwarded::create(const HistoryMessageVia *via) const { ? QString() : Lang::GetNonDefaultValue( kPsaForwardedPrefix + psaType.toUtf8()); - phrase = !custom.isEmpty() - ? custom.replace("{channel}", textcmdLink(1, phrase)) - : (psaType.isEmpty() + if (!custom.isEmpty()) { + custom = custom.replace("{channel}", phrase.text); + const auto index = int(custom.indexOf(phrase.text)); + const auto size = int(phrase.text.size()); + phrase = TextWithEntities{ + .text = custom, + .entities = {{ EntityType::CustomUrl, index, size, {} }}, + }; + } else { + phrase = (psaType.isEmpty() ? tr::lng_forwarded_channel : tr::lng_forwarded_psa_default)( tr::now, lt_channel, - textcmdLink(1, phrase)); + Ui::Text::Link(phrase.text, QString()), // Link 1. + Ui::Text::WithEntities); + } } else { phrase = tr::lng_forwarded( tr::now, lt_user, - textcmdLink(1, phrase)); + Ui::Text::Link(phrase.text, QString()), // Link 1. + Ui::Text::WithEntities); } } TextParseOptions opts = { @@ -166,7 +180,7 @@ void HistoryMessageForwarded::create(const HistoryMessageVia *via) const { 0, Qt::LayoutDirectionAuto }; - text.setText(st::fwdTextStyle, phrase, opts); + text.setMarkedText(st::fwdTextStyle, phrase, opts); static const auto hidden = std::make_shared([] { Ui::Toast::Show(tr::lng_forwarded_hidden(tr::now)); }); @@ -210,7 +224,7 @@ bool HistoryMessageReply::updateData( } if (replyToMsg) { - replyToText.setText( + replyToText.setMarkedText( st::messageTextStyle, replyToMsg->inReplyText(), Ui::DialogTextOptions()); diff --git a/Telegram/SourceFiles/history/history_service.cpp b/Telegram/SourceFiles/history/history_service.cpp index 58ebf3609..bca779db9 100644 --- a/Telegram/SourceFiles/history/history_service.cpp +++ b/Telegram/SourceFiles/history/history_service.cpp @@ -40,6 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "payments/payments_checkout_process.h" // CheckoutProcess::Start. #include "ui/text/format_values.h" #include "ui/text/text_options.h" +#include "ui/text/text_utilities.h" namespace { @@ -111,31 +112,65 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { auto u = history()->owner().user(users[0].v); if (u == _from) { result.links.push_back(fromLink()); - result.text = tr::lng_action_user_joined(tr::now, lt_from, fromLinkText()); + result.text = tr::lng_action_user_joined( + tr::now, + lt_from, + fromLinkText(), // Link 1. + Ui::Text::WithEntities); } else { result.links.push_back(fromLink()); result.links.push_back(u->createOpenLink()); - result.text = tr::lng_action_add_user(tr::now, lt_from, fromLinkText(), lt_user, textcmdLink(2, u->name)); + result.text = tr::lng_action_add_user( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_user, + Ui::Text::Link(u->name, {}), // Link 2. + Ui::Text::WithEntities); } } else if (users.isEmpty()) { result.links.push_back(fromLink()); - result.text = tr::lng_action_add_user(tr::now, lt_from, fromLinkText(), lt_user, qsl("somebody")); + result.text = tr::lng_action_add_user( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_user, + { .text = qsl("somebody") }, + Ui::Text::WithEntities); } else { result.links.push_back(fromLink()); for (auto i = 0, l = int(users.size()); i != l; ++i) { auto user = history()->owner().user(users[i].v); result.links.push_back(user->createOpenLink()); - auto linkText = textcmdLink(i + 2, user->name); + auto linkText = Ui::Text::Link(user->name, {}); if (i == 0) { result.text = linkText; } else if (i + 1 == l) { - result.text = tr::lng_action_add_users_and_last(tr::now, lt_accumulated, result.text, lt_user, linkText); + result.text = tr::lng_action_add_users_and_last( + tr::now, + lt_accumulated, + result.text, + lt_user, + linkText, + Ui::Text::WithEntities); } else { - result.text = tr::lng_action_add_users_and_one(tr::now, lt_accumulated, result.text, lt_user, linkText); + result.text = tr::lng_action_add_users_and_one( + tr::now, + lt_accumulated, + result.text, + lt_user, + linkText, + Ui::Text::WithEntities); } } - result.text = tr::lng_action_add_users_many(tr::now, lt_from, fromLinkText(), lt_users, result.text); + result.text = tr::lng_action_add_users_many( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_users, + result.text, + Ui::Text::WithEntities); } return result; }; @@ -143,24 +178,42 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { auto prepareChatJoinedByLink = [this](const MTPDmessageActionChatJoinedByLink &action) { auto result = PreparedText{}; result.links.push_back(fromLink()); - result.text = tr::lng_action_user_joined_by_link(tr::now, lt_from, fromLinkText()); + result.text = tr::lng_action_user_joined_by_link( + tr::now, + lt_from, + fromLinkText(), // Link 1. + Ui::Text::WithEntities); return result; }; auto prepareChatCreate = [this](const MTPDmessageActionChatCreate &action) { auto result = PreparedText{}; result.links.push_back(fromLink()); - result.text = tr::lng_action_created_chat(tr::now, lt_from, fromLinkText(), lt_title, TextUtilities::Clean(qs(action.vtitle()))); + result.text = tr::lng_action_created_chat( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_title, + { .text = qs(action.vtitle()) }, + Ui::Text::WithEntities); return result; }; auto prepareChannelCreate = [this](const MTPDmessageActionChannelCreate &action) { auto result = PreparedText {}; if (isPost()) { - result.text = tr::lng_action_created_channel(tr::now); + result.text = tr::lng_action_created_channel( + tr::now, + Ui::Text::WithEntities); } else { result.links.push_back(fromLink()); - result.text = tr::lng_action_created_chat(tr::now, lt_from, fromLinkText(), lt_title, TextUtilities::Clean(qs(action.vtitle()))); + result.text = tr::lng_action_created_chat( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_title, + { .text = qs(action.vtitle()) }, + Ui::Text::WithEntities); } return result; }; @@ -168,10 +221,16 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { auto prepareChatDeletePhoto = [this] { auto result = PreparedText{}; if (isPost()) { - result.text = tr::lng_action_removed_photo_channel(tr::now); + result.text = tr::lng_action_removed_photo_channel( + tr::now, + Ui::Text::WithEntities); } else { result.links.push_back(fromLink()); - result.text = tr::lng_action_removed_photo(tr::now, lt_from, fromLinkText()); + result.text = tr::lng_action_removed_photo( + tr::now, + lt_from, + fromLinkText(), // Link 1. + Ui::Text::WithEntities); } return result; }; @@ -180,12 +239,22 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { auto result = PreparedText{}; if (peerFromUser(action.vuser_id()) == _from->id) { result.links.push_back(fromLink()); - result.text = tr::lng_action_user_left(tr::now, lt_from, fromLinkText()); + result.text = tr::lng_action_user_left( + tr::now, + lt_from, + fromLinkText(), // Link 1. + Ui::Text::WithEntities); } else { auto user = history()->owner().user(action.vuser_id().v); result.links.push_back(fromLink()); result.links.push_back(user->createOpenLink()); - result.text = tr::lng_action_kick_user(tr::now, lt_from, fromLinkText(), lt_user, textcmdLink(2, user->name)); + result.text = tr::lng_action_kick_user( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_user, + Ui::Text::Link(user->name, {}), // Link 2. + Ui::Text::WithEntities); } return result; }; @@ -193,10 +262,16 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { auto prepareChatEditPhoto = [this](const MTPDmessageActionChatEditPhoto &action) { auto result = PreparedText{}; if (isPost()) { - result.text = tr::lng_action_changed_photo_channel(tr::now); + result.text = tr::lng_action_changed_photo_channel( + tr::now, + Ui::Text::WithEntities); } else { result.links.push_back(fromLink()); - result.text = tr::lng_action_changed_photo(tr::now, lt_from, fromLinkText()); + result.text = tr::lng_action_changed_photo( + tr::now, + lt_from, + fromLinkText(), // Link 1. + Ui::Text::WithEntities); } return result; }; @@ -204,10 +279,20 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { auto prepareChatEditTitle = [this](const MTPDmessageActionChatEditTitle &action) { auto result = PreparedText{}; if (isPost()) { - result.text = tr::lng_action_changed_title_channel(tr::now, lt_title, TextUtilities::Clean(qs(action.vtitle()))); + result.text = tr::lng_action_changed_title_channel( + tr::now, + lt_title, + { .text = (qs(action.vtitle())) }, + Ui::Text::WithEntities); } else { result.links.push_back(fromLink()); - result.text = tr::lng_action_changed_title(tr::now, lt_from, fromLinkText(), lt_title, TextUtilities::Clean(qs(action.vtitle()))); + result.text = tr::lng_action_changed_title( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_title, + { .text = qs(action.vtitle()) }, + Ui::Text::WithEntities); } return result; }; @@ -215,17 +300,23 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { auto prepareScreenshotTaken = [this] { auto result = PreparedText{}; if (out()) { - result.text = tr::lng_action_you_took_screenshot(tr::now); + result.text = tr::lng_action_you_took_screenshot( + tr::now, + Ui::Text::WithEntities); } else { result.links.push_back(fromLink()); - result.text = tr::lng_action_took_screenshot(tr::now, lt_from, fromLinkText()); + result.text = tr::lng_action_took_screenshot( + tr::now, + lt_from, + fromLinkText(), // Link 1. + Ui::Text::WithEntities); } return result; }; auto prepareCustomAction = [&](const MTPDmessageActionCustomAction &action) { auto result = PreparedText{}; - result.text = qs(action.vmessage()); + result.text = { .text = qs(action.vmessage()) }; return result; }; @@ -235,7 +326,8 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { result.text = tr::lng_action_bot_allowed_from_domain( tr::now, lt_domain, - textcmdLink(qstr("http://") + domain, domain)); + Ui::Text::Link(domain, qstr("http://") + domain), + Ui::Text::WithEntities); return result; }; @@ -272,16 +364,21 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { result.text = tr::lng_action_secure_values_sent( tr::now, lt_user, - textcmdLink(1, history()->peer->name), + Ui::Text::Link(history()->peer->name, {}), // Link 1. lt_documents, - documents.join(", ")); + { .text = documents.join(", ") }, + Ui::Text::WithEntities); return result; }; auto prepareContactSignUp = [this] { auto result = PreparedText{}; result.links.push_back(fromLink()); - result.text = tr::lng_action_user_registered(tr::now, lt_from, fromLinkText()); + result.text = tr::lng_action_user_registered( + tr::now, + lt_from, + fromLinkText(), // Link 1. + Ui::Text::WithEntities); return result; }; @@ -313,28 +410,31 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { return tr::lng_action_you_proximity_reached( tr::now, lt_distance, - distance, + { .text = distance }, lt_user, - textcmdLink(1, toPeer->name)); + Ui::Text::Link(toPeer->name, {}), // Link 1. + Ui::Text::WithEntities); } else if (toId == selfId) { result.links.push_back(fromPeer->createOpenLink()); return tr::lng_action_proximity_reached_you( tr::now, lt_from, - textcmdLink(1, fromPeer->name), + Ui::Text::Link(fromPeer->name, {}), // Link 1. lt_distance, - distance); + { .text = distance }, + Ui::Text::WithEntities); } else { result.links.push_back(fromPeer->createOpenLink()); result.links.push_back(toPeer->createOpenLink()); return tr::lng_action_proximity_reached( tr::now, lt_from, - textcmdLink(1, fromPeer->name), + Ui::Text::Link(fromPeer->name, {}), // Link 1. lt_distance, - distance, + { .text = distance }, lt_user, - textcmdLink(2, toPeer->name)); + Ui::Text::Link(toPeer->name, {}), // Link 2. + Ui::Text::WithEntities); } }(); return result; @@ -358,26 +458,31 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { result.text = tr::lng_action_group_call_finished( tr::now, lt_duration, - text); + { .text = text }, + Ui::Text::WithEntities); } else { result.links.push_back(fromLink()); result.text = tr::lng_action_group_call_finished_group( tr::now, lt_from, - fromLinkText(), + fromLinkText(), // Link 1. lt_duration, - text); + { .text = text }, + Ui::Text::WithEntities); } return result; } if (history()->peer->isBroadcast()) { - result.text = tr::lng_action_group_call_started_channel(tr::now); + result.text = tr::lng_action_group_call_started_channel( + tr::now, + Ui::Text::WithEntities); } else { result.links.push_back(fromLink()); result.text = tr::lng_action_group_call_started_group( tr::now, lt_from, - fromLinkText()); + fromLinkText(), // Link 1. + Ui::Text::WithEntities); } return result; }; @@ -410,22 +515,44 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { : tr::lng_ttl_about_duration3(tr::now); if (isPost()) { if (!period) { - result.text = tr::lng_action_ttl_removed_channel(tr::now); + result.text = tr::lng_action_ttl_removed_channel( + tr::now, + Ui::Text::WithEntities); } else { - result.text = tr::lng_action_ttl_changed_channel(tr::now, lt_duration, duration); + result.text = tr::lng_action_ttl_changed_channel( + tr::now, + lt_duration, + { .text = duration }, + Ui::Text::WithEntities); } } else if (_from->isSelf()) { if (!period) { - result.text = tr::lng_action_ttl_removed_you(tr::now); + result.text = tr::lng_action_ttl_removed_you( + tr::now, + Ui::Text::WithEntities); } else { - result.text = tr::lng_action_ttl_changed_you(tr::now, lt_duration, duration); + result.text = tr::lng_action_ttl_changed_you( + tr::now, + lt_duration, + { .text = duration }, + Ui::Text::WithEntities); } } else { result.links.push_back(fromLink()); if (!period) { - result.text = tr::lng_action_ttl_removed(tr::now, lt_from, fromLinkText()); + result.text = tr::lng_action_ttl_removed( + tr::now, + lt_from, + fromLinkText(), // Link 1. + Ui::Text::WithEntities); } else { - result.text = tr::lng_action_ttl_changed(tr::now, lt_from, fromLinkText(), lt_duration, duration); + result.text = tr::lng_action_ttl_changed( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_duration, + { .text = duration }, + Ui::Text::WithEntities); } } return result; @@ -436,17 +563,33 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { const auto text = qs(action.vemoticon()); if (!text.isEmpty()) { if (_from->isSelf()) { - result.text = tr::lng_action_you_theme_changed(tr::now, lt_emoji, text); + result.text = tr::lng_action_you_theme_changed( + tr::now, + lt_emoji, + { .text = text }, + Ui::Text::WithEntities); } else { result.links.push_back(fromLink()); - result.text = tr::lng_action_theme_changed(tr::now, lt_from, fromLinkText(), lt_emoji, text); + result.text = tr::lng_action_theme_changed( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_emoji, + { .text = text }, + Ui::Text::WithEntities); } } else { if (_from->isSelf()) { - result.text = tr::lng_action_you_theme_disabled(tr::now); + result.text = tr::lng_action_you_theme_disabled( + tr::now, + Ui::Text::WithEntities); } else { result.links.push_back(fromLink()); - result.text = tr::lng_action_theme_disabled(tr::now, lt_from, fromLinkText()); + result.text = tr::lng_action_theme_disabled( + tr::now, + lt_from, + fromLinkText(), // Link 1. + Ui::Text::WithEntities); } } return result; @@ -455,7 +598,11 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { auto prepareChatJoinedByRequest = [this](const MTPDmessageActionChatJoinedByRequest &action) { auto result = PreparedText{}; result.links.push_back(fromLink()); - result.text = tr::lng_action_user_joined_by_request(tr::now, lt_from, fromLinkText()); + result.text = tr::lng_action_user_joined_by_request( + tr::now, + lt_from, + fromLinkText(), // Link 1. + Ui::Text::WithEntities); return result; }; @@ -504,10 +651,14 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { return prepareProximityReached(data); }, [](const MTPDmessageActionPaymentSentMe &) { LOG(("API Error: messageActionPaymentSentMe received.")); - return PreparedText{ tr::lng_message_empty(tr::now) }; + return PreparedText{ + tr::lng_message_empty(tr::now, Ui::Text::WithEntities) + }; }, [](const MTPDmessageActionSecureValuesSentMe &) { LOG(("API Error: messageActionSecureValuesSentMe received.")); - return PreparedText{ tr::lng_message_empty(tr::now) }; + return PreparedText{ + tr::lng_message_empty(tr::now, Ui::Text::WithEntities) + }; }, [&](const MTPDmessageActionGroupCall &data) { return prepareGroupCall(data); }, [&](const MTPDmessageActionInviteToGroupCall &data) { @@ -521,7 +672,9 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { }, [&](const MTPDmessageActionChatJoinedByRequest &data) { return prepareChatJoinedByRequest(data); }, [](const MTPDmessageActionEmpty &) { - return PreparedText{ tr::lng_message_empty(tr::now) }; + return PreparedText{ + tr::lng_message_empty(tr::now, Ui::Text::WithEntities) + }; }); setServiceText(messageText); @@ -638,36 +791,74 @@ HistoryService::PreparedText HistoryService::prepareInvitedToCallText( const QVector &users, CallId linkCallId) { const auto owner = &history()->owner(); - auto chatText = tr::lng_action_invite_user_chat(tr::now); + auto chatText = tr::lng_action_invite_user_chat( + tr::now, + Ui::Text::WithEntities); auto result = PreparedText{}; result.links.push_back(fromLink()); auto linkIndex = 1; if (linkCallId) { const auto peer = history()->peer; result.links.push_back(GroupCallClickHandler(peer, linkCallId)); - chatText = textcmdLink(++linkIndex, chatText); + chatText = Ui::Text::Link(chatText.text, {}); } if (users.size() == 1) { auto user = owner->user(users[0].v); result.links.push_back(user->createOpenLink()); - result.text = tr::lng_action_invite_user(tr::now, lt_from, fromLinkText(), lt_user, textcmdLink(++linkIndex, user->name), lt_chat, chatText); + result.text = tr::lng_action_invite_user( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_user, + Ui::Text::Link(user->name, {}), // Link N. + lt_chat, + chatText, + Ui::Text::WithEntities); } else if (users.isEmpty()) { - result.text = tr::lng_action_invite_user(tr::now, lt_from, fromLinkText(), lt_user, qsl("somebody"), lt_chat, chatText); + result.text = tr::lng_action_invite_user( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_user, + { .text = qsl("somebody") }, + lt_chat, + chatText, + Ui::Text::WithEntities); } else { for (auto i = 0, l = int(users.size()); i != l; ++i) { auto user = owner->user(users[i].v); result.links.push_back(user->createOpenLink()); - auto linkText = textcmdLink(++linkIndex, user->name); + auto linkText = Ui::Text::Link(user->name, {}); if (i == 0) { result.text = linkText; } else if (i + 1 == l) { - result.text = tr::lng_action_invite_users_and_last(tr::now, lt_accumulated, result.text, lt_user, linkText); + result.text = tr::lng_action_invite_users_and_last( + tr::now, + lt_accumulated, + result.text, + lt_user, + linkText, + Ui::Text::WithEntities); } else { - result.text = tr::lng_action_invite_users_and_one(tr::now, lt_accumulated, result.text, lt_user, linkText); + result.text = tr::lng_action_invite_users_and_one( + tr::now, + lt_accumulated, + result.text, + lt_user, + linkText, + Ui::Text::WithEntities); } } - result.text = tr::lng_action_invite_users_many(tr::now, lt_from, fromLinkText(), lt_users, result.text, lt_chat, chatText); + result.text = tr::lng_action_invite_users_many( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_users, + result.text, + lt_chat, + chatText, + Ui::Text::WithEntities); } return result; } @@ -692,38 +883,63 @@ HistoryService::PreparedText HistoryService::preparePinnedText() { result.links.push_back(fromLink()); result.links.push_back(pinned->lnk); if (mediaText.isEmpty()) { - auto original = TextUtilities::TextWithSpoilerCommands( - pinned->msg->originalText()); + auto original = pinned->msg->originalText(); auto cutAt = 0; auto limit = kPinnedMessageTextLimit; - auto size = original.size(); + auto size = original.text.size(); for (; limit != 0;) { --limit; if (cutAt >= size) break; - if (original.at(cutAt).isLowSurrogate() && cutAt + 1 < size && original.at(cutAt + 1).isHighSurrogate()) { + if (original.text.at(cutAt).isLowSurrogate() + && (cutAt + 1 < size) + && original.text.at(cutAt + 1).isHighSurrogate()) { cutAt += 2; } else { ++cutAt; } } if (!limit && cutAt + 5 < size) { - original = TextUtilities::CutTextWithCommands( - std::move(original), - cutAt, - textcmdStartSpoiler(), - textcmdStopSpoiler()); + original = Ui::Text::Mid(original, 0, cutAt).append( + Ui::kQEllipsis); } - result.text = tr::lng_action_pinned_message(tr::now, lt_from, fromLinkText(), lt_text, textcmdLink(2, original)); + original = Ui::Text::Wrapped( + std::move(original), + EntityType::CustomUrl); + result.text = tr::lng_action_pinned_message( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_text, + std::move(original), // Link 2. + Ui::Text::WithEntities); } else { - result.text = tr::lng_action_pinned_media(tr::now, lt_from, fromLinkText(), lt_media, textcmdLink(2, mediaText)); + result.text = tr::lng_action_pinned_media( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_media, + Ui::Text::Link(mediaText, {}), // Link 2. + Ui::Text::WithEntities); } } else if (pinned && pinned->msgId) { result.links.push_back(fromLink()); result.links.push_back(pinned->lnk); - result.text = tr::lng_action_pinned_media(tr::now, lt_from, fromLinkText(), lt_media, textcmdLink(2, tr::lng_contacts_loading(tr::now))); + result.text = tr::lng_action_pinned_media( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_media, + Ui::Text::Link(tr::lng_contacts_loading(tr::now), {}), // Link 2. + Ui::Text::WithEntities); } else { result.links.push_back(fromLink()); - result.text = tr::lng_action_pinned_media(tr::now, lt_from, fromLinkText(), lt_media, tr::lng_deleted_message(tr::now)); + result.text = tr::lng_action_pinned_media( + tr::now, + lt_from, + fromLinkText(), // Link 1. + lt_media, + { .text = tr::lng_deleted_message(tr::now) }, + Ui::Text::WithEntities); } return result; } @@ -732,7 +948,7 @@ HistoryService::PreparedText HistoryService::prepareGameScoreText() { auto result = PreparedText {}; auto gamescore = Get(); - auto computeGameTitle = [&]() -> QString { + auto computeGameTitle = [&]() -> TextWithEntities { if (gamescore && gamescore->msg) { if (const auto media = gamescore->msg->media()) { if (const auto game = media->game()) { @@ -745,51 +961,55 @@ HistoryService::PreparedText HistoryService::prepareGameScoreText() { column, gamescore->msg->fullId())); auto titleText = game->title; - return textcmdLink(result.links.size(), titleText); + return Ui::Text::Link(titleText, {}); } } - return tr::lng_deleted_message(tr::now); + return tr::lng_deleted_message(tr::now, Ui::Text::WithEntities); } else if (gamescore && gamescore->msgId) { - return tr::lng_contacts_loading(tr::now); + return tr::lng_contacts_loading(tr::now, Ui::Text::WithEntities); } - return QString(); + return {}; }; const auto scoreNumber = gamescore ? gamescore->score : 0; if (_from->isSelf()) { auto gameTitle = computeGameTitle(); - if (gameTitle.isEmpty()) { + if (gameTitle.text.isEmpty()) { result.text = tr::lng_action_game_you_scored_no_game( tr::now, lt_count, - scoreNumber); + scoreNumber, + Ui::Text::WithEntities); } else { result.text = tr::lng_action_game_you_scored( tr::now, lt_count, scoreNumber, lt_game, - gameTitle); + gameTitle, + Ui::Text::WithEntities); } } else { result.links.push_back(fromLink()); auto gameTitle = computeGameTitle(); - if (gameTitle.isEmpty()) { + if (gameTitle.text.isEmpty()) { result.text = tr::lng_action_game_score_no_game( tr::now, lt_count, scoreNumber, lt_from, - fromLinkText()); + fromLinkText(), // Link 1. + Ui::Text::WithEntities); } else { result.text = tr::lng_action_game_score( tr::now, lt_count, scoreNumber, lt_from, - fromLinkText(), + fromLinkText(), // Link 1. lt_game, - gameTitle); + gameTitle, + Ui::Text::WithEntities); } } return result; @@ -804,17 +1024,31 @@ HistoryService::PreparedText HistoryService::preparePaymentSentText() { if (payment->msg) { if (const auto media = payment->msg->media()) { if (const auto invoice = media->invoice()) { - return textcmdLink(1, invoice->title); + return Ui::Text::Link(invoice->title, {}); } } } - return QString(); + return TextWithEntities(); }(); - if (invoiceTitle.isEmpty()) { - result.text = tr::lng_action_payment_done(tr::now, lt_amount, payment->amount, lt_user, history()->peer->name); + if (invoiceTitle.text.isEmpty()) { + result.text = tr::lng_action_payment_done( + tr::now, + lt_amount, + { .text = payment->amount }, + lt_user, + { .text = history()->peer->name }, + Ui::Text::WithEntities); } else { - result.text = tr::lng_action_payment_done_for(tr::now, lt_amount, payment->amount, lt_user, history()->peer->name, lt_invoice, invoiceTitle); + result.text = tr::lng_action_payment_done_for( + tr::now, + lt_amount, + { .text = payment->amount }, + lt_user, + { .text = history()->peer->name }, + lt_invoice, + invoiceTitle, + Ui::Text::WithEntities); if (payment->msg) { result.links.push_back(payment->lnk); } @@ -839,15 +1073,17 @@ HistoryService::PreparedText HistoryService::prepareCallScheduledText( result.text = tr::lng_action_group_call_scheduled_channel( tr::now, lt_date, - date); + { .text = date }, + Ui::Text::WithEntities); } else { result.links.push_back(fromLink()); result.text = tr::lng_action_group_call_scheduled_group( tr::now, lt_from, - fromLinkText(), + fromLinkText(), // Link 1. lt_date, - date); + { .text = date }, + Ui::Text::WithEntities); } }; const auto time = scheduled.time().toString(cTimeFormat()); @@ -948,21 +1184,21 @@ ItemPreview HistoryService::toPreview(ToPreviewOptions options) const { // Because larger version is shown exactly to the left of the preview. //auto media = _media ? _media->toPreview(options) : ItemPreview(); return { - .text = textcmdLink( - 1, - TextUtilities::Clean(notificationText(), true)), + .text = Ui::Text::Wrapped(notificationText(), EntityType::PlainLink), //.images = std::move(media.images), //.loadingContext = std::move(media.loadingContext), }; } -QString HistoryService::inReplyText() const { - const auto result = HistoryService::notificationText(); +TextWithEntities HistoryService::inReplyText() const { + auto result = HistoryService::notificationText(); const auto &name = author()->name; - const auto text = result.trimmed().startsWith(name) - ? result.trimmed().mid(name.size()).trimmed() - : result; - return textcmdLink(1, text); + TextUtilities::Trim(result); + if (result.text.startsWith(name)) { + result = Ui::Text::Mid(result, name.size()); + TextUtilities::Trim(result); + } + return Ui::Text::Wrapped(result, EntityType::PlainLink); } std::unique_ptr HistoryService::createView( @@ -971,8 +1207,8 @@ std::unique_ptr HistoryService::createView( return delegate->elementCreate(this, replacing); } -QString HistoryService::fromLinkText() const { - return textcmdLink(1, _from->name); +TextWithEntities HistoryService::fromLinkText() const { + return Ui::Text::Link(_from->name, {}); } ClickHandlerPtr HistoryService::fromLink() const { @@ -980,7 +1216,7 @@ ClickHandlerPtr HistoryService::fromLink() const { } void HistoryService::setServiceText(const PreparedText &prepared) { - _text.setText( + _text.setMarkedText( st::serviceTextStyle, prepared.text, Ui::ItemTextServiceOptions()); @@ -1018,7 +1254,7 @@ crl::time HistoryService::getSelfDestructIn(crl::time now) { } Unexpected("Type in HistoryServiceSelfDestruct::Type"); }; - setServiceText({ text() }); + setServiceText({ TextWithEntities{ .text = text() } }); return 0; } return selfdestruct->destructAt - now; @@ -1041,15 +1277,23 @@ void HistoryService::createFromMtp(const MTPDmessage &message) { setSelfDestruct(HistoryServiceSelfDestruct::Type::Photo, ttl->v); if (out()) { - setServiceText({ tr::lng_ttl_photo_sent(tr::now) }); + setServiceText({ + tr::lng_ttl_photo_sent(tr::now, Ui::Text::WithEntities) + }); } else { auto result = PreparedText(); result.links.push_back(fromLink()); - result.text = tr::lng_ttl_photo_received(tr::now, lt_from, fromLinkText()); + result.text = tr::lng_ttl_photo_received( + tr::now, + lt_from, + fromLinkText(), // Link 1. + Ui::Text::WithEntities); setServiceText(std::move(result)); } } else { - setServiceText({ tr::lng_ttl_photo_expired(tr::now) }); + setServiceText({ + tr::lng_ttl_photo_expired(tr::now, Ui::Text::WithEntities) + }); } } break; case mtpc_messageMediaDocument: { @@ -1060,15 +1304,23 @@ void HistoryService::createFromMtp(const MTPDmessage &message) { setSelfDestruct(HistoryServiceSelfDestruct::Type::Video, ttl->v); if (out()) { - setServiceText({ tr::lng_ttl_video_sent(tr::now) }); + setServiceText({ + tr::lng_ttl_video_sent(tr::now, Ui::Text::WithEntities) + }); } else { auto result = PreparedText(); result.links.push_back(fromLink()); - result.text = tr::lng_ttl_video_received(tr::now, lt_from, fromLinkText()); + result.text = tr::lng_ttl_video_received( + tr::now, + lt_from, + fromLinkText(), // Link 1. + Ui::Text::WithEntities); setServiceText(std::move(result)); } } else { - setServiceText({ tr::lng_ttl_video_expired(tr::now) }); + setServiceText({ + tr::lng_ttl_video_expired(tr::now, Ui::Text::WithEntities) + }); } } break; @@ -1264,11 +1516,14 @@ HistoryService::PreparedText GenerateJoinedText( : tr::lng_action_add_you)( tr::now, lt_from, - textcmdLink(1, inviter->name)); + Ui::Text::Link(inviter->name, {}), + Ui::Text::WithEntities); return result; } else if (history->peer->isMegagroup()) { if (viaRequest) { - return { tr::lng_action_you_joined_by_request(tr::now) }; + return { tr::lng_action_you_joined_by_request( + tr::now, + Ui::Text::WithEntities) }; } auto self = history->session().user(); auto result = HistoryService::PreparedText{}; @@ -1276,12 +1531,15 @@ HistoryService::PreparedText GenerateJoinedText( result.text = tr::lng_action_user_joined( tr::now, lt_from, - textcmdLink(1, self->name)); + Ui::Text::Link(self->name, {}), + Ui::Text::WithEntities); return result; } return { viaRequest - ? tr::lng_action_you_joined_by_request_channel(tr::now) - : tr::lng_action_you_joined(tr::now) }; + ? tr::lng_action_you_joined_by_request_channel( + tr::now, + Ui::Text::WithEntities) + : tr::lng_action_you_joined(tr::now, Ui::Text::WithEntities) }; } not_null GenerateJoinedMessage( diff --git a/Telegram/SourceFiles/history/history_service.h b/Telegram/SourceFiles/history/history_service.h index 01bc48749..09278b230 100644 --- a/Telegram/SourceFiles/history/history_service.h +++ b/Telegram/SourceFiles/history/history_service.h @@ -63,7 +63,7 @@ class ServiceMessagePainter; class HistoryService : public HistoryItem { public: struct PreparedText { - QString text; + TextWithEntities text; QList links; }; @@ -112,7 +112,7 @@ public: return true; } ItemPreview toPreview(ToPreviewOptions options) const override; - QString inReplyText() const override; + TextWithEntities inReplyText() const override; std::unique_ptr createView( not_null delegate, @@ -129,7 +129,7 @@ protected: void markMediaAsReadHook() override; - QString fromLinkText() const; + TextWithEntities fromLinkText() const; ClickHandlerPtr fromLink() const; void removeMedia(); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 1b8817325..40e47cd27 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -6952,7 +6952,7 @@ void HistoryWidget::messageDataReceived( } void HistoryWidget::updateReplyEditText(not_null item) { - _replyEditMsgText.setText( + _replyEditMsgText.setMarkedText( st::messageTextStyle, item->inReplyText(), Ui::DialogTextOptions()); @@ -7000,7 +7000,8 @@ void HistoryWidget::updateForwarding() { void HistoryWidget::updateForwardingTexts() { int32 version = 0; - QString from, text; + QString from; + TextWithEntities text; const auto keepNames = (_toForward.options == Data::ForwardOptions::PreserveInfo); const auto keepCaptions = (_toForward.options @@ -7047,13 +7048,12 @@ void HistoryWidget::updateForwardingTexts() { .generateImages = false, }).text; } else { - text = textcmdLink( - 1, + text = Ui::Text::PlainLink( tr::lng_forward_messages(tr::now, lt_count, count)); } } _toForwardFrom.setText(st::msgNameStyle, from, Ui::NameTextOptions()); - _toForwardText.setText( + _toForwardText.setMarkedText( st::messageTextStyle, text, Ui::DialogTextOptions()); diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index 8624f9aa1..5608234d2 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -325,7 +325,7 @@ void FieldHeader::init() { void FieldHeader::updateShownMessageText() { Expects(_shownMessage != nullptr); - _shownMessageText.setText( + _shownMessageText.setMarkedText( st::messageTextStyle, _shownMessage->inReplyText(), Ui::DialogTextOptions()); diff --git a/Telegram/SourceFiles/history/view/history_view_item_preview.h b/Telegram/SourceFiles/history/view/history_view_item_preview.h index b3e0ea159..20e4a2f6e 100644 --- a/Telegram/SourceFiles/history/view/history_view_item_preview.h +++ b/Telegram/SourceFiles/history/view/history_view_item_preview.h @@ -19,7 +19,7 @@ struct ItemPreviewImage { }; struct ItemPreview { - QString text; + TextWithEntities text; std::vector images; int imagesInTextPosition = 0; std::any loadingContext; diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp index 916555066..e48c843c7 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_bar.cpp @@ -24,7 +24,7 @@ namespace { [[nodiscard]] Ui::MessageBarContent ContentWithoutPreview( not_null item) { return Ui::MessageBarContent{ - .text = { item->inReplyText() }, + .text = item->inReplyText(), }; } diff --git a/Telegram/SourceFiles/window/notifications_manager.cpp b/Telegram/SourceFiles/window/notifications_manager.cpp index 29c358e25..950496eb0 100644 --- a/Telegram/SourceFiles/window/notifications_manager.cpp +++ b/Telegram/SourceFiles/window/notifications_manager.cpp @@ -47,30 +47,18 @@ constexpr auto kSystemAlertDuration = crl::time(1000); constexpr auto kSystemAlertDuration = crl::time(0); #endif // Q_OS_MAC -QString TextWithPermanentSpoiler(QString textWithCommands) { - const auto start = textcmdStartSpoiler(); - const auto stop = textcmdStopSpoiler(); - while (true) { - const auto startIndex = textWithCommands.indexOf(start); - if (startIndex == -1) { - break; +QString TextWithPermanentSpoiler(const TextWithEntities &textWithEntities) { + auto text = textWithEntities.text; + for (const auto &e : textWithEntities.entities) { + if (e.type() == EntityType::Spoiler) { + auto replacement = QString().fill(QChar(0x259A), e.length()); + text = text.replace( + e.offset(), + e.length(), + std::move(replacement)); } - const auto stopIndex = textWithCommands.indexOf(stop); - if (stopIndex == -1) { - break; - } - if (stopIndex < startIndex) { - break; - } - const auto length = stopIndex - startIndex - start.size(); - textWithCommands.remove(stopIndex, stop.size()); - textWithCommands.remove(startIndex, start.size()); - textWithCommands.replace( - startIndex, - length, - QString(QChar(0x259A)).repeated(length)); } - return textWithCommands; + return text; } } // namespace diff --git a/Telegram/SourceFiles/window/notifications_manager_default.cpp b/Telegram/SourceFiles/window/notifications_manager_default.cpp index 0c5dff83d..4769eb085 100644 --- a/Telegram/SourceFiles/window/notifications_manager_default.cpp +++ b/Telegram/SourceFiles/window/notifications_manager_default.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/input_fields.h" #include "ui/platform/ui_platform_utility.h" #include "ui/text/text_options.h" +#include "ui/text/text_utilities.h" #include "ui/emoji_config.h" #include "ui/empty_userpic.h" #include "ui/ui_utility.h" @@ -811,9 +812,9 @@ void Notification::updateNotifyDisplay() { .generateImages = false, }).text : ((!_author.isEmpty() - ? textcmdLink(1, _author) - : QString()) - + (_forwardedCount > 1 + ? Ui::Text::PlainLink(_author) + : TextWithEntities() + ).append(_forwardedCount > 1 ? ('\n' + tr::lng_forward_messages( tr::now, lt_count, @@ -826,7 +827,7 @@ void Notification::updateNotifyDisplay() { 0, Qt::LayoutDirectionAuto, }; - itemTextCache.setText(st::dialogsTextStyle, text, Options); + itemTextCache.setMarkedText(st::dialogsTextStyle, text, Options); itemTextCache.drawElided( p, r.left(),