Move Ui::Text::String to HistoryView::Element.

This commit is contained in:
John Preston 2022-09-21 18:55:27 +04:00
parent 140dcb033b
commit ffb024a5f7
23 changed files with 474 additions and 434 deletions

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/stickers_emoji_pack.h" #include "chat_helpers/stickers_emoji_pack.h"
#include "chat_helpers/stickers_emoji_image_loader.h" #include "chat_helpers/stickers_emoji_image_loader.h"
#include "history/view/history_view_element.h"
#include "history/history_item.h" #include "history/history_item.h"
#include "history/history.h" #include "history/history.h"
#include "lottie/lottie_common.h" #include "lottie/lottie_common.h"
@ -101,10 +102,10 @@ EmojiPack::EmojiPack(not_null<Main::Session*> session)
: _session(session) { : _session(session) {
refresh(); refresh();
session->data().itemRemoved( session->data().viewRemoved(
) | rpl::filter([](not_null<const HistoryItem*> item) { ) | rpl::filter([](not_null<const ViewElement*> view) {
return item->isIsolatedEmoji(); return view->isIsolatedEmoji() || view->isOnlyCustomEmoji();
}) | rpl::start_with_next([=](not_null<const HistoryItem*> item) { }) | rpl::start_with_next([=](not_null<const ViewElement*> item) {
remove(item); remove(item);
}, _lifetime); }, _lifetime);
@ -122,26 +123,26 @@ EmojiPack::EmojiPack(not_null<Main::Session*> session)
EmojiPack::~EmojiPack() = default; EmojiPack::~EmojiPack() = default;
bool EmojiPack::add(not_null<HistoryItem*> item) { bool EmojiPack::add(not_null<ViewElement*> view) {
if (const auto custom = item->onlyCustomEmoji()) { if (const auto custom = view->onlyCustomEmoji()) {
_onlyCustomItems.emplace(item); _onlyCustomItems.emplace(view);
return true; return true;
} else if (const auto emoji = item->isolatedEmoji()) { } else if (const auto emoji = view->isolatedEmoji()) {
_items[emoji].emplace(item); _items[emoji].emplace(view);
return true; return true;
} }
return false; return false;
} }
void EmojiPack::remove(not_null<const HistoryItem*> item) { void EmojiPack::remove(not_null<const ViewElement*> view) {
Expects(item->isIsolatedEmoji() || item->isOnlyCustomEmoji()); Expects(view->isIsolatedEmoji() || view->isOnlyCustomEmoji());
if (item->isOnlyCustomEmoji()) { if (view->isOnlyCustomEmoji()) {
_onlyCustomItems.remove(item); _onlyCustomItems.remove(view);
} else if (const auto emoji = item->isolatedEmoji()) { } else if (const auto emoji = view->isolatedEmoji()) {
const auto i = _items.find(emoji); const auto i = _items.find(emoji);
Assert(i != end(_items)); Assert(i != end(_items));
const auto j = i->second.find(item); const auto j = i->second.find(view);
Assert(j != end(i->second)); Assert(j != end(i->second));
i->second.erase(j); i->second.erase(j);
if (i->second.empty()) { if (i->second.empty()) {
@ -436,9 +437,20 @@ auto EmojiPack::collectAnimationsIndices(
} }
void EmojiPack::refreshAll() { void EmojiPack::refreshAll() {
auto items = base::flat_set<not_null<HistoryItem*>>();
auto count = 0;
for (const auto &[emoji, list] : _items) { for (const auto &[emoji, list] : _items) {
refreshItems(list); // refreshItems(list); // This call changes _items!
count += int(list.size());
} }
items.reserve(count);
for (const auto &[emoji, list] : _items) {
// refreshItems(list); // This call changes _items!
for (const auto &view : list) {
items.emplace(view->data());
}
}
refreshItems(items);
refreshItems(_onlyCustomItems); refreshItems(_onlyCustomItems);
} }
@ -458,8 +470,18 @@ void EmojiPack::refreshItems(EmojiPtr emoji) {
} }
void EmojiPack::refreshItems( void EmojiPack::refreshItems(
const base::flat_set<not_null<HistoryItem*>> &list) { const base::flat_set<not_null<ViewElement*>> &list) {
for (const auto &item : list) { auto items = base::flat_set<not_null<HistoryItem*>>();
items.reserve(list.size());
for (const auto &view : list) {
items.emplace(view->data());
}
refreshItems(items);
}
void EmojiPack::refreshItems(
const base::flat_set<not_null<HistoryItem*>> &items) {
for (const auto &item : items) {
_session->data().requestItemViewRefresh(item); _session->data().requestItemViewRefresh(item);
} }
} }

View file

@ -35,6 +35,10 @@ class UniversalImages;
} // namespace Emoji } // namespace Emoji
} // namespace Ui } // namespace Ui
namespace HistoryView {
class Element;
} // namespace HistoryView
namespace Stickers { namespace Stickers {
using IsolatedEmoji = Ui::Text::IsolatedEmoji; using IsolatedEmoji = Ui::Text::IsolatedEmoji;
@ -48,6 +52,8 @@ struct LargeEmojiImage {
class EmojiPack final { class EmojiPack final {
public: public:
using ViewElement = HistoryView::Element;
struct Sticker { struct Sticker {
DocumentData *document = nullptr; DocumentData *document = nullptr;
const Lottie::ColorReplacements *replacements = nullptr; const Lottie::ColorReplacements *replacements = nullptr;
@ -63,8 +69,8 @@ public:
explicit EmojiPack(not_null<Main::Session*> session); explicit EmojiPack(not_null<Main::Session*> session);
~EmojiPack(); ~EmojiPack();
bool add(not_null<HistoryItem*> item); bool add(not_null<ViewElement*> view);
void remove(not_null<const HistoryItem*> item); void remove(not_null<const ViewElement*> view);
[[nodiscard]] Sticker stickerForEmoji(EmojiPtr emoji); [[nodiscard]] Sticker stickerForEmoji(EmojiPtr emoji);
[[nodiscard]] Sticker stickerForEmoji(const IsolatedEmoji &emoji); [[nodiscard]] Sticker stickerForEmoji(const IsolatedEmoji &emoji);
@ -106,17 +112,18 @@ private:
-> base::flat_map<uint64, base::flat_set<int>>; -> base::flat_map<uint64, base::flat_set<int>>;
void refreshAll(); void refreshAll();
void refreshItems(EmojiPtr emoji); void refreshItems(EmojiPtr emoji);
void refreshItems(const base::flat_set<not_null<HistoryItem*>> &list); void refreshItems(const base::flat_set<not_null<ViewElement*>> &list);
void refreshItems(const base::flat_set<not_null<HistoryItem*>> &items);
not_null<Main::Session*> _session; const not_null<Main::Session*> _session;
base::flat_map<EmojiPtr, not_null<DocumentData*>> _map; base::flat_map<EmojiPtr, not_null<DocumentData*>> _map;
base::flat_map< base::flat_map<
IsolatedEmoji, IsolatedEmoji,
base::flat_set<not_null<HistoryItem*>>> _items; base::flat_set<not_null<HistoryView::Element*>>> _items;
base::flat_map<EmojiPtr, std::weak_ptr<LargeEmojiImage>> _images; base::flat_map<EmojiPtr, std::weak_ptr<LargeEmojiImage>> _images;
mtpRequestId _requestId = 0; mtpRequestId _requestId = 0;
base::flat_set<not_null<HistoryItem*>> _onlyCustomItems; base::flat_set<not_null<HistoryView::Element*>> _onlyCustomItems;
int _animationsVersion = 0; int _animationsVersion = 0;
base::flat_map< base::flat_map<

View file

@ -1529,11 +1529,10 @@ rpl::producer<not_null<HistoryItem*>> Session::itemDataChanges() const {
void Session::requestItemTextRefresh(not_null<HistoryItem*> item) { void Session::requestItemTextRefresh(not_null<HistoryItem*> item) {
if (const auto i = _views.find(item); i != _views.end()) { if (const auto i = _views.find(item); i != _views.end()) {
for (const auto view : i->second) { for (const auto &view : i->second) {
if (const auto media = view->media()) { view->itemTextUpdated();
media->parentTextUpdated();
}
} }
requestItemResize(item);
} }
} }
@ -1573,6 +1572,7 @@ rpl::producer<not_null<const HistoryItem*>> Session::itemRemoved(
} }
void Session::notifyViewRemoved(not_null<const ViewElement*> view) { void Session::notifyViewRemoved(not_null<const ViewElement*> view) {
_shownSpoilers.remove(view);
_viewRemoved.fire_copy(view); _viewRemoved.fire_copy(view);
} }
@ -1672,24 +1672,15 @@ void Session::unloadHeavyViewParts(
} }
} }
void Session::registerShownSpoiler(FullMsgId id) { void Session::registerShownSpoiler(not_null<ViewElement*> view) {
if (const auto item = message(id)) { _shownSpoilers.emplace(view);
_shownSpoilers.emplace(item);
}
}
void Session::unregisterShownSpoiler(FullMsgId id) {
if (const auto item = message(id)) {
_shownSpoilers.remove(item);
}
} }
void Session::hideShownSpoilers() { void Session::hideShownSpoilers() {
for (const auto &item : _shownSpoilers) { for (const auto &view : base::take(_shownSpoilers)) {
item->hideSpoilers(); view->hideSpoilers();
requestItemTextRefresh(item); requestViewRepaint(view);
} }
_shownSpoilers = base::flat_set<not_null<HistoryItem*>>();
} }
void Session::removeMegagroupParticipant( void Session::removeMegagroupParticipant(
@ -2156,7 +2147,6 @@ void Session::removeDependencyMessage(not_null<HistoryItem*> item) {
void Session::unregisterMessage(not_null<HistoryItem*> item) { void Session::unregisterMessage(not_null<HistoryItem*> item) {
const auto peerId = item->history()->peer->id; const auto peerId = item->history()->peer->id;
const auto itemId = item->id; const auto itemId = item->id;
_shownSpoilers.remove(item);
_itemRemoved.fire_copy(item); _itemRemoved.fire_copy(item);
session().changes().messageUpdated( session().changes().messageUpdated(
item, item,

View file

@ -296,8 +296,7 @@ public:
int from, int from,
int till); int till);
void registerShownSpoiler(FullMsgId id); void registerShownSpoiler(not_null<ViewElement*> view);
void unregisterShownSpoiler(FullMsgId id);
void hideShownSpoilers(); void hideShownSpoilers();
using MegagroupParticipant = std::tuple< using MegagroupParticipant = std::tuple<
@ -956,7 +955,7 @@ private:
rpl::event_stream<InviteToCall> _invitesToCalls; rpl::event_stream<InviteToCall> _invitesToCalls;
base::flat_map<uint64, base::flat_set<not_null<UserData*>>> _invitedToCallUsers; base::flat_map<uint64, base::flat_set<not_null<UserData*>>> _invitedToCallUsers;
base::flat_set<not_null<HistoryItem*>> _shownSpoilers; base::flat_set<not_null<ViewElement*>> _shownSpoilers;
History *_topPromoted = nullptr; History *_topPromoted = nullptr;

View file

@ -220,68 +220,71 @@ struct StickerSetIdentifier {
} }
}; };
enum class MessageFlag : uint32 { enum class MessageFlag : uint64 {
HideEdited = (1U << 0), HideEdited = (1ULL << 0),
Legacy = (1U << 1), Legacy = (1ULL << 1),
HasReplyMarkup = (1U << 2), HasReplyMarkup = (1ULL << 2),
HasFromId = (1U << 3), HasFromId = (1ULL << 3),
HasPostAuthor = (1U << 4), HasPostAuthor = (1ULL << 4),
HasViews = (1U << 5), HasViews = (1ULL << 5),
HasReplyInfo = (1U << 6), HasReplyInfo = (1ULL << 6),
CanViewReactions = (1U << 7), CanViewReactions = (1ULL << 7),
AdminLogEntry = (1U << 8), AdminLogEntry = (1ULL << 8),
Post = (1U << 9), Post = (1ULL << 9),
Silent = (1U << 10), Silent = (1ULL << 10),
Outgoing = (1U << 11), Outgoing = (1ULL << 11),
Pinned = (1U << 12), Pinned = (1ULL << 12),
MediaIsUnread = (1U << 13), MediaIsUnread = (1ULL << 13),
HasUnreadReaction = (1U << 14), HasUnreadReaction = (1ULL << 14),
MentionsMe = (1U << 15), MentionsMe = (1ULL << 15),
IsOrWasScheduled = (1U << 16), IsOrWasScheduled = (1ULL << 16),
NoForwards = (1U << 17), NoForwards = (1ULL << 17),
// Needs to return back to inline mode. // Needs to return back to inline mode.
HasSwitchInlineButton = (1U << 18), HasSwitchInlineButton = (1ULL << 18),
// For "shared links" indexing. // For "shared links" indexing.
HasTextLinks = (1U << 19), HasTextLinks = (1ULL << 19),
// Group / channel create or migrate service message. // Group / channel create or migrate service message.
IsGroupEssential = (1U << 20), IsGroupEssential = (1ULL << 20),
// Edited media is generated on the client // Edited media is generated on the client
// and should not update media from server. // and should not update media from server.
IsLocalUpdateMedia = (1U << 21), IsLocalUpdateMedia = (1ULL << 21),
// Sent from inline bot, need to re-set media when sent. // Sent from inline bot, need to re-set media when sent.
FromInlineBot = (1U << 22), FromInlineBot = (1ULL << 22),
// Generated on the client side and should be unread. // Generated on the client side and should be unread.
ClientSideUnread = (1U << 23), ClientSideUnread = (1ULL << 23),
// In a supergroup. // In a supergroup.
HasAdminBadge = (1U << 24), HasAdminBadge = (1ULL << 24),
// Outgoing message that is being sent. // Outgoing message that is being sent.
BeingSent = (1U << 25), BeingSent = (1ULL << 25),
// Outgoing message and failed to be sent. // Outgoing message and failed to be sent.
SendingFailed = (1U << 26), SendingFailed = (1ULL << 26),
// No media and only a several emoji or an only custom emoji text. // No media and only a several emoji or an only custom emoji text.
SpecialOnlyEmoji = (1U << 27), SpecialOnlyEmoji = (1ULL << 27),
// Message existing in the message history. // Message existing in the message history.
HistoryEntry = (1U << 28), HistoryEntry = (1ULL << 28),
// Local message, not existing on the server. // Local message, not existing on the server.
Local = (1U << 29), Local = (1ULL << 29),
// Fake message for some UI element. // Fake message for some UI element.
FakeHistoryItem = (1U << 30), FakeHistoryItem = (1ULL << 30),
// Contact sign-up message, notification should be skipped for Silent. // Contact sign-up message, notification should be skipped for Silent.
IsContactSignUp = (1U << 31), IsContactSignUp = (1ULL << 31),
// Optimization for item text custom emoji repainting.
CustomEmojiRepainting = (1ULL << 32),
}; };
inline constexpr bool is_flag_type(MessageFlag) { return true; } inline constexpr bool is_flag_type(MessageFlag) { return true; }
using MessageFlags = base::flags<MessageFlag>; using MessageFlags = base::flags<MessageFlag>;

View file

@ -724,7 +724,7 @@ void GenerateItems(
history->nextNonHistoryEntryId(), history->nextNonHistoryEntryId(),
MessageFlag::AdminLogEntry, MessageFlag::AdminLogEntry,
date, date,
message, std::move(message),
peerToUser(from->id), peerToUser(from->id),
photo)); photo));
}; };
@ -1060,7 +1060,7 @@ void GenerateItems(
history->nextNonHistoryEntryId(), history->nextNonHistoryEntryId(),
MessageFlag::AdminLogEntry, MessageFlag::AdminLogEntry,
date, date,
message, std::move(message),
peerToUser(from->id))); peerToUser(from->id)));
} }
}; };
@ -1137,7 +1137,7 @@ void GenerateItems(
history->nextNonHistoryEntryId(), history->nextNonHistoryEntryId(),
MessageFlag::AdminLogEntry, MessageFlag::AdminLogEntry,
date, date,
message, std::move(message),
peerToUser(from->id))); peerToUser(from->id)));
} }
}; };
@ -1233,7 +1233,7 @@ void GenerateItems(
history->nextNonHistoryEntryId(), history->nextNonHistoryEntryId(),
MessageFlag::AdminLogEntry, MessageFlag::AdminLogEntry,
date, date,
message, std::move(message),
peerToUser(from->id))); peerToUser(from->id)));
}; };
@ -1308,7 +1308,7 @@ void GenerateItems(
history->nextNonHistoryEntryId(), history->nextNonHistoryEntryId(),
MessageFlag::AdminLogEntry, MessageFlag::AdminLogEntry,
date, date,
message, std::move(message),
peerToUser(from->id), peerToUser(from->id),
nullptr)); nullptr));
}; };

