/* 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" namespace Ui { class RippleAnimation; class RoundImageCheckbox; class MultiSelect; template class WidgetSlideWrap; class FlatLabel; } // namespace Ui class PeerListBox : public BoxContent { class Inner; public: using RowId = uint64; class Row { public: Row(PeerData *peer); Row(PeerData *peer, RowId 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; PeerData *peer() const { return _peer; } RowId id() const { return _id; } void setCustomStatus(const QString &status); void clearCustomStatus(); virtual ~Row(); protected: virtual void paintStatusText(Painter &p, int x, int y, int outerWidth, bool selected); bool isInitialized() const { return _initialized; } virtual void lazyInitialize(); private: // Inner interface. friend class PeerListBox; friend class Inner; 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 isGlobalSearchResult() const { return _isGlobalSearchResult; } void setIsGlobalSearchResult(bool isGlobalSearchResult) { _isGlobalSearchResult = isGlobalSearchResult; } 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; } private: void createCheckbox(base::lambda updateCallback); void setCheckedInternal(bool checked, SetStyle style); void paintDisabledCheckUserpic(Painter &p, int x, int y, int outerWidth) const; RowId _id = 0; PeerData *_peer = nullptr; bool _initialized = false; std::unique_ptr _ripple; std::unique_ptr _checkbox; Text _name; QString _status; StatusType _statusType = StatusType::Online; bool _disabled = false; int _absoluteIndex = -1; OrderedSet _nameFirstChars; bool _isGlobalSearchResult = false; }; class Controller { public: virtual void prepare() = 0; virtual void rowClicked(Row *row) = 0; virtual void rowActionClicked(Row *row) { } virtual void preloadRows() { } virtual std::unique_ptr createGlobalRow(PeerData *peer) { return std::unique_ptr(); } virtual ~Controller() = default; protected: PeerListBox *view() const { return _view; } private: void setView(PeerListBox *box) { _view = box; prepare(); } PeerListBox *_view = nullptr; friend class PeerListBox; friend class Inner; }; PeerListBox(QWidget*, std::unique_ptr controller); // Interface for the controller. void appendRow(std::unique_ptr row); void prependRow(std::unique_ptr row); Row *findRow(RowId id); void updateRow(Row *row); void removeRow(Row *row); void setRowChecked(Row *row, bool checked); int fullRowsCount() const; Row *rowAt(int index) const; void setAboutText(const QString &aboutText); void setAbout(object_ptr about); void refreshRows(); // Search works only with RowId == peer->id. enum class SearchMode { None, Local, Global, }; void setSearchMode(SearchMode mode); void setSearchNoResultsText(const QString &noResultsText); void setSearchNoResults(object_ptr searchNoResults); void setSearchLoadingText(const QString &searchLoadingText); void setSearchLoading(object_ptr searchLoading); template void addSelectedRows(PeerDataRange &&range) { Expects(_select != nullptr); for (auto peer : range) { addSelectItem(peer, Row::SetStyle::Fast); } finishSelectItemsBunch(); } QVector collectSelectedRows() const; // callback takes two iterators, like [](auto &begin, auto &end). template void reorderRows(ReorderCallback &&callback); bool isRowSelected(PeerData *peer) const; protected: void prepare() override; void setInnerFocus() override; void keyPressEvent(QKeyEvent *e) override; void resizeEvent(QResizeEvent *e) override; private: void addSelectItem(PeerData *peer, Row::SetStyle style); void finishSelectItemsBunch(); object_ptr> createMultiSelect(); int getTopScrollSkip() const; void updateScrollSkips(); void searchQueryChanged(const QString &query); object_ptr> _select = { nullptr }; QPointer _inner; std::unique_ptr _controller; }; // This class is hold in header because it requires Qt preprocessing. class PeerListBox::Inner : public TWidget, private MTP::Sender, private base::Subscriber { Q_OBJECT public: Inner(QWidget *parent, Controller *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 prependRow(std::unique_ptr row); Row *findRow(RowId id); void updateRow(Row *row) { updateRow(row, RowIndex()); } void removeRow(Row *row); int fullRowsCount() const; Row *rowAt(int index) const; void setAbout(object_ptr about); void refreshRows(); void setSearchMode(SearchMode mode); void setSearchNoResults(object_ptr searchNoResults); void setSearchLoading(object_ptr searchLoading); void changeCheckState(Row *row, bool checked, Row::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 appendGlobalSearchRow(std::unique_ptr row); 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(Row *row, RowIndex hint); void updateRow(RowIndex row); int getRowTop(RowIndex row) const; Row *getRow(RowIndex element); RowIndex findRowIndex(Row *row, RowIndex hint = RowIndex()); QRect getActionRect(Row *row, RowIndex index) const; void paintRow(Painter &p, TimeMs ms, RowIndex index); void addRowEntry(Row *row); void addToSearchIndex(Row *row); bool addingToSearchIndex() const; void removeFromSearchIndex(Row *row); 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 needGlobalSearch(); bool globalSearchInCache(); void globalSearchOnServer(); void globalSearchDone(const MTPcontacts_Found &result, mtpRequestId requestId); bool globalSearchLoading() const; void clearGlobalSearchRows(); Controller *_controller = nullptr; 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; SearchMode _searchMode = SearchMode::None; std::map> _searchIndex; QString _searchQuery; std::vector _filterResults; object_ptr _about = { nullptr }; object_ptr _searchNoResults = { nullptr }; object_ptr _searchLoading = { nullptr }; QPoint _lastMousePosition; std::vector> _globalSearchRows; object_ptr _globalSearchTimer = { nullptr }; QString _globalSearchQuery; QString _globalSearchHighlight; mtpRequestId _globalSearchRequestId = 0; std::map _globalSearchCache; std::map _globalSearchQueries; }; template inline void PeerListBox::reorderRows(ReorderCallback &&callback) { _inner->reorderRows(std::forward(callback)); } class PeerListRowWithLink : public PeerListBox::Row { public: using Row::Row; void setActionLink(const QString &action); protected: 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 ChatsListBoxController : public PeerListBox::Controller, protected base::Subscriber { public: void prepare() override final; std::unique_ptr createGlobalRow(PeerData *peer) override final; protected: class Row : public PeerListBox::Row { public: Row(History *history) : PeerListBox::Row(history->peer), _history(history) { } History *history() const { return _history; } private: History *_history = nullptr; }; virtual std::unique_ptr createRow(History *history) = 0; virtual void prepareViewHook() = 0; virtual void updateRowHook(Row *row) { } private: void rebuildRows(); void checkForEmptyRows(); bool appendRow(History *history); };