diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 012049e4d..2e0b1890b 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1415,6 +1415,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_admin_log_admin_pin_messages" = "Pin messages"; "lng_admin_log_admin_add_admins" = "Add new admins"; +"lng_feed_show_next" = "Show Next"; + // Wnd specific "lng_wnd_choose_program_menu" = "Choose Default Program..."; diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 52dbee6e0..06ecd59e8 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -34,6 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/storage_shared_media.h" #include "storage/storage_user_photos.h" #include "storage/storage_media_prepare.h" +#include "storage/storage_feed_messages.h" #include "data/data_sparse_ids.h" #include "data/data_search_controller.h" #include "data/data_channel_admins.h" @@ -49,6 +50,7 @@ constexpr auto kUnreadMentionsPreloadIfLess = 5; constexpr auto kUnreadMentionsFirstRequestLimit = 10; constexpr auto kUnreadMentionsNextRequestLimit = 100; constexpr auto kSharedMediaLimit = 100; +constexpr auto kFeedMessagesLimit = 50; constexpr auto kReadFeaturedSetsTimeout = TimeMs(1000); constexpr auto kFileLoaderQueueStopTimeout = TimeMs(5000); @@ -2517,6 +2519,125 @@ void ApiWrap::userPhotosDone( )); } +void ApiWrap::requestFeedMessages( + not_null feed, + Data::MessagePosition messageId, + SliceType slice) { + const auto key = std::make_tuple(feed, messageId, slice); + if (_feedMessagesRequests.contains(key)) { + return; + } + + const auto addOffset = 0; + const auto limit = kFeedMessagesLimit; + const auto sourcesHash = int32(0); + const auto hash = int32(0); + const auto flags = (messageId && messageId.fullId.channel) + ? MTPchannels_GetFeed::Flag::f_offset_position + : MTPchannels_GetFeed::Flag::f_offset_to_max_read; + const auto requestId = request(MTPchannels_GetFeed( + MTP_flags(flags), + MTP_int(feed->id()), + MTP_feedPosition( + MTP_int(messageId.date), + MTP_peerChannel(MTP_int(messageId.fullId.channel)), + MTP_int(messageId.fullId.msg)), + MTP_int(addOffset), + MTP_int(limit), + MTPFeedPosition(), + MTPFeedPosition(), + MTP_int(sourcesHash), + MTP_int(hash) + )).done([=](const MTPmessages_FeedMessages &result) { + const auto key = std::make_tuple(feed, messageId, slice); + _feedMessagesRequests.remove(key); + feedMessagesDone(feed, messageId, slice, result); + }).fail([=](const RPCError &error) { + _feedMessagesRequests.remove(key); + }).send(); + _feedMessagesRequests.emplace(key, requestId); +} + +void ApiWrap::feedMessagesDone( + not_null feed, + Data::MessagePosition messageId, + SliceType slice, + const MTPmessages_FeedMessages &result) { + if (result.type() == mtpc_messages_feedMessagesNotModified) { + LOG(("API Error: Unexpected messages.feedMessagesNotModified.")); + _session->storage().add(Storage::FeedMessagesAddSlice( + feed->id(), + std::vector(), + Data::FullMessagesRange)); + return; + } + Assert(result.type() == mtpc_messages_feedMessages); + const auto &data = result.c_messages_feedMessages(); + const auto &messages = data.vmessages.v; + const auto type = NewMessageExisting; + + auto ids = std::vector(); + auto noSkipRange = Data::MessagesRange(messageId, messageId); + auto accumulateFrom = [](auto &from, const auto &candidate) { + if (!from || from > candidate) { + from = candidate; + } + }; + auto accumulateTill = [](auto &till, const auto &candidate) { + if (!till || till < candidate) { + till = candidate; + } + }; + App::feedUsers(data.vusers); + App::feedChats(data.vchats); + if (!messages.empty()) { + ids.reserve(messages.size()); + for (const auto &msg : messages) { + if (const auto item = App::histories().addNewMessage(msg, type)) { + const auto position = item->position(); + ids.push_back(position); + accumulateFrom(noSkipRange.from, position); + accumulateTill(noSkipRange.till, position); + } + } + ranges::reverse(ids); + } + if (data.has_min_position()) { + accumulateFrom( + noSkipRange.from, + Data::FeedPositionFromMTP(data.vmin_position)); + } else { + noSkipRange.from = Data::MinMessagePosition; + } + if (data.has_max_position()) { + accumulateTill( + noSkipRange.till, + Data::FeedPositionFromMTP(data.vmax_position)); + } else { + noSkipRange.till = Data::MaxMessagePosition; + } + + const auto unreadPosition = [&] { + if (data.has_read_max_position()) { + return Data::FeedPositionFromMTP(data.vread_max_position); + } else if (!messageId) { + return ids.empty() + ? noSkipRange.till + : ids.back(); + } + return Data::MessagePosition(); + }(); + + _session->storage().add(Storage::FeedMessagesAddSlice( + feed->id(), + std::move(ids), + noSkipRange)); + + if (unreadPosition) { + feed->setUnreadPosition(unreadPosition); + } +} + void ApiWrap::sendAction(const SendOptions &options) { readServerHistory(options.history); options.history->getReadyFor(ShowAtTheEndMsgId); diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 035cce204..bef746bc2 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -9,15 +9,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include "base/timer.h" -#include "core/single_timer.h" -#include "mtproto/sender.h" #include "base/flat_map.h" #include "base/flat_set.h" +#include "core/single_timer.h" +#include "mtproto/sender.h" #include "chat_helpers/stickers.h" +#include "data/data_messages.h" class TaskQueue; class AuthSession; -enum class SparseIdsLoadDirection; struct MessageGroupId; struct SendingAlbum; enum class SendMediaType; @@ -129,7 +129,7 @@ public: bool adminsEnabled, base::flat_set> &&admins); - using SliceType = SparseIdsLoadDirection; + using SliceType = Data::LoadDirection; void requestSharedMedia( not_null peer, Storage::SharedMediaType type, @@ -143,6 +143,11 @@ public: not_null user, PhotoId afterId); + void requestFeedMessages( + not_null feed, + Data::MessagePosition messageId, + SliceType slice); + void stickerSetInstalled(uint64 setId) { _stickerSetInstalled.fire_copy(setId); } @@ -300,6 +305,12 @@ private: PhotoId photoId, const MTPphotos_Photos &result); + void feedMessagesDone( + not_null feed, + Data::MessagePosition messageId, + SliceType slice, + const MTPmessages_FeedMessages &result); + void sendSharedContact( const QString &phone, const QString &firstName, @@ -417,6 +428,11 @@ private: base::flat_map, mtpRequestId> _userPhotosRequests; + base::flat_map, + Data::MessagePosition, + SliceType>, mtpRequestId> _feedMessagesRequests; + rpl::event_stream _sendActions; struct ReadRequest { diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 7fca36d1b..207809bf0 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -19,10 +19,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "data/data_abstract_structure.h" #include "data/data_session.h" -#include "history/history_service_layout.h" #include "history/history_location_manager.h" #include "history/history_media_types.h" #include "history/history_item_components.h" +#include "history/view/history_view_service_message.h" #include "media/media_audio.h" #include "inline_bots/inline_bot_layout_item.h" #include "messenger.h" @@ -2141,7 +2141,7 @@ namespace { if (App::main()) { App::main()->updateScrollColors(); } - HistoryLayout::serviceColorsUpdated(); + HistoryView::serviceColorsUpdated(); } else if (update.type == Update::Type::New) { prepareCorners(StickerCorners, st::dateRadius, st::msgServiceBg); prepareCorners(StickerSelectedCorners, st::dateRadius, st::msgServiceBgSelected); @@ -2149,7 +2149,7 @@ namespace { if (App::main()) { App::main()->updateScrollColors(); } - HistoryLayout::serviceColorsUpdated(); + HistoryView::serviceColorsUpdated(); } }); } diff --git a/Telegram/SourceFiles/boxes/peers/manage_peer_box.cpp b/Telegram/SourceFiles/boxes/peers/manage_peer_box.cpp index 711931281..10ca6eb93 100644 --- a/Telegram/SourceFiles/boxes/peers/manage_peer_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/manage_peer_box.cpp @@ -12,7 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/peers/edit_peer_info_box.h" #include "ui/wrap/vertical_layout.h" #include "ui/widgets/labels.h" -#include "history/history_admin_log_section.h" +#include "history/admin_log/history_admin_log_section.h" #include "window/window_controller.h" #include "profile/profile_channel_controllers.h" #include "info/profile/info_profile_button.h" diff --git a/Telegram/SourceFiles/data/data_feed.cpp b/Telegram/SourceFiles/data/data_feed.cpp index c29a8db66..4dc519a81 100644 --- a/Telegram/SourceFiles/data/data_feed.cpp +++ b/Telegram/SourceFiles/data/data_feed.cpp @@ -11,16 +11,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Data { -FeedPosition::FeedPosition(const MTPFeedPosition &position) -: date(position.c_feedPosition().vdate.v) -, msgId( - peerToChannel(peerFromMTP(position.c_feedPosition().vpeer)), - position.c_feedPosition().vid.v) { -} +MessagePosition FeedPositionFromMTP(const MTPFeedPosition &position) { + Expects(position.type() == mtpc_feedPosition); -FeedPosition::FeedPosition(not_null item) -: date(toServerTime(item->date.toTime_t()).v) -, msgId(item->fullId()) { + const auto &data = position.c_feedPosition(); + return MessagePosition(data.vdate.v, FullMsgId( + peerToChannel(peerFromMTP(data.vpeer)), + data.vid.v)); } Feed::Feed(FeedId id) @@ -82,7 +79,7 @@ void Feed::paintUserpic( } bool Feed::justSetLastMessage(not_null item) { - if (_lastMessage && FeedPosition(item) <= FeedPosition(_lastMessage)) { + if (_lastMessage && item->position() <= _lastMessage->position()) { return false; } _lastMessage = item; @@ -118,8 +115,4 @@ void Feed::setUnreadCounts(int unreadCount, int unreadMutedCount) { _unreadMutedCount = unreadMutedCount; } -void Feed::setUnreadPosition(const FeedPosition &position) { - _unreadPosition = position; -} - } // namespace Data diff --git a/Telegram/SourceFiles/data/data_feed.h b/Telegram/SourceFiles/data/data_feed.h index cd7258429..ce171403b 100644 --- a/Telegram/SourceFiles/data/data_feed.h +++ b/Telegram/SourceFiles/data/data_feed.h @@ -8,51 +8,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "dialogs/dialogs_entry.h" +#include "data/data_messages.h" class ChannelData; namespace Data { -struct FeedPosition { - FeedPosition() = default; - explicit FeedPosition(const MTPFeedPosition &position); - explicit FeedPosition(not_null item); - FeedPosition(TimeId date, FullMsgId msgId) : date(date), msgId(msgId) { - } - - explicit operator bool() const { - return (msgId.msg != 0); - } - - inline bool operator<(const FeedPosition &other) const { - if (date < other.date) { - return true; - } else if (other.date < date) { - return false; - } - return (msgId < other.msgId); - } - inline bool operator>(const FeedPosition &other) const { - return other < *this; - } - inline bool operator<=(const FeedPosition &other) const { - return !(other < *this); - } - inline bool operator>=(const FeedPosition &other) const { - return !(*this < other); - } - inline bool operator==(const FeedPosition &other) const { - return (date == other.date) - && (msgId == other.msgId); - } - inline bool operator!=(const FeedPosition &other) const { - return !(*this == other); - } - - TimeId date = 0; - FullMsgId msgId; - -}; +MessagePosition FeedPositionFromMTP(const MTPFeedPosition &position); class Feed : public Dialogs::Entry { public: @@ -69,7 +31,15 @@ public: void historyCleared(not_null history); void setUnreadCounts(int unreadCount, int unreadMutedCount); - void setUnreadPosition(const FeedPosition &position); + void setUnreadPosition(const MessagePosition &position) { + _unreadPosition = position; + } + MessagePosition unreadPosition() const { + return _unreadPosition.current(); + } + rpl::producer unreadPositionChanges() const { + return _unreadPosition.changes(); + } bool toImportant() const override { return false; // TODO feeds workmode @@ -99,7 +69,7 @@ private: HistoryItem *_lastMessage = nullptr; - FeedPosition _unreadPosition; + rpl::variable _unreadPosition; int _unreadCount = 0; int _unreadMutedCount = 0; bool _complete = false; diff --git a/Telegram/SourceFiles/data/data_feed_messages.cpp b/Telegram/SourceFiles/data/data_feed_messages.cpp new file mode 100644 index 000000000..4203f27ae --- /dev/null +++ b/Telegram/SourceFiles/data/data_feed_messages.cpp @@ -0,0 +1,87 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "data/data_feed_messages.h" + +#include "apiwrap.h" +#include "auth_session.h" +#include "data/data_session.h" +#include "storage/storage_feed_messages.h" + +namespace Data { + +rpl::producer FeedMessagesViewer( + Storage::FeedMessagesKey key, + int limitBefore, + int limitAfter) { + Expects(IsServerMsgId(key.position.fullId.msg) + || (key.position.fullId.msg == 0)); + + return [=](auto consumer) { + auto lifetime = rpl::lifetime(); + const auto builder = lifetime.make_state( + key.position, + limitBefore, + limitAfter); + const auto feed = Auth().data().feed(key.feedId); + using AroundData = MessagesSliceBuilder::AroundData; + const auto requestMediaAround = [=](const AroundData &data) { + if (data.aroundId || !key.position) { + Auth().api().requestFeedMessages( + feed, + data.aroundId, + data.direction); + } + }; + builder->insufficientAround( + ) | rpl::start_with_next(requestMediaAround, lifetime); + + const auto pushNextSnapshot = [=] { + consumer.put_next(builder->snapshot()); + }; + + using SliceUpdate = Storage::FeedMessagesSliceUpdate; + Auth().storage().feedMessagesSliceUpdated( + ) | rpl::filter([=](const SliceUpdate &update) { + return (update.feedId == key.feedId); + }) | rpl::filter([=](const SliceUpdate &update) { + return builder->applyUpdate(update.data); + }) | rpl::start_with_next(pushNextSnapshot, lifetime); + + using OneRemoved = Storage::FeedMessagesRemoveOne; + Auth().storage().feedMessagesOneRemoved( + ) | rpl::filter([=](const OneRemoved &update) { + return (update.feedId == key.feedId); + }) | rpl::filter([=](const OneRemoved &update) { + return builder->removeOne(update.messageId); + }) | rpl::start_with_next(pushNextSnapshot, lifetime); + + using AllRemoved = Storage::FeedMessagesRemoveAll; + Auth().storage().feedMessagesAllRemoved( + ) | rpl::filter([=](const AllRemoved &update) { + return (update.feedId == key.feedId); + }) | rpl::filter([=] { + return builder->removeAll(); + }) | rpl::start_with_next(pushNextSnapshot, lifetime); + + using Result = Storage::FeedMessagesResult; + Auth().storage().query(Storage::FeedMessagesQuery( + key, + limitBefore, + limitAfter + )) | rpl::filter([=](const Result &result) { + return builder->applyInitial(result); + }) | rpl::start_with_next_done( + pushNextSnapshot, + [=] { builder->checkInsufficient(); }, + lifetime); + + return lifetime; + }; +} + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_feed_messages.h b/Telegram/SourceFiles/data/data_feed_messages.h new file mode 100644 index 000000000..fac2aa06f --- /dev/null +++ b/Telegram/SourceFiles/data/data_feed_messages.h @@ -0,0 +1,24 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "data/data_feed.h" +#include "data/data_messages.h" + +namespace Storage { +struct FeedMessagesKey; +} // namespace Storage + +namespace Data { + +rpl::producer FeedMessagesViewer( + Storage::FeedMessagesKey key, + int limitBefore, + int limitAfter); + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_messages.cpp b/Telegram/SourceFiles/data/data_messages.cpp new file mode 100644 index 000000000..832244ac6 --- /dev/null +++ b/Telegram/SourceFiles/data/data_messages.cpp @@ -0,0 +1,444 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "data/data_feed_messages.h" + +namespace Data { + +MessagesList::Slice::Slice( + base::flat_set &&messages, + MessagesRange range) +: messages(std::move(messages)) +, range(range) { +} + +template +void MessagesList::Slice::merge( + const Range &moreMessages, + MessagesRange moreNoSkipRange) { + Expects(moreNoSkipRange.from <= range.till); + Expects(range.from <= moreNoSkipRange.till); + + messages.merge(std::begin(moreMessages), std::end(moreMessages)); + range = { + qMin(range.from, moreNoSkipRange.from), + qMax(range.till, moreNoSkipRange.till) + }; +} + +template +int MessagesList::uniteAndAdd( + MessagesSliceUpdate &update, + base::flat_set::iterator uniteFrom, + base::flat_set::iterator uniteTill, + const Range &messages, + MessagesRange noSkipRange) { + auto uniteFromIndex = uniteFrom - _slices.begin(); + auto was = uniteFrom->messages.size(); + _slices.modify(uniteFrom, [&](Slice &slice) { + slice.merge(messages, noSkipRange); + }); + auto firstToErase = uniteFrom + 1; + if (firstToErase != uniteTill) { + for (auto it = firstToErase; it != uniteTill; ++it) { + _slices.modify(uniteFrom, [&](Slice &slice) { + slice.merge(it->messages, it->range); + }); + } + _slices.erase(firstToErase, uniteTill); + uniteFrom = _slices.begin() + uniteFromIndex; + } + update.messages = &uniteFrom->messages; + update.range = uniteFrom->range; + return uniteFrom->messages.size() - was; +} + +template +int MessagesList::addRangeItemsAndCountNew( + MessagesSliceUpdate &update, + const Range &messages, + MessagesRange noSkipRange) { + Expects(noSkipRange.from <= noSkipRange.till); + + auto uniteFrom = ranges::lower_bound( + _slices, + noSkipRange.from, + std::less<>(), + [](const Slice &slice) { return slice.range.till; }); + auto uniteTill = ranges::upper_bound( + _slices, + noSkipRange.till, + std::less<>(), + [](const Slice &slice) { return slice.range.from; }); + if (uniteFrom < uniteTill) { + return uniteAndAdd(update, uniteFrom, uniteTill, messages, noSkipRange); + } + + auto sliceMessages = base::flat_set { + std::begin(messages), + std::end(messages) }; + auto slice = _slices.emplace( + std::move(sliceMessages), + noSkipRange); + update.messages = &slice->messages; + update.range = slice->range; + return slice->messages.size(); +} + +template +void MessagesList::addRange( + const Range &messages, + MessagesRange noSkipRange, + base::optional count, + bool incrementCount) { + Expects(!count || !incrementCount); + + auto wasCount = _count; + auto update = MessagesSliceUpdate(); + auto result = addRangeItemsAndCountNew( + update, + messages, + noSkipRange); + if (count) { + _count = count; + } else if (incrementCount && _count && result > 0) { + *_count += result; + } + if (_slices.size() == 1) { + if (_slices.front().range == FullMessagesRange) { + _count = _slices.front().messages.size(); + } + } + update.count = _count; + _sliceUpdated.fire(std::move(update)); +} + +void MessagesList::addNew(MessagePosition messageId) { + auto range = { messageId }; + addRange(range, { messageId, MaxMessagePosition }, base::none, true); +} + +void MessagesList::addSlice( + std::vector &&messageIds, + MessagesRange noSkipRange, + base::optional count) { + addRange(messageIds, noSkipRange, count); +} + +void MessagesList::removeOne(MessagePosition messageId) { + auto slice = ranges::lower_bound( + _slices, + messageId, + std::less<>(), + [](const Slice &slice) { return slice.range.till; }); + if (slice != _slices.end() && slice->range.from <= messageId) { + _slices.modify(slice, [messageId](Slice &slice) { + return slice.messages.remove(messageId); + }); + } + if (_count) { + --*_count; + } +} + +void MessagesList::removeAll(ChannelId channelId) { + // #TODO feeds show + //_slices.clear(); + //_slices.emplace(base::flat_set{}, FullMessagesRange); +} + +rpl::producer MessagesList::query( + MessagesQuery &&query) const { + return [this, query = std::move(query)](auto consumer) { + auto slice = query.aroundId + ? ranges::lower_bound( + _slices, + query.aroundId, + std::less<>(), + [](const Slice &slice) { return slice.range.till; }) + : _slices.end(); + if (slice != _slices.end() + && slice->range.from <= query.aroundId) { + consumer.put_next(queryFromSlice(query, *slice)); + } + consumer.put_done(); + return rpl::lifetime(); + }; +} + +rpl::producer MessagesList::sliceUpdated() const { + return _sliceUpdated.events(); +} + +MessagesResult MessagesList::queryFromSlice( + const MessagesQuery &query, + const Slice &slice) const { + auto result = MessagesResult {}; + auto position = ranges::lower_bound(slice.messages, query.aroundId); + auto haveBefore = int(position - begin(slice.messages)); + auto haveEqualOrAfter = int(end(slice.messages) - position); + auto before = qMin(haveBefore, query.limitBefore); + auto equalOrAfter = qMin(haveEqualOrAfter, query.limitAfter + 1); + auto ids = std::vector(position - before, position + equalOrAfter); + result.messageIds.merge(ids.begin(), ids.end()); + if (slice.range.from == MinMessagePosition) { + result.skippedBefore = haveBefore - before; + } + if (slice.range.till == MaxMessagePosition) { + result.skippedAfter = haveEqualOrAfter - equalOrAfter; + } + if (_count) { + result.count = _count; + if (!result.skippedBefore && result.skippedAfter) { + result.skippedBefore = *result.count + - *result.skippedAfter + - int(result.messageIds.size()); + } else if (!result.skippedAfter && result.skippedBefore) { + result.skippedAfter = *result.count + - *result.skippedBefore + - int(result.messageIds.size()); + } + } + return result; +} + +MessagesSliceBuilder::MessagesSliceBuilder( + Key key, + int limitBefore, + int limitAfter) +: _key(key) +, _limitBefore(limitBefore) +, _limitAfter(limitAfter) { +} + +bool MessagesSliceBuilder::applyInitial(const MessagesResult &result) { + mergeSliceData( + result.count, + result.messageIds, + result.skippedBefore, + result.skippedAfter); + return true; +} + +bool MessagesSliceBuilder::applyUpdate(const MessagesSliceUpdate &update) { + auto intersects = [](MessagesRange range1, MessagesRange range2) { + return (range1.from <= range2.till) + && (range2.from <= range1.till); + }; + auto needMergeMessages = (update.messages != nullptr) + && intersects(update.range, { + _ids.empty() ? _key : _ids.front(), + _ids.empty() ? _key : _ids.back() + }); + if (!needMergeMessages && !update.count) { + return false; + } + auto skippedBefore = (update.range.from == MinMessagePosition) + ? 0 + : base::optional {}; + auto skippedAfter = (update.range.till == MaxMessagePosition) + ? 0 + : base::optional {}; + mergeSliceData( + update.count, + needMergeMessages + ? *update.messages + : base::flat_set {}, + skippedBefore, + skippedAfter); + return true; +} + +bool MessagesSliceBuilder::removeOne(MessagePosition messageId) { + auto changed = false; + if (_fullCount && *_fullCount > 0) { + --*_fullCount; + changed = true; + } + if (_ids.contains(messageId)) { + _ids.remove(messageId); + changed = true; + } else if (!_ids.empty()) { + if (_ids.front() > messageId + && _skippedBefore + && *_skippedBefore > 0) { + --*_skippedBefore; + changed = true; + } else if (_ids.back() < messageId + && _skippedAfter + && *_skippedAfter > 0) { + --*_skippedAfter; + changed = true; + } + } + return changed; +} + +bool MessagesSliceBuilder::removeAll() { + _ids = {}; + _range = FullMessagesRange; + _fullCount = 0; + _skippedBefore = 0; + _skippedAfter = 0; + return true; +} + +bool MessagesSliceBuilder::removeFromChannel(ChannelId channelId) { + for (auto i = _ids.begin(); i != _ids.end();) { + if ((*i).fullId.channel == channelId) { + i = _ids.erase(i); + if (_fullCount) { + --*_fullCount; + } + } else { + ++i; + } + } + _skippedBefore = _skippedAfter = base::none; + return true; +} + +void MessagesSliceBuilder::checkInsufficient() { + sliceToLimits(); +} + +void MessagesSliceBuilder::mergeSliceData( + base::optional count, + const base::flat_set &messageIds, + base::optional skippedBefore, + base::optional skippedAfter) { + if (messageIds.empty()) { + if (count && _fullCount != count) { + _fullCount = count; + if (*_fullCount <= _ids.size()) { + _fullCount = _ids.size(); + _skippedBefore = _skippedAfter = 0; + } + } + fillSkippedAndSliceToLimits(); + return; + } + if (count) { + _fullCount = count; + } + const auto impossible = MessagePosition(-1, FullMsgId()); + auto wasMinId = _ids.empty() ? impossible : _ids.front(); + auto wasMaxId = _ids.empty() ? impossible : _ids.back(); + _ids.merge(messageIds.begin(), messageIds.end()); + + auto adjustSkippedBefore = [&](MessagePosition oldId, int oldSkippedBefore) { + auto it = _ids.find(oldId); + Assert(it != _ids.end()); + _skippedBefore = oldSkippedBefore - (it - _ids.begin()); + accumulate_max(*_skippedBefore, 0); + }; + if (skippedBefore) { + adjustSkippedBefore(messageIds.front(), *skippedBefore); + } else if (wasMinId != impossible && _skippedBefore) { + adjustSkippedBefore(wasMinId, *_skippedBefore); + } else { + _skippedBefore = base::none; + } + + auto adjustSkippedAfter = [&](MessagePosition oldId, int oldSkippedAfter) { + auto it = _ids.find(oldId); + Assert(it != _ids.end()); + _skippedAfter = oldSkippedAfter - (_ids.end() - it - 1); + accumulate_max(*_skippedAfter, 0); + }; + if (skippedAfter) { + adjustSkippedAfter(messageIds.back(), *skippedAfter); + } else if (wasMaxId != impossible && _skippedAfter) { + adjustSkippedAfter(wasMaxId, *_skippedAfter); + } else { + _skippedAfter = base::none; + } + fillSkippedAndSliceToLimits(); +} + +void MessagesSliceBuilder::fillSkippedAndSliceToLimits() { + if (_fullCount) { + if (_skippedBefore && !_skippedAfter) { + _skippedAfter = *_fullCount + - *_skippedBefore + - int(_ids.size()); + } else if (_skippedAfter && !_skippedBefore) { + _skippedBefore = *_fullCount + - *_skippedAfter + - int(_ids.size()); + } + } + sliceToLimits(); +} + +void MessagesSliceBuilder::sliceToLimits() { + if (!_key) { + if (!_fullCount) { + requestMessagesCount(); + } + return; + } + auto requestedSomething = false; + auto aroundIt = ranges::lower_bound(_ids, _key); + auto removeFromBegin = (aroundIt - _ids.begin() - _limitBefore); + auto removeFromEnd = (_ids.end() - aroundIt - _limitAfter - 1); + if (removeFromBegin > 0) { + _ids.erase(_ids.begin(), _ids.begin() + removeFromBegin); + if (_skippedBefore) { + *_skippedBefore += removeFromBegin; + } + } else if (removeFromBegin < 0 + && (!_skippedBefore || *_skippedBefore > 0)) { + requestedSomething = true; + requestMessages(RequestDirection::Before); + } + if (removeFromEnd > 0) { + _ids.erase(_ids.end() - removeFromEnd, _ids.end()); + if (_skippedAfter) { + *_skippedAfter += removeFromEnd; + } + } else if (removeFromEnd < 0 + && (!_skippedAfter || *_skippedAfter > 0)) { + requestedSomething = true; + requestMessages(RequestDirection::After); + } + if (!_fullCount && !requestedSomething) { + requestMessagesCount(); + } +} + +void MessagesSliceBuilder::requestMessages(RequestDirection direction) { + auto requestAroundData = [&]() -> AroundData { + if (_ids.empty()) { + return { _key, Data::LoadDirection::Around }; + } else if (direction == RequestDirection::Before) { + return { _ids.front(), Data::LoadDirection::Before }; + } + return { _ids.back(), Data::LoadDirection::After }; + }; + _insufficientAround.fire(requestAroundData()); +} + +void MessagesSliceBuilder::requestMessagesCount() { + _insufficientAround.fire({ + MessagePosition(), + Data::LoadDirection::Around }); +} + +MessagesSlice MessagesSliceBuilder::snapshot() const { + auto result = MessagesSlice(); + result.ids.reserve(_ids.size()); + for (const auto &position : _ids) { + result.ids.push_back(position.fullId); + } + result.skippedBefore = _skippedBefore; + result.skippedAfter = _skippedAfter; + result.fullCount = _fullCount; + return result; +} + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_messages.h b/Telegram/SourceFiles/data/data_messages.h new file mode 100644 index 000000000..5fa3f57bb --- /dev/null +++ b/Telegram/SourceFiles/data/data_messages.h @@ -0,0 +1,244 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace Data { + +enum class LoadDirection : char { + Around, + Before, + After, +}; + +struct MessagePosition { + constexpr MessagePosition() = default; + constexpr MessagePosition(TimeId date, FullMsgId fullId) + : fullId(fullId) + , date(date) { + } + + explicit operator bool() const { + return (fullId.msg != 0); + } + + inline constexpr bool operator<(const MessagePosition &other) const { + if (date < other.date) { + return true; + } else if (other.date < date) { + return false; + } + return (fullId < other.fullId); + } + inline constexpr bool operator>(const MessagePosition &other) const { + return other < *this; + } + inline constexpr bool operator<=(const MessagePosition &other) const { + return !(other < *this); + } + inline constexpr bool operator>=(const MessagePosition &other) const { + return !(*this < other); + } + inline constexpr bool operator==(const MessagePosition &other) const { + return (date == other.date) + && (fullId == other.fullId); + } + inline constexpr bool operator!=(const MessagePosition &other) const { + return !(*this == other); + } + + FullMsgId fullId; + TimeId date = 0; + +}; + +struct MessagesRange { + constexpr MessagesRange() = default; + constexpr MessagesRange(MessagePosition from, MessagePosition till) + : from(from) + , till(till) { + } + + inline constexpr bool operator==(const MessagesRange &other) const { + return (from == other.from) + && (till == other.till); + } + inline constexpr bool operator!=(const MessagesRange &other) const { + return !(*this == other); + } + + MessagePosition from; + MessagePosition till; + +}; + +constexpr auto MaxDate = std::numeric_limits::max(); +constexpr auto MinMessagePosition = MessagePosition(TimeId(0), FullMsgId()); +constexpr auto MaxMessagePosition = MessagePosition(MaxDate, FullMsgId()); +constexpr auto FullMessagesRange = MessagesRange( + MinMessagePosition, + MaxMessagePosition); +constexpr auto UnreadMessagePosition = MinMessagePosition; + +struct MessagesSlice { + std::vector ids; + base::optional skippedBefore; + base::optional skippedAfter; + base::optional fullCount; + +}; + +struct MessagesQuery { + MessagesQuery( + MessagePosition aroundId, + int limitBefore, + int limitAfter) + : aroundId(aroundId) + , limitBefore(limitBefore) + , limitAfter(limitAfter) { + } + + MessagePosition aroundId; + int limitBefore = 0; + int limitAfter = 0; + +}; + +struct MessagesResult { + base::optional count; + base::optional skippedBefore; + base::optional skippedAfter; + base::flat_set messageIds; +}; + +struct MessagesSliceUpdate { + const base::flat_set *messages = nullptr; + MessagesRange range; + base::optional count; +}; + +class MessagesList { +public: + void addNew(MessagePosition messageId); + void addSlice( + std::vector &&messageIds, + MessagesRange noSkipRange, + base::optional count); + void removeOne(MessagePosition messageId); + void removeAll(ChannelId channelId); + rpl::producer query(MessagesQuery &&query) const; + rpl::producer sliceUpdated() const; + +private: + struct Slice { + Slice( + base::flat_set &&messages, + MessagesRange range); + + template + void merge( + const Range &moreMessages, + MessagesRange moreNoSkipRange); + + base::flat_set messages; + MessagesRange range; + + inline bool operator<(const Slice &other) const { + return range.from < other.range.from; + } + + }; + + template + int uniteAndAdd( + MessagesSliceUpdate &update, + base::flat_set::iterator uniteFrom, + base::flat_set::iterator uniteTill, + const Range &messages, + MessagesRange noSkipRange); + template + int addRangeItemsAndCountNew( + MessagesSliceUpdate &update, + const Range &messages, + MessagesRange noSkipRange); + template + void addRange( + const Range &messages, + MessagesRange noSkipRange, + base::optional count, + bool incrementCount = false); + + MessagesResult queryFromSlice( + const MessagesQuery &query, + const Slice &slice) const; + + base::optional _count; + base::flat_set _slices; + + rpl::event_stream _sliceUpdated; + +}; + +class MessagesSliceBuilder { +public: + using Key = MessagePosition; + + MessagesSliceBuilder(Key key, int limitBefore, int limitAfter); + + bool applyInitial(const MessagesResult &result); + bool applyUpdate(const MessagesSliceUpdate &update); + bool removeOne(MessagePosition messageId); + bool removeFromChannel(ChannelId channelId); + bool removeAll(); + + void checkInsufficient(); + struct AroundData { + MessagePosition aroundId; + LoadDirection direction = LoadDirection::Around; + + inline bool operator<(const AroundData &other) const { + return (aroundId < other.aroundId) + || ((aroundId == other.aroundId) + && (direction < other.direction)); + } + }; + auto insufficientAround() const { + return _insufficientAround.events(); + } + + MessagesSlice snapshot() const; + +private: + enum class RequestDirection { + Before, + After, + }; + void requestMessages(RequestDirection direction); + void requestMessagesCount(); + void fillSkippedAndSliceToLimits(); + void sliceToLimits(); + + void mergeSliceData( + base::optional count, + const base::flat_set &messageIds, + base::optional skippedBefore = base::none, + base::optional skippedAfter = base::none); + + MessagePosition _key; + base::flat_set _ids; + MessagesRange _range; + base::optional _fullCount; + base::optional _skippedBefore; + base::optional _skippedAfter; + int _limitBefore = 0; + int _limitAfter = 0; + + rpl::event_stream _insufficientAround; + +}; + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_search_controller.cpp b/Telegram/SourceFiles/data/data_search_controller.cpp index 7e88734cb..8ca86dd81 100644 --- a/Telegram/SourceFiles/data/data_search_controller.cpp +++ b/Telegram/SourceFiles/data/data_search_controller.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "auth_session.h" #include "data/data_session.h" +#include "data/data_messages.h" namespace Api { namespace { @@ -23,7 +24,7 @@ MTPmessages_Search PrepareSearchRequest( Storage::SharedMediaType type, const QString &query, MsgId messageId, - SparseIdsLoadDirection direction) { + Data::LoadDirection direction) { const auto filter = [&] { using Type = Storage::SharedMediaType; switch (type) { @@ -58,17 +59,17 @@ MTPmessages_Search PrepareSearchRequest( const auto limit = messageId ? kSharedMediaLimit : 0; const auto offsetId = [&] { switch (direction) { - case SparseIdsLoadDirection::Before: - case SparseIdsLoadDirection::Around: return messageId; - case SparseIdsLoadDirection::After: return messageId + 1; + case Data::LoadDirection::Before: + case Data::LoadDirection::Around: return messageId; + case Data::LoadDirection::After: return messageId + 1; } Unexpected("Direction in PrepareSearchRequest"); }(); const auto addOffset = [&] { switch (direction) { - case SparseIdsLoadDirection::Before: return 0; - case SparseIdsLoadDirection::Around: return -limit / 2; - case SparseIdsLoadDirection::After: return -limit; + case Data::LoadDirection::Before: return 0; + case Data::LoadDirection::Around: return -limit / 2; + case Data::LoadDirection::After: return -limit; } Unexpected("Direction in PrepareSearchRequest"); }(); @@ -92,7 +93,7 @@ SearchResult ParseSearchResult( not_null peer, Storage::SharedMediaType type, MsgId messageId, - SparseIdsLoadDirection direction, + Data::LoadDirection direction, const MTPmessages_Messages &data) { auto result = SearchResult(); result.noSkipRange = MsgRange{ messageId, messageId }; @@ -158,11 +159,11 @@ SearchResult ParseSearchResult( if (messageId && result.messageIds.empty()) { result.noSkipRange = [&]() -> MsgRange { switch (direction) { - case SparseIdsLoadDirection::Before: // All old loaded. + case Data::LoadDirection::Before: // All old loaded. return { 0, result.noSkipRange.till }; - case SparseIdsLoadDirection::Around: // All loaded. + case Data::LoadDirection::Around: // All loaded. return { 0, ServerMaxMsgId }; - case SparseIdsLoadDirection::After: // All new loaded. + case Data::LoadDirection::After: // All new loaded. return { result.noSkipRange.from, ServerMaxMsgId }; } Unexpected("Direction in ParseSearchResult"); diff --git a/Telegram/SourceFiles/data/data_search_controller.h b/Telegram/SourceFiles/data/data_search_controller.h index 8b84a4db0..222a984d0 100644 --- a/Telegram/SourceFiles/data/data_search_controller.h +++ b/Telegram/SourceFiles/data/data_search_controller.h @@ -13,6 +13,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/storage_shared_media.h" #include "base/value_ordering.h" +namespace Data { +enum class LoadDirection : char; +} // namespace Data + namespace Api { struct SearchResult { @@ -26,13 +30,13 @@ MTPmessages_Search PrepareSearchRequest( Storage::SharedMediaType type, const QString &query, MsgId messageId, - SparseIdsLoadDirection direction); + Data::LoadDirection direction); SearchResult ParseSearchResult( not_null peer, Storage::SharedMediaType type, MsgId messageId, - SparseIdsLoadDirection direction, + Data::LoadDirection direction, const MTPmessages_Messages &data); class SearchController : private MTP::Sender { diff --git a/Telegram/SourceFiles/data/data_sparse_ids.cpp b/Telegram/SourceFiles/data/data_sparse_ids.cpp index e1c73c559..592136b95 100644 --- a/Telegram/SourceFiles/data/data_sparse_ids.cpp +++ b/Telegram/SourceFiles/data/data_sparse_ids.cpp @@ -361,17 +361,17 @@ void SparseIdsSliceBuilder::requestMessages( RequestDirection direction) { auto requestAroundData = [&]() -> AroundData { if (_ids.empty()) { - return { _key, SparseIdsLoadDirection::Around }; + return { _key, Data::LoadDirection::Around }; } else if (direction == RequestDirection::Before) { - return { _ids.front(), SparseIdsLoadDirection::Before }; + return { _ids.front(), Data::LoadDirection::Before }; } - return { _ids.back(), SparseIdsLoadDirection::After }; + return { _ids.back(), Data::LoadDirection::After }; }; _insufficientAround.fire(requestAroundData()); } void SparseIdsSliceBuilder::requestMessagesCount() { - _insufficientAround.fire({ 0, SparseIdsLoadDirection::Around }); + _insufficientAround.fire({ 0, Data::LoadDirection::Around }); } SparseIdsSlice SparseIdsSliceBuilder::snapshot() const { diff --git a/Telegram/SourceFiles/data/data_sparse_ids.h b/Telegram/SourceFiles/data/data_sparse_ids.h index e7b294bb1..7a49a425d 100644 --- a/Telegram/SourceFiles/data/data_sparse_ids.h +++ b/Telegram/SourceFiles/data/data_sparse_ids.h @@ -7,17 +7,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "data/data_messages.h" + namespace Storage { struct SparseIdsListResult; struct SparseIdsSliceUpdate; } // namespace Storage -enum class SparseIdsLoadDirection { - Around, - Before, - After, -}; - class SparseIdsSlice { public: using Key = MsgId; @@ -178,8 +174,7 @@ public: void checkInsufficient(); struct AroundData { MsgId aroundId = 0; - SparseIdsLoadDirection direction - = SparseIdsLoadDirection::Around; + Data::LoadDirection direction = Data::LoadDirection::Around; inline bool operator<(const AroundData &other) const { return (aroundId < other.aroundId) diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h index 138236b3c..880cbb647 100644 --- a/Telegram/SourceFiles/data/data_types.h +++ b/Telegram/SourceFiles/data/data_types.h @@ -133,29 +133,43 @@ inline bool operator!=(const MsgRange &a, const MsgRange &b) { } struct FullMsgId { - FullMsgId() = default; - FullMsgId(ChannelId channel, MsgId msg) : channel(channel), msg(msg) { + constexpr FullMsgId() = default; + constexpr FullMsgId(ChannelId channel, MsgId msg) : channel(channel), msg(msg) { } + explicit operator bool() const { return msg != 0; } + + + inline constexpr bool operator<(const FullMsgId &other) const { + if (channel < other.channel) { + return true; + } else if (channel > other.channel) { + return false; + } + return msg < other.msg; + } + inline constexpr bool operator>(const FullMsgId &other) const { + return other < *this; + } + inline constexpr bool operator<=(const FullMsgId &other) const { + return !(other < *this); + } + inline constexpr bool operator>=(const FullMsgId &other) const { + return !(*this < other); + } + inline constexpr bool operator==(const FullMsgId &other) const { + return (channel == other.channel) && (msg == other.msg); + } + inline constexpr bool operator!=(const FullMsgId &other) const { + return !(*this == other); + } + ChannelId channel = NoChannel; MsgId msg = 0; + }; -inline bool operator==(const FullMsgId &a, const FullMsgId &b) { - return (a.channel == b.channel) && (a.msg == b.msg); -} -inline bool operator!=(const FullMsgId &a, const FullMsgId &b) { - return !(a == b); -} -inline bool operator<(const FullMsgId &a, const FullMsgId &b) { - if (a.channel < b.channel) { - return true; - } else if (a.channel > b.channel) { - return false; - } - return a.msg < b.msg; -} using MessageIdsList = std::vector; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 09d45ee9c..02eebb62b 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/dialogs_indexed_list.h" #include "dialogs/dialogs_layout.h" #include "dialogs/dialogs_search_from_controllers.h" +#include "history/feed/history_feed_section.h" #include "styles/style_dialogs.h" #include "styles/style_chat_helpers.h" #include "styles/style_window.h" @@ -1739,8 +1740,8 @@ void DialogsInner::applyFeedDialog(const MTPDdialogFeed &dialog) { addSavedPeersAfter(feed->chatsListDate()); } if (dialog.has_read_max_position()) { - const auto position = Data::FeedPosition(dialog.vread_max_position); - feed->setUnreadPosition(position); + feed->setUnreadPosition( + Data::FeedPositionFromMTP(dialog.vread_max_position)); } } @@ -2307,8 +2308,7 @@ bool DialogsInner::chooseRow() { if (const auto history = chosen.key.history()) { App::main()->choosePeer(history->peer->id, chosen.messageId); } else if (const auto feed = chosen.key.feed()) { - // #TODO feeds open -// _controller->showSection(HistoryFeed::Memento(feed)); + _controller->showSection(HistoryFeed::Memento(feed)); } if (openSearchResult) { emit searchResultChosen(); diff --git a/Telegram/SourceFiles/history/history_admin_log_filter.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_filter.cpp similarity index 99% rename from Telegram/SourceFiles/history/history_admin_log_filter.cpp rename to Telegram/SourceFiles/history/admin_log/history_admin_log_filter.cpp index ce608d2bc..b8f5799ed 100644 --- a/Telegram/SourceFiles/history/history_admin_log_filter.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_filter.cpp @@ -5,7 +5,7 @@ the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ -#include "history/history_admin_log_filter.h" +#include "history/admin_log/history_admin_log_filter.h" #include "styles/style_boxes.h" #include "ui/widgets/checkbox.h" diff --git a/Telegram/SourceFiles/history/history_admin_log_filter.h b/Telegram/SourceFiles/history/admin_log/history_admin_log_filter.h similarity index 93% rename from Telegram/SourceFiles/history/history_admin_log_filter.h rename to Telegram/SourceFiles/history/admin_log/history_admin_log_filter.h index 76796a0e9..7cabcfd8c 100644 --- a/Telegram/SourceFiles/history/history_admin_log_filter.h +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_filter.h @@ -8,7 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "boxes/abstract_box.h" -#include "history/history_admin_log_section.h" +#include "history/admin_log/history_admin_log_section.h" namespace AdminLog { diff --git a/Telegram/SourceFiles/history/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp similarity index 99% rename from Telegram/SourceFiles/history/history_admin_log_inner.cpp rename to Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp index f44e8bc73..bd1eb1f40 100644 --- a/Telegram/SourceFiles/history/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -5,15 +5,15 @@ the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ -#include "history/history_admin_log_inner.h" +#include "history/admin_log/history_admin_log_inner.h" #include "styles/style_history.h" #include "history/history_media_types.h" #include "history/history_message.h" -#include "history/history_service_layout.h" -#include "history/history_admin_log_section.h" -#include "history/history_admin_log_filter.h" #include "history/history_item_components.h" +#include "history/admin_log/history_admin_log_section.h" +#include "history/admin_log/history_admin_log_filter.h" +#include "history/view/history_view_service_message.h" #include "chat_helpers/message_field.h" #include "mainwindow.h" #include "mainwidget.h" @@ -691,7 +691,11 @@ void InnerWidget::paintEvent(QPaintEvent *e) { if (auto date = item->Get()) { date->paint(p, dateY, width); } else { - HistoryLayout::ServiceMessagePainter::paintDate(p, item->date, dateY, width); + HistoryView::ServiceMessagePainter::paintDate( + p, + item->date, + dateY, + width); } } } @@ -723,7 +727,12 @@ void InnerWidget::paintEmpty(Painter &p) { auto innerWidth = rectWidth - st::historyAdminLogEmptyPadding.left() - st::historyAdminLogEmptyPadding.right(); auto rectHeight = st::historyAdminLogEmptyPadding.top() + _emptyText.countHeight(innerWidth) + st::historyAdminLogEmptyPadding.bottom(); auto rect = QRect((width() - rectWidth) / 2, (height() - rectHeight) / 3, rectWidth, rectHeight); - HistoryLayout::ServiceMessagePainter::paintBubble(p, rect.x(), rect.y(), rect.width(), rect.height()); + HistoryView::ServiceMessagePainter::paintBubble( + p, + rect.x(), + rect.y(), + rect.width(), + rect.height()); p.setPen(st::msgServiceFg); _emptyText.draw(p, rect.x() + st::historyAdminLogEmptyPadding.left(), rect.y() + st::historyAdminLogEmptyPadding.top(), innerWidth, style::al_top); diff --git a/Telegram/SourceFiles/history/history_admin_log_inner.h b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h similarity index 98% rename from Telegram/SourceFiles/history/history_admin_log_inner.h rename to Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h index 8d1fa3d2d..a58ca95bf 100644 --- a/Telegram/SourceFiles/history/history_admin_log_inner.h +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h @@ -7,8 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -#include "history/history_admin_log_item.h" -#include "history/history_admin_log_section.h" +#include "history/admin_log/history_admin_log_item.h" +#include "history/admin_log/history_admin_log_section.h" #include "ui/widgets/tooltip.h" #include "ui/rp_widget.h" #include "mtproto/sender.h" diff --git a/Telegram/SourceFiles/history/history_admin_log_item.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp similarity index 99% rename from Telegram/SourceFiles/history/history_admin_log_item.cpp rename to Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp index f85fa352b..0b3c3efb5 100644 --- a/Telegram/SourceFiles/history/history_admin_log_item.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp @@ -5,11 +5,11 @@ the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ -#include "history/history_admin_log_item.h" +#include "history/admin_log/history_admin_log_item.h" #include "history/history_service.h" #include "history/history_message.h" -#include "history/history_admin_log_inner.h" +#include "history/admin_log/history_admin_log_inner.h" #include "lang/lang_keys.h" #include "boxes/sticker_set_box.h" #include "core/tl_help.h" diff --git a/Telegram/SourceFiles/history/history_admin_log_item.h b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.h similarity index 100% rename from Telegram/SourceFiles/history/history_admin_log_item.h rename to Telegram/SourceFiles/history/admin_log/history_admin_log_item.h diff --git a/Telegram/SourceFiles/history/history_admin_log_section.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp similarity index 89% rename from Telegram/SourceFiles/history/history_admin_log_section.cpp rename to Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp index 0a4db9c35..67505191a 100644 --- a/Telegram/SourceFiles/history/history_admin_log_section.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp @@ -5,10 +5,10 @@ the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ -#include "history/history_admin_log_section.h" +#include "history/admin_log/history_admin_log_section.h" -#include "history/history_admin_log_inner.h" -#include "history/history_admin_log_filter.h" +#include "history/admin_log/history_admin_log_inner.h" +#include "history/admin_log/history_admin_log_filter.h" #include "profile/profile_back_button.h" #include "styles/style_history.h" #include "styles/style_window.h" @@ -379,45 +379,10 @@ void Widget::paintEvent(QPaintEvent *e) { // updateListSize(); //} - Painter p(this); - auto clip = e->rect(); - auto ms = getms(); + //auto ms = getms(); //_historyDownShown.step(ms); - auto fill = QRect(0, 0, width(), App::main()->height()); - auto fromy = App::main()->backgroundFromY(); - auto x = 0, y = 0; - auto cached = App::main()->cachedBackground(fill, x, y); - if (cached.isNull()) { - if (Window::Theme::Background()->tile()) { - auto &pix = Window::Theme::Background()->pixmapForTiled(); - auto left = clip.left(); - auto top = clip.top(); - auto right = clip.left() + clip.width(); - auto bottom = clip.top() + clip.height(); - auto w = pix.width() / cRetinaFactor(); - auto h = pix.height() / cRetinaFactor(); - auto sx = qFloor(left / w); - auto sy = qFloor((top - fromy) / h); - auto cx = qCeil(right / w); - auto cy = qCeil((bottom - fromy) / h); - for (auto i = sx; i < cx; ++i) { - for (auto j = sy; j < cy; ++j) { - p.drawPixmap(QPointF(i * w, fromy + j * h), pix); - } - } - } else { - PainterHighQualityEnabler hq(p); - - auto &pix = Window::Theme::Background()->pixmap(); - QRect to, from; - Window::Theme::ComputeBackgroundRects(fill, pix.size(), to, from); - to.moveTop(to.top() + fromy); - p.drawPixmap(to, pix, from); - } - } else { - p.drawPixmap(x, fromy + y, cached); - } + SectionWidget::PaintBackground(this, e); } void Widget::onScroll() { diff --git a/Telegram/SourceFiles/history/history_admin_log_section.h b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.h similarity index 98% rename from Telegram/SourceFiles/history/history_admin_log_section.h rename to Telegram/SourceFiles/history/admin_log/history_admin_log_section.h index 551af5c56..902f4c92e 100644 --- a/Telegram/SourceFiles/history/history_admin_log_section.h +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.h @@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/section_widget.h" #include "window/section_memento.h" -#include "history/history_admin_log_item.h" +#include "history/admin_log/history_admin_log_item.h" #include "mtproto/sender.h" namespace Notify { diff --git a/Telegram/SourceFiles/history/feed/history_feed_section.cpp b/Telegram/SourceFiles/history/feed/history_feed_section.cpp new file mode 100644 index 000000000..533f1b892 --- /dev/null +++ b/Telegram/SourceFiles/history/feed/history_feed_section.cpp @@ -0,0 +1,265 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "history/feed/history_feed_section.h" + +#include "history/view/history_view_top_bar_widget.h" +#include "history/view/history_view_list_widget.h" +#include "lang/lang_keys.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/shadow.h" +#include "ui/widgets/scroll_area.h" +#include "boxes/confirm_box.h" +#include "window/window_controller.h" +#include "data/data_feed_messages.h" +#include "storage/storage_feed_messages.h" +#include "styles/style_widgets.h" +#include "styles/style_history.h" + +namespace HistoryFeed { + +Memento::Memento( + not_null feed, + Data::MessagePosition aroundPosition) +: _feed(feed) +, _list(std::make_unique(aroundPosition)) { +} + +Memento::~Memento() = default; + +object_ptr Memento::createWidget( + QWidget *parent, + not_null controller, + Window::Column column, + const QRect &geometry) { + if (column == Window::Column::Third) { + return nullptr; + } + auto result = object_ptr(parent, controller, _feed); + result->setInternalState(geometry, this); + return std::move(result); +} + +Widget::Widget( + QWidget *parent, + not_null controller, + not_null feed) +: Window::SectionWidget(parent, controller) +, _feed(feed) +, _scroll(this, st::historyScroll, false) +, _topBar(this, controller) +, _topBarShadow(this) +, _showNext( + this, + lang(lng_feed_show_next).toUpper(), + st::historyComposeButton) { + _topBar->move(0, 0); + _topBar->resizeToWidth(width()); + _topBar->show(); + + _topBarShadow->raise(); + updateAdaptiveLayout(); + subscribe(Adaptive::Changed(), [this] { updateAdaptiveLayout(); }); + + _inner = _scroll->setOwnedWidget( + object_ptr(this, controller, this)); + _scroll->move(0, _topBar->height()); + _scroll->show(); + + connect( + _scroll, + &Ui::ScrollArea::scrolled, + this, + [this] { onScroll(); }); + + _showNext->setClickedCallback([this] { + // #TODO feeds show next + Ui::show(Box(lang(lng_admin_log_about_text))); + }); + + _feed->unreadPositionChanges( + ) | rpl::filter([=](const Data::MessagePosition &position) { + return _undefinedAroundPosition && position; + }) | rpl::start_with_next([=](const Data::MessagePosition &position) { + auto memento = HistoryView::ListMemento(position); + _inner->restoreState(&memento); + }, lifetime()); +} + +void Widget::updateAdaptiveLayout() { + _topBarShadow->moveToLeft( + Adaptive::OneColumn() ? 0 : st::lineWidth, + _topBar->height()); +} + +QPixmap Widget::grabForShowAnimation(const Window::SectionSlideParams ¶ms) { + if (params.withTopBarShadow) _topBarShadow->hide(); + auto result = Ui::GrabWidget(this); + if (params.withTopBarShadow) _topBarShadow->show(); + return result; +} + +void Widget::doSetInnerFocus() { + _inner->setFocus(); +} + +bool Widget::showInternal( + not_null memento, + const Window::SectionShow ¶ms) { + if (const auto feedMemento = dynamic_cast(memento.get())) { + if (feedMemento->feed() == _feed) { + restoreState(feedMemento); + return true; + } + } + return false; +} + +void Widget::setInternalState( + const QRect &geometry, + not_null memento) { + setGeometry(geometry); + Ui::SendPendingMoveResizeEvents(this); + restoreState(memento); +} + +bool Widget::cmd_search() { + if (!inFocusChain()) { + return false; + } + // #TODO feeds search + return true; +} + +void Widget::listScrollTo(int top) { + if (_scroll->scrollTop() != top) { + _scroll->scrollToY(top); + } else { + updateInnerVisibleArea(); + } +} + +void Widget::listCloseRequest() { + controller()->showBackFromStack(); +} + +rpl::producer Widget::listSource( + Data::MessagePosition aroundId, + int limitBefore, + int limitAfter) { + return Data::FeedMessagesViewer( + Storage::FeedMessagesKey(_feed->id(), aroundId), + limitBefore, + limitAfter); +} + +std::unique_ptr Widget::createMemento() { + auto result = std::make_unique(_feed); + saveState(result.get()); + return std::move(result); +} + +void Widget::saveState(not_null memento) { + _inner->saveState(memento->list()); +} + +void Widget::restoreState(not_null memento) { + const auto list = memento->list(); + if (!list->aroundPosition()) { + if (const auto position = _feed->unreadPosition()) { + list->setAroundPosition(position); + } + } + _undefinedAroundPosition = !list->aroundPosition(); + _inner->restoreState(memento->list()); +} + +void Widget::resizeEvent(QResizeEvent *e) { + if (!width() || !height()) { + return; + } + updateControlsGeometry(); +} + +void Widget::updateControlsGeometry() { + const auto contentWidth = width(); + + const auto newScrollTop = _scroll->scrollTop() + topDelta(); + _topBar->resizeToWidth(contentWidth); + _topBarShadow->resize(contentWidth, st::lineWidth); + + const auto bottom = height(); + const auto scrollHeight = bottom + - _topBar->height() + - _showNext->height(); + const auto scrollSize = QSize(contentWidth, scrollHeight); + if (_scroll->size() != scrollSize) { + _scroll->resize(scrollSize); + _inner->resizeToWidth(scrollSize.width(), _scroll->height()); + //_inner->restoreScrollPosition(); + } + + if (!_scroll->isHidden()) { + if (topDelta()) { + _scroll->scrollToY(newScrollTop); + } + updateInnerVisibleArea(); + } + const auto fullWidthButtonRect = myrtlrect( + 0, + bottom - _showNext->height(), + contentWidth, + _showNext->height()); + _showNext->setGeometry(fullWidthButtonRect); +} + +void Widget::paintEvent(QPaintEvent *e) { + if (animating()) { + SectionWidget::paintEvent(e); + return; + } + if (Ui::skipPaintEvent(this, e)) { + return; + } + //if (hasPendingResizedItems()) { + // updateListSize(); + //} + + //auto ms = getms(); + //_historyDownShown.step(ms); + + SectionWidget::PaintBackground(this, e); +} + +void Widget::onScroll() { + updateInnerVisibleArea(); +} + +void Widget::updateInnerVisibleArea() { + const auto scrollTop = _scroll->scrollTop(); + _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); +} + +void Widget::showAnimatedHook( + const Window::SectionSlideParams ¶ms) { + _topBar->setAnimatingMode(true); + if (params.withTopBarShadow) _topBarShadow->show(); +} + +void Widget::showFinishedHook() { + _topBar->setAnimatingMode(false); +} + +bool Widget::wheelEventFromFloatPlayer(QEvent *e) { + return _scroll->viewportEvent(e); +} + +QRect Widget::rectForFloatPlayer() const { + return mapToGlobal(_scroll->geometry()); +} + +} // namespace HistoryFeed diff --git a/Telegram/SourceFiles/history/feed/history_feed_section.h b/Telegram/SourceFiles/history/feed/history_feed_section.h new file mode 100644 index 000000000..dcbbea6d9 --- /dev/null +++ b/Telegram/SourceFiles/history/feed/history_feed_section.h @@ -0,0 +1,122 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "history/view/history_view_list_widget.h" +#include "window/section_widget.h" +#include "window/section_memento.h" +#include "data/data_feed.h" + +namespace Ui { +class ScrollArea; +class PlainShadow; +class FlatButton; +} // namespace Ui + +namespace HistoryView { +class ListWidget; +class TopBarWidget; +} // namespace HistoryView + +namespace HistoryFeed { + +class Memento; + +class Widget final + : public Window::SectionWidget + , public HistoryView::ListDelegate { +public: + Widget( + QWidget *parent, + not_null controller, + not_null feed); + + bool hasTopBarShadow() const override { + return true; + } + + QPixmap grabForShowAnimation( + const Window::SectionSlideParams ¶ms) override; + + bool showInternal( + not_null memento, + const Window::SectionShow ¶ms) override; + std::unique_ptr createMemento() override; + + void setInternalState( + const QRect &geometry, + not_null memento); + + // Float player interface. + bool wheelEventFromFloatPlayer(QEvent *e) override; + QRect rectForFloatPlayer() const override; + + bool cmd_search() override; + + // HistoryView::ListDelegate interface. + void listScrollTo(int top) override; + void listCloseRequest() override; + rpl::producer listSource( + Data::MessagePosition aroundId, + int limitBefore, + int limitAfter) override; + +protected: + void resizeEvent(QResizeEvent *e) override; + void paintEvent(QPaintEvent *e) override; + + void showAnimatedHook( + const Window::SectionSlideParams ¶ms) override; + void showFinishedHook() override; + void doSetInnerFocus() override; + +private: + void onScroll(); + void updateInnerVisibleArea(); + void updateControlsGeometry(); + void updateAdaptiveLayout(); + void saveState(not_null memento); + void restoreState(not_null memento); + + not_null _feed; + object_ptr _scroll; + QPointer _inner; + object_ptr _topBar; + object_ptr _topBarShadow; + object_ptr _showNext; + bool _undefinedAroundPosition = false; + +}; + +class Memento : public Window::SectionMemento { +public: + explicit Memento( + not_null feed, + Data::MessagePosition aroundPosition = Data::UnreadMessagePosition); + ~Memento(); + + object_ptr createWidget( + QWidget *parent, + not_null controller, + Window::Column column, + const QRect &geometry) override; + + not_null feed() const { + return _feed; + } + not_null list() const { + return _list.get(); + } + +private: + not_null _feed; + std::unique_ptr _list; + +}; + +} // namespace HistoryFeed diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 2297bdcd1..b2ca2e57e 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -11,9 +11,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_history.h" #include "core/file_utilities.h" #include "history/history_message.h" -#include "history/history_service_layout.h" #include "history/history_media_types.h" #include "history/history_item_components.h" +#include "history/view/history_view_service_message.h" #include "ui/text_options.h" #include "ui/widgets/popup_menu.h" #include "window/window_controller.h" @@ -475,7 +475,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) { p.restoreTextPalette(); } } else if (noHistoryDisplayed) { - HistoryLayout::paintEmpty(p, width(), height()); + HistoryView::paintEmpty(p, width(), height()); } if (!noHistoryDisplayed) { auto readMentions = base::flat_set>(); @@ -637,7 +637,11 @@ void HistoryInner::paintEvent(QPaintEvent *e) { if (auto date = item->Get()) { date->paint(p, dateY, width); } else { - HistoryLayout::ServiceMessagePainter::paintDate(p, item->date, dateY, width); + HistoryView::ServiceMessagePainter::paintDate( + p, + item->date, + dateY, + width); } } } @@ -2117,8 +2121,9 @@ bool HistoryInner::canDeleteSelected() const { return (selectedState.count > 0) && (selectedState.count == selectedState.canDeleteCount); } -HistoryTopBarWidget::SelectedState HistoryInner::getSelectionState() const { - auto result = HistoryTopBarWidget::SelectedState {}; +auto HistoryInner::getSelectionState() const +-> HistoryView::TopBarWidget::SelectedState { + auto result = HistoryView::TopBarWidget::SelectedState {}; for (auto &selected : _selected) { if (selected.second == FullSelection) { ++result.count; diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index dc32602bd..d203f573d 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -10,7 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/rp_widget.h" #include "ui/widgets/tooltip.h" #include "ui/widgets/scroll_area.h" -#include "history/history_top_bar_widget.h" +#include "history/view/history_view_top_bar_widget.h" namespace Window { class Controller; @@ -50,7 +50,7 @@ public: bool canCopySelected() const; bool canDeleteSelected() const; - HistoryTopBarWidget::SelectedState getSelectionState() const; + HistoryView::TopBarWidget::SelectedState getSelectionState() const; void clearSelectedItems(bool onlyTextSelection = false); MessageIdsList getSelectedItems() const; void selectItem(not_null item); diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index fb77fea2d..d339c20dd 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -10,10 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "mainwidget.h" #include "history/history_item_components.h" -#include "history/history_service_layout.h" #include "history/history_media_types.h" #include "history/history_media_grouped.h" #include "history/history_message.h" +#include "history/view/history_view_service_message.h" #include "media/media_clip_reader.h" #include "styles/style_dialogs.h" #include "styles/style_history.h" @@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_controller.h" #include "core/crash_reports.h" #include "data/data_session.h" +#include "data/data_messages.h" #include "data/data_feed.h" namespace { @@ -635,6 +636,10 @@ QString HistoryItem::directLink() const { return QString(); } +Data::MessagePosition HistoryItem::position() const { + return Data::MessagePosition(toServerTime(date.toTime_t()).v, fullId()); +} + MsgId HistoryItem::replyToId() const { if (auto reply = Get()) { return reply->replyToId(); diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index d0e62059c..a5ebe0aaf 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -37,6 +37,10 @@ struct BotKeyboardButton; struct RippleAnimation; } // namespace style +namespace Data { +struct MessagePosition; +} // namespace Data + class HistoryElement { public: HistoryElement() = default; @@ -434,6 +438,7 @@ public: FullMsgId fullId() const { return FullMsgId(channelId(), id); } + Data::MessagePosition position() const; HistoryMedia *getMedia() const { return _media.get(); diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 5eea91596..7f70cd312 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -10,10 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "ui/effects/ripple_animation.h" #include "ui/text_options.h" -#include "history/history_service_layout.h" #include "history/history_message.h" #include "history/history_media.h" #include "history/history_media_types.h" +#include "history/view/history_view_service_message.h" #include "media/media_audio.h" #include "media/player/media_player_instance.h" #include "styles/style_widgets.h" @@ -825,7 +825,7 @@ int HistoryMessageDate::height() const { } void HistoryMessageDate::paint(Painter &p, int y, int w) const { - HistoryLayout::ServiceMessagePainter::paintDate(p, _text, _width, y, w); + HistoryView::ServiceMessagePainter::paintDate(p, _text, _width, y, w); } HistoryMessageLogEntryOriginal::HistoryMessageLogEntryOriginal() = default; diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index b8e4eedb9..803edfbaa 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -13,9 +13,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "history/history_item_components.h" #include "history/history_location_manager.h" -#include "history/history_service_layout.h" #include "history/history_media_types.h" #include "history/history_service.h" +#include "history/view/history_view_service_message.h" #include "auth_session.h" #include "boxes/share_box.h" #include "boxes/confirm_box.h" @@ -1797,7 +1797,7 @@ void HistoryMessage::draw(Painter &p, QRect clip, TextSelection selection, TimeM || (_media && _media->skipBubbleTail()) || (keyboard != nullptr); auto displayTail = skipTail ? RectPart::None : (outbg && !Adaptive::ChatWide()) ? RectPart::Right : RectPart::Left; - HistoryLayout::paintBubble(p, g, width(), selected, outbg, displayTail); + HistoryView::paintBubble(p, g, width(), selected, outbg, displayTail); // Entry page is always a bubble bottom. auto mediaOnBottom = (mediaDisplayed && _media->isBubbleBottom()) || (entry/* && entry->_page->isBubbleBottom()*/); diff --git a/Telegram/SourceFiles/history/history_service.cpp b/Telegram/SourceFiles/history/history_service.cpp index e3a205e98..da0abc5b5 100644 --- a/Telegram/SourceFiles/history/history_service.cpp +++ b/Telegram/SourceFiles/history/history_service.cpp @@ -10,10 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "mainwidget.h" #include "apiwrap.h" -#include "history/history_service_layout.h" #include "history/history_media_types.h" #include "history/history_message.h" #include "history/history_item_components.h" +#include "history/view/history_view_service_message.h" #include "data/data_feed.h" #include "auth_session.h" #include "window/notifications_manager.h" @@ -512,8 +512,8 @@ void HistoryService::draw(Painter &p, QRect clip, TextSelection selection, TimeM height -= unreadbarh; } - HistoryLayout::PaintContext context(ms, clip, selection); - HistoryLayout::ServiceMessagePainter::paint(p, this, context, height); + HistoryView::PaintContext context(ms, clip, selection); + HistoryView::ServiceMessagePainter::paint(p, this, context, height); if (auto skiph = dateh + unreadbarh) { p.translate(0, -skiph); diff --git a/Telegram/SourceFiles/history/history_service.h b/Telegram/SourceFiles/history/history_service.h index d03842bc9..cb8284e93 100644 --- a/Telegram/SourceFiles/history/history_service.h +++ b/Telegram/SourceFiles/history/history_service.h @@ -34,9 +34,9 @@ struct HistoryServiceSelfDestruct : public RuntimeComponent { public: @@ -102,7 +102,7 @@ public: ~HistoryService(); protected: - friend class HistoryLayout::ServiceMessagePainter; + friend class HistoryView::ServiceMessagePainter; HistoryService(not_null history, const MTPDmessage &message); HistoryService(not_null history, const MTPDmessageService &message); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 58bb947f8..d9ca01526 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -24,11 +24,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_drafts.h" #include "data/data_session.h" #include "history/history_message.h" -#include "history/history_service_layout.h" #include "history/history_media_types.h" #include "history/history_drag_area.h" #include "history/history_inner_widget.h" #include "history/history_item_components.h" +#include "history/view/history_view_service_message.h" #include "profile/profile_block_group_members.h" #include "info/info_memento.h" #include "core/click_handler_types.h" @@ -51,7 +51,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/media_audio_capture.h" #include "media/player/media_player_instance.h" #include "apiwrap.h" -#include "history/history_top_bar_widget.h" +#include "history/view/history_view_top_bar_widget.h" #include "observer_peer.h" #include "base/qthelp_regex.h" #include "ui/widgets/popup_menu.h" @@ -1704,7 +1704,7 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re noSelectingScroll(); _nonEmptySelection = false; - _topBar->showSelected(HistoryTopBarWidget::SelectedState {}); + _topBar->showSelected(HistoryView::TopBarWidget::SelectedState {}); App::hoveredItem(nullptr); App::pressedItem(nullptr); @@ -3039,7 +3039,7 @@ void HistoryWidget::showAnimated( _a_show.start([this] { animationCallback(); }, 0., 1., st::slideDuration, Window::SlideAnimation::transition()); if (_history) { _topBar->show(); - _topBar->setAnimationMode(true); + _topBar->setAnimatingMode(true); } activate(); @@ -3056,7 +3056,7 @@ void HistoryWidget::animationCallback() { } void HistoryWidget::doneShow() { - _topBar->setAnimationMode(false); + _topBar->setAnimatingMode(false); updateReportSpamStatus(); updateBotKeyboard(); updateControlsVisibility(); @@ -6142,7 +6142,7 @@ MessageIdsList HistoryWidget::getSelectedItems() const { void HistoryWidget::updateTopBarSelection() { if (!_list) { - _topBar->showSelected(HistoryTopBarWidget::SelectedState {}); + _topBar->showSelected(HistoryView::TopBarWidget::SelectedState {}); return; } @@ -6489,81 +6489,52 @@ void HistoryWidget::drawPinnedBar(Painter &p) { } } +bool HistoryWidget::paintShowAnimationFrame(TimeMs ms) { + auto progress = _a_show.current(ms, 1.); + if (!_a_show.animating()) { + return false; + } + + Painter p(this); + auto animationWidth = width(); + auto retina = cIntRetinaFactor(); + auto fromLeft = (_showDirection == Window::SlideDirection::FromLeft); + auto coordUnder = fromLeft ? anim::interpolate(-st::slideShift, 0, progress) : anim::interpolate(0, -st::slideShift, progress); + auto coordOver = fromLeft ? anim::interpolate(0, animationWidth, progress) : anim::interpolate(animationWidth, 0, progress); + auto shadow = fromLeft ? (1. - progress) : progress; + if (coordOver > 0) { + p.drawPixmap(QRect(0, 0, coordOver, height()), _cacheUnder, QRect(-coordUnder * retina, 0, coordOver * retina, height() * retina)); + p.setOpacity(shadow); + p.fillRect(0, 0, coordOver, height(), st::slideFadeOutBg); + p.setOpacity(1); + } + p.drawPixmap(QRect(coordOver, 0, _cacheOver.width() / retina, height()), _cacheOver, QRect(0, 0, _cacheOver.width(), height() * retina)); + p.setOpacity(shadow); + st::slideShadow.fill(p, QRect(coordOver - st::slideShadow.width(), 0, st::slideShadow.width(), height())); + return true; +} + void HistoryWidget::paintEvent(QPaintEvent *e) { - if (!App::main() || (App::wnd() && App::wnd()->contentOverlapped(this, e))) { + auto ms = getms(); + _historyDownShown.step(ms); + _unreadMentionsShown.step(ms); + if (paintShowAnimationFrame(ms)) { + return; + } + if (Ui::skipPaintEvent(this, e)) { return; } if (hasPendingResizedItems()) { updateListSize(); } + Window::SectionWidget::PaintBackground(this, e); + Painter p(this); - QRect r(e->rect()); - if (r != rect()) { - p.setClipRect(r); - } - - auto ms = getms(); - _historyDownShown.step(ms); - _unreadMentionsShown.step(ms); - auto progress = _a_show.current(ms, 1.); - if (_a_show.animating()) { - auto animationWidth = width(); - auto retina = cIntRetinaFactor(); - auto fromLeft = (_showDirection == Window::SlideDirection::FromLeft); - auto coordUnder = fromLeft ? anim::interpolate(-st::slideShift, 0, progress) : anim::interpolate(0, -st::slideShift, progress); - auto coordOver = fromLeft ? anim::interpolate(0, animationWidth, progress) : anim::interpolate(animationWidth, 0, progress); - auto shadow = fromLeft ? (1. - progress) : progress; - if (coordOver > 0) { - p.drawPixmap(QRect(0, 0, coordOver, height()), _cacheUnder, QRect(-coordUnder * retina, 0, coordOver * retina, height() * retina)); - p.setOpacity(shadow); - p.fillRect(0, 0, coordOver, height(), st::slideFadeOutBg); - p.setOpacity(1); - } - p.drawPixmap(QRect(coordOver, 0, _cacheOver.width() / retina, height()), _cacheOver, QRect(0, 0, _cacheOver.width(), height() * retina)); - p.setOpacity(shadow); - st::slideShadow.fill(p, QRect(coordOver - st::slideShadow.width(), 0, st::slideShadow.width(), height())); - return; - } - - QRect fill(0, 0, width(), App::main()->height()); - auto fromy = App::main()->backgroundFromY(); - auto x = 0, y = 0; - QPixmap cached = App::main()->cachedBackground(fill, x, y); - if (cached.isNull()) { - if (Window::Theme::Background()->tile()) { - auto &pix = Window::Theme::Background()->pixmapForTiled(); - auto left = r.left(); - auto top = r.top(); - auto right = r.left() + r.width(); - auto bottom = r.top() + r.height(); - auto w = pix.width() / cRetinaFactor(); - auto h = pix.height() / cRetinaFactor(); - auto sx = qFloor(left / w); - auto sy = qFloor((top - fromy) / h); - auto cx = qCeil(right / w); - auto cy = qCeil((bottom - fromy) / h); - for (auto i = sx; i < cx; ++i) { - for (auto j = sy; j < cy; ++j) { - p.drawPixmap(QPointF(i * w, fromy + j * h), pix); - } - } - } else { - PainterHighQualityEnabler hq(p); - - auto &pix = Window::Theme::Background()->pixmap(); - QRect to, from; - Window::Theme::ComputeBackgroundRects(fill, pix.size(), to, from); - to.moveTop(to.top() + fromy); - p.drawPixmap(to, pix, from); - } - } else { - p.drawPixmap(x, fromy + y, cached); - } - + const auto clip = e->rect(); if (_list) { if (!_field->isHidden() || _recording) { - drawField(p, r); + drawField(p, clip); if (!_send->isHidden() && _recording) { drawRecording(p, _send->recordActiveRatio()); } @@ -6575,13 +6546,13 @@ void HistoryWidget::paintEvent(QPaintEvent *e) { } if (_scroll->isHidden()) { p.setClipRect(_scroll->geometry()); - HistoryLayout::paintEmpty(p, width(), height() - _field->height() - 2 * st::historySendPadding); + HistoryView::paintEmpty(p, width(), height() - _field->height() - 2 * st::historySendPadding); } } else { style::font font(st::msgServiceFont); int32 w = font->width(lang(lng_willbe_history)) + st::msgPadding.left() + st::msgPadding.right(), h = font->height + st::msgServicePadding.top() + st::msgServicePadding.bottom() + 2; QRect tr((width() - w) / 2, (height() - _field->height() - 2 * st::historySendPadding - h) / 2, w, h); - HistoryLayout::ServiceMessagePainter::paintBubble(p, tr.x(), tr.y(), tr.width(), tr.height()); + HistoryView::ServiceMessagePainter::paintBubble(p, tr.x(), tr.y(), tr.width(), tr.height()); p.setPen(st::msgServiceFg); p.setFont(font->f); diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 1b0d3da0f..77e9ea5cd 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -65,12 +65,15 @@ enum class MimeDataState; struct PreparedList; } // namespace Storage +namespace HistoryView { +class TopBarWidget; +} // namespace HistoryView + class DragArea; class SendFilesBox; class BotKeyboard; class MessageField; class HistoryInner; -class HistoryTopBarWidget; struct HistoryMessageMarkupButton; class ReportSpamPanel : public TWidget { @@ -592,6 +595,7 @@ private: void drawRecording(Painter &p, float64 recordActive); void drawPinnedBar(Painter &p); void drawRestrictedWrite(Painter &p); + bool paintShowAnimationFrame(TimeMs ms); void updateMouseTracking(); @@ -740,7 +744,7 @@ private: MsgId _delayedShowAtMsgId = -1; // wtf? mtpRequestId _delayedShowAtRequest = 0; - object_ptr _topBar; + object_ptr _topBar; object_ptr _scroll; QPointer _list; History *_migrated = nullptr; diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp new file mode 100644 index 000000000..6f2879800 --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -0,0 +1,1364 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "history/view/history_view_list_widget.h" + +#include "history/history_media_types.h" +#include "history/history_message.h" +#include "history/history_item_components.h" +#include "history/view/history_view_service_message.h" +#include "chat_helpers/message_field.h" +#include "mainwindow.h" +#include "mainwidget.h" +#include "messenger.h" +#include "apiwrap.h" +#include "window/window_controller.h" +#include "auth_session.h" +#include "ui/widgets/popup_menu.h" +#include "core/file_utilities.h" +#include "core/tl_help.h" +#include "base/overload.h" +#include "lang/lang_keys.h" +#include "boxes/edit_participant_box.h" +#include "data/data_session.h" +#include "data/data_feed.h" +#include "styles/style_history.h" + +namespace HistoryView { +namespace { + +constexpr auto kScrollDateHideTimeout = 1000; +constexpr auto kPreloadedScreensCount = 4; +constexpr auto kPreloadIfLessThanScreens = 2; +constexpr auto kPreloadedScreensCountFull + = kPreloadedScreensCount + 1 + kPreloadedScreensCount; + +} // namespace + +template +void ListWidget::enumerateItems(Method method) { + constexpr auto TopToBottom = (direction == EnumItemsDirection::TopToBottom); + + // No displayed messages in this history. + if (_items.empty()) { + return; + } + if (_visibleBottom <= _itemsTop || _itemsTop + _itemsHeight <= _visibleTop) { + return; + } + + const auto beginning = begin(_items); + const auto ending = end(_items); + auto from = TopToBottom + ? std::lower_bound( + beginning, + ending, + _visibleTop, + [this](auto &elem, int top) { + return this->itemTop(elem) + elem->height() <= top; + }) + : std::upper_bound( + beginning, + ending, + _visibleBottom, + [this](int bottom, auto &elem) { + return this->itemTop(elem) + elem->height() >= bottom; + }); + auto wasEnd = (from == ending); + if (wasEnd) { + --from; + } + if (TopToBottom) { + Assert(itemTop(from->get()) + from->get()->height() > _visibleTop); + } else { + Assert(itemTop(from->get()) < _visibleBottom); + } + + while (true) { + auto item = from->get(); + auto itemtop = itemTop(item); + auto itembottom = itemtop + item->height(); + + // Binary search should've skipped all the items that are above / below the visible area. + if (TopToBottom) { + Assert(itembottom > _visibleTop); + } else { + Assert(itemtop < _visibleBottom); + } + + if (!method(item, itemtop, itembottom)) { + return; + } + + // Skip all the items that are below / above the visible area. + if (TopToBottom) { + if (itembottom >= _visibleBottom) { + return; + } + } else { + if (itemtop <= _visibleTop) { + return; + } + } + + if (TopToBottom) { + if (++from == ending) { + break; + } + } else { + if (from == beginning) { + break; + } + --from; + } + } +} + +template +void ListWidget::enumerateUserpics(Method method) { + // Find and remember the top of an attached messages pack + // -1 means we didn't find an attached to next message yet. + int lowestAttachedItemTop = -1; + + auto userpicCallback = [this, &lowestAttachedItemTop, &method](HistoryItem *item, int itemtop, int itembottom) { + // Skip all service messages. + auto message = item->toHistoryMessage(); + if (!message) return true; + + if (lowestAttachedItemTop < 0 && message->isAttachedToNext()) { + lowestAttachedItemTop = itemtop + message->marginTop(); + } + + // Call method on a userpic for all messages that have it and for those who are not showing it + // because of their attachment to the next message if they are bottom-most visible. + if (message->displayFromPhoto() || (message->hasFromPhoto() && itembottom >= _visibleBottom)) { + if (lowestAttachedItemTop < 0) { + lowestAttachedItemTop = itemtop + message->marginTop(); + } + // Attach userpic to the bottom of the visible area with the same margin as the last message. + auto userpicMinBottomSkip = st::historyPaddingBottom + st::msgMargin.bottom(); + auto userpicBottom = qMin(itembottom - message->marginBottom(), _visibleBottom - userpicMinBottomSkip); + + // Do not let the userpic go above the attached messages pack top line. + userpicBottom = qMax(userpicBottom, lowestAttachedItemTop + st::msgPhotoSize); + + // Call the template callback function that was passed + // and return if it finished everything it needed. + if (!method(message, userpicBottom - st::msgPhotoSize)) { + return false; + } + } + + // Forget the found top of the pack, search for the next one from scratch. + if (!message->isAttachedToNext()) { + lowestAttachedItemTop = -1; + } + + return true; + }; + + enumerateItems(userpicCallback); +} + +template +void ListWidget::enumerateDates(Method method) { + // Find and remember the bottom of an single-day messages pack + // -1 means we didn't find a same-day with previous message yet. + auto lowestInOneDayItemBottom = -1; + + auto dateCallback = [this, &lowestInOneDayItemBottom, &method](HistoryItem *item, int itemtop, int itembottom) { + if (lowestInOneDayItemBottom < 0 && item->isInOneDayWithPrevious()) { + lowestInOneDayItemBottom = itembottom - item->marginBottom(); + } + + // Call method on a date for all messages that have it and for those who are not showing it + // because they are in a one day together with the previous message if they are top-most visible. + if (item->displayDate() || (!item->isEmpty() && itemtop <= _visibleTop)) { + if (lowestInOneDayItemBottom < 0) { + lowestInOneDayItemBottom = itembottom - item->marginBottom(); + } + // Attach date to the top of the visible area with the same margin as it has in service message. + auto dateTop = qMax(itemtop, _visibleTop) + st::msgServiceMargin.top(); + + // Do not let the date go below the single-day messages pack bottom line. + auto dateHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top(); + dateTop = qMin(dateTop, lowestInOneDayItemBottom - dateHeight); + + // Call the template callback function that was passed + // and return if it finished everything it needed. + if (!method(item, itemtop, dateTop)) { + return false; + } + } + + // Forget the found bottom of the pack, search for the next one from scratch. + if (!item->isInOneDayWithPrevious()) { + lowestInOneDayItemBottom = -1; + } + + return true; + }; + + enumerateItems(dateCallback); +} + +ListWidget::ListWidget( + QWidget *parent, + not_null controller, + not_null delegate) +: RpWidget(parent) +, _delegate(delegate) +, _controller(controller) +, _scrollDateCheck([this] { scrollDateCheck(); }) { + setMouseTracking(true); + _scrollDateHideTimer.setCallback([this] { scrollDateHideByTimer(); }); + Auth().data().itemRepaintRequest( + ) | rpl::start_with_next([this](auto item) { + if (ranges::find(_items, item) != _items.end()) { + repaintItem(item); + } + }, lifetime()); + subscribe(Auth().data().pendingHistoryResize(), [this] { handlePendingHistoryResize(); }); + subscribe(Auth().data().queryItemVisibility(), [this](const Data::Session::ItemVisibilityQuery &query) { + if (ranges::find(_items, query.item) != _items.end()) { + auto top = itemTop(query.item); + if (top >= 0 && top + query.item->height() > _visibleTop && top < _visibleBottom) { + *query.isVisible = true; + } + } + }); +} + +void ListWidget::refreshViewer() { + _viewerLifetime.destroy(); + _delegate->listSource( + _aroundPosition, + _idsLimit, + _idsLimit + ) | rpl::start_with_next([=](Data::MessagesSlice &&slice) { + _slice = std::move(slice); + refreshRows(); + }, _viewerLifetime); +} + +void ListWidget::refreshRows() { + saveScrollState(); + + _items.clear(); + _items.reserve(_slice.ids.size()); + for (const auto &fullId : _slice.ids) { + if (const auto item = App::histItemById(fullId)) { + _items.push_back(item); + } + } + updateAroundPositionFromRows(); + + RpWidget::resizeToWidth(width()); + restoreScrollState(); + mouseActionUpdate(QCursor::pos()); +} + +void ListWidget::saveScrollState() { + if (!_scrollTopState.item) { + _scrollTopState = countScrollState(); + } +} + +void ListWidget::restoreScrollState() { + if (_items.empty() || !_scrollTopState.item) { + return; + } + const auto index = findNearestItem(_scrollTopState.item); + if (index >= 0) { + const auto item = _items[index]; + auto newVisibleTop = itemTop(item) + _scrollTopState.shift; + if (_visibleTop != newVisibleTop) { + _delegate->listScrollTo(newVisibleTop); + } + } + _scrollTopState = ScrollTopState(); +} + +void ListWidget::updateAroundPositionFromRows() { + _aroundIndex = findNearestItem(_aroundPosition); + if (_aroundIndex >= 0) { + _aroundPosition = _items[_aroundIndex]->position(); + } +} + +int ListWidget::findNearestItem(Data::MessagePosition position) const { + if (_items.empty()) { + return -1; + } + const auto after = ranges::find_if( + _items, + [&](not_null item) { + return (item->position() >= position); + }); + return (after == end(_items)) + ? int(_items.size() - 1) + : int(after - begin(_items)); +} + +void ListWidget::visibleTopBottomUpdated( + int visibleTop, + int visibleBottom) { + auto scrolledUp = (visibleTop < _visibleTop); + _visibleTop = visibleTop; + _visibleBottom = visibleBottom; + + updateVisibleTopItem(); + checkMoveToOtherViewer(); + if (scrolledUp) { + _scrollDateCheck.call(); + } else { + scrollDateHideByTimer(); + } + _controller->floatPlayerAreaUpdated().notify(true); +} + +void ListWidget::updateVisibleTopItem() { + if (_visibleBottom == height()) { + _visibleTopItem = nullptr; + } else if (_items.empty()) { + _visibleTopItem = nullptr; + _visibleTopFromItem = _visibleTop; + } else { + _visibleTopItem = findItemByY(_visibleTop); + _visibleTopFromItem = _visibleTop - itemTop(_visibleTopItem); + } +} + +bool ListWidget::displayScrollDate() const { + return (_visibleTop <= height() - 2 * (_visibleBottom - _visibleTop)); +} + +void ListWidget::scrollDateCheck() { + if (!_visibleTopItem) { + _scrollDateLastItem = nullptr; + _scrollDateLastItemTop = 0; + scrollDateHide(); + } else if (_visibleTopItem != _scrollDateLastItem || _visibleTopFromItem != _scrollDateLastItemTop) { + // Show scroll date only if it is not the initial onScroll() event (with empty _scrollDateLastItem). + if (_scrollDateLastItem && !_scrollDateShown) { + toggleScrollDateShown(); + } + _scrollDateLastItem = _visibleTopItem; + _scrollDateLastItemTop = _visibleTopFromItem; + _scrollDateHideTimer.callOnce(kScrollDateHideTimeout); + } +} + +void ListWidget::scrollDateHideByTimer() { + _scrollDateHideTimer.cancel(); + scrollDateHide(); +} + +void ListWidget::scrollDateHide() { + if (_scrollDateShown) { + toggleScrollDateShown(); + } +} + +void ListWidget::toggleScrollDateShown() { + _scrollDateShown = !_scrollDateShown; + auto from = _scrollDateShown ? 0. : 1.; + auto to = _scrollDateShown ? 1. : 0.; + _scrollDateOpacity.start([this] { repaintScrollDateCallback(); }, from, to, st::historyDateFadeDuration); +} + +void ListWidget::repaintScrollDateCallback() { + auto updateTop = _visibleTop; + auto updateHeight = st::msgServiceMargin.top() + st::msgServicePadding.top() + st::msgServiceFont->height + st::msgServicePadding.bottom(); + update(0, updateTop, width(), updateHeight); +} + +void ListWidget::checkMoveToOtherViewer() { + auto visibleHeight = (_visibleBottom - _visibleTop); + if (width() <= 0 + || visibleHeight <= 0 + || _items.empty() + || _aroundIndex < 0 + || _scrollTopState.item) { + return; + } + + auto topItem = findItemByY(_visibleTop); + auto bottomItem = findItemByY(_visibleBottom); + auto preloadedHeight = kPreloadedScreensCountFull * visibleHeight; + auto minItemHeight = st::msgMarginTopAttached + + st::msgPhotoSize + + st::msgMargin.bottom(); + auto preloadedCount = preloadedHeight / minItemHeight; + auto preloadIdsLimitMin = (preloadedCount / 2) + 1; + auto preloadIdsLimit = preloadIdsLimitMin + + (visibleHeight / minItemHeight); + + auto preloadBefore = kPreloadIfLessThanScreens * visibleHeight; + auto after = _slice.skippedAfter; + auto preloadTop = (_visibleTop < preloadBefore); + auto topLoaded = after && (*after == 0); + auto before = _slice.skippedBefore; + auto preloadBottom = (height() - _visibleBottom < preloadBefore); + auto bottomLoaded = before && (*before == 0); + + auto minScreenDelta = kPreloadedScreensCount + - kPreloadIfLessThanScreens; + auto minUniversalIdDelta = (minScreenDelta * visibleHeight) + / minItemHeight; + auto preloadAroundItem = [&](not_null item) { + auto preloadRequired = false; + auto itemPosition = item->position(); + auto itemIndex = ranges::find(_items, item) - begin(_items); + Assert(itemIndex < _items.size()); + + if (!preloadRequired) { + preloadRequired = (_idsLimit < preloadIdsLimitMin); + } + if (!preloadRequired) { + Assert(_aroundIndex >= 0); + auto delta = std::abs(itemIndex - _aroundIndex); + preloadRequired = (delta >= minUniversalIdDelta); + } + if (preloadRequired) { + _idsLimit = preloadIdsLimit; + _aroundPosition = itemPosition; + _aroundIndex = itemIndex; + refreshViewer(); + } + }; + + if (preloadTop && !topLoaded) { + preloadAroundItem(topItem); + } else if (preloadBottom && !bottomLoaded) { + preloadAroundItem(bottomItem); + } +} + +QString ListWidget::tooltipText() const { + if (_mouseCursorState == HistoryInDateCursorState && _mouseAction == MouseAction::None) { + if (const auto item = App::hoveredItem()) { + auto dateText = item->date.toString(QLocale::system().dateTimeFormat(QLocale::LongFormat)); + return dateText; + } + } else if (_mouseCursorState == HistoryInForwardedCursorState && _mouseAction == MouseAction::None) { + if (const auto item = App::hoveredItem()) { + if (const auto forwarded = item->Get()) { + return forwarded->text.originalText(AllTextSelection, ExpandLinksNone); + } + } + } else if (const auto lnk = ClickHandler::getActive()) { + return lnk->tooltip(); + } + return QString(); +} + +QPoint ListWidget::tooltipPos() const { + return _mousePosition; +} + +void ListWidget::saveState(not_null memento) { + memento->setAroundPosition(_aroundPosition); + auto state = countScrollState(); + if (state.item) { + memento->setIdsLimit(_idsLimit); + memento->setScrollTopState(state); + } +} + +void ListWidget::restoreState(not_null memento) { + _aroundPosition = memento->aroundPosition(); + _aroundIndex = -1; + if (const auto limit = memento->idsLimit()) { + _idsLimit = limit; + _scrollTopState = memento->scrollTopState(); + } + refreshViewer(); +} + +void ListWidget::itemsAdded(Direction direction, int addedCount) { + Expects(addedCount >= 0); + + auto checkFrom = (direction == Direction::Up) + ? (_items.size() - addedCount) + : 1; // Should be ": 0", but zero is skipped anyway. + auto checkTo = (direction == Direction::Up) + ? (_items.size() + 1) + : (addedCount + 1); + for (auto i = checkFrom; i != checkTo; ++i) { + if (i > 0) { + auto item = _items[i - 1].get(); + if (i < _items.size()) { + auto previous = _items[i].get(); + item->setLogEntryDisplayDate(item->date.date() != previous->date.date()); + auto attachToPrevious = item->computeIsAttachToPrevious(previous); + item->setLogEntryAttachToPrevious(attachToPrevious); + previous->setLogEntryAttachToNext(attachToPrevious); + } else { + item->setLogEntryDisplayDate(true); + } + } + } + updateSize(); +} + +void ListWidget::updateSize() { + TWidget::resizeToWidth(width()); + restoreScrollPosition(); + updateVisibleTopItem(); +} + +int ListWidget::resizeGetHeight(int newWidth) { + update(); + + auto newHeight = 0; + for (auto &item : _items) { + item->setY(newHeight); + newHeight += item->resizeGetHeight(newWidth); + } + _itemsHeight = newHeight; + _itemsTop = (_minHeight > _itemsHeight + st::historyPaddingBottom) ? (_minHeight - _itemsHeight - st::historyPaddingBottom) : 0; + return _itemsTop + _itemsHeight + st::historyPaddingBottom; +} + +void ListWidget::restoreScrollPosition() { + auto newVisibleTop = _visibleTopItem + ? (itemTop(_visibleTopItem) + _visibleTopFromItem) + : ScrollMax; + _delegate->listScrollTo(newVisibleTop); +} + +void ListWidget::paintEvent(QPaintEvent *e) { + if (Ui::skipPaintEvent(this, e)) { + return; + } + + Painter p(this); + + auto ms = getms(); + auto clip = e->rect(); + + auto from = std::lower_bound(begin(_items), end(_items), clip.top(), [this](auto &elem, int top) { + return this->itemTop(elem) + elem->height() <= top; + }); + auto to = std::lower_bound(begin(_items), end(_items), clip.top() + clip.height(), [this](auto &elem, int bottom) { + return this->itemTop(elem) < bottom; + }); + if (from != end(_items)) { + auto top = itemTop(from->get()); + p.translate(0, top); + for (auto i = from; i != to; ++i) { + auto selection = (*i == _selectedItem) ? _selectedText : TextSelection(); + (*i)->draw(p, clip.translated(0, -top), selection, ms); + auto height = (*i)->height(); + top += height; + p.translate(0, height); + } + p.translate(0, -top); + + enumerateUserpics([&p, &clip](not_null message, int userpicTop) { + // stop the enumeration if the userpic is below the painted rect + if (userpicTop >= clip.top() + clip.height()) { + return false; + } + + // paint the userpic if it intersects the painted rect + if (userpicTop + st::msgPhotoSize > clip.top()) { + message->from()->paintUserpicLeft(p, st::historyPhotoLeft, userpicTop, message->width(), st::msgPhotoSize); + } + return true; + }); + + auto dateHeight = st::msgServicePadding.bottom() + st::msgServiceFont->height + st::msgServicePadding.top(); + auto scrollDateOpacity = _scrollDateOpacity.current(ms, _scrollDateShown ? 1. : 0.); + enumerateDates([&p, &clip, scrollDateOpacity, dateHeight/*, lastDate, showFloatingBefore*/](not_null item, int itemtop, int dateTop) { + // stop the enumeration if the date is above the painted rect + if (dateTop + dateHeight <= clip.top()) { + return false; + } + + bool displayDate = item->displayDate(); + bool dateInPlace = displayDate; + if (dateInPlace) { + int correctDateTop = itemtop + st::msgServiceMargin.top(); + dateInPlace = (dateTop < correctDateTop + dateHeight); + } + //bool noFloatingDate = (item->date.date() == lastDate && displayDate); + //if (noFloatingDate) { + // if (itemtop < showFloatingBefore) { + // noFloatingDate = false; + // } + //} + + // paint the date if it intersects the painted rect + if (dateTop < clip.top() + clip.height()) { + auto opacity = (dateInPlace/* || noFloatingDate*/) ? 1. : scrollDateOpacity; + if (opacity > 0.) { + p.setOpacity(opacity); + int dateY = /*noFloatingDate ? itemtop :*/ (dateTop - st::msgServiceMargin.top()); + int width = item->width(); + if (auto date = item->Get()) { + date->paint(p, dateY, width); + } else { + ServiceMessagePainter::paintDate( + p, item->date, dateY, width); + } + } + } + return true; + }); + } +} + +TextWithEntities ListWidget::getSelectedText() const { + return _selectedItem + ? _selectedItem->selectedText(_selectedText) + : TextWithEntities(); +} + +not_null ListWidget::findItemByY(int y) const { + Expects(!_items.empty()); + + if (y < _itemsTop) { + return _items.front().get(); + } + auto i = std::lower_bound( + begin(_items), + end(_items), + y, + [this](auto &elem, int top) { + return this->itemTop(elem) + elem->height() <= top; + }); + return (i != end(_items)) ? i->get() : _items.back().get(); +} + +HistoryItem *ListWidget::strictFindItemByY(int y) const { + if (_items.empty()) { + return nullptr; + } + return (y >= _itemsTop && y < _itemsTop + _itemsHeight) + ? findItemByY(y).get() + : nullptr; +} + +auto ListWidget::countScrollState() const -> ScrollTopState { + if (_items.empty()) { + return { Data::MessagePosition(), 0 }; + } + auto topItem = findItemByY(_visibleTop); + return { + topItem->position(), + _visibleTop - itemTop(topItem) + }; +} + +void ListWidget::keyPressEvent(QKeyEvent *e) { + if (e->key() == Qt::Key_Escape || e->key() == Qt::Key_Back) { + _delegate->listCloseRequest(); + } else if (e == QKeySequence::Copy && _selectedItem != nullptr) { + copySelectedText(); +#ifdef Q_OS_MAC + } else if (e->key() == Qt::Key_E && e->modifiers().testFlag(Qt::ControlModifier)) { + setToClipboard(getSelectedText(), QClipboard::FindBuffer); +#endif // Q_OS_MAC + } else { + e->ignore(); + } +} + +void ListWidget::mouseDoubleClickEvent(QMouseEvent *e) { + mouseActionStart(e->globalPos(), e->button()); + if (((_mouseAction == MouseAction::Selecting && _selectedItem != nullptr) || (_mouseAction == MouseAction::None)) && _mouseSelectType == TextSelectType::Letters && _mouseActionItem) { + HistoryStateRequest request; + request.flags |= Text::StateRequest::Flag::LookupSymbol; + auto dragState = _mouseActionItem->getState(_dragStartPosition, request); + if (dragState.cursor == HistoryInTextCursorState) { + _mouseTextSymbol = dragState.symbol; + _mouseSelectType = TextSelectType::Words; + if (_mouseAction == MouseAction::None) { + _mouseAction = MouseAction::Selecting; + auto selection = TextSelection { dragState.symbol, dragState.symbol }; + repaintItem(std::exchange(_selectedItem, _mouseActionItem)); + _selectedText = selection; + } + mouseMoveEvent(e); + + _trippleClickPoint = e->globalPos(); + _trippleClickTimer.callOnce(QApplication::doubleClickInterval()); + } + } +} + +void ListWidget::contextMenuEvent(QContextMenuEvent *e) { + showContextMenu(e); +} + +void ListWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { + if (_menu) { + _menu->deleteLater(); + _menu = 0; + } + if (e->reason() == QContextMenuEvent::Mouse) { + mouseActionUpdate(e->globalPos()); + } + + // -1 - has selection, but no over, 0 - no selection, 1 - over text + auto isUponSelected = 0; + auto hasSelected = 0; + if (_selectedItem) { + isUponSelected = -1; + + auto selFrom = _selectedText.from; + auto selTo = _selectedText.to; + hasSelected = (selTo > selFrom) ? 1 : 0; + if (App::mousedItem() && App::mousedItem() == App::hoveredItem()) { + auto mousePos = mapPointToItem(mapFromGlobal(_mousePosition), App::mousedItem()); + HistoryStateRequest request; + request.flags |= Text::StateRequest::Flag::LookupSymbol; + auto dragState = App::mousedItem()->getState(mousePos, request); + if (dragState.cursor == HistoryInTextCursorState && dragState.symbol >= selFrom && dragState.symbol < selTo) { + isUponSelected = 1; + } + } + } + if (showFromTouch && hasSelected && isUponSelected < hasSelected) { + isUponSelected = hasSelected; + } + + _menu = new Ui::PopupMenu(nullptr); + + _contextMenuLink = ClickHandler::getActive(); + auto item = App::hoveredItem() ? App::hoveredItem() : App::hoveredLinkItem(); + auto lnkPhoto = dynamic_cast(_contextMenuLink.get()); + auto lnkDocument = dynamic_cast(_contextMenuLink.get()); + auto lnkPeer = dynamic_cast(_contextMenuLink.get()); + auto lnkIsVideo = lnkDocument ? lnkDocument->document()->isVideoFile() : false; + auto lnkIsVoice = lnkDocument ? lnkDocument->document()->isVoiceMessage() : false; + auto lnkIsAudio = lnkDocument ? lnkDocument->document()->isAudioFile() : false; + if (lnkPhoto || lnkDocument) { + if (isUponSelected > 0) { + _menu->addAction(lang(lng_context_copy_selected), [this] { copySelectedText(); })->setEnabled(true); + } + if (lnkPhoto) { + _menu->addAction(lang(lng_context_save_image), App::LambdaDelayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [this, photo = lnkPhoto->photo()] { + savePhotoToFile(photo); + }))->setEnabled(true); + _menu->addAction(lang(lng_context_copy_image), [this, photo = lnkPhoto->photo()] { + copyContextImage(photo); + })->setEnabled(true); + } else { + auto document = lnkDocument->document(); + if (document->loading()) { + _menu->addAction(lang(lng_context_cancel_download), [this] { cancelContextDownload(); })->setEnabled(true); + } else { + if (document->loaded() && document->isGifv()) { + if (!cAutoPlayGif()) { + _menu->addAction(lang(lng_context_open_gif), [this] { openContextGif(); })->setEnabled(true); + } + } + if (!document->filepath(DocumentData::FilePathResolveChecked).isEmpty()) { + _menu->addAction(lang((cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? lng_context_show_in_finder : lng_context_show_in_folder), [this] { showContextInFolder(); })->setEnabled(true); + } + _menu->addAction(lang(lnkIsVideo ? lng_context_save_video : (lnkIsVoice ? lng_context_save_audio : (lnkIsAudio ? lng_context_save_audio_file : lng_context_save_file))), App::LambdaDelayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [this, document] { + saveDocumentToFile(document); + }))->setEnabled(true); + } + } + if (App::hoveredLinkItem()) { + App::contextItem(App::hoveredLinkItem()); + } + } else if (lnkPeer) { // suggest to block + // #TODO suggest restrict peer + } else { // maybe cursor on some text history item? + bool canDelete = item && item->canDelete() && (item->id > 0 || !item->serviceMsg()); + bool canForward = item && item->canForward(); + + auto msg = dynamic_cast(item); + if (isUponSelected > 0) { + _menu->addAction(lang(lng_context_copy_selected), [this] { copySelectedText(); })->setEnabled(true); + } else { + if (item && !isUponSelected) { + auto mediaHasTextForCopy = false; + if (auto media = (msg ? msg->getMedia() : nullptr)) { + mediaHasTextForCopy = media->hasTextForCopy(); + if (media->type() == MediaTypeWebPage && static_cast(media)->attach()) { + media = static_cast(media)->attach(); + } + if (media->type() == MediaTypeSticker) { + if (auto document = media->getDocument()) { + if (document->sticker() && document->sticker()->set.type() != mtpc_inputStickerSetEmpty) { + _menu->addAction(lang(document->sticker()->setInstalled() ? lng_context_pack_info : lng_context_pack_add), [this] { showStickerPackInfo(); }); + } + _menu->addAction(lang(lng_context_save_image), App::LambdaDelayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [this, document] { + saveDocumentToFile(document); + }))->setEnabled(true); + } + } else if (media->type() == MediaTypeGif && !_contextMenuLink) { + if (auto document = media->getDocument()) { + if (document->loading()) { + _menu->addAction(lang(lng_context_cancel_download), [this] { cancelContextDownload(); })->setEnabled(true); + } else { + if (document->isGifv()) { + if (!cAutoPlayGif()) { + _menu->addAction(lang(lng_context_open_gif), [this] { openContextGif(); })->setEnabled(true); + } + } + if (!document->filepath(DocumentData::FilePathResolveChecked).isEmpty()) { + _menu->addAction(lang((cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? lng_context_show_in_finder : lng_context_show_in_folder), [this] { showContextInFolder(); })->setEnabled(true); + } + _menu->addAction(lang(lng_context_save_file), App::LambdaDelayed(st::defaultDropdownMenu.menu.ripple.hideDuration, this, [this, document] { + saveDocumentToFile(document); + }))->setEnabled(true); + } + } + } + } + if (msg && !_contextMenuLink && (!msg->emptyText() || mediaHasTextForCopy)) { + _menu->addAction(lang(lng_context_copy_text), [this] { copyContextText(); })->setEnabled(true); + } + } + } + + auto linkCopyToClipboardText = _contextMenuLink ? _contextMenuLink->copyToClipboardContextItemText() : QString(); + if (!linkCopyToClipboardText.isEmpty()) { + _menu->addAction(linkCopyToClipboardText, [this] { copyContextUrl(); })->setEnabled(true); + } + App::contextItem(item); + } + + if (_menu->actions().isEmpty()) { + delete base::take(_menu); + } else { + connect(_menu, &QObject::destroyed, this, [this](QObject *object) { + if (_menu == object) { + _menu = nullptr; + } + }); + _menu->popup(e->globalPos()); + e->accept(); + } +} + +void ListWidget::savePhotoToFile(PhotoData *photo) { + if (!photo || !photo->date || !photo->loaded()) return; + + auto filter = qsl("JPEG Image (*.jpg);;") + FileDialog::AllFilesFilter(); + FileDialog::GetWritePath(lang(lng_save_photo), filter, filedialogDefaultName(qsl("photo"), qsl(".jpg")), base::lambda_guarded(this, [this, photo](const QString &result) { + if (!result.isEmpty()) { + photo->full->pix().toImage().save(result, "JPG"); + } + })); +} + +void ListWidget::saveDocumentToFile(DocumentData *document) { + DocumentSaveClickHandler::doSave(document, true); +} + +void ListWidget::copyContextImage(PhotoData *photo) { + if (!photo || !photo->date || !photo->loaded()) return; + + QApplication::clipboard()->setPixmap(photo->full->pix()); +} + +void ListWidget::copySelectedText() { + setToClipboard(getSelectedText()); +} + +void ListWidget::copyContextUrl() { + if (_contextMenuLink) { + _contextMenuLink->copyToClipboard(); + } +} + +void ListWidget::showStickerPackInfo() { + if (!App::contextItem()) return; + + if (auto media = App::contextItem()->getMedia()) { + if (auto doc = media->getDocument()) { + if (auto sticker = doc->sticker()) { + if (sticker->set.type() != mtpc_inputStickerSetEmpty) { + App::main()->stickersBox(sticker->set); + } + } + } + } +} + +void ListWidget::cancelContextDownload() { + if (auto lnkDocument = dynamic_cast(_contextMenuLink.get())) { + lnkDocument->document()->cancel(); + } else if (auto item = App::contextItem()) { + if (auto media = item->getMedia()) { + if (auto doc = media->getDocument()) { + doc->cancel(); + } + } + } +} + +void ListWidget::showContextInFolder() { + QString filepath; + if (auto lnkDocument = dynamic_cast(_contextMenuLink.get())) { + filepath = lnkDocument->document()->filepath(DocumentData::FilePathResolveChecked); + } else if (auto item = App::contextItem()) { + if (auto media = item->getMedia()) { + if (auto doc = media->getDocument()) { + filepath = doc->filepath(DocumentData::FilePathResolveChecked); + } + } + } + if (!filepath.isEmpty()) { + File::ShowInFolder(filepath); + } +} + +void ListWidget::openContextGif() { + if (auto item = App::contextItem()) { + if (auto media = item->getMedia()) { + if (auto document = media->getDocument()) { + Messenger::Instance().showDocument(document, item); + } + } + } +} + +void ListWidget::copyContextText() { + auto item = App::contextItem(); + if (!item || (item->getMedia() && item->getMedia()->type() == MediaTypeSticker)) { + return; + } + + setToClipboard(item->selectedText(FullSelection)); +} + +void ListWidget::setToClipboard(const TextWithEntities &forClipboard, QClipboard::Mode mode) { + if (auto data = MimeDataFromTextWithEntities(forClipboard)) { + QApplication::clipboard()->setMimeData(data.release(), mode); + } +} + +void ListWidget::mousePressEvent(QMouseEvent *e) { + if (_menu) { + e->accept(); + return; // ignore mouse press, that was hiding context menu + } + mouseActionStart(e->globalPos(), e->button()); +} + +void ListWidget::mouseMoveEvent(QMouseEvent *e) { + auto buttonsPressed = (e->buttons() & (Qt::LeftButton | Qt::MiddleButton)); + if (!buttonsPressed && _mouseAction != MouseAction::None) { + mouseReleaseEvent(e); + } + mouseActionUpdate(e->globalPos()); +} + +void ListWidget::mouseReleaseEvent(QMouseEvent *e) { + mouseActionFinish(e->globalPos(), e->button()); + if (!rect().contains(e->pos())) { + leaveEvent(e); + } +} + +void ListWidget::enterEventHook(QEvent *e) { + mouseActionUpdate(QCursor::pos()); + return TWidget::enterEventHook(e); +} + +void ListWidget::leaveEventHook(QEvent *e) { + if (auto item = App::hoveredItem()) { + repaintItem(item); + App::hoveredItem(nullptr); + } + ClickHandler::clearActive(); + Ui::Tooltip::Hide(); + if (!ClickHandler::getPressed() && _cursor != style::cur_default) { + _cursor = style::cur_default; + setCursor(_cursor); + } + return TWidget::leaveEventHook(e); +} + +void ListWidget::mouseActionStart(const QPoint &screenPos, Qt::MouseButton button) { + mouseActionUpdate(screenPos); + if (button != Qt::LeftButton) return; + + ClickHandler::pressed(); + if (App::pressedItem() != App::hoveredItem()) { + repaintItem(App::pressedItem()); + App::pressedItem(App::hoveredItem()); + repaintItem(App::pressedItem()); + } + + _mouseAction = MouseAction::None; + _mouseActionItem = App::mousedItem(); + _dragStartPosition = mapPointToItem(mapFromGlobal(screenPos), _mouseActionItem); + _pressWasInactive = _controller->window()->wasInactivePress(); + if (_pressWasInactive) _controller->window()->setInactivePress(false); + + if (ClickHandler::getPressed()) { + _mouseAction = MouseAction::PrepareDrag; + } + if (_mouseAction == MouseAction::None && _mouseActionItem) { + HistoryTextState dragState; + if (_trippleClickTimer.isActive() && (screenPos - _trippleClickPoint).manhattanLength() < QApplication::startDragDistance()) { + HistoryStateRequest request; + request.flags = Text::StateRequest::Flag::LookupSymbol; + dragState = _mouseActionItem->getState(_dragStartPosition, request); + if (dragState.cursor == HistoryInTextCursorState) { + auto selection = TextSelection { dragState.symbol, dragState.symbol }; + repaintItem(std::exchange(_selectedItem, _mouseActionItem)); + _selectedText = selection; + _mouseTextSymbol = dragState.symbol; + _mouseAction = MouseAction::Selecting; + _mouseSelectType = TextSelectType::Paragraphs; + mouseActionUpdate(_mousePosition); + _trippleClickTimer.callOnce(QApplication::doubleClickInterval()); + } + } else if (App::pressedItem()) { + HistoryStateRequest request; + request.flags = Text::StateRequest::Flag::LookupSymbol; + dragState = _mouseActionItem->getState(_dragStartPosition, request); + } + if (_mouseSelectType != TextSelectType::Paragraphs) { + if (App::pressedItem()) { + _mouseTextSymbol = dragState.symbol; + auto uponSelected = (dragState.cursor == HistoryInTextCursorState); + if (uponSelected) { + if (!_selectedItem || _selectedItem != _mouseActionItem) { + uponSelected = false; + } else if (_mouseTextSymbol < _selectedText.from || _mouseTextSymbol >= _selectedText.to) { + uponSelected = false; + } + } + if (uponSelected) { + _mouseAction = MouseAction::PrepareDrag; // start text drag + } else if (!_pressWasInactive) { + if (dragState.afterSymbol) ++_mouseTextSymbol; + auto selection = TextSelection { _mouseTextSymbol, _mouseTextSymbol }; + repaintItem(std::exchange(_selectedItem, _mouseActionItem)); + _selectedText = selection; + _mouseAction = MouseAction::Selecting; + repaintItem(_mouseActionItem); + } + } + } + } + + if (!_mouseActionItem) { + _mouseAction = MouseAction::None; + } else if (_mouseAction == MouseAction::None) { + _mouseActionItem = nullptr; + } +} + +void ListWidget::mouseActionUpdate(const QPoint &screenPos) { + _mousePosition = screenPos; + updateSelected(); +} + +void ListWidget::mouseActionCancel() { + _mouseActionItem = nullptr; + _mouseAction = MouseAction::None; + _dragStartPosition = QPoint(0, 0); + _wasSelectedText = false; + //_widget->noSelectingScroll(); // #TODO select scroll +} + +void ListWidget::mouseActionFinish(const QPoint &screenPos, Qt::MouseButton button) { + mouseActionUpdate(screenPos); + + auto activated = ClickHandler::unpressed(); + if (_mouseAction == MouseAction::Dragging) { + activated = nullptr; + } + if (App::pressedItem()) { + repaintItem(App::pressedItem()); + App::pressedItem(nullptr); + } + + _wasSelectedText = false; + + if (activated) { + mouseActionCancel(); + App::activateClickHandler(activated, button); + return; + } + if (_mouseAction == MouseAction::PrepareDrag && !_pressWasInactive && button != Qt::RightButton) { + repaintItem(base::take(_selectedItem)); + } else if (_mouseAction == MouseAction::Selecting) { + if (_selectedItem && !_pressWasInactive) { + if (_selectedText.from == _selectedText.to) { + _selectedItem = nullptr; + App::wnd()->setInnerFocus(); + } + } + } + _mouseAction = MouseAction::None; + _mouseActionItem = nullptr; + _mouseSelectType = TextSelectType::Letters; + //_widget->noSelectingScroll(); // #TODO select scroll + +#if defined Q_OS_LINUX32 || defined Q_OS_LINUX64 + if (_selectedItem && _selectedText.from != _selectedText.to) { + setToClipboard(_selectedItem->selectedText(_selectedText), QClipboard::Selection); + } +#endif // Q_OS_LINUX32 || Q_OS_LINUX64 +} + +void ListWidget::updateSelected() { + auto mousePosition = mapFromGlobal(_mousePosition); + auto point = QPoint(snap(mousePosition.x(), 0, width()), snap(mousePosition.y(), _visibleTop, _visibleBottom)); + + auto itemPoint = QPoint(); + auto item = strictFindItemByY(point.y()); + if (item) { + App::mousedItem(item); + itemPoint = mapPointToItem(point, item); + if (item->hasPoint(itemPoint)) { + if (App::hoveredItem() != item) { + repaintItem(App::hoveredItem()); + App::hoveredItem(item); + repaintItem(App::hoveredItem()); + } + } else if (App::hoveredItem()) { + repaintItem(App::hoveredItem()); + App::hoveredItem(nullptr); + } + } + + HistoryTextState dragState; + ClickHandlerHost *lnkhost = nullptr; + auto selectingText = (item == _mouseActionItem && item == App::hoveredItem() && _selectedItem); + if (item) { + if (item != _mouseActionItem || (itemPoint - _dragStartPosition).manhattanLength() >= QApplication::startDragDistance()) { + if (_mouseAction == MouseAction::PrepareDrag) { + _mouseAction = MouseAction::Dragging; + InvokeQueued(this, [this] { performDrag(); }); + } + } + HistoryStateRequest request; + if (_mouseAction == MouseAction::Selecting) { + request.flags |= Text::StateRequest::Flag::LookupSymbol; + } else { + selectingText = false; + } + dragState = item->getState(itemPoint, request); + lnkhost = item; + if (!dragState.link && itemPoint.x() >= st::historyPhotoLeft && itemPoint.x() < st::historyPhotoLeft + st::msgPhotoSize) { + if (auto message = item->toHistoryMessage()) { + if (message->hasFromPhoto()) { + enumerateUserpics([&dragState, &lnkhost, &point](not_null message, int userpicTop) -> bool { + // stop enumeration if the userpic is below our point + if (userpicTop > point.y()) { + return false; + } + + // stop enumeration if we've found a userpic under the cursor + if (point.y() >= userpicTop && point.y() < userpicTop + st::msgPhotoSize) { + dragState.link = message->from()->openLink(); + lnkhost = message; + return false; + } + return true; + }); + } + } + } + } + auto lnkChanged = ClickHandler::setActive(dragState.link, lnkhost); + if (lnkChanged || dragState.cursor != _mouseCursorState) { + Ui::Tooltip::Hide(); + } + if (dragState.link || dragState.cursor == HistoryInDateCursorState || dragState.cursor == HistoryInForwardedCursorState) { + Ui::Tooltip::Show(1000, this); + } + + auto cursor = style::cur_default; + if (_mouseAction == MouseAction::None) { + _mouseCursorState = dragState.cursor; + if (dragState.link) { + cursor = style::cur_pointer; + } else if (_mouseCursorState == HistoryInTextCursorState) { + cursor = style::cur_text; + } else if (_mouseCursorState == HistoryInDateCursorState) { +// cursor = style::cur_cross; + } + } else if (item) { + if (_mouseAction == MouseAction::Selecting) { + if (selectingText) { + auto second = dragState.symbol; + if (dragState.afterSymbol && _mouseSelectType == TextSelectType::Letters) { + ++second; + } + auto selection = TextSelection { qMin(second, _mouseTextSymbol), qMax(second, _mouseTextSymbol) }; + if (_mouseSelectType != TextSelectType::Letters) { + selection = _mouseActionItem->adjustSelection(selection, _mouseSelectType); + } + if (_selectedText != selection) { + _selectedText = selection; + repaintItem(_mouseActionItem); + } + if (!_wasSelectedText && (selection.from != selection.to)) { + _wasSelectedText = true; + setFocus(); + } + } + } else if (_mouseAction == MouseAction::Dragging) { + } + + if (ClickHandler::getPressed()) { + cursor = style::cur_pointer; + } else if (_mouseAction == MouseAction::Selecting && _selectedItem) { + cursor = style::cur_text; + } + } + + // Voice message seek support. + if (auto pressedItem = App::pressedLinkItem()) { + if (!pressedItem->detached()) { + // #TODO seek support + //if (pressedItem->history() == _history) { + // auto adjustedPoint = mapPointToItem(point, pressedItem); + // pressedItem->updatePressed(adjustedPoint); + //} + } + } + + //if (_mouseAction == MouseAction::Selecting) { + // _widget->checkSelectingScroll(mousePos); + //} else { + // _widget->noSelectingScroll(); + //} // #TODO select scroll + + if (_mouseAction == MouseAction::None && (lnkChanged || cursor != _cursor)) { + setCursor(_cursor = cursor); + } +} + +void ListWidget::performDrag() { + if (_mouseAction != MouseAction::Dragging) return; + + auto uponSelected = false; + //if (_mouseActionItem) { + // if (!_selected.isEmpty() && _selected.cbegin().value() == FullSelection) { + // uponSelected = _selected.contains(_mouseActionItem); + // } else { + // HistoryStateRequest request; + // request.flags |= Text::StateRequest::Flag::LookupSymbol; + // auto dragState = _mouseActionItem->getState(_dragStartPosition.x(), _dragStartPosition.y(), request); + // uponSelected = (dragState.cursor == HistoryInTextCursorState); + // if (uponSelected) { + // if (_selected.isEmpty() || + // _selected.cbegin().value() == FullSelection || + // _selected.cbegin().key() != _mouseActionItem + // ) { + // uponSelected = false; + // } else { + // uint16 selFrom = _selected.cbegin().value().from, selTo = _selected.cbegin().value().to; + // if (dragState.symbol < selFrom || dragState.symbol >= selTo) { + // uponSelected = false; + // } + // } + // } + // } + //} + //auto pressedHandler = ClickHandler::getPressed(); + + //if (dynamic_cast(pressedHandler.data())) { + // return; + //} + + //TextWithEntities sel; + //QList urls; + //if (uponSelected) { + // sel = getSelectedText(); + //} else if (pressedHandler) { + // sel = { pressedHandler->dragText(), EntitiesInText() }; + // //if (!sel.isEmpty() && sel.at(0) != '/' && sel.at(0) != '@' && sel.at(0) != '#') { + // // urls.push_back(QUrl::fromEncoded(sel.toUtf8())); // Google Chrome crashes in Mac OS X O_o + // //} + //} + //if (auto mimeData = mimeDataFromTextWithEntities(sel)) { + // updateDragSelection(0, 0, false); + // _widget->noSelectingScroll(); + + // if (!urls.isEmpty()) mimeData->setUrls(urls); + // if (uponSelected && !Adaptive::OneColumn()) { + // auto selectedState = getSelectionState(); + // if (selectedState.count > 0 && selectedState.count == selectedState.canForwardCount) { + // mimeData->setData(qsl("application/x-td-forward-selected"), "1"); + // } + // } + // _controller->window()->launchDrag(std::move(mimeData)); + // return; + //} else { + // auto forwardMimeType = QString(); + // auto pressedMedia = static_cast(nullptr); + // if (auto pressedItem = App::pressedItem()) { + // pressedMedia = pressedItem->getMedia(); + // if (_mouseCursorState == HistoryInDateCursorState || (pressedMedia && pressedMedia->dragItem())) { + // forwardMimeType = qsl("application/x-td-forward-pressed"); + // } + // } + // if (auto pressedLnkItem = App::pressedLinkItem()) { + // if ((pressedMedia = pressedLnkItem->getMedia())) { + // if (forwardMimeType.isEmpty() && pressedMedia->dragItemByHandler(pressedHandler)) { + // forwardMimeType = qsl("application/x-td-forward-pressed-link"); + // } + // } + // } + // if (!forwardMimeType.isEmpty()) { + // auto mimeData = std::make_unique(); + // mimeData->setData(forwardMimeType, "1"); + // if (auto document = (pressedMedia ? pressedMedia->getDocument() : nullptr)) { + // auto filepath = document->filepath(DocumentData::FilePathResolveChecked); + // if (!filepath.isEmpty()) { + // QList urls; + // urls.push_back(QUrl::fromLocalFile(filepath)); + // mimeData->setUrls(urls); + // } + // } + + // // This call enters event loop and can destroy any QObject. + // _controller->window()->launchDrag(std::move(mimeData)); + // return; + // } + //} // #TODO drag +} + +int ListWidget::itemTop(not_null item) const { + return _itemsTop + item->y(); +} + +void ListWidget::repaintItem(const HistoryItem *item) { + if (!item) { + return; + } + update(0, itemTop(item), width(), item->height()); +} + +QPoint ListWidget::mapPointToItem( + QPoint point, + const HistoryItem *item) const { + if (!item) { + return QPoint(); + } + return point - QPoint(0, itemTop(item)); +} + +void ListWidget::handlePendingHistoryResize() { + // #TODO resize + //if (_history->hasPendingResizedItems()) { + // _history->resizeGetHeight(width()); + // updateSize(); + //} +} + +ListWidget::~ListWidget() = default; + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h new file mode 100644 index 000000000..401e57056 --- /dev/null +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -0,0 +1,261 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "ui/widgets/tooltip.h" +#include "ui/rp_widget.h" +#include "mtproto/sender.h" +#include "base/timer.h" +#include "data/data_messages.h" + +namespace Ui { +class PopupMenu; +} // namespace Ui + +namespace Window { +class Controller; +} // namespace Window + +namespace HistoryView { + +class ListDelegate { +public: + virtual void listScrollTo(int top) = 0; + virtual void listCloseRequest() = 0; + virtual rpl::producer listSource( + Data::MessagePosition aroundId, + int limitBefore, + int limitAfter) = 0; +}; + +class ListMemento { +public: + struct ScrollTopState { + Data::MessagePosition item; + int shift = 0; + }; + + explicit ListMemento(Data::MessagePosition position) + : _aroundPosition(position) { + } + void setAroundPosition(Data::MessagePosition position) { + _aroundPosition = position; + } + Data::MessagePosition aroundPosition() const { + return _aroundPosition; + } + void setIdsLimit(int limit) { + _idsLimit = limit; + } + int idsLimit() const { + return _idsLimit; + } + void setScrollTopState(ScrollTopState state) { + _scrollTopState = state; + } + ScrollTopState scrollTopState() const { + return _scrollTopState; + } + +private: + Data::MessagePosition _aroundPosition; + ScrollTopState _scrollTopState; + int _idsLimit = 0; + +}; + +class ListWidget final + : public Ui::RpWidget + , public Ui::AbstractTooltipShower + , private base::Subscriber { +public: + ListWidget( + QWidget *parent, + not_null controller, + not_null delegate); + + // Set the correct scroll position after being resized. + void restoreScrollPosition(); + + void resizeToWidth(int newWidth, int minHeight) { + _minHeight = minHeight; + return TWidget::resizeToWidth(newWidth); + } + + void saveState(not_null memento); + void restoreState(not_null memento); + + // AbstractTooltipShower interface + QString tooltipText() const override; + QPoint tooltipPos() const override; + + ~ListWidget(); + +protected: + void visibleTopBottomUpdated( + int visibleTop, + int visibleBottom) override; + + void paintEvent(QPaintEvent *e) override; + void keyPressEvent(QKeyEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void mouseDoubleClickEvent(QMouseEvent *e) override; + void enterEventHook(QEvent *e) override; + void leaveEventHook(QEvent *e) override; + void contextMenuEvent(QContextMenuEvent *e) override; + + // Resize content and count natural widget height for the desired width. + int resizeGetHeight(int newWidth) override; + +private: + enum class Direction { + Up, + Down, + }; + enum class MouseAction { + None, + PrepareDrag, + Dragging, + Selecting, + }; + enum class EnumItemsDirection { + TopToBottom, + BottomToTop, + }; + using ScrollTopState = ListMemento::ScrollTopState; + + void refreshViewer(); + void updateAroundPositionFromRows(); + void refreshRows(); + ScrollTopState countScrollState() const; + void saveScrollState(); + void restoreScrollState(); + + void mouseActionStart(const QPoint &screenPos, Qt::MouseButton button); + void mouseActionUpdate(const QPoint &screenPos); + void mouseActionFinish(const QPoint &screenPos, Qt::MouseButton button); + void mouseActionCancel(); + void updateSelected(); + void performDrag(); + int itemTop(not_null item) const; + void repaintItem(const HistoryItem *item); + QPoint mapPointToItem(QPoint point, const HistoryItem *item) const; + void handlePendingHistoryResize(); + + void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false); + void savePhotoToFile(PhotoData *photo); + void saveDocumentToFile(DocumentData *document); + void copyContextImage(PhotoData *photo); + void showStickerPackInfo(); + void copyContextUrl(); + void cancelContextDownload(); + void showContextInFolder(); + void openContextGif(); + void copyContextText(); + void copySelectedText(); + TextWithEntities getSelectedText() const; + void setToClipboard( + const TextWithEntities &forClipboard, + QClipboard::Mode mode = QClipboard::Clipboard); + + not_null findItemByY(int y) const; + HistoryItem *strictFindItemByY(int y) const; + int findNearestItem(Data::MessagePosition position) const; + + void checkMoveToOtherViewer(); + void updateVisibleTopItem(); + void itemsAdded(Direction direction, int addedCount); + void updateSize(); + + void toggleScrollDateShown(); + void repaintScrollDateCallback(); + bool displayScrollDate() const; + void scrollDateHide(); + void scrollDateCheck(); + void scrollDateHideByTimer(); + + // This function finds all history items that are displayed and calls template method + // for each found message (in given direction) in the passed history with passed top offset. + // + // Method has "bool (*Method)(HistoryItem *item, int itemtop, int itembottom)" signature + // if it returns false the enumeration stops immediately. + template + void enumerateItems(Method method); + + // This function finds all userpics on the left that are displayed and calls template method + // for each found userpic (from the top to the bottom) using enumerateItems() method. + // + // Method has "bool (*Method)(not_null message, int userpicTop)" signature + // if it returns false the enumeration stops immediately. + template + void enumerateUserpics(Method method); + + // This function finds all date elements that are displayed and calls template method + // for each found date element (from the bottom to the top) using enumerateItems() method. + // + // Method has "bool (*Method)(not_null item, int itemtop, int dateTop)" signature + // if it returns false the enumeration stops immediately. + template + void enumerateDates(Method method); + + static constexpr auto kMinimalIdsLimit = 24; + + not_null _delegate; + not_null _controller; + Data::MessagePosition _aroundPosition; + int _aroundIndex = -1; + int _idsLimit = kMinimalIdsLimit; + Data::MessagesSlice _slice; + std::vector> _items; + std::map _itemsByIds; + int _itemsTop = 0; + int _itemsHeight = 0; + + int _minHeight = 0; + int _visibleTop = 0; + int _visibleBottom = 0; + HistoryItem *_visibleTopItem = nullptr; + int _visibleTopFromItem = 0; + ScrollTopState _scrollTopState; + + bool _scrollDateShown = false; + Animation _scrollDateOpacity; + SingleQueuedInvokation _scrollDateCheck; + base::Timer _scrollDateHideTimer; + HistoryItem *_scrollDateLastItem = nullptr; + int _scrollDateLastItemTop = 0; + + MouseAction _mouseAction = MouseAction::None; + TextSelectType _mouseSelectType = TextSelectType::Letters; + QPoint _dragStartPosition; + QPoint _mousePosition; + HistoryItem *_mouseActionItem = nullptr; + HistoryCursorState _mouseCursorState = HistoryDefaultCursorState; + uint16 _mouseTextSymbol = 0; + bool _pressWasInactive = false; + + HistoryItem *_selectedItem = nullptr; + TextSelection _selectedText; + bool _wasSelectedText = false; // was some text selected in current drag action + Qt::CursorShape _cursor = style::cur_default; + + // context menu + Ui::PopupMenu *_menu = nullptr; + + QPoint _trippleClickPoint; + base::Timer _trippleClickTimer; + + ClickHandlerPtr _contextMenuLink; + + rpl::lifetime _viewerLifetime; + +}; + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/history_service_layout.cpp b/Telegram/SourceFiles/history/view/history_view_service_message.cpp similarity index 99% rename from Telegram/SourceFiles/history/history_service_layout.cpp rename to Telegram/SourceFiles/history/view/history_view_service_message.cpp index aa066d62c..a5b96638b 100644 --- a/Telegram/SourceFiles/history/history_service_layout.cpp +++ b/Telegram/SourceFiles/history/view/history_view_service_message.cpp @@ -5,7 +5,7 @@ the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ -#include "history/history_service_layout.h" +#include "history/view/history_view_service_message.h" #include "history/history_service.h" #include "history/history_media.h" @@ -14,7 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mainwidget.h" #include "lang/lang_keys.h" -namespace HistoryLayout { +namespace HistoryView { namespace { enum CircleMask { @@ -359,4 +359,4 @@ void paintBubble(Painter &p, QRect rect, int outerWidth, bool selected, bool out App::roundRect(p, rect, bg, cors, &sh, parts); } -} // namespace HistoryLayout +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/history_service_layout.h b/Telegram/SourceFiles/history/view/history_view_service_message.h similarity index 95% rename from Telegram/SourceFiles/history/history_service_layout.h rename to Telegram/SourceFiles/history/view/history_view_service_message.h index ac39f7d45..90cc670a1 100644 --- a/Telegram/SourceFiles/history/history_service_layout.h +++ b/Telegram/SourceFiles/history/view/history_view_service_message.h @@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class HistoryService; -namespace HistoryLayout { +namespace HistoryView { int WideChatWidth(); @@ -49,4 +49,4 @@ void serviceColorsUpdated(); void paintBubble(Painter &p, QRect rect, int outerWidth, bool selected, bool outbg, RectPart tailSide); -} // namespace HistoryLayout +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/history_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp similarity index 90% rename from Telegram/SourceFiles/history/history_top_bar_widget.cpp rename to Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp index f67e1e37b..474a9a4d9 100644 --- a/Telegram/SourceFiles/history/history_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -5,7 +5,7 @@ the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ -#include "history/history_top_bar_widget.h" +#include "history/view/history_view_top_bar_widget.h" #include #include @@ -34,7 +34,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "observer_peer.h" #include "apiwrap.h" -HistoryTopBarWidget::HistoryTopBarWidget( +namespace HistoryView { + +TopBarWidget::TopBarWidget( QWidget *parent, not_null controller) : RpWidget(parent) @@ -122,35 +124,35 @@ HistoryTopBarWidget::HistoryTopBarWidget( updateControlsVisibility(); } -void HistoryTopBarWidget::refreshLang() { +void TopBarWidget::refreshLang() { InvokeQueued(this, [this] { updateControlsGeometry(); }); } -void HistoryTopBarWidget::onForwardSelection() { +void TopBarWidget::onForwardSelection() { if (App::main()) App::main()->forwardSelectedItems(); } -void HistoryTopBarWidget::onDeleteSelection() { +void TopBarWidget::onDeleteSelection() { if (App::main()) App::main()->confirmDeleteSelectedItems(); } -void HistoryTopBarWidget::onClearSelection() { +void TopBarWidget::onClearSelection() { if (App::main()) App::main()->clearSelectedItems(); } -void HistoryTopBarWidget::onSearch() { +void TopBarWidget::onSearch() { if (_historyPeer) { App::main()->searchInPeer(_historyPeer); } } -void HistoryTopBarWidget::onCall() { +void TopBarWidget::onCall() { if (auto user = _historyPeer->asUser()) { Calls::Current().startOutgoingCall(user); } } -void HistoryTopBarWidget::showMenu() { +void TopBarWidget::showMenu() { if (!_historyPeer || _menu) { return; } @@ -184,7 +186,7 @@ void HistoryTopBarWidget::showMenu() { _menu->showAnimated(Ui::PanelAnimation::Origin::TopRight); } -void HistoryTopBarWidget::toggleInfoSection() { +void TopBarWidget::toggleInfoSection() { if (Adaptive::ThreeColumn() && (Auth().settings().thirdSectionInfoEnabled() || Auth().settings().tabbedReplacedWithInfo())) { @@ -209,7 +211,7 @@ void HistoryTopBarWidget::toggleInfoSection() { } } -bool HistoryTopBarWidget::eventFilter(QObject *obj, QEvent *e) { +bool TopBarWidget::eventFilter(QObject *obj, QEvent *e) { if (obj == _membersShowArea) { switch (e->type()) { case QEvent::MouseButtonPress: @@ -228,8 +230,8 @@ bool HistoryTopBarWidget::eventFilter(QObject *obj, QEvent *e) { return TWidget::eventFilter(obj, e); } -void HistoryTopBarWidget::paintEvent(QPaintEvent *e) { - if (_animationMode) { +void TopBarWidget::paintEvent(QPaintEvent *e) { + if (_animatingMode) { return; } Painter p(this); @@ -247,7 +249,7 @@ void HistoryTopBarWidget::paintEvent(QPaintEvent *e) { } } -void HistoryTopBarWidget::paintTopBar(Painter &p, TimeMs ms) { +void TopBarWidget::paintTopBar(Painter &p, TimeMs ms) { auto history = App::historyLoaded(_historyPeer); if (!history) return; @@ -291,7 +293,7 @@ void HistoryTopBarWidget::paintTopBar(Painter &p, TimeMs ms) { } } -QRect HistoryTopBarWidget::getMembersShowAreaGeometry() const { +QRect TopBarWidget::getMembersShowAreaGeometry() const { int membersTextLeft = _leftTaken; int membersTextTop = st::topBarHeight - st::topBarArrowPadding.bottom() - st::dialogsTextFont->height; int membersTextWidth = _titlePeerTextWidth; @@ -300,12 +302,12 @@ QRect HistoryTopBarWidget::getMembersShowAreaGeometry() const { return myrtlrect(membersTextLeft, membersTextTop, membersTextWidth, membersTextHeight); } -void HistoryTopBarWidget::mousePressEvent(QMouseEvent *e) { +void TopBarWidget::mousePressEvent(QMouseEvent *e) { auto handleClick = (e->button() == Qt::LeftButton) && (e->pos().y() < st::topBarHeight) && (!_selectedCount); if (handleClick) { - if (_animationMode && _back->rect().contains(e->pos())) { + if (_animatingMode && _back->rect().contains(e->pos())) { backClicked(); } else if (_historyPeer) { infoClicked(); @@ -313,7 +315,7 @@ void HistoryTopBarWidget::mousePressEvent(QMouseEvent *e) { } } -void HistoryTopBarWidget::infoClicked() { +void TopBarWidget::infoClicked() { if (_historyPeer && _historyPeer->isSelf()) { _controller->showSection(Info::Memento( _historyPeer->id, @@ -323,11 +325,11 @@ void HistoryTopBarWidget::infoClicked() { } } -void HistoryTopBarWidget::backClicked() { +void TopBarWidget::backClicked() { _controller->showBackFromStack(); } -void HistoryTopBarWidget::setHistoryPeer(PeerData *historyPeer) { +void TopBarWidget::setHistoryPeer(PeerData *historyPeer) { if (_historyPeer != historyPeer) { _historyPeer = historyPeer; _back->clearState(); @@ -353,15 +355,15 @@ void HistoryTopBarWidget::setHistoryPeer(PeerData *historyPeer) { } } -void HistoryTopBarWidget::resizeEvent(QResizeEvent *e) { +void TopBarWidget::resizeEvent(QResizeEvent *e) { updateControlsGeometry(); } -int HistoryTopBarWidget::countSelectedButtonsTop(float64 selectedShown) { +int TopBarWidget::countSelectedButtonsTop(float64 selectedShown) { return (1. - selectedShown) * (-st::topBarHeight); } -void HistoryTopBarWidget::updateControlsGeometry() { +void TopBarWidget::updateControlsGeometry() { auto hasSelected = (_selectedCount > 0); auto selectedButtonsTop = countSelectedButtonsTop(_selectedShown.current(hasSelected ? 1. : 0.)); auto otherButtonsTop = selectedButtonsTop + st::topBarHeight; @@ -417,22 +419,22 @@ void HistoryTopBarWidget::updateControlsGeometry() { updateMembersShowArea(); } -void HistoryTopBarWidget::finishAnimating() { +void TopBarWidget::finishAnimating() { _selectedShown.finish(); updateControlsVisibility(); } -void HistoryTopBarWidget::setAnimationMode(bool enabled) { - if (_animationMode != enabled) { - _animationMode = enabled; - setAttribute(Qt::WA_OpaquePaintEvent, !_animationMode); +void TopBarWidget::setAnimatingMode(bool enabled) { + if (_animatingMode != enabled) { + _animatingMode = enabled; + setAttribute(Qt::WA_OpaquePaintEvent, !_animatingMode); finishAnimating(); updateControlsVisibility(); } } -void HistoryTopBarWidget::updateControlsVisibility() { - if (_animationMode) { +void TopBarWidget::updateControlsVisibility() { + if (_animatingMode) { hideChildren(); return; } @@ -465,7 +467,7 @@ void HistoryTopBarWidget::updateControlsVisibility() { updateControlsGeometry(); } -void HistoryTopBarWidget::updateMembersShowArea() { +void TopBarWidget::updateMembersShowArea() { if (!App::main()) { return; } @@ -496,7 +498,7 @@ void HistoryTopBarWidget::updateMembersShowArea() { _membersShowArea->setGeometry(getMembersShowAreaGeometry()); } -void HistoryTopBarWidget::showSelected(SelectedState state) { +void TopBarWidget::showSelected(SelectedState state) { auto canDelete = (state.count > 0 && state.count == state.canDeleteCount); auto canForward = (state.count > 0 && state.count == state.canForwardCount); if (_selectedCount == state.count && _canDelete == canDelete && _canForward == canForward) { @@ -539,12 +541,12 @@ void HistoryTopBarWidget::showSelected(SelectedState state) { } } -void HistoryTopBarWidget::selectedShowCallback() { +void TopBarWidget::selectedShowCallback() { updateControlsGeometry(); update(); } -void HistoryTopBarWidget::updateAdaptiveLayout() { +void TopBarWidget::updateAdaptiveLayout() { updateControlsVisibility(); if (Adaptive::OneColumn()) { createUnreadBadge(); @@ -555,7 +557,7 @@ void HistoryTopBarWidget::updateAdaptiveLayout() { updateInfoToggleActive(); } -void HistoryTopBarWidget::createUnreadBadge() { +void TopBarWidget::createUnreadBadge() { if (_unreadBadge) { return; } @@ -569,7 +571,7 @@ void HistoryTopBarWidget::createUnreadBadge() { updateUnreadBadge(); } -void HistoryTopBarWidget::updateUnreadBadge() { +void TopBarWidget::updateUnreadBadge() { if (!_unreadBadge) return; auto mutedCount = App::histories().unreadMutedCount(); @@ -596,7 +598,7 @@ void HistoryTopBarWidget::updateUnreadBadge() { }(), active); } -void HistoryTopBarWidget::updateInfoToggleActive() { +void TopBarWidget::updateInfoToggleActive() { auto infoThirdActive = Adaptive::ThreeColumn() && (Auth().settings().thirdSectionInfoEnabled() || Auth().settings().tabbedReplacedWithInfo()); @@ -610,7 +612,7 @@ void HistoryTopBarWidget::updateInfoToggleActive() { _infoToggle->setRippleColorOverride(rippleOverride); } -void HistoryTopBarWidget::updateOnlineDisplay() { +void TopBarWidget::updateOnlineDisplay() { if (!_historyPeer) return; QString text; @@ -689,7 +691,7 @@ void HistoryTopBarWidget::updateOnlineDisplay() { updateOnlineDisplayTimer(); } -void HistoryTopBarWidget::updateOnlineDisplayTimer() { +void TopBarWidget::updateOnlineDisplayTimer() { if (!_historyPeer) return; const auto now = unixtime(); @@ -709,6 +711,8 @@ void HistoryTopBarWidget::updateOnlineDisplayTimer() { updateOnlineDisplayIn(minTimeout); } -void HistoryTopBarWidget::updateOnlineDisplayIn(TimeMs timeout) { +void TopBarWidget::updateOnlineDisplayIn(TimeMs timeout) { _onlineUpdater.callOnce(timeout); } + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/history_top_bar_widget.h b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h similarity index 91% rename from Telegram/SourceFiles/history/history_top_bar_widget.h rename to Telegram/SourceFiles/history/view/history_view_top_bar_widget.h index de4834f93..64e05df8a 100644 --- a/Telegram/SourceFiles/history/history_top_bar_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h @@ -22,9 +22,11 @@ namespace Window { class Controller; } // namespace Window -class HistoryTopBarWidget : public Ui::RpWidget, private base::Subscriber { +namespace HistoryView { + +class TopBarWidget : public Ui::RpWidget, private base::Subscriber { public: - HistoryTopBarWidget( + TopBarWidget( QWidget *parent, not_null controller); @@ -41,15 +43,10 @@ public: rpl::producer membersShowAreaActive() const { return _membersShowAreaActive.events(); } - void setAnimationMode(bool enabled); + void setAnimatingMode(bool enabled); void setHistoryPeer(PeerData *historyPeer); - static void paintUnreadCounter( - Painter &p, - int outerWidth, - PeerData *substractPeer = nullptr); - protected: void paintEvent(QPaintEvent *e) override; void mousePressEvent(QMouseEvent *e) override; @@ -116,9 +113,11 @@ private: int _titlePeerTextWidth = 0; int _leftTaken = 0; int _rightTaken = 0; - bool _animationMode = false; + bool _animatingMode = false; int _unreadCounterSubscription = 0; base::Timer _onlineUpdater; }; + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp index 94cc362b2..b45ec413f 100644 --- a/Telegram/SourceFiles/info/media/info_media_list_widget.cpp +++ b/Telegram/SourceFiles/info/media/info_media_list_widget.cpp @@ -791,8 +791,7 @@ void ListWidget::refreshViewer() { idForViewer, _idsLimit, _idsLimit - ) | rpl::start_with_next([=]( - SparseIdsMergedSlice &&slice) { + ) | rpl::start_with_next([=](SparseIdsMergedSlice &&slice) { if (!slice.fullCount()) { // Don't display anything while full count is unknown. return; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 5d885ae79..8c9b2605c 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -38,7 +38,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_widget.h" #include "history/history_message.h" #include "history/history_media.h" -#include "history/history_service_layout.h" +#include "history/view/history_view_service_message.h" #include "lang/lang_keys.h" #include "lang/lang_cloud_manager.h" #include "boxes/add_contact_box.h" @@ -3530,7 +3530,7 @@ void MainWidget::updateWindowAdaptiveLayout() { // auto thirdColumnWidth = _history->tabbedSelectorSectionWidth(); // auto twoColumnsWidth = (layout.bodyWidth - thirdColumnWidth); // auto sameRatioChatWidth = twoColumnsWidth - qRound(dialogsWidthRatio * twoColumnsWidth); - // auto desiredChatWidth = qMax(sameRatioChatWidth, HistoryLayout::WideChatWidth()); + // auto desiredChatWidth = qMax(sameRatioChatWidth, HistoryView::WideChatWidth()); // chatWidth -= thirdColumnWidth; // auto extendChatBy = desiredChatWidth - chatWidth; // accumulate_min(extendChatBy, layout.dialogsWidth - st::columnMinimalWidthLeft); diff --git a/Telegram/SourceFiles/media/view/media_view_group_thumbs.cpp b/Telegram/SourceFiles/media/view/media_view_group_thumbs.cpp index 1ad437759..c132972b9 100644 --- a/Telegram/SourceFiles/media/view/media_view_group_thumbs.cpp +++ b/Telegram/SourceFiles/media/view/media_view_group_thumbs.cpp @@ -503,9 +503,7 @@ auto GroupThumbs::validateCacheEntry(Key key) -> not_null { } void GroupThumbs::markCacheStale() { - while (!_dying.empty()) { - _dying.pop_back(); - } + _dying.clear(); for (const auto &cacheItem : _cache) { const auto &thumb = cacheItem.second; thumb->setState(Thumb::State::Unknown); diff --git a/Telegram/SourceFiles/profile/profile_back_button.cpp b/Telegram/SourceFiles/profile/profile_back_button.cpp index f4a127bc9..5c8eef547 100644 --- a/Telegram/SourceFiles/profile/profile_back_button.cpp +++ b/Telegram/SourceFiles/profile/profile_back_button.cpp @@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "profile/profile_back_button.h" -//#include "history/history_top_bar_widget.h" +//#include "history/view/history_view_top_bar_widget.h" #include "styles/style_widgets.h" #include "styles/style_window.h" #include "styles/style_profile.h" @@ -41,8 +41,6 @@ void BackButton::paintEvent(QPaintEvent *e) { p.setFont(st::topBarButton.font); p.setPen(st::topBarButton.textFg); p.drawTextLeft(st::topBarArrowPadding.left(), st::topBarButton.padding.top() + st::topBarButton.textTop, width(), _text); - -// HistoryTopBarWidget::paintUnreadCounter(p, width()); } void BackButton::onStateChanged(State was, StateChangeSource source) { diff --git a/Telegram/SourceFiles/storage/storage_facade.cpp b/Telegram/SourceFiles/storage/storage_facade.cpp index bc033fb8b..9e426b173 100644 --- a/Telegram/SourceFiles/storage/storage_facade.cpp +++ b/Telegram/SourceFiles/storage/storage_facade.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/storage_shared_media.h" #include "storage/storage_user_photos.h" +#include "storage/storage_feed_messages.h" namespace Storage { @@ -31,9 +32,20 @@ public: rpl::producer query(UserPhotosQuery &&query) const; rpl::producer userPhotosSliceUpdated() const; + void add(FeedMessagesAddNew &&query); + void add(FeedMessagesAddSlice &&query); + void remove(FeedMessagesRemoveOne &&query); + void remove(FeedMessagesRemoveAll &&query); + rpl::producer query( + FeedMessagesQuery &&query) const; + rpl::producer feedMessagesSliceUpdated() const; + rpl::producer feedMessagesOneRemoved() const; + rpl::producer feedMessagesAllRemoved() const; + private: SharedMedia _sharedMedia; UserPhotos _userPhotos; + FeedMessages _feedMessages; }; @@ -97,6 +109,39 @@ rpl::producer Facade::Impl::userPhotosSliceUpdated() cons return _userPhotos.sliceUpdated(); } +void Facade::Impl::add(FeedMessagesAddNew &&query) { + return _feedMessages.add(std::move(query)); +} + +void Facade::Impl::add(FeedMessagesAddSlice &&query) { + return _feedMessages.add(std::move(query)); +} + +void Facade::Impl::remove(FeedMessagesRemoveOne &&query) { + return _feedMessages.remove(std::move(query)); +} + +void Facade::Impl::remove(FeedMessagesRemoveAll &&query) { + return _feedMessages.remove(std::move(query)); +} + +rpl::producer Facade::Impl::query( + FeedMessagesQuery &&query) const { + return _feedMessages.query(std::move(query)); +} + +rpl::producer Facade::Impl::feedMessagesSliceUpdated() const { + return _feedMessages.sliceUpdated(); +} + +rpl::producer Facade::Impl::feedMessagesOneRemoved() const { + return _feedMessages.oneRemoved(); +} + +rpl::producer Facade::Impl::feedMessagesAllRemoved() const { + return _feedMessages.allRemoved(); +} + Facade::Facade() : _impl(std::make_unique()) { } @@ -160,6 +205,39 @@ rpl::producer Facade::userPhotosSliceUpdated() const { return _impl->userPhotosSliceUpdated(); } +void Facade::add(FeedMessagesAddNew &&query) { + return _impl->add(std::move(query)); +} + +void Facade::add(FeedMessagesAddSlice &&query) { + return _impl->add(std::move(query)); +} + +void Facade::remove(FeedMessagesRemoveOne &&query) { + return _impl->remove(std::move(query)); +} + +void Facade::remove(FeedMessagesRemoveAll &&query) { + return _impl->remove(std::move(query)); +} + +rpl::producer Facade::query( + FeedMessagesQuery &&query) const { + return _impl->query(std::move(query)); +} + +rpl::producer Facade::feedMessagesSliceUpdated() const { + return _impl->feedMessagesSliceUpdated(); +} + +rpl::producer Facade::feedMessagesOneRemoved() const { + return _impl->feedMessagesOneRemoved(); +} + +rpl::producer Facade::feedMessagesAllRemoved() const { + return _impl->feedMessagesAllRemoved(); +} + Facade::~Facade() = default; } // namespace Storage diff --git a/Telegram/SourceFiles/storage/storage_facade.h b/Telegram/SourceFiles/storage/storage_facade.h index ae4b81983..8a804acad 100644 --- a/Telegram/SourceFiles/storage/storage_facade.h +++ b/Telegram/SourceFiles/storage/storage_facade.h @@ -10,6 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include "base/enum_mask.h" +namespace Data { +struct MessagesResult; +} // namespace Data + namespace Storage { struct SparseIdsListResult; @@ -31,6 +35,14 @@ struct UserPhotosQuery; struct UserPhotosResult; struct UserPhotosSliceUpdate; +struct FeedMessagesAddNew; +struct FeedMessagesAddSlice; +struct FeedMessagesRemoveOne; +struct FeedMessagesRemoveAll; +struct FeedMessagesQuery; +using FeedMessagesResult = Data::MessagesResult; +struct FeedMessagesSliceUpdate; + class Facade { public: Facade(); @@ -54,6 +66,17 @@ public: rpl::producer query(UserPhotosQuery &&query) const; rpl::producer userPhotosSliceUpdated() const; + void add(FeedMessagesAddNew &&query); + void add(FeedMessagesAddSlice &&query); + void remove(FeedMessagesRemoveOne &&query); + void remove(FeedMessagesRemoveAll &&query); + + rpl::producer query( + FeedMessagesQuery &&query) const; + rpl::producer feedMessagesSliceUpdated() const; + rpl::producer feedMessagesOneRemoved() const; + rpl::producer feedMessagesAllRemoved() const; + ~Facade(); private: diff --git a/Telegram/SourceFiles/storage/storage_feed_messages.cpp b/Telegram/SourceFiles/storage/storage_feed_messages.cpp new file mode 100644 index 000000000..dd958b956 --- /dev/null +++ b/Telegram/SourceFiles/storage/storage_feed_messages.cpp @@ -0,0 +1,85 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "storage/storage_feed_messages.h" + +namespace Storage { + +auto FeedMessages::enforceLists(FeedId feedId) +-> std::map::iterator { + auto result = _lists.find(feedId); + if (result != _lists.end()) { + return result; + } + result = _lists.emplace(feedId, List {}).first; + result->second.sliceUpdated( + ) | rpl::map([=](Data::MessagesSliceUpdate &&update) { + return FeedMessagesSliceUpdate( + feedId, + std::move(update)); + }) | rpl::start_to_stream(_sliceUpdated, _lifetime); + return result; +} + +void FeedMessages::add(FeedMessagesAddNew &&query) { + auto feedId = query.feedId; + auto feedIt = enforceLists(feedId); + feedIt->second.addNew(query.messageId); +} + +void FeedMessages::add(FeedMessagesAddSlice &&query) { + auto feedIt = enforceLists(query.feedId); + feedIt->second.addSlice( + std::move(query.messageIds), + query.noSkipRange, + base::none); +} + +void FeedMessages::remove(FeedMessagesRemoveOne &&query) { + auto feedIt = _lists.find(query.feedId); + if (feedIt != _lists.end()) { + feedIt->second.removeOne(query.messageId); + _oneRemoved.fire(std::move(query)); + } +} + +void FeedMessages::remove(FeedMessagesRemoveAll &&query) { + auto feedIt = _lists.find(query.feedId); + if (feedIt != _lists.end()) { + feedIt->second.removeAll(query.channelId); + _allRemoved.fire(std::move(query)); + } +} + +rpl::producer FeedMessages::query( + FeedMessagesQuery &&query) const { + auto feedIt = _lists.find(query.key.feedId); + if (feedIt != _lists.end()) { + return feedIt->second.query(Data::MessagesQuery( + query.key.position, + query.limitBefore, + query.limitAfter)); + } + return [](auto consumer) { + consumer.put_done(); + return rpl::lifetime(); + }; +} + +rpl::producer FeedMessages::sliceUpdated() const { + return _sliceUpdated.events(); +} + +rpl::producer FeedMessages::oneRemoved() const { + return _oneRemoved.events(); +} + +rpl::producer FeedMessages::allRemoved() const { + return _allRemoved.events(); +} + +} // namespace Storage diff --git a/Telegram/SourceFiles/storage/storage_feed_messages.h b/Telegram/SourceFiles/storage/storage_feed_messages.h new file mode 100644 index 000000000..6a87a2953 --- /dev/null +++ b/Telegram/SourceFiles/storage/storage_feed_messages.h @@ -0,0 +1,147 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include +#include "storage/storage_facade.h" +#include "data/data_feed_messages.h" + +namespace Storage { + +struct FeedMessagesAddNew { + FeedMessagesAddNew(FeedId feedId, Data::MessagePosition messageId) + : feedId(feedId) + , messageId(messageId) { + } + + FeedId feedId = 0; + Data::MessagePosition messageId; + +}; + +struct FeedMessagesAddSlice { + FeedMessagesAddSlice( + FeedId feedId, + std::vector &&messageIds, + Data::MessagesRange noSkipRange) + : feedId(feedId) + , messageIds(std::move(messageIds)) + , noSkipRange(noSkipRange) { + } + + FeedId feedId = 0; + std::vector messageIds; + Data::MessagesRange noSkipRange; + +}; + +struct FeedMessagesRemoveOne { + FeedMessagesRemoveOne( + FeedId feedId, + Data::MessagePosition messageId) + : feedId(feedId) + , messageId(messageId) { + } + + FeedId feedId = 0; + Data::MessagePosition messageId; + +}; + +struct FeedMessagesRemoveAll { + FeedMessagesRemoveAll(FeedId feedId, ChannelId channelId) + : feedId(feedId) + , channelId(channelId) { + } + + FeedId feedId = 0; + ChannelId channelId = 0; + +}; + +struct FeedMessagesKey { + FeedMessagesKey( + FeedId feedId, + Data::MessagePosition position) + : feedId(feedId) + , position(position) { + } + + bool operator==(const FeedMessagesKey &other) const { + return (feedId == other.feedId) + && (position == other.position); + } + bool operator!=(const FeedMessagesKey &other) const { + return !(*this == other); + } + + FeedId feedId = 0; + Data::MessagePosition position; + +}; + +struct FeedMessagesQuery { + FeedMessagesQuery( + FeedMessagesKey key, + int limitBefore, + int limitAfter) + : key(key) + , limitBefore(limitBefore) + , limitAfter(limitAfter) { + } + + FeedMessagesKey key; + int limitBefore = 0; + int limitAfter = 0; + +}; + +using FeedMessagesResult = Data::MessagesResult; + +struct FeedMessagesSliceUpdate { + FeedMessagesSliceUpdate( + FeedId feedId, + Data::MessagesSliceUpdate &&data) + : feedId(feedId) + , data(std::move(data)) { + } + + FeedId feedId = 0; + Data::MessagesSliceUpdate data; + +}; + +class FeedMessages { +public: + void add(FeedMessagesAddNew &&query); + void add(FeedMessagesAddSlice &&query); + void remove(FeedMessagesRemoveOne &&query); + void remove(FeedMessagesRemoveAll &&query); + + rpl::producer query( + FeedMessagesQuery &&query) const; + rpl::producer sliceUpdated() const; + rpl::producer oneRemoved() const; + rpl::producer allRemoved() const; + +private: + using List = Data::MessagesList; + + std::map::iterator enforceLists(FeedId feedId); + + std::map _lists; + + rpl::event_stream _sliceUpdated; + rpl::event_stream _oneRemoved; + rpl::event_stream _allRemoved; + + rpl::lifetime _lifetime; + +}; + +} // namespace Storage diff --git a/Telegram/SourceFiles/storage/storage_shared_media.cpp b/Telegram/SourceFiles/storage/storage_shared_media.cpp index e06cc204a..f5a88773f 100644 --- a/Telegram/SourceFiles/storage/storage_shared_media.cpp +++ b/Telegram/SourceFiles/storage/storage_shared_media.cpp @@ -56,6 +56,7 @@ void SharedMedia::add(SharedMediaAddExisting &&query) { void SharedMedia::add(SharedMediaAddSlice &&query) { Expects(IsValidSharedMediaType(query.type)); + auto peerIt = enforceLists(query.peerId); auto index = static_cast(query.type); peerIt->second[index].addSlice( diff --git a/Telegram/SourceFiles/window/section_widget.cpp b/Telegram/SourceFiles/window/section_widget.cpp index 0e9a03add..3cfb1b16d 100644 --- a/Telegram/SourceFiles/window/section_widget.cpp +++ b/Telegram/SourceFiles/window/section_widget.cpp @@ -9,8 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include "application.h" +#include "mainwidget.h" #include "window/section_memento.h" #include "window/window_slide_animation.h" +#include "window/themes/window_theme.h" namespace Window { @@ -71,6 +73,46 @@ void SectionWidget::showFast() { showFinished(); } +void SectionWidget::PaintBackground(QWidget *widget, QPaintEvent *event) { + Painter p(widget); + + auto clip = event->rect(); + auto fill = QRect(0, 0, widget->width(), App::main()->height()); + auto fromy = App::main()->backgroundFromY(); + auto x = 0, y = 0; + auto cached = App::main()->cachedBackground(fill, x, y); + if (cached.isNull()) { + if (Window::Theme::Background()->tile()) { + auto &pix = Window::Theme::Background()->pixmapForTiled(); + auto left = clip.left(); + auto top = clip.top(); + auto right = clip.left() + clip.width(); + auto bottom = clip.top() + clip.height(); + auto w = pix.width() / cRetinaFactor(); + auto h = pix.height() / cRetinaFactor(); + auto sx = qFloor(left / w); + auto sy = qFloor((top - fromy) / h); + auto cx = qCeil(right / w); + auto cy = qCeil((bottom - fromy) / h); + for (auto i = sx; i < cx; ++i) { + for (auto j = sy; j < cy; ++j) { + p.drawPixmap(QPointF(i * w, fromy + j * h), pix); + } + } + } else { + PainterHighQualityEnabler hq(p); + + auto &pix = Window::Theme::Background()->pixmap(); + QRect to, from; + Window::Theme::ComputeBackgroundRects(fill, pix.size(), to, from); + to.moveTop(to.top() + fromy); + p.drawPixmap(to, pix, from); + } + } else { + p.drawPixmap(x, fromy + y, cached); + } +} + void SectionWidget::paintEvent(QPaintEvent *e) { if (_showAnimation) { Painter p(this); diff --git a/Telegram/SourceFiles/window/section_widget.h b/Telegram/SourceFiles/window/section_widget.h index f82ef95b5..e421742e6 100644 --- a/Telegram/SourceFiles/window/section_widget.h +++ b/Telegram/SourceFiles/window/section_widget.h @@ -126,6 +126,8 @@ public: return false; } + static void PaintBackground(QWidget *widget, QPaintEvent *event); + protected: void paintEvent(QPaintEvent *e) override; diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index c1eea427d..010010c04 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -169,8 +169,12 @@ <(src_loc)/data/data_drafts.h <(src_loc)/data/data_feed.cpp <(src_loc)/data/data_feed.h +<(src_loc)/data/data_feed_messages.cpp +<(src_loc)/data/data_feed_messages.h <(src_loc)/data/data_flags.h <(src_loc)/data/data_game.h +<(src_loc)/data/data_messages.cpp +<(src_loc)/data/data_messages.h <(src_loc)/data/data_notify_settings.cpp <(src_loc)/data/data_notify_settings.h <(src_loc)/data/data_peer.cpp @@ -211,16 +215,24 @@ <(src_loc)/dialogs/dialogs_search_from_controllers.h <(src_loc)/dialogs/dialogs_widget.cpp <(src_loc)/dialogs/dialogs_widget.h +<(src_loc)/history/admin_log/history_admin_log_filter.cpp +<(src_loc)/history/admin_log/history_admin_log_filter.h +<(src_loc)/history/admin_log/history_admin_log_inner.cpp +<(src_loc)/history/admin_log/history_admin_log_inner.h +<(src_loc)/history/admin_log/history_admin_log_item.cpp +<(src_loc)/history/admin_log/history_admin_log_item.h +<(src_loc)/history/admin_log/history_admin_log_section.cpp +<(src_loc)/history/admin_log/history_admin_log_section.h +<(src_loc)/history/feed/history_feed_section.cpp +<(src_loc)/history/feed/history_feed_section.h +<(src_loc)/history/view/history_view_list_widget.cpp +<(src_loc)/history/view/history_view_list_widget.h +<(src_loc)/history/view/history_view_service_message.cpp +<(src_loc)/history/view/history_view_service_message.h +<(src_loc)/history/view/history_view_top_bar_widget.cpp +<(src_loc)/history/view/history_view_top_bar_widget.h <(src_loc)/history/history.cpp <(src_loc)/history/history.h -<(src_loc)/history/history_admin_log_filter.cpp -<(src_loc)/history/history_admin_log_filter.h -<(src_loc)/history/history_admin_log_inner.cpp -<(src_loc)/history/history_admin_log_inner.h -<(src_loc)/history/history_admin_log_item.cpp -<(src_loc)/history/history_admin_log_item.h -<(src_loc)/history/history_admin_log_section.cpp -<(src_loc)/history/history_admin_log_section.h <(src_loc)/history/history_drag_area.cpp <(src_loc)/history/history_drag_area.h <(src_loc)/history/history_item.cpp @@ -241,10 +253,6 @@ <(src_loc)/history/history_message.h <(src_loc)/history/history_service.cpp <(src_loc)/history/history_service.h -<(src_loc)/history/history_service_layout.cpp -<(src_loc)/history/history_service_layout.h -<(src_loc)/history/history_top_bar_widget.cpp -<(src_loc)/history/history_top_bar_widget.h <(src_loc)/history/history_widget.cpp <(src_loc)/history/history_widget.h <(src_loc)/info/info_content_widget.cpp @@ -531,6 +539,8 @@ <(src_loc)/storage/serialize_document.h <(src_loc)/storage/storage_facade.cpp <(src_loc)/storage/storage_facade.h +<(src_loc)/storage/storage_feed_messages.cpp +<(src_loc)/storage/storage_feed_messages.h <(src_loc)/storage/storage_media_prepare.cpp <(src_loc)/storage/storage_media_prepare.h <(src_loc)/storage/storage_shared_media.cpp