View file

@ -2312,7 +2312,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
const auto media = (view ? view->media() : nullptr); const auto media = (view ? view->media() : nullptr);
const auto mediaHasTextForCopy = media && media->hasTextForCopy(); const auto mediaHasTextForCopy = media && media->hasTextForCopy();
if (const auto document = media ? media->getDocument() : nullptr) { if (const auto document = media ? media->getDocument() : nullptr) {
if (!item->isIsolatedEmoji() && document->sticker()) { if (!view->isIsolatedEmoji() && document->sticker()) {
if (document->sticker()->set) { if (document->sticker()->set) {
_menu->addAction(document->isStickerSetInstalled() ? tr::lng_context_pack_info(tr::now) : tr::lng_context_pack_add(tr::now), [=] { _menu->addAction(document->isStickerSetInstalled() ? tr::lng_context_pack_info(tr::now) : tr::lng_context_pack_add(tr::now), [=] {
showStickerPackInfo(document); showStickerPackInfo(document);

View file

@ -376,8 +376,8 @@ void HistoryItem::invalidateChatListEntry() {
} }
void HistoryItem::customEmojiRepaint() { void HistoryItem::customEmojiRepaint() {
if (!_customEmojiRepaintScheduled) { if (!(_flags & MessageFlag::CustomEmojiRepainting)) {
_customEmojiRepaintScheduled = true; _flags |= MessageFlag::CustomEmojiRepainting;
history()->owner().requestItemRepaint(this); history()->owner().requestItemRepaint(this);
} }
} }
@ -1250,7 +1250,7 @@ MessageGroupId HistoryItem::groupId() const {
} }
bool HistoryItem::isEmpty() const { bool HistoryItem::isEmpty() const {
return _text.isEmpty() return _text.empty()
&& !_media && !_media
&& !Has<HistoryMessageLogEntryOriginal>(); && !Has<HistoryMessageLogEntryOriginal>();
} }
@ -1260,7 +1260,7 @@ TextWithEntities HistoryItem::notificationText() const {
if (_media && !isService()) { if (_media && !isService()) {
return _media->notificationText(); return _media->notificationText();
} else if (!emptyText()) { } else if (!emptyText()) {
return _text.toTextWithEntities(); return _text;
} }
return TextWithEntities(); return TextWithEntities();
}(); }();
@ -1271,14 +1271,17 @@ TextWithEntities HistoryItem::notificationText() const {
Ui::kQEllipsis); Ui::kQEllipsis);
} }
const std::vector<ClickHandlerPtr> &HistoryItem::customTextLinks() const {
static const auto result = std::vector<ClickHandlerPtr>();
return result;
}
ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const { ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const {
auto result = [&]() -> ItemPreview { auto result = [&]() -> ItemPreview {
if (_media) { if (_media) {
return _media->toPreview(options); return _media->toPreview(options);
} else if (!emptyText()) { } else if (!emptyText()) {
return { return { .text = _text };
.text = _text.toTextWithEntities()
};
} }
return {}; return {};
}(); }();
@ -1325,14 +1328,6 @@ TextWithEntities HistoryItem::inReplyText() const {
}).text; }).text;
} }
Ui::Text::IsolatedEmoji HistoryItem::isolatedEmoji() const {
return {};
}
Ui::Text::OnlyCustomEmoji HistoryItem::onlyCustomEmoji() const {
return {};
}
HistoryItem::~HistoryItem() { HistoryItem::~HistoryItem() {
applyTTL(0); applyTTL(0);
} }
@ -1470,14 +1465,14 @@ not_null<HistoryItem*> HistoryItem::Create(
data.vdate().v, data.vdate().v,
data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0)); data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0));
} else if (checked == MediaCheckResult::Empty) { } else if (checked == MediaCheckResult::Empty) {
const auto text = HistoryService::PreparedText{ auto text = HistoryService::PreparedText{
tr::lng_message_empty(tr::now, Ui::Text::WithEntities) tr::lng_message_empty(tr::now, Ui::Text::WithEntities)
}; };
return history->makeServiceMessage( return history->makeServiceMessage(
id, id,
FlagsFromMTP(id, data.vflags().v, localFlags), FlagsFromMTP(id, data.vflags().v, localFlags),
data.vdate().v, data.vdate().v,
text, std::move(text),
data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0)); data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0));
} else if (checked == MediaCheckResult::HasTimeToLive) { } else if (checked == MediaCheckResult::HasTimeToLive) {
return history->makeServiceMessage(id, data, localFlags); return history->makeServiceMessage(id, data, localFlags);
@ -1489,9 +1484,12 @@ not_null<HistoryItem*> HistoryItem::Create(
} }
return history->makeServiceMessage(id, data, localFlags); return history->makeServiceMessage(id, data, localFlags);
}, [&](const MTPDmessageEmpty &data) -> HistoryItem* { }, [&](const MTPDmessageEmpty &data) -> HistoryItem* {
const auto text = HistoryService::PreparedText{ return history->makeServiceMessage(
tr::lng_message_empty(tr::now, Ui::Text::WithEntities) id,
}; localFlags,
return history->makeServiceMessage(id, localFlags, TimeId(0), text); TimeId(0),
HistoryService::PreparedText{ tr::lng_message_empty(
tr::now,
Ui::Text::WithEntities) });
}); });
} }

