PeerListBox can have many rows with the same PeerData. PeerListBox::Row can have arbitrary action on the right side.
		
			
				
	
	
		
			493 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			493 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*
 | 
						|
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 <typename Widget>
 | 
						|
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<void()> 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 <typename UpdateCallback>
 | 
						|
		void setChecked(bool checked, SetStyle style, UpdateCallback callback) {
 | 
						|
			if (checked && !_checkbox) {
 | 
						|
				createCheckbox(std::move(callback));
 | 
						|
			}
 | 
						|
			setCheckedInternal(checked, style);
 | 
						|
		}
 | 
						|
		void invalidatePixmapsCache();
 | 
						|
 | 
						|
		template <typename UpdateCallback>
 | 
						|
		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<QChar> &nameFirstChars) {
 | 
						|
			_nameFirstChars = nameFirstChars;
 | 
						|
		}
 | 
						|
		const OrderedSet<QChar> &nameFirstChars() const {
 | 
						|
			return _nameFirstChars;
 | 
						|
		}
 | 
						|
 | 
						|
	private:
 | 
						|
		void createCheckbox(base::lambda<void()> 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<Ui::RippleAnimation> _ripple;
 | 
						|
		std::unique_ptr<Ui::RoundImageCheckbox> _checkbox;
 | 
						|
		Text _name;
 | 
						|
		QString _status;
 | 
						|
		StatusType _statusType = StatusType::Online;
 | 
						|
		bool _disabled = false;
 | 
						|
		int _absoluteIndex = -1;
 | 
						|
		OrderedSet<QChar> _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<Row> createGlobalRow(PeerData *peer) {
 | 
						|
			return std::unique_ptr<Row>();
 | 
						|
		}
 | 
						|
 | 
						|
		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> controller);
 | 
						|
 | 
						|
	// Interface for the controller.
 | 
						|
	void appendRow(std::unique_ptr<Row> row);
 | 
						|
	void prependRow(std::unique_ptr<Row> 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<Ui::FlatLabel> 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<Ui::FlatLabel> searchNoResults);
 | 
						|
	void setSearchLoadingText(const QString &searchLoadingText);
 | 
						|
	void setSearchLoading(object_ptr<Ui::FlatLabel> searchLoading);
 | 
						|
 | 
						|
	template <typename PeerDataRange>
 | 
						|
	void addSelectedRows(PeerDataRange &&range) {
 | 
						|
		Expects(_select != nullptr);
 | 
						|
		for (auto peer : range) {
 | 
						|
			addSelectItem(peer, Row::SetStyle::Fast);
 | 
						|
		}
 | 
						|
		finishSelectItemsBunch();
 | 
						|
	}
 | 
						|
	QVector<PeerData*> collectSelectedRows() const;
 | 
						|
 | 
						|
	// callback takes two iterators, like [](auto &begin, auto &end).
 | 
						|
	template <typename ReorderCallback>
 | 
						|
	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<Ui::WidgetSlideWrap<Ui::MultiSelect>> createMultiSelect();
 | 
						|
	int getTopScrollSkip() const;
 | 
						|
	void updateScrollSkips();
 | 
						|
	void searchQueryChanged(const QString &query);
 | 
						|
 | 
						|
	object_ptr<Ui::WidgetSlideWrap<Ui::MultiSelect>> _select = { nullptr };
 | 
						|
 | 
						|
	QPointer<Inner> _inner;
 | 
						|
 | 
						|
	std::unique_ptr<Controller> _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> row);
 | 
						|
	void prependRow(std::unique_ptr<Row> 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<Ui::FlatLabel> about);
 | 
						|
	void refreshRows();
 | 
						|
	void setSearchMode(SearchMode mode);
 | 
						|
	void setSearchNoResults(object_ptr<Ui::FlatLabel> searchNoResults);
 | 
						|
	void setSearchLoading(object_ptr<Ui::FlatLabel> searchLoading);
 | 
						|
 | 
						|
	void changeCheckState(Row *row, bool checked, Row::SetStyle style);
 | 
						|
 | 
						|
	template <typename ReorderCallback>
 | 
						|
	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> 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 <typename Callback>
 | 
						|
	bool enumerateShownRows(Callback callback);
 | 
						|
	template <typename Callback>
 | 
						|
	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<std::unique_ptr<Row>> _rows;
 | 
						|
	std::map<RowId, Row*> _rowsById;
 | 
						|
	std::map<PeerData*, std::vector<Row*>> _rowsByPeer;
 | 
						|
 | 
						|
	SearchMode _searchMode = SearchMode::None;
 | 
						|
	std::map<QChar, std::vector<Row*>> _searchIndex;
 | 
						|
	QString _searchQuery;
 | 
						|
	std::vector<Row*> _filterResults;
 | 
						|
 | 
						|
	object_ptr<Ui::FlatLabel> _about = { nullptr };
 | 
						|
	object_ptr<Ui::FlatLabel> _searchNoResults = { nullptr };
 | 
						|
	object_ptr<Ui::FlatLabel> _searchLoading = { nullptr };
 | 
						|
 | 
						|
	QPoint _lastMousePosition;
 | 
						|
 | 
						|
	std::vector<std::unique_ptr<Row>> _globalSearchRows;
 | 
						|
	object_ptr<SingleTimer> _globalSearchTimer = { nullptr };
 | 
						|
	QString _globalSearchQuery;
 | 
						|
	QString _globalSearchHighlight;
 | 
						|
	mtpRequestId _globalSearchRequestId = 0;
 | 
						|
	std::map<QString, MTPcontacts_Found> _globalSearchCache;
 | 
						|
	std::map<mtpRequestId, QString> _globalSearchQueries;
 | 
						|
 | 
						|
};
 | 
						|
 | 
						|
template <typename ReorderCallback>
 | 
						|
inline void PeerListBox::reorderRows(ReorderCallback &&callback) {
 | 
						|
	_inner->reorderRows(std::forward<ReorderCallback>(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<PeerListBox::Row> 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<Row> createRow(History *history) = 0;
 | 
						|
	virtual void prepareViewHook() = 0;
 | 
						|
	virtual void updateRowHook(Row *row) {
 | 
						|
	}
 | 
						|
 | 
						|
private:
 | 
						|
	void rebuildRows();
 | 
						|
	void checkForEmptyRows();
 | 
						|
	bool appendRow(History *history);
 | 
						|
 | 
						|
};
 |