/* 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 "dialogs/dialogs_key.h" #include "data/data_messages.h" #include "ui/dragging_scroll_manager.h" #include "ui/effects/animations.h" #include "ui/rp_widget.h" #include "ui/userpic_view.h" #include "base/flags.h" #include "base/object_ptr.h" namespace style { struct DialogRow; } // namespace style namespace MTP { class Error; } // namespace MTP namespace Main { class Session; } // namespace Main namespace Ui { class IconButton; class PopupMenu; class FlatLabel; struct ScrollToRequest; } // namespace Ui namespace Window { class SessionController; } // namespace Window namespace Data { class Thread; class Folder; class Forum; } // namespace Data namespace Dialogs::Ui { using namespace ::Ui; class VideoUserpic; struct PaintContext; struct TopicJumpCache; } // namespace Dialogs::Ui namespace Dialogs::Stories { class List; } // namespace Dialogs::Stories namespace Dialogs { class Row; class FakeRow; class IndexedList; struct ChosenRow { Key key; Data::MessagePosition message; bool filteredRow = false; bool newWindow = false; }; enum class SearchRequestType { FromStart, FromOffset, PeerFromStart, PeerFromOffset, MigratedFromStart, MigratedFromOffset, }; enum class WidgetState { Default, Filtered, }; class InnerWidget final : public Ui::RpWidget { public: struct ChildListShown { PeerId peerId = 0; float64 shown = 0.; }; InnerWidget( QWidget *parent, not_null controller, rpl::producer childListShown); void searchReceived( std::vector> result, HistoryItem *inject, SearchRequestType type, int fullCount); void peerSearchReceived( const QString &query, const QVector &my, const QVector &result); [[nodiscard]] rpl::producer<> scrollToVeryTopRequests() const; [[nodiscard]] int defaultScrollTop() const; void setViewportHeight(int viewportHeight); [[nodiscard]] FilterId filterId() const; void clearSelection(); void changeOpenedFolder(Data::Folder *folder); void changeOpenedForum(Data::Forum *forum); void selectSkip(int32 direction); void selectSkipPage(int32 pixels, int32 direction); void dragLeft(); void setNarrowRatio(float64 narrowRatio); void clearFilter(); void refresh(bool toTop = false); void refreshForDefaultScroll(); void refreshEmptyLabel(); void resizeEmptyLabel(); bool chooseRow( Qt::KeyboardModifiers modifiers = {}, MsgId pressedTopicRootId = {}); void scrollToEntry(const RowDescriptor &entry); [[nodiscard]] Data::Folder *shownFolder() const; [[nodiscard]] Data::Forum *shownForum() const; [[nodiscard]] WidgetState state() const; [[nodiscard]] not_null st() const { return _st; } [[nodiscard]] bool waitingForSearch() const { return _waitingForSearch; } [[nodiscard]] bool hasFilteredResults() const; void searchInChat(Key key, PeerData *from); void applyFilterUpdate(QString newFilter, bool force = false); void onHashtagFilterUpdate(QStringView newFilter); void appendToFiltered(Key key); Data::Thread *updateFromParentDrag(QPoint globalPosition); void setLoadMoreCallback(Fn callback); void setLoadMoreFilteredCallback(Fn callback); [[nodiscard]] rpl::producer<> listBottomReached() const; [[nodiscard]] rpl::producer<> cancelSearchFromUserRequests() const; [[nodiscard]] rpl::producer chosenRow() const; [[nodiscard]] rpl::producer<> updated() const; [[nodiscard]] rpl::producer scrollByDeltaRequests() const; [[nodiscard]] rpl::producer mustScrollTo() const; [[nodiscard]] rpl::producer dialogMoved() const; [[nodiscard]] rpl::producer<> searchMessages() const; [[nodiscard]] rpl::producer<> cancelSearchInChatRequests() const; [[nodiscard]] rpl::producer completeHashtagRequests() const; [[nodiscard]] rpl::producer<> refreshHashtagsRequests() const; [[nodiscard]] RowDescriptor resolveChatNext(RowDescriptor from = {}) const; [[nodiscard]] RowDescriptor resolveChatPrevious(RowDescriptor from = {}) const; ~InnerWidget(); void parentGeometryChanged(); protected: void visibleTopBottomUpdated( int visibleTop, int visibleBottom) override; void paintEvent(QPaintEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void resizeEvent(QResizeEvent *e) override; void enterEventHook(QEnterEvent *e) override; void leaveEventHook(QEvent *e) override; void contextMenuEvent(QContextMenuEvent *e) override; private: struct CollapsedRow; struct HashtagResult; struct PeerSearchResult; enum class JumpSkip { PreviousOrBegin, NextOrEnd, PreviousOrOriginal, NextOrOriginal, }; enum class EmptyState : uchar { None, Loading, NoContacts, EmptyFolder, EmptyForum, }; struct PinnedRow { anim::value yadd; crl::time animStartTime = 0; }; struct FilterResult { FilterResult(not_null row) : row(row) { } not_null row; int top = 0; [[nodiscard]] Key key() const; [[nodiscard]] int bottom() const; }; Main::Session &session() const; void dialogRowReplaced(Row *oldRow, Row *newRow); void setState(WidgetState state); void editOpenedFilter(); void repaintCollapsedFolderRow(not_null folder); void refreshWithCollapsedRows(bool toTop = false); bool needCollapsedRowsRefresh() const; bool chooseCollapsedRow(); void switchToFilter(FilterId filterId); bool chooseHashtag(); ChosenRow computeChosenRow() const; bool isSearchResultActive( not_null result, const RowDescriptor &entry) const; void repaintDialogRow(FilterId filterId, not_null row); void repaintDialogRow(RowDescriptor row); void refreshDialogRow(RowDescriptor row); bool updateEntryHeight(not_null entry); void clearMouseSelection(bool clearSelection = false); void mousePressReleased( QPoint globalPosition, Qt::MouseButton button, Qt::KeyboardModifiers modifiers); void clearIrrelevantState(); void selectByMouse(QPoint globalPosition); void preloadRowsData(); void scrollToItem(int top, int height); void scrollToDefaultSelected(); void setCollapsedPressed(int pressed); void setPressed(Row *pressed, bool pressedTopicJump); void clearPressed(); void setHashtagPressed(int pressed); void setFilteredPressed(int pressed, bool pressedTopicJump); void setPeerSearchPressed(int pressed); void setSearchedPressed(int pressed); bool isPressed() const { return (_collapsedPressed >= 0) || _pressed || (_hashtagPressed >= 0) || (_filteredPressed >= 0) || (_peerSearchPressed >= 0) || (_searchedPressed >= 0); } bool isSelected() const { return (_collapsedSelected >= 0) || _selected || (_hashtagSelected >= 0) || (_filteredSelected >= 0) || (_peerSearchSelected >= 0) || (_searchedSelected >= 0); } bool uniqueSearchResults() const; bool hasHistoryInResults(not_null history) const; int defaultRowTop(not_null row) const; void setupOnlineStatusCheck(); void jumpToTop(); void updateRowCornerStatusShown(not_null history); void repaintDialogRowCornerStatus(not_null history); void setupShortcuts(); RowDescriptor computeJump( const RowDescriptor &to, JumpSkip skip) const; bool jumpToDialogRow(RowDescriptor to); RowDescriptor chatListEntryBefore(const RowDescriptor &which) const; RowDescriptor chatListEntryAfter(const RowDescriptor &which) const; RowDescriptor chatListEntryFirst() const; RowDescriptor chatListEntryLast() const; void itemRemoved(not_null item); enum class UpdateRowSection { Default = (1 << 0), Filtered = (1 << 1), PeerSearch = (1 << 2), MessageSearch = (1 << 3), All = Default | Filtered | PeerSearch | MessageSearch, }; using UpdateRowSections = base::flags; friend inline constexpr auto is_flag_type(UpdateRowSection) { return true; }; void updateSearchResult(not_null peer); void updateDialogRow( RowDescriptor row, QRect updateRect = QRect(), UpdateRowSections sections = UpdateRowSection::All); void fillSupportSearchMenu(not_null menu); void fillArchiveSearchMenu(not_null menu); void refreshShownList(); [[nodiscard]] bool storiesShown() const; [[nodiscard]] int skipTopHeight() const; [[nodiscard]] int collapsedRowsOffset() const; [[nodiscard]] int dialogsOffset() const; [[nodiscard]] int shownHeight(int till = -1) const; [[nodiscard]] int fixedOnTopCount() const; [[nodiscard]] int pinnedOffset() const; [[nodiscard]] int filteredOffset() const; [[nodiscard]] int filteredIndex(int y) const; [[nodiscard]] int filteredHeight(int till = -1) const; [[nodiscard]] int peerSearchOffset() const; [[nodiscard]] int searchedOffset() const; [[nodiscard]] int searchInChatSkip() const; void paintCollapsedRows( Painter &p, QRect clip) const; void paintCollapsedRow( Painter &p, not_null row, bool selected) const; void paintPeerSearchResult( Painter &p, not_null result, const Ui::PaintContext &context); void paintSearchInChat( Painter &p, const Ui::PaintContext &context) const; void paintSearchInPeer( Painter &p, not_null peer, Ui::PeerUserpicView &userpic, int top, const Ui::Text::String &text) const; void paintSearchInSaved( Painter &p, int top, const Ui::Text::String &text) const; void paintSearchInReplies( Painter &p, int top, const Ui::Text::String &text) const; void paintSearchInTopic( Painter &p, const Ui::PaintContext &context, not_null topic, Ui::PeerUserpicView &userpic, int top, const Ui::Text::String &text) const; template void paintSearchInFilter( Painter &p, PaintUserpic paintUserpic, int top, const style::icon *icon, const Ui::Text::String &text) const; void refreshSearchInChatLabel(); void repaintSearchResult(int index); Ui::VideoUserpic *validateVideoUserpic(not_null row); Ui::VideoUserpic *validateVideoUserpic(not_null history); Row *shownRowByKey(Key key); void clearSearchResults(bool clearPeerSearchResults = true); void updateSelectedRow(Key key = Key()); void trackSearchResultsHistory(not_null history); void trackSearchResultsForum(Data::Forum *forum); [[nodiscard]] QBrush currentBg() const; [[nodiscard]] const std::vector &pinnedChatsOrder() const; void checkReorderPinnedStart(QPoint localPosition); int updateReorderIndexGetCount(); bool updateReorderPinned(QPoint localPosition); void finishReorderPinned(); void stopReorderPinned(); int countPinnedIndex(Row *ofRow); void savePinnedOrder(); bool pinnedShiftAnimationCallback(crl::time now); void handleChatListEntryRefreshes(); void moveCancelSearchButtons(); void saveChatsFilterScrollState(FilterId filterId); void restoreChatsFilterScrollState(FilterId filterId); const not_null _controller; const std::unique_ptr _stories; int _viewportHeight = 0; not_null _shownList; FilterId _filterId = 0; bool _mouseSelection = false; std::optional _lastMousePosition; Qt::MouseButton _pressButton = Qt::LeftButton; Data::Folder *_openedFolder = nullptr; Data::Forum *_openedForum = nullptr; rpl::lifetime _openedForumLifetime; std::vector> _collapsedRows; not_null _st; mutable std::unique_ptr _topicJumpCache; int _collapsedSelected = -1; int _collapsedPressed = -1; bool _skipTopDialog = false; Row *_selected = nullptr; Row *_pressed = nullptr; MsgId _pressedTopicJumpRootId; bool _selectedTopicJump = false; bool _pressedTopicJump = false; Row *_dragging = nullptr; int _draggingIndex = -1; int _aboveIndex = -1; QPoint _dragStart; std::vector _pinnedRows; Ui::Animations::Basic _pinnedShiftAnimation; base::flat_set _pinnedOnDragStart; // Remember the last currently dragged row top shift for updating area. int _aboveTopShift = -1; int _narrowWidth = 0; int _visibleTop = 0; int _visibleBottom = 0; QString _filter, _hashtagFilter; std::vector> _hashtagResults; int _hashtagSelected = -1; int _hashtagPressed = -1; bool _hashtagDeleteSelected = false; bool _hashtagDeletePressed = false; std::vector _filterResults; base::flat_map> _filterResultsGlobal; int _filteredSelected = -1; int _filteredPressed = -1; bool _waitingForSearch = false; EmptyState _emptyState = EmptyState::None; QString _peerSearchQuery; std::vector> _peerSearchResults; int _peerSearchSelected = -1; int _peerSearchPressed = -1; std::vector> _searchResults; base::flat_set> _searchResultsHistories; rpl::lifetime _searchResultsLifetime; int _searchedCount = 0; int _searchedMigratedCount = 0; int _searchedSelected = -1; int _searchedPressed = -1; WidgetState _state = WidgetState::Default; object_ptr _empty = { nullptr }; object_ptr _cancelSearchInChat; object_ptr _cancelSearchFromUser; Ui::DraggingScrollManager _draggingScroll; Key _searchInChat; History *_searchInMigrated = nullptr; PeerData *_searchFromPeer = nullptr; mutable Ui::PeerUserpicView _searchInChatUserpic; mutable Ui::PeerUserpicView _searchFromUserUserpic; Ui::Text::String _searchInChatText; Ui::Text::String _searchFromUserText; RowDescriptor _menuRow; base::flat_map< not_null, std::unique_ptr> _videoUserpics; base::flat_map _chatsFilterScrollStates; Fn _loadMoreCallback; Fn _loadMoreFilteredCallback; rpl::event_stream<> _listBottomReached; rpl::event_stream _chosenRow; rpl::event_stream<> _updated; rpl::event_stream _mustScrollTo; rpl::event_stream _dialogMoved; rpl::event_stream<> _searchMessages; rpl::event_stream _completeHashtagRequests; rpl::event_stream<> _refreshHashtagsRequests; rpl::variable _childListShown; float64 _narrowRatio = 0.; bool _geometryInited = false; base::unique_qptr _menu; }; } // namespace Dialogs