/* 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 "boxes/abstract_box.h" #include "mtproto/sender.h" #include "base/timer.h" namespace Ui { class RippleAnimation; class RoundImageCheckbox; class MultiSelect; template class WidgetSlideWrap; class FlatLabel; } // namespace Ui using PeerListRowId = uint64; class PeerListRow { public: PeerListRow(gsl::not_null peer); PeerListRow(gsl::not_null peer, PeerListRowId id); void setDisabled(bool disabled) { _disabled = disabled; } // 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; gsl::not_null peer() const { return _peer; } PeerListRowId id() const { return _id; } void setCustomStatus(const QString &status); void clearCustomStatus(); virtual ~PeerListRow(); // Box interface. virtual bool needsVerifiedIcon() const { return _peer->isVerified(); } 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 actionSelected) { } void refreshName(); const Text &name() const { return _name; } enum class StatusType { Online, LastSeen, Custom, }; void refreshStatus(); void setAbsoluteIndex(int index) { _absoluteIndex = index; } int absoluteIndex() const { return _absoluteIndex; } bool disabled() const { return _disabled; } bool isSearchResult() const { return _isSearchResult; } void setIsSearchResult(bool isSearchResult) { _isSearchResult = isSearchResult; } 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(QSize size, QPoint point, UpdateCallback updateCallback); void stopLastRipple(); void paintRipple(Painter &p, TimeMs ms, int x, int y, int outerWidth); void paintUserpic(Painter &p, TimeMs ms, int x, int y, int outerWidth); float64 checkedRatio(); void setNameFirstChars(const OrderedSet &nameFirstChars) { _nameFirstChars = nameFirstChars; } const OrderedSet &nameFirstChars() const { return _nameFirstChars; } virtual void lazyInitialize(); virtual void paintStatusText(Painter &p, 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, int x, int y, int outerWidth) const; void setStatusText(const QString &text); PeerListRowId _id = 0; gsl::not_null _peer; std::unique_ptr _ripple; std::unique_ptr _checkbox; Text _name; Text _status; StatusType _statusType = StatusType::Online; OrderedSet _nameFirstChars; int _absoluteIndex = -1; bool _initialized : 1; bool _disabled : 1; bool _isSearchResult : 1; }; enum class PeerListSearchMode { Disabled, Enabled, }; class PeerListDelegate { public: virtual void peerListSetTitle(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 peerListSetSearchMode(PeerListSearchMode mode) = 0; virtual void peerListAppendRow(std::unique_ptr row) = 0; virtual void peerListAppendSearchRow(std::unique_ptr row) = 0; virtual void peerListAppendFoundRow(gsl::not_null row) = 0; virtual void peerListPrependRow(std::unique_ptr row) = 0; virtual void peerListPrependRowFromSearchResult(gsl::not_null row) = 0; virtual void peerListUpdateRow(gsl::not_null row) = 0; virtual void peerListRemoveRow(gsl::not_null row) = 0; virtual void peerListConvertRowToSearchResult(gsl::not_null row) = 0; virtual bool peerListIsRowSelected(gsl::not_null peer) = 0; virtual void peerListSetRowChecked(gsl::not_null row, bool checked) = 0; virtual gsl::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 void peerListPartitionRows(base::lambda border) = 0; template void peerListAddSelectedRows(PeerDataRange &&range) { for (auto peer : range) { peerListAddSelectedRowInBunch(peer); } peerListFinishSelectedRowsBunch(); } virtual std::vector> peerListCollectSelectedRows() = 0; virtual ~PeerListDelegate() = default; private: virtual void peerListAddSelectedRowInBunch(gsl::not_null peer) = 0; virtual void peerListFinishSelectedRowsBunch() = 0; }; class PeerListSearchDelegate { public: virtual void peerListSearchAddRow(gsl::not_null peer) = 0; virtual void peerListSearchRefreshRows() = 0; virtual ~PeerListSearchDelegate() = default; }; class PeerListSearchController { public: virtual void searchQuery(const QString &query) = 0; virtual bool isLoading() = 0; virtual bool loadMoreRows() = 0; virtual ~PeerListSearchController() = default; void setDelegate(gsl::not_null delegate) { _delegate = delegate; } protected: gsl::not_null delegate() const { return _delegate; } private: PeerListSearchDelegate *_delegate = nullptr; }; class PeerListController : public PeerListSearchDelegate { public: // Search works only with RowId == peer->id. PeerListController(std::unique_ptr searchController = nullptr); void setDelegate(gsl::not_null delegate) { _delegate = delegate; prepare(); } virtual void prepare() = 0; virtual void rowClicked(gsl::not_null row) = 0; virtual void rowActionClicked(gsl::not_null row) { } virtual void loadMoreRows() { } bool isSearchLoading() const { return _searchController ? _searchController->isLoading() : false; } virtual std::unique_ptr createSearchRow(gsl::not_null peer) { return nullptr; } bool isRowSelected(gsl::not_null peer) { return delegate()->peerListIsRowSelected(peer); } virtual bool searchInLocal() { return true; } bool hasComplexSearch() const; void search(const QString &query); void peerListSearchAddRow(gsl::not_null peer) override; void peerListSearchRefreshRows() override; virtual ~PeerListController() = default; protected: gsl::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; }; class PeerListBox : public BoxContent, public PeerListDelegate { public: PeerListBox(QWidget*, std::unique_ptr controller, base::lambda init); void peerListSetTitle(base::lambda title) override { setTitle(std::move(title)); } void peerListSetDescription(object_ptr description) override; void peerListSetSearchLoading(object_ptr loading) override; void peerListSetSearchNoResults(object_ptr noResults) override; void peerListSetSearchMode(PeerListSearchMode mode) override; void peerListAppendRow(std::unique_ptr row) override; void peerListAppendSearchRow(std::unique_ptr row) override; void peerListAppendFoundRow(gsl::not_null row) override; void peerListPrependRow(std::unique_ptr row) override; void peerListPrependRowFromSearchResult(gsl::not_null row) override; void peerListUpdateRow(gsl::not_null row) override; void peerListRemoveRow(gsl::not_null row) override; void peerListConvertRowToSearchResult(gsl::not_null row) override; void peerListSetRowChecked(gsl::not_null row, bool checked) override; gsl::not_null peerListRowAt(int index) override; bool peerListIsRowSelected(gsl::not_null peer) override; std::vector> peerListCollectSelectedRows() override; void peerListRefreshRows() override; void peerListScrollToTop() override; int peerListFullRowsCount() override; PeerListRow *peerListFindRow(PeerListRowId id) override; void peerListSortRows(base::lambda compare) override; void peerListPartitionRows(base::lambda border) 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(gsl::not_null peer) override { addSelectItem(peer, PeerListRow::SetStyle::Fast); } void peerListFinishSelectedRowsBunch() override; void addSelectItem(gsl::not_null peer, PeerListRow::SetStyle style); object_ptr> createMultiSelect(); int getTopScrollSkip() const; void updateScrollSkips(); void searchQueryChanged(const QString &query); object_ptr> _select = { nullptr }; class Inner; QPointer _inner; std::unique_ptr _controller; base::lambda _init; }; // This class is hold in header because it requires Qt preprocessing. class PeerListBox::Inner : public TWidget, private base::Subscriber { Q_OBJECT public: Inner(QWidget *parent, gsl::not_null controller); void selectSkip(int direction); void selectSkipPage(int height, int direction); void clearSelection(); void setVisibleTopBottom(int visibleTop, int visibleBottom) override; void searchQueryChanged(QString query); void submitted(); // Interface for the controller. void appendRow(std::unique_ptr row); void appendSearchRow(std::unique_ptr row); void appendFoundRow(gsl::not_null row); void prependRow(std::unique_ptr row); void prependRowFromSearchResult(gsl::not_null row); PeerListRow *findRow(PeerListRowId id); void updateRow(gsl::not_null row) { updateRow(row, RowIndex()); } void removeRow(gsl::not_null row); void convertRowToSearchResult(gsl::not_null row); int fullRowsCount() const; gsl::not_null rowAt(int index) const; void setDescription(object_ptr description); void setSearchLoading(object_ptr loading); void setSearchNoResults(object_ptr noResults); void refreshRows(); void setSearchMode(PeerListSearchMode mode); void changeCheckState(gsl::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(); } signals: void mustScrollTo(int ymin, int ymax); public slots: void peerUpdated(PeerData *peer); void onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars); protected: 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; private: void refreshIndices(); void removeRowAtIndex(std::vector> &from, int index); 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); } void setSelected(Selected selected); void setPressed(Selected pressed); void restoreSelection(); void updateSelection(); void loadProfilePhotos(); void checkScrollForPreload(); void updateRow(gsl::not_null row, RowIndex hint); void updateRow(RowIndex row); int getRowTop(RowIndex row) const; PeerListRow *getRow(RowIndex element); RowIndex findRowIndex(gsl::not_null row, RowIndex hint = RowIndex()); QRect getActionRect(gsl::not_null row, RowIndex index) const; void paintRow(Painter &p, TimeMs ms, RowIndex index); void addRowEntry(gsl::not_null row); void addToSearchIndex(gsl::not_null row); bool addingToSearchIndex() const; void removeFromSearchIndex(gsl::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 labelHeight() const; void clearSearchRows(); gsl::not_null _controller; PeerListSearchMode _searchMode = PeerListSearchMode::Disabled; int _rowHeight = 0; int _visibleTop = 0; int _visibleBottom = 0; Selected _selected; Selected _pressed; bool _mouseSelection = false; std::vector> _rows; std::map> _rowsById; std::map>> _rowsByPeer; std::map>> _searchIndex; QString _searchQuery; QString _normalizedSearchQuery; QString _mentionHighlight; std::vector> _filterResults; object_ptr _description = { nullptr }; object_ptr _searchNoResults = { nullptr }; object_ptr _searchLoading = { nullptr }; QPoint _lastMousePosition; std::vector> _searchRows; }; class PeerListRowWithLink : public PeerListRow { public: using PeerListRow::PeerListRow; void setActionLink(const QString &action); void lazyInitialize() override; private: void refreshActionLink(); QSize actionSize() const override; QMargins actionMargins() const override; void paintAction(Painter &p, TimeMs ms, int x, int y, int outerWidth, bool actionSelected) override; QString _action; int _actionWidth = 0; }; class PeerListGlobalSearchController : public PeerListSearchController, private MTP::Sender { public: PeerListGlobalSearchController(); void searchQuery(const QString &query) override; bool isLoading() override; bool loadMoreRows() override { return false; } private: bool searchInCache(); void searchOnServer(); void searchDone(const MTPcontacts_Found &result, mtpRequestId requestId); base::Timer _timer; QString _query; mtpRequestId _requestId = 0; std::map _cache; std::map _queries; }; class ChatsListBoxController : public PeerListController, protected base::Subscriber { public: ChatsListBoxController(std::unique_ptr searchController = std::make_unique()); void prepare() override final; std::unique_ptr createSearchRow(gsl::not_null peer) override final; protected: class Row : public PeerListRow { public: Row(gsl::not_null history) : PeerListRow(history->peer), _history(history) { } gsl::not_null history() const { return _history; } private: gsl::not_null _history; }; virtual std::unique_ptr createRow(gsl::not_null history) = 0; virtual void prepareViewHook() = 0; virtual void updateRowHook(Row *row) { } private: void rebuildRows(); void checkForEmptyRows(); bool appendRow(History *history); };