View file

@ -195,14 +195,6 @@ public:
[[nodiscard]] bool isGroupMigrate() const { [[nodiscard]] bool isGroupMigrate() const {
return isGroupEssential() && isEmpty(); return isGroupEssential() && isEmpty();
} }
[[nodiscard]] bool isIsolatedEmoji() const {
return (_flags & MessageFlag::SpecialOnlyEmoji)
&& _text.isIsolatedEmoji();
}
[[nodiscard]] bool isOnlyCustomEmoji() const {
return (_flags & MessageFlag::SpecialOnlyEmoji)
&& _text.isOnlyCustomEmoji();
}
[[nodiscard]] bool hasViews() const { [[nodiscard]] bool hasViews() const {
return _flags & MessageFlag::HasViews; return _flags & MessageFlag::HasViews;
} }
@ -322,8 +314,6 @@ public:
[[nodiscard]] virtual ItemPreview toPreview( [[nodiscard]] virtual ItemPreview toPreview(
ToPreviewOptions options) const; ToPreviewOptions options) const;
[[nodiscard]] virtual TextWithEntities inReplyText() const; [[nodiscard]] virtual TextWithEntities inReplyText() const;
[[nodiscard]] virtual Ui::Text::IsolatedEmoji isolatedEmoji() const;
[[nodiscard]] virtual Ui::Text::OnlyCustomEmoji onlyCustomEmoji() const;
[[nodiscard]] virtual TextWithEntities originalText() const { [[nodiscard]] virtual TextWithEntities originalText() const {
return TextWithEntities(); return TextWithEntities();
} }
@ -331,6 +321,8 @@ public:
-> TextWithEntities { -> TextWithEntities {
return TextWithEntities(); return TextWithEntities();
} }
[[nodiscard]] virtual auto customTextLinks() const
-> const std::vector<ClickHandlerPtr> &;
[[nodiscard]] virtual TextForMimeData clipboardText() const { [[nodiscard]] virtual TextForMimeData clipboardText() const {
return TextForMimeData(); return TextForMimeData();
} }
@ -356,11 +348,9 @@ public:
virtual void setRealId(MsgId newId); virtual void setRealId(MsgId newId);
virtual void incrementReplyToTopCounter() { virtual void incrementReplyToTopCounter() {
} }
virtual void hideSpoilers() {
}
[[nodiscard]] bool emptyText() const { [[nodiscard]] bool emptyText() const {
return _text.isEmpty(); return _text.empty();
} }
[[nodiscard]] bool canPin() const; [[nodiscard]] bool canPin() const;
@ -414,9 +404,6 @@ public:
[[nodiscard]] bool computeDropForwardedInfo() const; [[nodiscard]] bool computeDropForwardedInfo() const;
virtual void setText(const TextWithEntities &textWithEntities) { virtual void setText(const TextWithEntities &textWithEntities) {
} }
[[nodiscard]] virtual bool textHasLinks() const {
return false;
}
[[nodiscard]] MsgId replyToId() const; [[nodiscard]] MsgId replyToId() const;
[[nodiscard]] MsgId replyToTop() const; [[nodiscard]] MsgId replyToTop() const;
@ -494,10 +481,7 @@ protected:
void applyTTL(const MTPDmessageService &data); void applyTTL(const MTPDmessageService &data);
void applyTTL(TimeId destroyAt); void applyTTL(TimeId destroyAt);
Ui::Text::String _text = { st::msgMinWidth }; TextWithEntities _text;
int _textWidth = -1;
int _textHeight = 0;
bool _customEmojiRepaintScheduled = false;
struct SavedMediaData { struct SavedMediaData {
TextWithEntities text; TextWithEntities text;

View file

@ -15,7 +15,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_unread_things.h" #include "history/history_unread_things.h"
#include "history/view/history_view_service_message.h" #include "history/view/history_view_service_message.h"
#include "history/view/history_view_context_menu.h" // CopyPostLink. #include "history/view/history_view_context_menu.h" // CopyPostLink.
#include "history/view/history_view_spoiler_click_handler.h"
#include "history/view/media/history_view_media.h" // AddTimestampLinks. #include "history/view/media/history_view_media.h" // AddTimestampLinks.
#include "chat_helpers/stickers_emoji_pack.h" #include "chat_helpers/stickers_emoji_pack.h"
#include "main/main_session.h" #include "main/main_session.h"
@ -24,8 +23,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/share_box.h" #include "boxes/share_box.h"
#include "ui/text/text_isolated_emoji.h" #include "ui/text/text_isolated_emoji.h"
#include "ui/text/format_values.h" #include "ui/text/format_values.h"
#include "ui/item_text_options.h"
#include "core/ui_integration.h"
#include "storage/storage_shared_media.h" #include "storage/storage_shared_media.h"
#include "mtproto/mtproto_config.h" #include "mtproto/mtproto_config.h"
#include "data/notify/data_notify_settings.h" #include "data/notify/data_notify_settings.h"
@ -100,10 +97,7 @@ namespace {
[[nodiscard]] TextWithEntities EnsureNonEmpty( [[nodiscard]] TextWithEntities EnsureNonEmpty(
const TextWithEntities &text = TextWithEntities()) { const TextWithEntities &text = TextWithEntities()) {
if (!text.text.isEmpty()) { return !text.text.isEmpty() ? text : TextWithEntities{ u":-("_q };
return text;
}
return { QString::fromUtf8(":-("), EntitiesInText() };
} }
} // namespace } // namespace
@ -384,7 +378,7 @@ HistoryMessage::HistoryMessage(
_media = std::make_unique<Data::MediaCall>( _media = std::make_unique<Data::MediaCall>(
this, this,
Data::ComputeCallData(data)); Data::ComputeCallData(data));
setEmptyText(); setTextValue({});
}, [](const auto &) { }, [](const auto &) {
Unexpected("Service message action type in HistoryMessage."); Unexpected("Service message action type in HistoryMessage.");
}); });
@ -607,7 +601,7 @@ HistoryMessage::HistoryMessage(
std::move(markup)); std::move(markup));
_media = std::make_unique<Data::MediaGame>(this, game); _media = std::make_unique<Data::MediaGame>(this, game);
setEmptyText(); setTextValue({});
} }
HistoryMessage::HistoryMessage( HistoryMessage::HistoryMessage(
@ -863,10 +857,6 @@ void HistoryMessage::setCommentsItemId(FullMsgId id) {
} }
} }
void HistoryMessage::hideSpoilers() {
HistoryView::HideSpoilers(_text);
}
bool HistoryMessage::updateDependencyItem() { bool HistoryMessage::updateDependencyItem() {
if (const auto reply = Get<HistoryMessageReply>()) { if (const auto reply = Get<HistoryMessageReply>()) {
const auto documentId = reply->replyToDocumentId; const auto documentId = reply->replyToDocumentId;
@ -875,7 +865,7 @@ bool HistoryMessage::updateDependencyItem() {
const auto mediaIdChanged = (documentId != reply->replyToDocumentId) const auto mediaIdChanged = (documentId != reply->replyToDocumentId)
|| (webpageId != reply->replyToWebPageId); || (webpageId != reply->replyToWebPageId);
if (mediaIdChanged && generateLocalEntitiesByReply()) { if (mediaIdChanged && generateLocalEntitiesByReply()) {
reapplyText(); history()->owner().requestItemTextRefresh(this);
} }
return result; return result;
} }
@ -1315,7 +1305,7 @@ void HistoryMessage::applyEdition(const MTPDmessageService &message) {
const auto wasGrouped = history()->owner().groups().isGrouped(this); const auto wasGrouped = history()->owner().groups().isGrouped(this);
setReplyMarkup({}); setReplyMarkup({});
refreshMedia(nullptr); refreshMedia(nullptr);
setEmptyText(); setTextValue({});
changeViewsCount(-1); changeViewsCount(-1);
setForwardsCount(-1); setForwardsCount(-1);
if (wasGrouped) { if (wasGrouped) {
@ -1337,14 +1327,13 @@ void HistoryMessage::applyEdition(const MTPMessageExtendedMedia &media) {
void HistoryMessage::updateSentContent( void HistoryMessage::updateSentContent(
const TextWithEntities &textWithEntities, const TextWithEntities &textWithEntities,
const MTPMessageMedia *media) { const MTPMessageMedia *media) {
const auto isolated = isolatedEmoji();
setText(textWithEntities); setText(textWithEntities);
if (_flags & MessageFlag::FromInlineBot) { if (_flags & MessageFlag::FromInlineBot) {
if (!media || !_media || !_media->updateInlineResultMedia(*media)) { if (!media || !_media || !_media->updateInlineResultMedia(*media)) {
refreshSentMedia(media); refreshSentMedia(media);
} }
_flags &= ~MessageFlag::FromInlineBot; _flags &= ~MessageFlag::FromInlineBot;
} else if (media || _media || !isolated || isolated != isolatedEmoji()) { } else if (media || _media) {
if (!media || !_media || !_media->updateSentMedia(*media)) { if (!media || !_media || !_media->updateSentMedia(*media)) {
refreshSentMedia(media); refreshSentMedia(media);
} }
@ -1509,65 +1498,16 @@ void HistoryMessage::setText(const TextWithEntities &textWithEntities) {
break; break;
} }
} }
setTextValue((_media && _media->consumeMessageText(textWithEntities))
if (_media && _media->consumeMessageText(textWithEntities)) { ? TextWithEntities()
setEmptyText(); : std::move(textWithEntities));
return;
}
clearSpecialOnlyEmoji();
const auto context = Core::MarkedTextContext{
.session = &history()->session(),
.customEmojiRepaint = [=] { customEmojiRepaint(); },
};
_text.setMarkedText(
st::messageTextStyle,
withLocalEntities(textWithEntities),
Ui::ItemTextOptions(this),
context);
HistoryView::FillTextWithAnimatedSpoilers(_text);
if (!textWithEntities.text.isEmpty() && _text.isEmpty()) {
// If server has allowed some text that we've trim-ed entirely,
// just replace it with something so that UI won't look buggy.
_text.setMarkedText(
st::messageTextStyle,
EnsureNonEmpty(),
Ui::ItemTextOptions(this));
} else if (!_media) {
checkSpecialOnlyEmoji();
}
_textWidth = -1;
_textHeight = 0;
} }
void HistoryMessage::reapplyText() { void HistoryMessage::setTextValue(TextWithEntities text) {
setText(originalText()); const auto had = !_text.empty();
history()->owner().requestItemResize(this); _text = std::move(text);
} if (had) {
history()->owner().requestItemTextRefresh(this);
void HistoryMessage::setEmptyText() {
clearSpecialOnlyEmoji();
_text.setMarkedText(
st::messageTextStyle,
{ QString(), EntitiesInText() },
Ui::ItemTextOptions(this));
_textWidth = -1;
_textHeight = 0;
}
void HistoryMessage::clearSpecialOnlyEmoji() {
if (!(_flags & MessageFlag::SpecialOnlyEmoji)) {
return;
}
history()->session().emojiStickersPack().remove(this);
_flags &= ~MessageFlag::SpecialOnlyEmoji;
}
void HistoryMessage::checkSpecialOnlyEmoji() {
if (history()->session().emojiStickersPack().add(this)) {
_flags |= MessageFlag::SpecialOnlyEmoji;
} }
} }
@ -1616,19 +1556,8 @@ void HistoryMessage::setReplyMarkup(HistoryMessageMarkupData &&markup) {
} }
} }
Ui::Text::IsolatedEmoji HistoryMessage::isolatedEmoji() const {
return _text.toIsolatedEmoji();
}
Ui::Text::OnlyCustomEmoji HistoryMessage::onlyCustomEmoji() const {
return _text.toOnlyCustomEmoji();
}
TextWithEntities HistoryMessage::originalText() const { TextWithEntities HistoryMessage::originalText() const {
if (emptyText()) { return _text;
return { QString(), EntitiesInText() };
}
return _text.toTextWithEntities();
} }
TextWithEntities HistoryMessage::originalTextWithLocalEntities() const { TextWithEntities HistoryMessage::originalTextWithLocalEntities() const {
@ -1636,14 +1565,7 @@ TextWithEntities HistoryMessage::originalTextWithLocalEntities() const {
} }
TextForMimeData HistoryMessage::clipboardText() const { TextForMimeData HistoryMessage::clipboardText() const {
if (emptyText()) { return TextForMimeData::WithExpandedLinks(_text);
return TextForMimeData();
}
return _text.toTextForMimeData();
}
bool HistoryMessage::textHasLinks() const {
return emptyText() ? false : _text.hasLinks();
} }
bool HistoryMessage::changeViewsCount(int count) { bool HistoryMessage::changeViewsCount(int count) {
@ -1923,7 +1845,7 @@ void HistoryMessage::dependencyItemRemoved(HistoryItem *dependency) {
reply->itemRemoved(this, dependency); reply->itemRemoved(this, dependency);
if (documentId != reply->replyToDocumentId if (documentId != reply->replyToDocumentId
&& generateLocalEntitiesByReply()) { && generateLocalEntitiesByReply()) {
reapplyText(); history()->owner().requestItemTextRefresh(this);
} }
} }
} }

View file

@ -181,13 +181,10 @@ public:
[[nodiscard]] Storage::SharedMediaTypesMask sharedMediaTypes() const override; [[nodiscard]] Storage::SharedMediaTypesMask sharedMediaTypes() const override;
void setText(const TextWithEntities &textWithEntities) override; void setText(const TextWithEntities &textWithEntities) override;
[[nodiscard]] Ui::Text::IsolatedEmoji isolatedEmoji() const override;
[[nodiscard]] Ui::Text::OnlyCustomEmoji onlyCustomEmoji() const override;
[[nodiscard]] TextWithEntities originalText() const override; [[nodiscard]] TextWithEntities originalText() const override;
[[nodiscard]] auto originalTextWithLocalEntities() const [[nodiscard]] auto originalTextWithLocalEntities() const
-> TextWithEntities override; -> TextWithEntities override;
[[nodiscard]] TextForMimeData clipboardText() const override; [[nodiscard]] TextForMimeData clipboardText() const override;
[[nodiscard]] bool textHasLinks() const override;
[[nodiscard]] int viewsCount() const override; [[nodiscard]] int viewsCount() const override;
[[nodiscard]] int repliesCount() const override; [[nodiscard]] int repliesCount() const override;
@ -212,7 +209,6 @@ public:
[[nodiscard]] MsgId dependencyMsgId() const override { [[nodiscard]] MsgId dependencyMsgId() const override {
return replyToId(); return replyToId();
} }
void hideSpoilers() override;
void applySentMessage(const MTPDmessage &data) override; void applySentMessage(const MTPDmessage &data) override;
void applySentMessage( void applySentMessage(
@ -227,7 +223,7 @@ public:
~HistoryMessage(); ~HistoryMessage();
private: private:
void setEmptyText(); void setTextValue(TextWithEntities text);
[[nodiscard]] bool isTooOldForEdit(TimeId now) const; [[nodiscard]] bool isTooOldForEdit(TimeId now) const;
[[nodiscard]] bool isLegacyMessage() const { [[nodiscard]] bool isLegacyMessage() const {
return _flags & MessageFlag::Legacy; return _flags & MessageFlag::Legacy;
@ -235,9 +231,6 @@ private:
[[nodiscard]] bool checkCommentsLinkedChat(ChannelId id) const; [[nodiscard]] bool checkCommentsLinkedChat(ChannelId id) const;
void clearSpecialOnlyEmoji();
void checkSpecialOnlyEmoji();
// For an invoice button we replace the button text with a "Receipt" key. // For an invoice button we replace the button text with a "Receipt" key.
// It should show the receipt for the payed invoice. Still let mobile apps do that. // It should show the receipt for the payed invoice. Still let mobile apps do that.
void replaceBuyWithReceiptInMarkup(); void replaceBuyWithReceiptInMarkup();
@ -271,7 +264,6 @@ private:
[[nodiscard]] bool generateLocalEntitiesByReply() const; [[nodiscard]] bool generateLocalEntitiesByReply() const;
[[nodiscard]] TextWithEntities withLocalEntities( [[nodiscard]] TextWithEntities withLocalEntities(
const TextWithEntities &textWithEntities) const; const TextWithEntities &textWithEntities) const;
void reapplyText();
[[nodiscard]] bool checkRepliesPts( [[nodiscard]] bool checkRepliesPts(
const HistoryMessageRepliesData &data) const; const HistoryMessageRepliesData &data) const;

View file

@ -20,7 +20,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item_components.h" #include "history/history_item_components.h"
#include "history/view/history_view_service_message.h" #include "history/view/history_view_service_message.h"
#include "history/view/history_view_item_preview.h" #include "history/view/history_view_item_preview.h"
#include "history/view/history_view_spoiler_click_handler.h"
#include "data/data_folder.h" #include "data/data_folder.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_media_types.h" #include "data/data_media_types.h"
@ -32,7 +31,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_group_call.h" // Data::GroupCall::id(). #include "data/data_group_call.h" // Data::GroupCall::id().
#include "core/application.h" #include "core/application.h"
#include "core/click_handler_types.h" #include "core/click_handler_types.h"
#include "core/ui_integration.h"
#include "base/unixtime.h" #include "base/unixtime.h"
#include "base/timer_rpl.h" #include "base/timer_rpl.h"
#include "calls/calls_instance.h" // Core::App().calls().joinGroupCall. #include "calls/calls_instance.h" // Core::App().calls().joinGroupCall.
@ -42,7 +40,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/storage_shared_media.h" #include "storage/storage_shared_media.h"
#include "payments/payments_checkout_process.h" // CheckoutProcess::Start. #include "payments/payments_checkout_process.h" // CheckoutProcess::Start.
#include "ui/text/format_values.h" #include "ui/text/format_values.h"
#include "ui/text/text_options.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
namespace { namespace {
@ -636,8 +633,8 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
return result; return result;
}; };
const auto messageText = action.match([&]( setServiceText(action.match([&](
const MTPDmessageActionChatAddUser &data) { const MTPDmessageActionChatAddUser &data) {
return prepareChatAddUserText(data); return prepareChatAddUserText(data);
}, [&](const MTPDmessageActionChatJoinedByLink &data) { }, [&](const MTPDmessageActionChatJoinedByLink &data) {
return prepareChatJoinedByLink(data); return prepareChatJoinedByLink(data);
@ -714,9 +711,7 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
return PreparedText{ return PreparedText{
tr::lng_message_empty(tr::now, Ui::Text::WithEntities) tr::lng_message_empty(tr::now, Ui::Text::WithEntities)
}; };
}); }));
setServiceText(messageText);
// Additional information. // Additional information.
applyAction(action); applyAction(action);
@ -1219,11 +1214,11 @@ HistoryService::HistoryService(
MsgId id, MsgId id,
MessageFlags flags, MessageFlags flags,
TimeId date, TimeId date,
const PreparedText &message, PreparedText &&message,
PeerId from, PeerId from,
PhotoData *photo) PhotoData *photo)
: HistoryItem(history, id, flags, date, from) { : HistoryItem(history, id, flags, date, from) {
setServiceText(message); setServiceText(std::move(message));
if (photo) { if (photo) {
_media = std::make_unique<Data::MediaPhoto>( _media = std::make_unique<Data::MediaPhoto>(
this, this,
@ -1279,28 +1274,13 @@ ClickHandlerPtr HistoryService::fromLink() const {
return _from->createOpenLink(); return _from->createOpenLink();
} }
void HistoryService::setServiceText(const PreparedText &prepared) { void HistoryService::setServiceText(PreparedText &&prepared) {
const auto context = Core::MarkedTextContext{ const auto had = !_text.empty();
.session = &history()->session(), _text = std::move(prepared.text);
.customEmojiRepaint = [=] { customEmojiRepaint(); }, _textLinks = std::move(prepared.links);
}; if (had) {
_text.setMarkedText( history()->owner().requestItemTextRefresh(this);
st::serviceTextStyle,
prepared.text,
Ui::ItemTextServiceOptions(),
context);
HistoryView::FillTextWithAnimatedSpoilers(_text);
auto linkIndex = 0;
for (const auto &link : prepared.links) {
// Link indices start with 1.
_text.setLink(++linkIndex, link);
} }
_textWidth = -1;
_textHeight = 0;
}
void HistoryService::hideSpoilers() {
HistoryView::HideSpoilers(_text);
} }
void HistoryService::markMediaAsReadHook() { void HistoryService::markMediaAsReadHook() {
@ -1506,6 +1486,10 @@ void HistoryService::createFromMtp(const MTPDmessageService &message) {
setMessageByAction(message.vaction()); setMessageByAction(message.vaction());
} }
const std::vector<ClickHandlerPtr> &HistoryService::customTextLinks() const {
return _textLinks;
}
void HistoryService::applyEdition(const MTPDmessageService &message) { void HistoryService::applyEdition(const MTPDmessageService &message) {
clearDependency(); clearDependency();
UpdateComponents(0); UpdateComponents(0);
@ -1525,8 +1509,6 @@ void HistoryService::removeMedia() {
if (!_media) return; if (!_media) return;
_media.reset(); _media.reset();
_textWidth = -1;
_textHeight = 0;
history()->owner().requestItemResize(this); history()->owner().requestItemResize(this);
} }
@ -1552,7 +1534,7 @@ void HistoryService::updateDependentText() {
} }
void HistoryService::updateText(PreparedText &&text) { void HistoryService::updateText(PreparedText &&text) {
setServiceText(text); setServiceText(std::move(text));
history()->owner().requestItemResize(this); history()->owner().requestItemResize(this);
invalidateChatListEntry(); invalidateChatListEntry();
history()->owner().updateDependentMessages(this); history()->owner().updateDependentMessages(this);

View file

@ -77,7 +77,7 @@ class HistoryService : public HistoryItem {
public: public:
struct PreparedText { struct PreparedText {
TextWithEntities text; TextWithEntities text;
QList<ClickHandlerPtr> links; std::vector<ClickHandlerPtr> links;
}; };
HistoryService( HistoryService(
@ -95,7 +95,7 @@ public:
MsgId id, MsgId id,
MessageFlags flags, MessageFlags flags,
TimeId date, TimeId date,
const PreparedText &message, PreparedText &&message,
PeerId from = 0, PeerId from = 0,
PhotoData *photo = nullptr); PhotoData *photo = nullptr);
@ -113,6 +113,8 @@ public:
return true; return true;
} }
const std::vector<ClickHandlerPtr> &customTextLinks() const override;
void applyEdition(const MTPDmessageService &message) override; void applyEdition(const MTPDmessageService &message) override;
crl::time getSelfDestructIn(crl::time now) override; crl::time getSelfDestructIn(crl::time now) override;
@ -131,9 +133,7 @@ public:
not_null<HistoryView::ElementDelegate*> delegate, not_null<HistoryView::ElementDelegate*> delegate,
HistoryView::Element *replacing = nullptr) override; HistoryView::Element *replacing = nullptr) override;
void setServiceText(const PreparedText &prepared); void setServiceText(PreparedText &&prepared);
void hideSpoilers() override;
~HistoryService(); ~HistoryService();
@ -187,6 +187,8 @@ private:
friend class HistoryView::Service; friend class HistoryView::Service;
std::vector<ClickHandlerPtr> _textLinks;
}; };
[[nodiscard]] not_null<HistoryService*> GenerateJoinedMessage( [[nodiscard]] not_null<HistoryService*> GenerateJoinedMessage(

View file

@ -21,11 +21,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/reactions/history_view_reactions_button.h" #include "history/view/reactions/history_view_reactions_button.h"
#include "history/view/reactions/history_view_reactions.h" #include "history/view/reactions/history_view_reactions.h"
#include "history/view/history_view_cursor_state.h" #include "history/view/history_view_cursor_state.h"
#include "history/view/history_view_spoiler_click_handler.h"
#include "history/history.h" #include "history/history.h"
#include "base/unixtime.h" #include "base/unixtime.h"
#include "core/application.h" #include "core/application.h"
#include "core/core_settings.h" #include "core/core_settings.h"
#include "core/click_handler_types.h" #include "core/click_handler_types.h"
#include "core/ui_integration.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "main/main_domain.h" #include "main/main_domain.h"
#include "chat_helpers/stickers_emoji_pack.h" #include "chat_helpers/stickers_emoji_pack.h"
@ -34,6 +36,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/chat/chat_style.h" #include "ui/chat/chat_style.h"
#include "ui/toast/toast.h" #include "ui/toast/toast.h"
#include "ui/toasts/common_toasts.h" #include "ui/toasts/common_toasts.h"
#include "ui/text/text_options.h"
#include "ui/item_text_options.h"
#include "ui/painter.h" #include "ui/painter.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_groups.h" #include "data/data_groups.h"
@ -355,11 +359,16 @@ void DateBadge::paint(
Element::Element( Element::Element(
not_null<ElementDelegate*> delegate, not_null<ElementDelegate*> delegate,
not_null<HistoryItem*> data, not_null<HistoryItem*> data,
Element *replacing) Element *replacing,
Flag serviceFlag)
: _delegate(delegate) : _delegate(delegate)
, _data(data) , _data(data)
, _dateTime(IsItemScheduledUntilOnline(data)
? QDateTime()
: ItemDateTime(data))
, _text(st::msgMinWidth)
, _isScheduledUntilOnline(IsItemScheduledUntilOnline(data)) , _isScheduledUntilOnline(IsItemScheduledUntilOnline(data))
, _dateTime(_isScheduledUntilOnline ? QDateTime() : ItemDateTime(data)) , _flags(serviceFlag | Flag::NeedsResize)
, _context(delegate->elementContext()) { , _context(delegate->elementContext()) {
history()->owner().registerItemView(this); history()->owner().registerItemView(this);
refreshMedia(replacing); refreshMedia(replacing);
@ -403,16 +412,36 @@ void Element::setY(int y) {
void Element::refreshDataIdHook() { void Element::refreshDataIdHook() {
} }
void Element::clearSpecialOnlyEmoji() {
if (!(_flags & Flag::SpecialOnlyEmoji)) {
return;
}
history()->session().emojiStickersPack().remove(this);
_flags &= ~Flag::SpecialOnlyEmoji;
}
void Element::checkSpecialOnlyEmoji() {
if (history()->session().emojiStickersPack().add(this)) {
_flags |= Flag::SpecialOnlyEmoji;
}
}
void Element::hideSpoilers() {
if (_text.hasSpoilers()) {
_text.setSpoilerRevealed(false, anim::type::instant);
}
}
void Element::customEmojiRepaint() { void Element::customEmojiRepaint() {
if (!_customEmojiRepaintScheduled) { if (!(_flags & Flag::CustomEmojiRepainting)) {
_customEmojiRepaintScheduled = true; _flags |= Flag::CustomEmojiRepainting;
history()->owner().requestViewRepaint(this); history()->owner().requestViewRepaint(this);
} }
} }
void Element::clearCustomEmojiRepaint() const { void Element::clearCustomEmojiRepaint() const {
_customEmojiRepaintScheduled = false; _flags &= ~Flag::CustomEmojiRepainting;
data()->_customEmojiRepaintScheduled = false; data()->_flags &= ~MessageFlag::CustomEmojiRepainting;
} }
void Element::prepareCustomEmojiPaint( void Element::prepareCustomEmojiPaint(
@ -548,35 +577,33 @@ void Element::refreshMedia(Element *replacing) {
_flags &= ~Flag::HiddenByGroup; _flags &= ~Flag::HiddenByGroup;
const auto item = data(); const auto item = data();
const auto media = item->media(); if (const auto media = item->media()) {
if (media && media->canBeGrouped()) { if (media->canBeGrouped()) {
if (const auto group = history()->owner().groups().find(item)) { if (const auto group = history()->owner().groups().find(item)) {
if (group->items.front() != item) { if (group->items.front() != item) {
_media = nullptr; _media = nullptr;
_flags |= Flag::HiddenByGroup; _flags |= Flag::HiddenByGroup;
} else { } else {
_media = std::make_unique<GroupedMedia>( _media = std::make_unique<GroupedMedia>(
this, this,
group->items); group->items);
if (!pendingResize()) { if (!pendingResize()) {
history()->owner().requestViewResize(this); history()->owner().requestViewResize(this);
}
} }
return;
} }
return;
} }
}
const auto session = &history()->session();
if (const auto media = _data->media()) {
_media = media->createView(this, replacing); _media = media->createView(this, replacing);
} else if (_data->isOnlyCustomEmoji() } else if (isOnlyCustomEmoji()
&& Core::App().settings().largeEmoji()) { && Core::App().settings().largeEmoji()) {
_media = std::make_unique<UnwrappedMedia>( _media = std::make_unique<UnwrappedMedia>(
this, this,
std::make_unique<CustomEmoji>(this, _data->onlyCustomEmoji())); std::make_unique<CustomEmoji>(this, onlyCustomEmoji()));
} else if (_data->isIsolatedEmoji() } else if (isIsolatedEmoji()
&& Core::App().settings().largeEmoji()) { && Core::App().settings().largeEmoji()) {
const auto emoji = _data->isolatedEmoji(); const auto emoji = isolatedEmoji();
const auto emojiStickers = &session->emojiStickersPack(); const auto emojiStickers = &history()->session().emojiStickersPack();
const auto skipPremiumEffect = false; const auto skipPremiumEffect = false;
if (const auto sticker = emojiStickers->stickerForEmoji(emoji)) { if (const auto sticker = emojiStickers->stickerForEmoji(emoji)) {
_media = std::make_unique<UnwrappedMedia>( _media = std::make_unique<UnwrappedMedia>(
@ -597,6 +624,90 @@ void Element::refreshMedia(Element *replacing) {
} }
} }
Ui::Text::IsolatedEmoji Element::isolatedEmoji() const {
return _text.toIsolatedEmoji();
}
Ui::Text::OnlyCustomEmoji Element::onlyCustomEmoji() const {
return _text.toOnlyCustomEmoji();
}
const Ui::Text::String &Element::text() const {
return _text;
}
int Element::textHeightFor(int textWidth) {
validateText();
if (_textWidth != textWidth) {
_textWidth = textWidth;
_textHeight = _text.countHeight(textWidth);
}
return _textHeight;
}
void Element::validateText() {
const auto item = data();
const auto &text = item->_text;
if (_text.isEmpty() == text.empty()) {
return;
}
const auto context = Core::MarkedTextContext{
.session = &history()->session(),
.customEmojiRepaint = [=] { customEmojiRepaint(); },
};
if (_flags & Flag::ServiceMessage) {
_text.setMarkedText(
st::serviceTextStyle,
text,
Ui::ItemTextServiceOptions(),
context);
auto linkIndex = 0;
for (const auto &link : item->customTextLinks()) {
// Link indices start with 1.
_text.setLink(++linkIndex, link);
}
} else {
clearSpecialOnlyEmoji();
const auto context = Core::MarkedTextContext{
.session = &history()->session(),
.customEmojiRepaint = [=] { customEmojiRepaint(); },
};
_text.setMarkedText(
st::messageTextStyle,
item->originalTextWithLocalEntities(),
Ui::ItemTextOptions(item),
context);
if (!text.empty() && _text.isEmpty()) {
// If server has allowed some text that we've trim-ed entirely,
// just replace it with something so that UI won't look buggy.
_text.setMarkedText(
st::messageTextStyle,
{ u":-("_q },
Ui::ItemTextOptions(item));
}
if (!item->media()) {
checkSpecialOnlyEmoji();
refreshMedia(nullptr);
}
}
FillTextWithAnimatedSpoilers(this, _text);
_textWidth = -1;
_textHeight = 0;
}
void Element::validateTextSkipBlock(bool has, int width, int height) {
validateText();
if (!has) {
if (_text.removeSkipBlock()) {
_textWidth = -1;
_textHeight = 0;
}
} else if (_text.updateSkipBlock(width, height)) {
_textWidth = -1;
_textHeight = 0;
}
}
void Element::previousInBlocksChanged() { void Element::previousInBlocksChanged() {
recountDisplayDateInBlocks(); recountDisplayDateInBlocks();
recountAttachToPreviousInBlocks(); recountAttachToPreviousInBlocks();
@ -938,6 +1049,19 @@ bool Element::isSignedAuthorElided() const {
void Element::itemDataChanged() { void Element::itemDataChanged() {
} }
void Element::itemTextUpdated() {
if (const auto media = _media.get()) {
media->parentTextUpdated();
}
clearSpecialOnlyEmoji();
_text = Ui::Text::String(st::msgMinWidth);
_textWidth = -1;
_textHeight = 0;
if (_media && !data()->media()) {
refreshMedia(nullptr);
}
}
void Element::unloadHeavyPart() { void Element::unloadHeavyPart() {
history()->owner().unregisterHeavyViewPart(this); history()->owner().unregisterHeavyViewPart(this);
if (_media) { if (_media) {
@ -945,7 +1069,7 @@ void Element::unloadHeavyPart() {
} }
if (_heavyCustomEmoji) { if (_heavyCustomEmoji) {
_heavyCustomEmoji = false; _heavyCustomEmoji = false;
data()->_text.unloadPersistentAnimation(); _text.unloadPersistentAnimation();
if (const auto reply = data()->Get<HistoryMessageReply>()) { if (const auto reply = data()->Get<HistoryMessageReply>()) {
reply->replyToText.unloadPersistentAnimation(); reply->replyToText.unloadPersistentAnimation();
} }
@ -1125,7 +1249,7 @@ Element::~Element() {
base::take(_media); base::take(_media);
if (_heavyCustomEmoji) { if (_heavyCustomEmoji) {
_heavyCustomEmoji = false; _heavyCustomEmoji = false;
data()->_text.unloadPersistentAnimation(); _text.unloadPersistentAnimation();
checkHeavyPart(); checkHeavyPart();
} }
if (_data->mainView() == this) { if (_data->mainView() == this) {

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_object.h" #include "history/view/history_view_object.h"
#include "base/runtime_composer.h" #include "base/runtime_composer.h"
#include "base/flags.h" #include "base/flags.h"
#include "base/weak_ptr.h"
class History; class History;
class HistoryBlock; class HistoryBlock;
@ -236,54 +237,71 @@ struct DateBadge : public RuntimeComponent<DateBadge, Element> {
class Element class Element
: public Object : public Object
, public RuntimeComposer<Element> , public RuntimeComposer<Element>
, public ClickHandlerHost { , public ClickHandlerHost
, public base::has_weak_ptr {
public: public:
Element(
not_null<ElementDelegate*> delegate,
not_null<HistoryItem*> data,
Element *replacing);
enum class Flag : uchar { enum class Flag : uchar {
NeedsResize = 0x01, ServiceMessage = 0x01,
AttachedToPrevious = 0x02, NeedsResize = 0x02,
AttachedToNext = 0x04, AttachedToPrevious = 0x04,
HiddenByGroup = 0x08, AttachedToNext = 0x08,
HiddenByGroup = 0x10,
SpecialOnlyEmoji = 0x20,
CustomEmojiRepainting = 0x40,
}; };
using Flags = base::flags<Flag>; using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; } friend inline constexpr auto is_flag_type(Flag) { return true; }
not_null<ElementDelegate*> delegate() const; Element(
not_null<HistoryItem*> data() const; not_null<ElementDelegate*> delegate,
not_null<History*> history() const; not_null<HistoryItem*> data,
Media *media() const; Element *replacing,
Context context() const; Flag serviceFlag);
[[nodiscard]] not_null<ElementDelegate*> delegate() const;
[[nodiscard]] not_null<HistoryItem*> data() const;
[[nodiscard]] not_null<History*> history() const;
[[nodiscard]] Media *media() const;
[[nodiscard]] Context context() const;
void refreshDataId(); void refreshDataId();
QDateTime dateTime() const; [[nodiscard]] QDateTime dateTime() const;
int y() const; [[nodiscard]] int y() const;
void setY(int y); void setY(int y);
virtual int marginTop() const = 0; [[nodiscard]] virtual int marginTop() const = 0;
virtual int marginBottom() const = 0; [[nodiscard]] virtual int marginBottom() const = 0;
void setPendingResize(); void setPendingResize();
bool pendingResize() const; [[nodiscard]] bool pendingResize() const;
bool isUnderCursor() const; [[nodiscard]] bool isUnderCursor() const;
bool isLastAndSelfMessage() const; [[nodiscard]] bool isLastAndSelfMessage() const;
bool isAttachedToPrevious() const; [[nodiscard]] bool isAttachedToPrevious() const;
bool isAttachedToNext() const; [[nodiscard]] bool isAttachedToNext() const;
int skipBlockWidth() const; [[nodiscard]] int skipBlockWidth() const;
int skipBlockHeight() const; [[nodiscard]] int skipBlockHeight() const;
virtual int infoWidth() const; [[nodiscard]] virtual int infoWidth() const;
virtual int bottomInfoFirstLineWidth() const; [[nodiscard]] virtual int bottomInfoFirstLineWidth() const;
virtual bool bottomInfoIsWide() const; [[nodiscard]] virtual bool bottomInfoIsWide() const;
bool isHiddenByGroup() const; [[nodiscard]] bool isHiddenByGroup() const;
virtual bool isHidden() const; [[nodiscard]] virtual bool isHidden() const;
[[nodiscard]] bool isIsolatedEmoji() const {
return (_flags & Flag::SpecialOnlyEmoji)
&& _text.isIsolatedEmoji();
}
[[nodiscard]] bool isOnlyCustomEmoji() const {
return (_flags & Flag::SpecialOnlyEmoji)
&& _text.isOnlyCustomEmoji();
}
[[nodiscard]] Ui::Text::IsolatedEmoji isolatedEmoji() const;
[[nodiscard]] Ui::Text::OnlyCustomEmoji onlyCustomEmoji() const;
// For blocks context this should be called only from recountAttachToPreviousInBlocks(). // For blocks context this should be called only from recountAttachToPreviousInBlocks().
void setAttachToPrevious(bool attachToNext); void setAttachToPrevious(bool attachToNext);
@ -300,9 +318,9 @@ public:
void createUnreadBar(rpl::producer<QString> text); void createUnreadBar(rpl::producer<QString> text);
void destroyUnreadBar(); void destroyUnreadBar();
int displayedDateHeight() const; [[nodiscard]] int displayedDateHeight() const;
bool displayDate() const; [[nodiscard]] bool displayDate() const;
bool isInOneDayWithPrevious() const; [[nodiscard]] bool isInOneDayWithPrevious() const;
virtual void draw(Painter &p, const PaintContext &context) const = 0; virtual void draw(Painter &p, const PaintContext &context) const = 0;
[[nodiscard]] virtual PointState pointState(QPoint point) const = 0; [[nodiscard]] virtual PointState pointState(QPoint point) const = 0;
@ -382,8 +400,9 @@ public:
[[nodiscard]] virtual bool isSignedAuthorElided() const; [[nodiscard]] virtual bool isSignedAuthorElided() const;
virtual void itemDataChanged(); virtual void itemDataChanged();
void itemTextUpdated();
virtual bool hasHeavyPart() const; [[nodiscard]] virtual bool hasHeavyPart() const;
virtual void unloadHeavyPart(); virtual void unloadHeavyPart();
void checkHeavyPart(); void checkHeavyPart();
@ -395,21 +414,21 @@ public:
not_null<const HistoryItem*> item) const; not_null<const HistoryItem*> item) const;
// Legacy blocks structure. // Legacy blocks structure.
HistoryBlock *block(); [[nodiscard]] HistoryBlock *block();
const HistoryBlock *block() const; [[nodiscard]] const HistoryBlock *block() const;
void attachToBlock(not_null<HistoryBlock*> block, int index); void attachToBlock(not_null<HistoryBlock*> block, int index);
void removeFromBlock(); void removeFromBlock();
void refreshInBlock(); void refreshInBlock();
void setIndexInBlock(int index); void setIndexInBlock(int index);
int indexInBlock() const; [[nodiscard]] int indexInBlock() const;
Element *previousInBlocks() const; [[nodiscard]] Element *previousInBlocks() const;
Element *previousDisplayedInBlocks() const; [[nodiscard]] Element *previousDisplayedInBlocks() const;
Element *nextInBlocks() const; [[nodiscard]] Element *nextInBlocks() const;
Element *nextDisplayedInBlocks() const; [[nodiscard]] Element *nextDisplayedInBlocks() const;
void previousInBlocksChanged(); void previousInBlocksChanged();
void nextInBlocksRemoved(); void nextInBlocksRemoved();
virtual QRect innerGeometry() const = 0; [[nodiscard]] virtual QRect innerGeometry() const = 0;
void customEmojiRepaint(); void customEmojiRepaint();
void prepareCustomEmojiPaint( void prepareCustomEmojiPaint(
@ -421,6 +440,7 @@ public:
const PaintContext &context, const PaintContext &context,
const Reactions::InlineList &reactions) const; const Reactions::InlineList &reactions) const;
void clearCustomEmojiRepaint() const; void clearCustomEmojiRepaint() const;
void hideSpoilers();
[[nodiscard]] ClickHandlerPtr fromPhotoLink() const { [[nodiscard]] ClickHandlerPtr fromPhotoLink() const {
return fromLink(); return fromLink();
@ -461,6 +481,14 @@ protected:
virtual void refreshDataIdHook(); virtual void refreshDataIdHook();
[[nodiscard]] const Ui::Text::String &text() const;
[[nodiscard]] int textHeightFor(int textWidth);
void validateText();
void validateTextSkipBlock(bool has, int width, int height);
void clearSpecialOnlyEmoji();
void checkSpecialOnlyEmoji();
private: private:
// This should be called only from previousInBlocksChanged() // This should be called only from previousInBlocksChanged()
// to add required bits to the Composer mask // to add required bits to the Composer mask
@ -483,22 +511,23 @@ private:
const not_null<ElementDelegate*> _delegate; const not_null<ElementDelegate*> _delegate;
const not_null<HistoryItem*> _data; const not_null<HistoryItem*> _data;
HistoryBlock *_block = nullptr;
std::unique_ptr<Media> _media; std::unique_ptr<Media> _media;
mutable ClickHandlerPtr _fromLink; mutable ClickHandlerPtr _fromLink;
bool _isScheduledUntilOnline = false;
mutable bool _heavyCustomEmoji = false;
mutable bool _customEmojiRepaintScheduled = false;
const QDateTime _dateTime; const QDateTime _dateTime;
mutable Ui::Text::String _text = { st::msgMinWidth };
mutable int _textWidth = -1;
mutable int _textHeight = 0;
int _y = 0; int _y = 0;
Context _context = Context();
Flags _flags = Flag::NeedsResize;
HistoryBlock *_block = nullptr;
int _indexInBlock = -1; int _indexInBlock = -1;
bool _isScheduledUntilOnline = false;
mutable bool _heavyCustomEmoji = false;
mutable Flags _flags = Flag(0);
Context _context = Context();
}; };
} // namespace HistoryView } // namespace HistoryView

View file

@ -255,7 +255,7 @@ Message::Message(
not_null<ElementDelegate*> delegate, not_null<ElementDelegate*> delegate,
not_null<HistoryMessage*> data, not_null<HistoryMessage*> data,
Element *replacing) Element *replacing)
: Element(delegate, data, replacing) : Element(delegate, data, replacing, Flag(0))
, _bottomInfo( , _bottomInfo(
&data->history()->owner().reactions(), &data->history()->owner().reactions(),
BottomInfoDataFromMessage(this)) { BottomInfoDataFromMessage(this)) {
@ -447,17 +447,18 @@ auto Message::takeReactionAnimations()
} }
QSize Message::performCountOptimalSize() { QSize Message::performCountOptimalSize() {
validateText();
updateViewButtonExistence();
updateMediaInBubbleState();
refreshRightBadge();
refreshInfoSkipBlock();
const auto item = message(); const auto item = message();
const auto media = this->media(); const auto media = this->media();
auto maxWidth = 0; auto maxWidth = 0;
auto minHeight = 0; auto minHeight = 0;
updateViewButtonExistence();
updateMediaInBubbleState();
refreshRightBadge();
refreshInfoSkipBlock();
const auto reactionsInBubble = _reactions && embedReactionsInBubble(); const auto reactionsInBubble = _reactions && embedReactionsInBubble();
if (_reactions) { if (_reactions) {
_reactions->initDimensions(); _reactions->initDimensions();
@ -490,7 +491,7 @@ QSize Message::performCountOptimalSize() {
if (context() == Context::Replies && item->isDiscussionPost()) { if (context() == Context::Replies && item->isDiscussionPost()) {
maxWidth = std::max(maxWidth, st::msgMaxWidth); maxWidth = std::max(maxWidth, st::msgMaxWidth);
} }
minHeight = hasVisibleText() ? item->_text.minHeight() : 0; minHeight = hasVisibleText() ? text().minHeight() : 0;
if (reactionsInBubble) { if (reactionsInBubble) {
const auto reactionsMaxWidth = st::msgPadding.left() const auto reactionsMaxWidth = st::msgPadding.left()
+ _reactions->maxWidth() + _reactions->maxWidth()
@ -529,8 +530,8 @@ QSize Message::performCountOptimalSize() {
- st::msgPadding.left() - st::msgPadding.left()
- st::msgPadding.right(); - st::msgPadding.right();
if (hasVisibleText() && maxWidth < plainMaxWidth()) { if (hasVisibleText() && maxWidth < plainMaxWidth()) {
minHeight -= item->_text.minHeight(); minHeight -= text().minHeight();
minHeight += item->_text.countHeight(innerWidth); minHeight += text().countHeight(innerWidth);
} }
if (reactionsInBubble) { if (reactionsInBubble) {
minHeight -= _reactions->minHeight(); minHeight -= _reactions->minHeight();
@ -1298,8 +1299,8 @@ void Message::paintText(
const auto stm = context.messageStyle(); const auto stm = context.messageStyle();
p.setPen(stm->historyTextFg); p.setPen(stm->historyTextFg);
p.setFont(st::msgFont); p.setFont(st::msgFont);
prepareCustomEmojiPaint(p, context, item->_text); prepareCustomEmojiPaint(p, context, text());
item->_text.draw(p, { text().draw(p, {
.position = trect.topLeft(), .position = trect.topLeft(),
.availableWidth = trect.width(), .availableWidth = trect.width(),
.palette = &stm->textPalette, .palette = &stm->textPalette,
@ -1606,7 +1607,7 @@ TextState Message::textState(
result = entry->textState( result = entry->textState(
point - QPoint(entryLeft, entryTop), point - QPoint(entryLeft, entryTop),
request); request);
result.symbol += item->_text.length() + (mediaDisplayed ? media->fullSelectionLength() : 0); result.symbol += text().length() + (mediaDisplayed ? media->fullSelectionLength() : 0);
} }
} }
@ -1632,18 +1633,18 @@ TextState Message::textState(
if (point.y() >= mediaTop && point.y() < mediaTop + mediaHeight) { if (point.y() >= mediaTop && point.y() < mediaTop + mediaHeight) {
result = media->textState(point - QPoint(mediaLeft, mediaTop), request); result = media->textState(point - QPoint(mediaLeft, mediaTop), request);
result.symbol += item->_text.length(); result.symbol += text().length();
} else if (getStateText(point, trect, &result, request)) { } else if (getStateText(point, trect, &result, request)) {
checkBottomInfoState(); checkBottomInfoState();
return result; return result;
} else if (point.y() >= trect.y() + trect.height()) { } else if (point.y() >= trect.y() + trect.height()) {
result.symbol = item->_text.length(); result.symbol = text().length();
} }
} else if (getStateText(point, trect, &result, request)) { } else if (getStateText(point, trect, &result, request)) {
checkBottomInfoState(); checkBottomInfoState();
return result; return result;
} else if (point.y() >= trect.y() + trect.height()) { } else if (point.y() >= trect.y() + trect.height()) {
result.symbol = item->_text.length(); result.symbol = text().length();
} }
} }
checkBottomInfoState(); checkBottomInfoState();
@ -1665,7 +1666,7 @@ TextState Message::textState(
} }
} else if (media && media->isDisplayed()) { } else if (media && media->isDisplayed()) {
result = media->textState(point - g.topLeft(), request); result = media->textState(point - g.topLeft(), request);
result.symbol += item->_text.length(); result.symbol += text().length();
} }
if (keyboard && item->isHistoryEntry()) { if (keyboard && item->isHistoryEntry()) {
@ -1938,7 +1939,7 @@ bool Message::getStateText(
} }
const auto item = message(); const auto item = message();
if (base::in_range(point.y(), trect.y(), trect.y() + trect.height())) { if (base::in_range(point.y(), trect.y(), trect.y() + trect.height())) {
*outResult = TextState(item, item->_text.getState( *outResult = TextState(item, text().getState(
point - trect.topLeft(), point - trect.topLeft(),
trect.width(), trect.width(),
request.forText())); request.forText()));
@ -2004,7 +2005,7 @@ TextForMimeData Message::selectedText(TextSelection selection) const {
const auto media = this->media(); const auto media = this->media();
auto logEntryOriginalResult = TextForMimeData(); auto logEntryOriginalResult = TextForMimeData();
auto textResult = item->_text.toTextForMimeData(selection); auto textResult = text().toTextForMimeData(selection);
auto skipped = skipTextSelection(selection); auto skipped = skipTextSelection(selection);
auto mediaDisplayed = (media && media->isDisplayed()); auto mediaDisplayed = (media && media->isDisplayed());
auto mediaResult = (mediaDisplayed || isHiddenByGroup()) auto mediaResult = (mediaDisplayed || isHiddenByGroup())
@ -2036,8 +2037,8 @@ TextSelection Message::adjustSelection(
const auto item = message(); const auto item = message();
const auto media = this->media(); const auto media = this->media();
auto result = item->_text.adjustSelection(selection, type); auto result = text().adjustSelection(selection, type);
auto beforeMediaLength = item->_text.length(); auto beforeMediaLength = text().length();
if (selection.to <= beforeMediaLength) { if (selection.to <= beforeMediaLength) {
return result; return result;
} }
@ -2370,13 +2371,13 @@ void Message::refreshDataIdHook() {
int Message::plainMaxWidth() const { int Message::plainMaxWidth() const {
return st::msgPadding.left() return st::msgPadding.left()
+ (hasVisibleText() ? message()->_text.maxWidth() : 0) + (hasVisibleText() ? text().maxWidth() : 0)
+ st::msgPadding.right(); + st::msgPadding.right();
} }
int Message::monospaceMaxWidth() const { int Message::monospaceMaxWidth() const {
return st::msgPadding.left() return st::msgPadding.left()
+ (hasVisibleText() ? message()->_text.countMaxMonospaceWidth() : 0) + (hasVisibleText() ? text().countMaxMonospaceWidth() : 0)
+ st::msgPadding.right(); + st::msgPadding.right();
} }
@ -2893,11 +2894,11 @@ TextSelection Message::skipTextSelection(TextSelection selection) const {
if (selection.from == 0xFFFF) { if (selection.from == 0xFFFF) {
return selection; return selection;
} }
return HistoryView::UnshiftItemSelection(selection, message()->_text); return HistoryView::UnshiftItemSelection(selection, text());
} }
TextSelection Message::unskipTextSelection(TextSelection selection) const { TextSelection Message::unskipTextSelection(TextSelection selection) const {
return HistoryView::ShiftItemSelection(selection, message()->_text); return HistoryView::ShiftItemSelection(selection, text());
} }
QRect Message::innerGeometry() const { QRect Message::innerGeometry() const {
@ -3058,15 +3059,7 @@ int Message::resizeContentGetHeight(int newWidth) {
entry->resizeGetHeight(contentWidth); entry->resizeGetHeight(contentWidth);
} }
} else { } else {
if (hasVisibleText()) { newHeight = hasVisibleText() ? textHeightFor(textWidth) : 0;
if (textWidth != item->_textWidth) {
item->_textWidth = textWidth;
item->_textHeight = item->_text.countHeight(textWidth);
}
newHeight = item->_textHeight;
} else {
newHeight = 0;
}
if (!mediaOnBottom && (!_viewButton || !reactionsInBubble)) { if (!mediaOnBottom && (!_viewButton || !reactionsInBubble)) {
newHeight += st::msgPadding.bottom(); newHeight += st::msgPadding.bottom();
if (mediaDisplayed) { if (mediaDisplayed) {
@ -3181,7 +3174,7 @@ void Message::refreshInfoSkipBlock() {
const auto item = message(); const auto item = message();
const auto media = this->media(); const auto media = this->media();
const auto hasTextSkipBlock = [&] { const auto hasTextSkipBlock = [&] {
if (item->_text.isEmpty()) { if (item->_text.empty()) {
return false; return false;
} else if (item->Has<HistoryMessageLogEntryOriginal>()) { } else if (item->Has<HistoryMessageLogEntryOriginal>()) {
return false; return false;
@ -3201,15 +3194,7 @@ void Message::refreshInfoSkipBlock() {
_reactions->removeSkipBlock(); _reactions->removeSkipBlock();
} }
} }
if (!hasTextSkipBlock) { validateTextSkipBlock(hasTextSkipBlock, skipWidth, skipHeight);
if (item->_text.removeSkipBlock()) {
item->_textWidth = -1;
item->_textHeight = 0;
}
} else if (item->_text.updateSkipBlock(skipWidth, skipHeight)) {
item->_textWidth = -1;
item->_textHeight = 0;
}
} }
TimeId Message::displayedEditDate() const { TimeId Message::displayedEditDate() const {

View file

@ -10,7 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_element.h" #include "history/view/history_view_element.h"
#include "history/view/history_view_bottom_info.h" #include "history/view/history_view_bottom_info.h"
#include "ui/effects/animations.h" #include "ui/effects/animations.h"
#include "base/weak_ptr.h"
class HistoryMessage; class HistoryMessage;
struct HistoryMessageEdited; struct HistoryMessageEdited;
@ -47,7 +46,7 @@ struct PsaTooltipState : public RuntimeComponent<PsaTooltipState, Element> {
mutable bool buttonVisible = true; mutable bool buttonVisible = true;
}; };
class Message : public Element, public base::has_weak_ptr { class Message final : public Element {
public: public:
Message( Message(
not_null<ElementDelegate*> delegate, not_null<ElementDelegate*> delegate,

View file

@ -388,7 +388,7 @@ Service::Service(
not_null<ElementDelegate*> delegate, not_null<ElementDelegate*> delegate,
not_null<HistoryService*> data, not_null<HistoryService*> data,
Element *replacing) Element *replacing)
: Element(delegate, data, replacing) { : Element(delegate, data, replacing, Flag::ServiceMessage) {
} }
not_null<HistoryService*> Service::message() const { not_null<HistoryService*> Service::message() const {
@ -420,9 +420,7 @@ QSize Service::performCountCurrentSize(int newWidth) {
const auto item = message(); const auto item = message();
const auto media = this->media(); const auto media = this->media();
if (item->_text.isEmpty()) { if (!text().isEmpty()) {
item->_textHeight = 0;
} else {
auto contentWidth = newWidth; auto contentWidth = newWidth;
if (delegate()->elementIsChatWide()) { if (delegate()->elementIsChatWide()) {
accumulate_min(contentWidth, st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left()); accumulate_min(contentWidth, st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left());
@ -433,15 +431,9 @@ QSize Service::performCountCurrentSize(int newWidth) {
} }
auto nwidth = qMax(contentWidth - st::msgServicePadding.left() - st::msgServicePadding.right(), 0); auto nwidth = qMax(contentWidth - st::msgServicePadding.left() - st::msgServicePadding.right(), 0);
if (nwidth != item->_textWidth) { newHeight += (contentWidth >= maxWidth())
item->_textWidth = nwidth; ? minHeight()
item->_textHeight = item->_text.countHeight(nwidth); : textHeightFor(nwidth);
}
if (contentWidth >= maxWidth()) {
newHeight += minHeight();
} else {
newHeight += item->_textHeight;
}
newHeight += st::msgServicePadding.top() + st::msgServicePadding.bottom() + st::msgServiceMargin.top() + st::msgServiceMargin.bottom(); newHeight += st::msgServicePadding.top() + st::msgServicePadding.bottom() + st::msgServiceMargin.top() + st::msgServiceMargin.bottom();
if (media) { if (media) {
newHeight += st::msgServiceMargin.top() + media->resizeGetHeight(media->maxWidth()); newHeight += st::msgServiceMargin.top() + media->resizeGetHeight(media->maxWidth());
@ -454,9 +446,10 @@ QSize Service::performCountCurrentSize(int newWidth) {
QSize Service::performCountOptimalSize() { QSize Service::performCountOptimalSize() {
const auto item = message(); const auto item = message();
const auto media = this->media(); const auto media = this->media();
validateText();
auto maxWidth = item->_text.maxWidth() + st::msgServicePadding.left() + st::msgServicePadding.right(); auto maxWidth = text().maxWidth() + st::msgServicePadding.left() + st::msgServicePadding.right();
auto minHeight = item->_text.minHeight(); auto minHeight = text().minHeight();
if (media) { if (media) {
media->initDimensions(); media->initDimensions();
} }
@ -533,14 +526,14 @@ void Service::draw(Painter &p, const PaintContext &context) const {
context.st, context.st,
g.left(), g.left(),
g.width(), g.width(),
item->_text, text(),
trect); trect);
p.setBrush(Qt::NoBrush); p.setBrush(Qt::NoBrush);
p.setPen(st->msgServiceFg()); p.setPen(st->msgServiceFg());
p.setFont(st::msgServiceFont); p.setFont(st::msgServiceFont);
prepareCustomEmojiPaint(p, context, item->_text); prepareCustomEmojiPaint(p, context, text());
item->_text.draw(p, { text().draw(p, {
.position = trect.topLeft(), .position = trect.topLeft(),
.availableWidth = trect.width(), .availableWidth = trect.width(),
.align = style::al_top, .align = style::al_top,
@ -618,7 +611,7 @@ TextState Service::textState(QPoint point, StateRequest request) const {
if (trect.contains(point)) { if (trect.contains(point)) {
auto textRequest = request.forText(); auto textRequest = request.forText();
textRequest.align = style::al_center; textRequest.align = style::al_center;
result = TextState(item, item->_text.getState( result = TextState(item, text().getState(
point - trect.topLeft(), point - trect.topLeft(),
trect.width(), trect.width(),
textRequest)); textRequest));
@ -652,13 +645,13 @@ void Service::updatePressed(QPoint point) {
} }
TextForMimeData Service::selectedText(TextSelection selection) const { TextForMimeData Service::selectedText(TextSelection selection) const {
return message()->_text.toTextForMimeData(selection); return text().toTextForMimeData(selection);
} }
TextSelection Service::adjustSelection( TextSelection Service::adjustSelection(
TextSelection selection, TextSelection selection,
TextSelectType type) const { TextSelectType type) const {
return message()->_text.adjustSelection(selection, type); return text().adjustSelection(selection, type);
} }
EmptyPainter::EmptyPainter(not_null<History*> history) : _history(history) { EmptyPainter::EmptyPainter(not_null<History*> history) : _history(history) {

View file

@ -18,7 +18,7 @@ struct CornersPixmaps;
namespace HistoryView { namespace HistoryView {
class Service : public Element { class Service final : public Element {
public: public:
Service( Service(
not_null<ElementDelegate*> delegate, not_null<ElementDelegate*> delegate,

View file

@ -12,49 +12,55 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_element.h" #include "history/view/history_view_element.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "base/weak_ptr.h"
namespace HistoryView { namespace HistoryView {
namespace { namespace {
class AnimatedSpoilerClickHandler final : public ClickHandler { class AnimatedSpoilerClickHandler final : public ClickHandler {
public: public:
explicit AnimatedSpoilerClickHandler(Ui::Text::String &text) AnimatedSpoilerClickHandler(
: _text(text) { not_null<Element*> view,
} Ui::Text::String &text);
void onClick(ClickContext context) const override; void onClick(ClickContext context) const override;
private: private:
base::weak_ptr<Element> _weak;
Ui::Text::String &_text; Ui::Text::String &_text;
}; };
AnimatedSpoilerClickHandler::AnimatedSpoilerClickHandler(
not_null<Element*> view,
Ui::Text::String &text)
: _weak(view)
, _text(text) {
}
void AnimatedSpoilerClickHandler::onClick(ClickContext context) const { void AnimatedSpoilerClickHandler::onClick(ClickContext context) const {
const auto button = context.button; const auto button = context.button;
if (button != Qt::LeftButton) { const auto view = _weak.get();
if (button != Qt::LeftButton || !view) {
return; return;
} }
const auto my = context.other.value<ClickHandlerContext>(); const auto my = context.other.value<ClickHandlerContext>();
if (const auto d = my.elementDelegate ? my.elementDelegate() : nullptr) { if (const auto d = my.elementDelegate ? my.elementDelegate() : nullptr) {
_text.setSpoilerRevealed(true, anim::type::normal); _text.setSpoilerRevealed(true, anim::type::normal);
if (const auto controller = my.sessionWindow.get()) { if (const auto controller = my.sessionWindow.get()) {
controller->session().data().registerShownSpoiler(my.itemId); controller->session().data().registerShownSpoiler(view);
} }
} }
} }
} // namespace } // namespace
void FillTextWithAnimatedSpoilers(Ui::Text::String &text) { void FillTextWithAnimatedSpoilers(
not_null<Element*> view,
Ui::Text::String &text) {
if (text.hasSpoilers()) { if (text.hasSpoilers()) {
text.setSpoilerLink( text.setSpoilerLink(
std::make_shared<AnimatedSpoilerClickHandler>(text)); std::make_shared<AnimatedSpoilerClickHandler>(view, text));
}
}
void HideSpoilers(Ui::Text::String &text) {
if (text.hasSpoilers()) {
text.setSpoilerRevealed(false, anim::type::instant);
} }
} }

View file

@ -9,7 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace HistoryView { namespace HistoryView {
void FillTextWithAnimatedSpoilers(Ui::Text::String &text); class Element;
void HideSpoilers(Ui::Text::String &text);
void FillTextWithAnimatedSpoilers(
not_null<Element*> view,
Ui::Text::String &text);
} // namespace HistoryView } // namespace HistoryView

View file

@ -208,7 +208,7 @@ Ui::Text::String Media::createCaption(not_null<HistoryItem*> item) const {
item->originalTextWithLocalEntities(), item->originalTextWithLocalEntities(),
Ui::ItemTextOptions(item), Ui::ItemTextOptions(item),
context); context);
FillTextWithAnimatedSpoilers(result); FillTextWithAnimatedSpoilers(_parent, result);
if (const auto width = _parent->skipBlockWidth()) { if (const auto width = _parent->skipBlockWidth()) {
result.updateSkipBlock(width, _parent->skipBlockHeight()); result.updateSkipBlock(width, _parent->skipBlockHeight());
} }

@ -1 +1 @@
Subproject commit 18580e46a1206f4ba875ed6d4da553d60407288f Subproject commit 06f3c837f6b65868ec1ed048ba8843fbed247677