/* This file is part of Telegram Desktop, the official desktop version of Telegram messaging app, see https://telegram.org Telegram Desktop is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library. Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #pragma once #include #include "ui/rp_widget.h" #include "ui/empty_userpic.h" #include "boxes/abstract_box.h" #include "mtproto/sender.h" #include "base/timer.h" namespace style { struct PeerList; struct PeerListItem; } // namespace style namespace Ui { class RippleAnimation; class RoundImageCheckbox; class MultiSelect; template class SlideWrap; class FlatLabel; struct ScrollToRequest; class PopupMenu; } // namespace Ui namespace Notify { struct PeerUpdate; } // namespace Notify inline auto PaintUserpicCallback( not_null peer, bool respectSavedMessagesChat) ->base::lambda { if (respectSavedMessagesChat && peer->isSelf()) { return [](Painter &p, int x, int y, int outerWidth, int size) { Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size); }; } return [peer](Painter &p, int x, int y, int outerWidth, int size) { peer->paintUserpicLeft(p, x, y, outerWidth, size); }; } using PeerListRowId = uint64; class PeerListRow { public: PeerListRow(not_null peer); PeerListRow(not_null peer, PeerListRowId id); enum class State { Active, Disabled, DisabledChecked, }; void setDisabledState(State state) { _disabledState = state; } // Checked state is controlled by the box with multiselect, // not by the row itself, so there is no setChecked() method. // We can query the checked state from row, but before it is // added to the box it is always false. bool checked() const; not_null peer() const { return _peer; } PeerListRowId id() const { return _id; } void setCustomStatus(const QString &status); void clearCustomStatus(); virtual ~PeerListRow(); // Box interface. virtual int nameIconWidth() const; virtual void paintNameIcon( Painter &p, int x, int y, int outerWidth, bool selected); virtual QSize actionSize() const { return QSize(); } virtual QMargins actionMargins() const { return QMargins(); } virtual void addActionRipple(QPoint point, base::lambda updateCallback) { } virtual void stopLastActionRipple() { } virtual void paintAction( Painter &p, TimeMs ms, int x, int y, int outerWidth, bool selected, bool actionSelected) { } void refreshName(const style::PeerListItem &st); const Text &name() const { return _name; } enum class StatusType { Online, LastSeen, Custom, }; void refreshStatus(); TimeMs refreshStatusTime() const; void setAbsoluteIndex(int index) { _absoluteIndex = index; } int absoluteIndex() const { return _absoluteIndex; } bool disabled() const { return (_disabledState != State::Active); } bool isSearchResult() const { return _isSearchResult; } void setIsSearchResult(bool isSearchResult) { _isSearchResult = isSearchResult; } bool isSavedMessagesChat() const { return _isSavedMessagesChat; } void setIsSavedMessagesChat(bool isSavedMessagesChat) { _isSavedMessagesChat = isSavedMessagesChat; } enum class SetStyle { Animated, Fast, }; template void setChecked( bool checked, SetStyle style, UpdateCallback callback) { if (checked && !_checkbox) { createCheckbox(std::move(callback)); } setCheckedInternal(checked, style); } void invalidatePixmapsCache(); template void addRipple( const style::PeerListItem &st, QSize size, QPoint point, UpdateCallback updateCallback); void stopLastRipple(); void paintRipple(Painter &p, TimeMs ms, int x, int y, int outerWidth); void paintUserpic( Painter &p, const style::PeerListItem &st, TimeMs ms, int x, int y, int outerWidth); float64 checkedRatio(); void setNameFirstChars(const base::flat_set &nameFirstChars) { _nameFirstChars = nameFirstChars; } const base::flat_set &nameFirstChars() const { return _nameFirstChars; } virtual void lazyInitialize(const style::PeerListItem &st); virtual void paintStatusText( Painter &p, const style::PeerListItem &st, int x, int y, int availableWidth, int outerWidth, bool selected); protected: bool isInitialized() const { return _initialized; } private: void createCheckbox(base::lambda updateCallback); void setCheckedInternal(bool checked, SetStyle style); void paintDisabledCheckUserpic( Painter &p, const style::PeerListItem &st, int x, int y, int outerWidth) const; void setStatusText(const QString &text); PeerListRowId _id = 0; not_null _peer; std::unique_ptr _ripple; std::unique_ptr _checkbox; Text _name; Text _status; StatusType _statusType = StatusType::Online; TimeMs _statusValidTill = 0; base::flat_set _nameFirstChars; int _absoluteIndex = -1; State _disabledState = State::Active; bool _initialized : 1; bool _isSearchResult : 1; bool _isSavedMessagesChat : 1; }; enum class PeerListSearchMode { Disabled, Enabled, }; struct PeerListState; class PeerListDelegate { public: virtual void peerListSetTitle(base::lambda title) = 0; virtual void peerListSetAdditionalTitle(base::lambda title) = 0; virtual void peerListSetDescription(object_ptr description) = 0; virtual void peerListSetSearchLoading(object_ptr loading) = 0; virtual void peerListSetSearchNoResults(object_ptr noResults) = 0; virtual void peerListSetAboveWidget(object_ptr aboveWidget) = 0; virtual void peerListSetSearchMode(PeerListSearchMode mode) = 0; virtual void peerListAppendRow(std::unique_ptr row) = 0; virtual void peerListAppendSearchRow(std::unique_ptr row) = 0; virtual void peerListAppendFoundRow(not_null row) = 0; virtual void peerListPrependRow(std::unique_ptr row) = 0; virtual void peerListPrependRowFromSearchResult(not_null row) = 0; virtual void peerListUpdateRow(not_null row) = 0; virtual void peerListRemoveRow(not_null row) = 0; virtual void peerListConvertRowToSearchResult(not_null row) = 0; virtual bool peerListIsRowSelected(not_null peer) = 0; virtual void peerListSetRowChecked(not_null row, bool checked) = 0; virtual not_null peerListRowAt(int index) = 0; virtual void peerListRefreshRows() = 0; virtual void peerListScrollToTop() = 0; virtual int peerListFullRowsCount() = 0; virtual PeerListRow *peerListFindRow(PeerListRowId id) = 0; virtual void peerListSortRows(base::lambda compare) = 0; virtual int peerListPartitionRows(base::lambda border) = 0; template void peerListAddSelectedRows(PeerDataRange &&range) { for (auto peer : range) { peerListAddSelectedRowInBunch(peer); } peerListFinishSelectedRowsBunch(); } virtual int peerListSelectedRowsCount() = 0; virtual std::vector> peerListCollectSelectedRows() = 0; virtual std::unique_ptr peerListSaveState() const = 0; virtual void peerListRestoreState( std::unique_ptr state) = 0; virtual ~PeerListDelegate() = default; private: virtual void peerListAddSelectedRowInBunch(not_null peer) = 0; virtual void peerListFinishSelectedRowsBunch() = 0; }; class PeerListSearchDelegate { public: virtual void peerListSearchAddRow(not_null peer) = 0; virtual void peerListSearchRefreshRows() = 0; virtual ~PeerListSearchDelegate() = default; }; class PeerListSearchController { public: struct SavedStateBase { virtual ~SavedStateBase() = default; }; virtual void searchQuery(const QString &query) = 0; virtual bool isLoading() = 0; virtual bool loadMoreRows() = 0; virtual ~PeerListSearchController() = default; void setDelegate(not_null delegate) { _delegate = delegate; } virtual std::unique_ptr saveState() const { return nullptr; } virtual void restoreState( std::unique_ptr state) { } protected: not_null delegate() const { return _delegate; } private: PeerListSearchDelegate *_delegate = nullptr; }; class PeerListController : public PeerListSearchDelegate { public: struct SavedStateBase { virtual ~SavedStateBase() = default; }; // Search works only with RowId == peer->id. PeerListController(std::unique_ptr searchController = nullptr); void setDelegate(not_null delegate) { _delegate = delegate; prepare(); } virtual void prepare() = 0; virtual void rowClicked(not_null row) = 0; virtual void rowActionClicked(not_null row) { } virtual void loadMoreRows() { } virtual void itemDeselectedHook(not_null peer) { } virtual Ui::PopupMenu *rowContextMenu( not_null row) { return nullptr; } bool isSearchLoading() const { return _searchController ? _searchController->isLoading() : false; } virtual std::unique_ptr createSearchRow( not_null peer) { return nullptr; } virtual std::unique_ptr createRestoredRow( not_null peer) { return nullptr; } virtual std::unique_ptr saveState() const ; virtual void restoreState( std::unique_ptr state); bool isRowSelected(not_null peer) { return delegate()->peerListIsRowSelected(peer); } virtual bool searchInLocal() { return true; } bool hasComplexSearch() const; void search(const QString &query); void peerListSearchAddRow(not_null peer) override; void peerListSearchRefreshRows() override; virtual bool respectSavedMessagesChat() const { return false; } virtual rpl::producer onlineCountValue() const; rpl::lifetime &lifetime() { return _lifetime; } virtual ~PeerListController() = default; protected: not_null delegate() const { return _delegate; } PeerListSearchController *searchController() const { return _searchController.get(); } void setDescriptionText(const QString &text); void setSearchLoadingText(const QString &text); void setSearchNoResultsText(const QString &text); void setDescription(object_ptr description) { delegate()->peerListSetDescription(std::move(description)); } void setSearchLoading(object_ptr loading) { delegate()->peerListSetSearchLoading(std::move(loading)); } void setSearchNoResults(object_ptr noResults) { delegate()->peerListSetSearchNoResults(std::move(noResults)); } private: PeerListDelegate *_delegate = nullptr; std::unique_ptr _searchController = nullptr; rpl::lifetime _lifetime; }; struct PeerListState { PeerListState() = default; PeerListState(PeerListState &&other) = delete; PeerListState &operator=(PeerListState &&other) = delete; std::unique_ptr controllerState; std::vector> list; std::vector> filterResults; QString searchQuery; }; class PeerListContent : public Ui::RpWidget , private base::Subscriber { public: PeerListContent( QWidget *parent, not_null controller, const style::PeerList &st); void selectSkip(int direction); void selectSkipPage(int height, int direction); void clearSelection(); void searchQueryChanged(QString query); void submitted(); // Interface for the controller. void appendRow(std::unique_ptr row); void appendSearchRow(std::unique_ptr row); void appendFoundRow(not_null row); void prependRow(std::unique_ptr row); void prependRowFromSearchResult(not_null row); PeerListRow *findRow(PeerListRowId id); void updateRow(not_null row) { updateRow(row, RowIndex()); } void removeRow(not_null row); void convertRowToSearchResult(not_null row); int fullRowsCount() const; not_null rowAt(int index) const; void setDescription(object_ptr description); void setSearchLoading(object_ptr loading); void setSearchNoResults(object_ptr noResults); void setAboveWidget(object_ptr aboveWidget); void refreshRows(); void setSearchMode(PeerListSearchMode mode); void changeCheckState(not_null row, bool checked, PeerListRow::SetStyle style); template void reorderRows(ReorderCallback &&callback) { callback(_rows.begin(), _rows.end()); for (auto &searchEntity : _searchIndex) { callback(searchEntity.second.begin(), searchEntity.second.end()); } refreshIndices(); update(); } std::unique_ptr saveState() const; void restoreState(std::unique_ptr state); auto scrollToRequests() const { return _scrollToRequests.events(); } protected: int resizeGetHeight(int newWidth) override; void visibleTopBottomUpdated( int visibleTop, int visibleBottom) override; void paintEvent(QPaintEvent *e) override; void enterEventHook(QEvent *e) override; void leaveEventHook(QEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void contextMenuEvent(QContextMenuEvent *e) override; private: void refreshIndices(); void removeRowAtIndex(std::vector> &from, int index); void handleNameChanged(const Notify::PeerUpdate &update); void invalidatePixmapsCache(); struct RowIndex { RowIndex() { } explicit RowIndex(int value) : value(value) { } int value = -1; }; friend inline bool operator==(RowIndex a, RowIndex b) { return (a.value == b.value); } friend inline bool operator!=(RowIndex a, RowIndex b) { return !(a == b); } struct Selected { Selected() { } Selected(RowIndex index, bool action) : index(index), action(action) { } Selected(int index, bool action) : index(index), action(action) { } RowIndex index; bool action = false; }; friend inline bool operator==(Selected a, Selected b) { return (a.index == b.index) && (a.action == b.action); } friend inline bool operator!=(Selected a, Selected b) { return !(a == b); } struct SelectedSaved { SelectedSaved(PeerListRowId id, Selected old) : id(id), old(old) { } PeerListRowId id = 0; Selected old; }; void setSelected(Selected selected); void setPressed(Selected pressed); void setContexted(Selected contexted); void restoreSelection(); SelectedSaved saveSelectedData(Selected from); Selected restoreSelectedData(SelectedSaved from); void updateSelection(); void loadProfilePhotos(); void checkScrollForPreload(); void updateRow(not_null row, RowIndex hint); void updateRow(RowIndex row); int getRowTop(RowIndex row) const; PeerListRow *getRow(RowIndex element); RowIndex findRowIndex(not_null row, RowIndex hint = RowIndex()); QRect getActionRect(not_null row, RowIndex index) const; TimeMs paintRow(Painter &p, TimeMs ms, RowIndex index); void addRowEntry(not_null row); void addToSearchIndex(not_null row); bool addingToSearchIndex() const; void removeFromSearchIndex(not_null row); void setSearchQuery(const QString &query, const QString &normalizedQuery); bool showingSearch() const { return !_searchQuery.isEmpty(); } int shownRowsCount() const { return showingSearch() ? _filterResults.size() : _rows.size(); } template bool enumerateShownRows(Callback callback); template bool enumerateShownRows(int from, int to, Callback callback); int rowsTop() const; int labelHeight() const; void clearSearchRows(); void clearAllContent(); void handleMouseMove(QPoint position); void mousePressReleased(Qt::MouseButton button); const style::PeerList &_st; not_null _controller; PeerListSearchMode _searchMode = PeerListSearchMode::Disabled; int _rowHeight = 0; int _visibleTop = 0; int _visibleBottom = 0; Selected _selected; Selected _pressed; Selected _contexted; bool _mouseSelection = false; Qt::MouseButton _pressButton = Qt::LeftButton; rpl::event_stream _scrollToRequests; std::vector> _rows; std::map> _rowsById; std::map>> _rowsByPeer; std::map>> _searchIndex; QString _searchQuery; QString _normalizedSearchQuery; QString _mentionHighlight; std::vector> _filterResults; int _aboveHeight = 0; object_ptr _aboveWidget = { nullptr }; object_ptr _description = { nullptr }; object_ptr _searchNoResults = { nullptr }; object_ptr _searchLoading = { nullptr }; QPoint _lastMousePosition; std::vector> _searchRows; base::Timer _repaintByStatus; QPointer _contextMenu; }; class PeerListContentDelegate : public PeerListDelegate { public: void setContent(PeerListContent *content) { _content = content; } void peerListAppendRow( std::unique_ptr row) override { _content->appendRow(std::move(row)); } void peerListAppendSearchRow( std::unique_ptr row) override { _content->appendSearchRow(std::move(row)); } void peerListAppendFoundRow( not_null row) override { _content->appendFoundRow(row); } void peerListPrependRow( std::unique_ptr row) override { _content->prependRow(std::move(row)); } void peerListPrependRowFromSearchResult( not_null row) override { _content->prependRowFromSearchResult(row); } PeerListRow *peerListFindRow(PeerListRowId id) override { return _content->findRow(id); } void peerListUpdateRow(not_null row) override { _content->updateRow(row); } void peerListRemoveRow(not_null row) override { _content->removeRow(row); } void peerListConvertRowToSearchResult( not_null row) override { _content->convertRowToSearchResult(row); } void peerListSetRowChecked( not_null row, bool checked) override { _content->changeCheckState( row, checked, PeerListRow::SetStyle::Animated); } int peerListFullRowsCount() override { return _content->fullRowsCount(); } not_null peerListRowAt(int index) override { return _content->rowAt(index); } void peerListRefreshRows() override { _content->refreshRows(); } void peerListSetDescription(object_ptr description) override { _content->setDescription(std::move(description)); } void peerListSetSearchLoading(object_ptr loading) override { _content->setSearchLoading(std::move(loading)); } void peerListSetSearchNoResults(object_ptr noResults) override { _content->setSearchNoResults(std::move(noResults)); } void peerListSetAboveWidget(object_ptr aboveWidget) override { _content->setAboveWidget(std::move(aboveWidget)); } void peerListSetSearchMode(PeerListSearchMode mode) override { _content->setSearchMode(mode); } void peerListSortRows( base::lambda compare) override { _content->reorderRows([&]( auto &&begin, auto &&end) { std::sort(begin, end, [&](auto &&a, auto &&b) { return compare(*a, *b); }); }); } int peerListPartitionRows( base::lambda border) override { auto result = 0; _content->reorderRows([&]( auto &&begin, auto &&end) { auto edge = std::stable_partition(begin, end, [&]( auto &¤t) { return border(*current); }); result = (edge - begin); }); return result; } std::unique_ptr peerListSaveState() const override { return _content->saveState(); } void peerListRestoreState( std::unique_ptr state) override { _content->restoreState(std::move(state)); } protected: not_null content() const { return _content; } private: PeerListContent *_content = nullptr; }; class PeerListBox : public BoxContent , public PeerListContentDelegate { public: PeerListBox( QWidget*, std::unique_ptr controller, base::lambda)> init); void peerListSetTitle(base::lambda title) override { setTitle(std::move(title)); } void peerListSetAdditionalTitle( base::lambda title) override { setAdditionalTitle(std::move(title)); } void peerListSetSearchMode(PeerListSearchMode mode) override; void peerListSetRowChecked( not_null row, bool checked) override; bool peerListIsRowSelected(not_null peer) override; int peerListSelectedRowsCount() override; std::vector> peerListCollectSelectedRows() override; void peerListScrollToTop() override; protected: void prepare() override; void setInnerFocus() override; void keyPressEvent(QKeyEvent *e) override; void resizeEvent(QResizeEvent *e) override; void paintEvent(QPaintEvent *e) override; private: void peerListAddSelectedRowInBunch( not_null peer) override { addSelectItem(peer, PeerListRow::SetStyle::Fast); } void peerListFinishSelectedRowsBunch() override; void addSelectItem( not_null peer, PeerListRow::SetStyle style); void createMultiSelect(); int getTopScrollSkip() const; void updateScrollSkips(); void searchQueryChanged(const QString &query); object_ptr> _select = { nullptr }; std::unique_ptr _controller; base::lambda _init; bool _scrollBottomFixed = false; };