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

View file

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

View file

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

View file

@ -296,8 +296,7 @@ public:
int from,
int till);
void registerShownSpoiler(FullMsgId id);
void unregisterShownSpoiler(FullMsgId id);
void registerShownSpoiler(not_null<ViewElement*> view);
void hideShownSpoilers();
using MegagroupParticipant = std::tuple<
@ -956,7 +955,7 @@ private:
rpl::event_stream<InviteToCall> _invitesToCalls;
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;

View file

@ -220,68 +220,71 @@ struct StickerSetIdentifier {
}
};
enum class MessageFlag : uint32 {
HideEdited = (1U << 0),
Legacy = (1U << 1),
HasReplyMarkup = (1U << 2),
HasFromId = (1U << 3),
HasPostAuthor = (1U << 4),
HasViews = (1U << 5),
HasReplyInfo = (1U << 6),
CanViewReactions = (1U << 7),
AdminLogEntry = (1U << 8),
Post = (1U << 9),
Silent = (1U << 10),
Outgoing = (1U << 11),
Pinned = (1U << 12),
MediaIsUnread = (1U << 13),
HasUnreadReaction = (1U << 14),
MentionsMe = (1U << 15),
IsOrWasScheduled = (1U << 16),
NoForwards = (1U << 17),
enum class MessageFlag : uint64 {
HideEdited = (1ULL << 0),
Legacy = (1ULL << 1),
HasReplyMarkup = (1ULL << 2),
HasFromId = (1ULL << 3),
HasPostAuthor = (1ULL << 4),
HasViews = (1ULL << 5),
HasReplyInfo = (1ULL << 6),
CanViewReactions = (1ULL << 7),
AdminLogEntry = (1ULL << 8),
Post = (1ULL << 9),
Silent = (1ULL << 10),
Outgoing = (1ULL << 11),
Pinned = (1ULL << 12),
MediaIsUnread = (1ULL << 13),
HasUnreadReaction = (1ULL << 14),
MentionsMe = (1ULL << 15),
IsOrWasScheduled = (1ULL << 16),
NoForwards = (1ULL << 17),
// Needs to return back to inline mode.
HasSwitchInlineButton = (1U << 18),
HasSwitchInlineButton = (1ULL << 18),
// For "shared links" indexing.
HasTextLinks = (1U << 19),
HasTextLinks = (1ULL << 19),
// Group / channel create or migrate service message.
IsGroupEssential = (1U << 20),
IsGroupEssential = (1ULL << 20),
// Edited media is generated on the client
// and should not update media from server.
IsLocalUpdateMedia = (1U << 21),
IsLocalUpdateMedia = (1ULL << 21),
// 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.
ClientSideUnread = (1U << 23),
ClientSideUnread = (1ULL << 23),
// In a supergroup.
HasAdminBadge = (1U << 24),
HasAdminBadge = (1ULL << 24),
// Outgoing message that is being sent.
BeingSent = (1U << 25),
BeingSent = (1ULL << 25),
// 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.
SpecialOnlyEmoji = (1U << 27),
SpecialOnlyEmoji = (1ULL << 27),
// Message existing in the message history.
HistoryEntry = (1U << 28),
HistoryEntry = (1ULL << 28),
// Local message, not existing on the server.
Local = (1U << 29),
Local = (1ULL << 29),
// Fake message for some UI element.
FakeHistoryItem = (1U << 30),
FakeHistoryItem = (1ULL << 30),
// 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; }
using MessageFlags = base::flags<MessageFlag>;

View file

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

View file

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

View file

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

View file

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

View file

@ -15,7 +15,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_unread_things.h"
#include "history/view/history_view_service_message.h"
#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 "chat_helpers/stickers_emoji_pack.h"
#include "main/main_session.h"
@ -24,8 +23,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/share_box.h"
#include "ui/text/text_isolated_emoji.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 "mtproto/mtproto_config.h"
#include "data/notify/data_notify_settings.h"
@ -100,10 +97,7 @@ namespace {
[[nodiscard]] TextWithEntities EnsureNonEmpty(
const TextWithEntities &text = TextWithEntities()) {
if (!text.text.isEmpty()) {
return text;
}
return { QString::fromUtf8(":-("), EntitiesInText() };
return !text.text.isEmpty() ? text : TextWithEntities{ u":-("_q };
}
} // namespace
@ -384,7 +378,7 @@ HistoryMessage::HistoryMessage(
_media = std::make_unique<Data::MediaCall>(
this,
Data::ComputeCallData(data));
setEmptyText();
setTextValue({});
}, [](const auto &) {
Unexpected("Service message action type in HistoryMessage.");
});
@ -607,7 +601,7 @@ HistoryMessage::HistoryMessage(
std::move(markup));
_media = std::make_unique<Data::MediaGame>(this, game);
setEmptyText();
setTextValue({});
}
HistoryMessage::HistoryMessage(
@ -863,10 +857,6 @@ void HistoryMessage::setCommentsItemId(FullMsgId id) {
}
}
void HistoryMessage::hideSpoilers() {
HistoryView::HideSpoilers(_text);
}
bool HistoryMessage::updateDependencyItem() {
if (const auto reply = Get<HistoryMessageReply>()) {
const auto documentId = reply->replyToDocumentId;
@ -875,7 +865,7 @@ bool HistoryMessage::updateDependencyItem() {
const auto mediaIdChanged = (documentId != reply->replyToDocumentId)
|| (webpageId != reply->replyToWebPageId);
if (mediaIdChanged && generateLocalEntitiesByReply()) {
reapplyText();
history()->owner().requestItemTextRefresh(this);
}
return result;
}
@ -1315,7 +1305,7 @@ void HistoryMessage::applyEdition(const MTPDmessageService &message) {
const auto wasGrouped = history()->owner().groups().isGrouped(this);
setReplyMarkup({});
refreshMedia(nullptr);
setEmptyText();
setTextValue({});
changeViewsCount(-1);
setForwardsCount(-1);
if (wasGrouped) {
@ -1337,14 +1327,13 @@ void HistoryMessage::applyEdition(const MTPMessageExtendedMedia &media) {
void HistoryMessage::updateSentContent(
const TextWithEntities &textWithEntities,
const MTPMessageMedia *media) {
const auto isolated = isolatedEmoji();
setText(textWithEntities);
if (_flags & MessageFlag::FromInlineBot) {
if (!media || !_media || !_media->updateInlineResultMedia(*media)) {
refreshSentMedia(media);
}
_flags &= ~MessageFlag::FromInlineBot;
} else if (media || _media || !isolated || isolated != isolatedEmoji()) {
} else if (media || _media) {
if (!media || !_media || !_media->updateSentMedia(*media)) {
refreshSentMedia(media);
}
@ -1509,65 +1498,16 @@ void HistoryMessage::setText(const TextWithEntities &textWithEntities) {
break;
}
}
if (_media && _media->consumeMessageText(textWithEntities)) {
setEmptyText();
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;
setTextValue((_media && _media->consumeMessageText(textWithEntities))
? TextWithEntities()
: std::move(textWithEntities));
}
void HistoryMessage::reapplyText() {
setText(originalText());
history()->owner().requestItemResize(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;
void HistoryMessage::setTextValue(TextWithEntities text) {
const auto had = !_text.empty();
_text = std::move(text);
if (had) {
history()->owner().requestItemTextRefresh(this);
}
}
@ -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 {
if (emptyText()) {
return { QString(), EntitiesInText() };
}
return _text.toTextWithEntities();
return _text;
}
TextWithEntities HistoryMessage::originalTextWithLocalEntities() const {
@ -1636,14 +1565,7 @@ TextWithEntities HistoryMessage::originalTextWithLocalEntities() const {
}
TextForMimeData HistoryMessage::clipboardText() const {
if (emptyText()) {
return TextForMimeData();
}
return _text.toTextForMimeData();
}
bool HistoryMessage::textHasLinks() const {
return emptyText() ? false : _text.hasLinks();
return TextForMimeData::WithExpandedLinks(_text);
}
bool HistoryMessage::changeViewsCount(int count) {
@ -1923,7 +1845,7 @@ void HistoryMessage::dependencyItemRemoved(HistoryItem *dependency) {
reply->itemRemoved(this, dependency);
if (documentId != reply->replyToDocumentId
&& generateLocalEntitiesByReply()) {
reapplyText();
history()->owner().requestItemTextRefresh(this);
}
}
}

View file

@ -181,13 +181,10 @@ public:
[[nodiscard]] Storage::SharedMediaTypesMask sharedMediaTypes() const 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]] auto originalTextWithLocalEntities() const
-> TextWithEntities override;
[[nodiscard]] TextForMimeData clipboardText() const override;
[[nodiscard]] bool textHasLinks() const override;
[[nodiscard]] int viewsCount() const override;
[[nodiscard]] int repliesCount() const override;
@ -212,7 +209,6 @@ public:
[[nodiscard]] MsgId dependencyMsgId() const override {
return replyToId();
}
void hideSpoilers() override;
void applySentMessage(const MTPDmessage &data) override;
void applySentMessage(
@ -227,7 +223,7 @@ public:
~HistoryMessage();
private:
void setEmptyText();
void setTextValue(TextWithEntities text);
[[nodiscard]] bool isTooOldForEdit(TimeId now) const;
[[nodiscard]] bool isLegacyMessage() const {
return _flags & MessageFlag::Legacy;
@ -235,9 +231,6 @@ private:
[[nodiscard]] bool checkCommentsLinkedChat(ChannelId id) const;
void clearSpecialOnlyEmoji();
void checkSpecialOnlyEmoji();
// 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.
void replaceBuyWithReceiptInMarkup();
@ -271,7 +264,6 @@ private:
[[nodiscard]] bool generateLocalEntitiesByReply() const;
[[nodiscard]] TextWithEntities withLocalEntities(
const TextWithEntities &textWithEntities) const;
void reapplyText();
[[nodiscard]] bool checkRepliesPts(
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/view/history_view_service_message.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_session.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 "core/application.h"
#include "core/click_handler_types.h"
#include "core/ui_integration.h"
#include "base/unixtime.h"
#include "base/timer_rpl.h"
#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 "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 {
@ -636,8 +633,8 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
return result;
};
const auto messageText = action.match([&](
const MTPDmessageActionChatAddUser &data) {
setServiceText(action.match([&](
const MTPDmessageActionChatAddUser &data) {
return prepareChatAddUserText(data);
}, [&](const MTPDmessageActionChatJoinedByLink &data) {
return prepareChatJoinedByLink(data);
@ -714,9 +711,7 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
return PreparedText{
tr::lng_message_empty(tr::now, Ui::Text::WithEntities)
};
});
setServiceText(messageText);
}));
// Additional information.
applyAction(action);
@ -1219,11 +1214,11 @@ HistoryService::HistoryService(
MsgId id,
MessageFlags flags,
TimeId date,
const PreparedText &message,
PreparedText &&message,
PeerId from,
PhotoData *photo)
: HistoryItem(history, id, flags, date, from) {
setServiceText(message);
setServiceText(std::move(message));
if (photo) {
_media = std::make_unique<Data::MediaPhoto>(
this,
@ -1279,28 +1274,13 @@ ClickHandlerPtr HistoryService::fromLink() const {
return _from->createOpenLink();
}
void HistoryService::setServiceText(const PreparedText &prepared) {
const auto context = Core::MarkedTextContext{
.session = &history()->session(),
.customEmojiRepaint = [=] { customEmojiRepaint(); },
};
_text.setMarkedText(
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);
void HistoryService::setServiceText(PreparedText &&prepared) {
const auto had = !_text.empty();
_text = std::move(prepared.text);
_textLinks = std::move(prepared.links);
if (had) {
history()->owner().requestItemTextRefresh(this);
}
_textWidth = -1;
_textHeight = 0;
}
void HistoryService::hideSpoilers() {
HistoryView::HideSpoilers(_text);
}
void HistoryService::markMediaAsReadHook() {
@ -1506,6 +1486,10 @@ void HistoryService::createFromMtp(const MTPDmessageService &message) {
setMessageByAction(message.vaction());
}
const std::vector<ClickHandlerPtr> &HistoryService::customTextLinks() const {
return _textLinks;
}
void HistoryService::applyEdition(const MTPDmessageService &message) {
clearDependency();
UpdateComponents(0);
@ -1525,8 +1509,6 @@ void HistoryService::removeMedia() {
if (!_media) return;
_media.reset();
_textWidth = -1;
_textHeight = 0;
history()->owner().requestItemResize(this);
}
@ -1552,7 +1534,7 @@ void HistoryService::updateDependentText() {
}
void HistoryService::updateText(PreparedText &&text) {
setServiceText(text);
setServiceText(std::move(text));
history()->owner().requestItemResize(this);
invalidateChatListEntry();
history()->owner().updateDependentMessages(this);

View file

@ -77,7 +77,7 @@ class HistoryService : public HistoryItem {
public:
struct PreparedText {
TextWithEntities text;
QList<ClickHandlerPtr> links;
std::vector<ClickHandlerPtr> links;
};
HistoryService(
@ -95,7 +95,7 @@ public:
MsgId id,
MessageFlags flags,
TimeId date,
const PreparedText &message,
PreparedText &&message,
PeerId from = 0,
PhotoData *photo = nullptr);
@ -113,6 +113,8 @@ public:
return true;
}
const std::vector<ClickHandlerPtr> &customTextLinks() const override;
void applyEdition(const MTPDmessageService &message) override;
crl::time getSelfDestructIn(crl::time now) override;
@ -131,9 +133,7 @@ public:
not_null<HistoryView::ElementDelegate*> delegate,
HistoryView::Element *replacing = nullptr) override;
void setServiceText(const PreparedText &prepared);
void hideSpoilers() override;
void setServiceText(PreparedText &&prepared);
~HistoryService();
@ -187,6 +187,8 @@ private:
friend class HistoryView::Service;
std::vector<ClickHandlerPtr> _textLinks;
};
[[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.h"
#include "history/view/history_view_cursor_state.h"
#include "history/view/history_view_spoiler_click_handler.h"
#include "history/history.h"
#include "base/unixtime.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "core/click_handler_types.h"
#include "core/ui_integration.h"
#include "main/main_session.h"
#include "main/main_domain.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/toast/toast.h"
#include "ui/toasts/common_toasts.h"
#include "ui/text/text_options.h"
#include "ui/item_text_options.h"
#include "ui/painter.h"
#include "data/data_session.h"
#include "data/data_groups.h"
@ -355,11 +359,16 @@ void DateBadge::paint(
Element::Element(
not_null<ElementDelegate*> delegate,
not_null<HistoryItem*> data,
Element *replacing)
Element *replacing,
Flag serviceFlag)
: _delegate(delegate)
, _data(data)
, _dateTime(IsItemScheduledUntilOnline(data)
? QDateTime()
: ItemDateTime(data))
, _text(st::msgMinWidth)
, _isScheduledUntilOnline(IsItemScheduledUntilOnline(data))
, _dateTime(_isScheduledUntilOnline ? QDateTime() : ItemDateTime(data))
, _flags(serviceFlag | Flag::NeedsResize)
, _context(delegate->elementContext()) {
history()->owner().registerItemView(this);
refreshMedia(replacing);
@ -403,16 +412,36 @@ void Element::setY(int y) {
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() {
if (!_customEmojiRepaintScheduled) {
_customEmojiRepaintScheduled = true;
if (!(_flags & Flag::CustomEmojiRepainting)) {
_flags |= Flag::CustomEmojiRepainting;
history()->owner().requestViewRepaint(this);
}
}
void Element::clearCustomEmojiRepaint() const {
_customEmojiRepaintScheduled = false;
data()->_customEmojiRepaintScheduled = false;
_flags &= ~Flag::CustomEmojiRepainting;
data()->_flags &= ~MessageFlag::CustomEmojiRepainting;
}
void Element::prepareCustomEmojiPaint(
@ -548,35 +577,33 @@ void Element::refreshMedia(Element *replacing) {
_flags &= ~Flag::HiddenByGroup;
const auto item = data();
const auto media = item->media();
if (media && media->canBeGrouped()) {
if (const auto group = history()->owner().groups().find(item)) {
if (group->items.front() != item) {
_media = nullptr;
_flags |= Flag::HiddenByGroup;
} else {
_media = std::make_unique<GroupedMedia>(
this,
group->items);
if (!pendingResize()) {
history()->owner().requestViewResize(this);
if (const auto media = item->media()) {
if (media->canBeGrouped()) {
if (const auto group = history()->owner().groups().find(item)) {
if (group->items.front() != item) {
_media = nullptr;
_flags |= Flag::HiddenByGroup;
} else {
_media = std::make_unique<GroupedMedia>(
this,
group->items);
if (!pendingResize()) {
history()->owner().requestViewResize(this);
}
}
return;
}
return;
}
}
const auto session = &history()->session();
if (const auto media = _data->media()) {
_media = media->createView(this, replacing);
} else if (_data->isOnlyCustomEmoji()
} else if (isOnlyCustomEmoji()
&& Core::App().settings().largeEmoji()) {
_media = std::make_unique<UnwrappedMedia>(
this,
std::make_unique<CustomEmoji>(this, _data->onlyCustomEmoji()));
} else if (_data->isIsolatedEmoji()
std::make_unique<CustomEmoji>(this, onlyCustomEmoji()));
} else if (isIsolatedEmoji()
&& Core::App().settings().largeEmoji()) {
const auto emoji = _data->isolatedEmoji();
const auto emojiStickers = &session->emojiStickersPack();
const auto emoji = isolatedEmoji();
const auto emojiStickers = &history()->session().emojiStickersPack();
const auto skipPremiumEffect = false;
if (const auto sticker = emojiStickers->stickerForEmoji(emoji)) {
_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() {
recountDisplayDateInBlocks();
recountAttachToPreviousInBlocks();
@ -938,6 +1049,19 @@ bool Element::isSignedAuthorElided() const {
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() {
history()->owner().unregisterHeavyViewPart(this);
if (_media) {
@ -945,7 +1069,7 @@ void Element::unloadHeavyPart() {
}
if (_heavyCustomEmoji) {
_heavyCustomEmoji = false;
data()->_text.unloadPersistentAnimation();
_text.unloadPersistentAnimation();
if (const auto reply = data()->Get<HistoryMessageReply>()) {
reply->replyToText.unloadPersistentAnimation();
}
@ -1125,7 +1249,7 @@ Element::~Element() {
base::take(_media);
if (_heavyCustomEmoji) {
_heavyCustomEmoji = false;
data()->_text.unloadPersistentAnimation();
_text.unloadPersistentAnimation();
checkHeavyPart();
}
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 "base/runtime_composer.h"
#include "base/flags.h"
#include "base/weak_ptr.h"
class History;
class HistoryBlock;
@ -236,54 +237,71 @@ struct DateBadge : public RuntimeComponent<DateBadge, Element> {
class Element
: public Object
, public RuntimeComposer<Element>
, public ClickHandlerHost {
, public ClickHandlerHost
, public base::has_weak_ptr {
public:
Element(
not_null<ElementDelegate*> delegate,
not_null<HistoryItem*> data,
Element *replacing);
enum class Flag : uchar {
NeedsResize = 0x01,
AttachedToPrevious = 0x02,
AttachedToNext = 0x04,
HiddenByGroup = 0x08,
ServiceMessage = 0x01,
NeedsResize = 0x02,
AttachedToPrevious = 0x04,
AttachedToNext = 0x08,
HiddenByGroup = 0x10,
SpecialOnlyEmoji = 0x20,
CustomEmojiRepainting = 0x40,
};
using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; }
not_null<ElementDelegate*> delegate() const;
not_null<HistoryItem*> data() const;
not_null<History*> history() const;
Media *media() const;
Context context() const;
Element(
not_null<ElementDelegate*> delegate,
not_null<HistoryItem*> data,
Element *replacing,
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();
QDateTime dateTime() const;
[[nodiscard]] QDateTime dateTime() const;
int y() const;
[[nodiscard]] int y() const;
void setY(int y);
virtual int marginTop() const = 0;
virtual int marginBottom() const = 0;
[[nodiscard]] virtual int marginTop() const = 0;
[[nodiscard]] virtual int marginBottom() const = 0;
void setPendingResize();
bool pendingResize() const;
bool isUnderCursor() const;
[[nodiscard]] bool pendingResize() const;
[[nodiscard]] bool isUnderCursor() const;
bool isLastAndSelfMessage() const;
[[nodiscard]] bool isLastAndSelfMessage() const;
bool isAttachedToPrevious() const;
bool isAttachedToNext() const;
[[nodiscard]] bool isAttachedToPrevious() const;
[[nodiscard]] bool isAttachedToNext() const;
int skipBlockWidth() const;
int skipBlockHeight() const;
virtual int infoWidth() const;
virtual int bottomInfoFirstLineWidth() const;
virtual bool bottomInfoIsWide() const;
[[nodiscard]] int skipBlockWidth() const;
[[nodiscard]] int skipBlockHeight() const;
[[nodiscard]] virtual int infoWidth() const;
[[nodiscard]] virtual int bottomInfoFirstLineWidth() const;
[[nodiscard]] virtual bool bottomInfoIsWide() const;
bool isHiddenByGroup() const;
virtual bool isHidden() const;
[[nodiscard]] bool isHiddenByGroup() 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().
void setAttachToPrevious(bool attachToNext);
@ -300,9 +318,9 @@ public:
void createUnreadBar(rpl::producer<QString> text);
void destroyUnreadBar();
int displayedDateHeight() const;
bool displayDate() const;
bool isInOneDayWithPrevious() const;
[[nodiscard]] int displayedDateHeight() const;
[[nodiscard]] bool displayDate() const;
[[nodiscard]] bool isInOneDayWithPrevious() const;
virtual void draw(Painter &p, const PaintContext &context) const = 0;
[[nodiscard]] virtual PointState pointState(QPoint point) const = 0;
@ -382,8 +400,9 @@ public:
[[nodiscard]] virtual bool isSignedAuthorElided() const;
virtual void itemDataChanged();
void itemTextUpdated();
virtual bool hasHeavyPart() const;
[[nodiscard]] virtual bool hasHeavyPart() const;
virtual void unloadHeavyPart();
void checkHeavyPart();
@ -395,21 +414,21 @@ public:
not_null<const HistoryItem*> item) const;
// Legacy blocks structure.
HistoryBlock *block();
const HistoryBlock *block() const;
[[nodiscard]] HistoryBlock *block();
[[nodiscard]] const HistoryBlock *block() const;
void attachToBlock(not_null<HistoryBlock*> block, int index);
void removeFromBlock();
void refreshInBlock();
void setIndexInBlock(int index);
int indexInBlock() const;
Element *previousInBlocks() const;
Element *previousDisplayedInBlocks() const;
Element *nextInBlocks() const;
Element *nextDisplayedInBlocks() const;
[[nodiscard]] int indexInBlock() const;
[[nodiscard]] Element *previousInBlocks() const;
[[nodiscard]] Element *previousDisplayedInBlocks() const;
[[nodiscard]] Element *nextInBlocks() const;
[[nodiscard]] Element *nextDisplayedInBlocks() const;
void previousInBlocksChanged();
void nextInBlocksRemoved();
virtual QRect innerGeometry() const = 0;
[[nodiscard]] virtual QRect innerGeometry() const = 0;
void customEmojiRepaint();
void prepareCustomEmojiPaint(
@ -421,6 +440,7 @@ public:
const PaintContext &context,
const Reactions::InlineList &reactions) const;
void clearCustomEmojiRepaint() const;
void hideSpoilers();
[[nodiscard]] ClickHandlerPtr fromPhotoLink() const {
return fromLink();
@ -461,6 +481,14 @@ protected:
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:
// This should be called only from previousInBlocksChanged()
// to add required bits to the Composer mask
@ -483,22 +511,23 @@ private:
const not_null<ElementDelegate*> _delegate;
const not_null<HistoryItem*> _data;
HistoryBlock *_block = nullptr;
std::unique_ptr<Media> _media;
mutable ClickHandlerPtr _fromLink;
bool _isScheduledUntilOnline = false;
mutable bool _heavyCustomEmoji = false;
mutable bool _customEmojiRepaintScheduled = false;
const QDateTime _dateTime;
mutable Ui::Text::String _text = { st::msgMinWidth };
mutable int _textWidth = -1;
mutable int _textHeight = 0;
int _y = 0;
Context _context = Context();
Flags _flags = Flag::NeedsResize;
HistoryBlock *_block = nullptr;
int _indexInBlock = -1;
bool _isScheduledUntilOnline = false;
mutable bool _heavyCustomEmoji = false;
mutable Flags _flags = Flag(0);
Context _context = Context();
};
} // namespace HistoryView

View file

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

View file

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

View file

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

View file

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

View file

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

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