1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4 
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #pragma once
9 
10 #include "ui/rp_widget.h"
11 #include "ui/empty_userpic.h"
12 #include "boxes/abstract_box.h"
13 #include "mtproto/sender.h"
14 #include "data/data_cloud_file.h"
15 #include "base/timer.h"
16 
17 namespace style {
18 struct PeerList;
19 struct PeerListItem;
20 struct MultiSelect;
21 } // namespace style
22 
23 namespace Main {
24 class Session;
25 } // namespace Main
26 
27 namespace Ui {
28 class RippleAnimation;
29 class RoundImageCheckbox;
30 class MultiSelect;
31 template <typename Widget>
32 class SlideWrap;
33 class FlatLabel;
34 struct ScrollToRequest;
35 class PopupMenu;
36 } // namespace Ui
37 
38 using PaintRoundImageCallback = Fn<void(
39 	Painter &p,
40 	int x,
41 	int y,
42 	int outerWidth,
43 	int size)>;
44 
45 [[nodiscard]] PaintRoundImageCallback PaintUserpicCallback(
46 	not_null<PeerData*> peer,
47 	bool respectSavedMessagesChat);
48 
49 using PeerListRowId = uint64;
50 
51 class PeerListRow {
52 public:
53 	enum class State {
54 		Active,
55 		Disabled,
56 		DisabledChecked,
57 	};
58 
59 	explicit PeerListRow(not_null<PeerData*> peer);
60 	PeerListRow(not_null<PeerData*> peer, PeerListRowId id);
61 
62 	virtual ~PeerListRow();
63 
setDisabledState(State state)64 	void setDisabledState(State state) {
65 		_disabledState = state;
66 	}
67 
68 	// Checked state is controlled by the box with multiselect,
69 	// not by the row itself, so there is no setChecked() method.
70 	// We can query the checked state from row, but before it is
71 	// added to the box it is always false.
72 	[[nodiscard]] bool checked() const;
73 
special()74 	[[nodiscard]] bool special() const {
75 		return !_peer;
76 	}
peer()77 	[[nodiscard]] not_null<PeerData*> peer() const {
78 		Expects(!special());
79 
80 		return _peer;
81 	}
id()82 	[[nodiscard]] PeerListRowId id() const {
83 		return _id;
84 	}
85 
86 	[[nodiscard]] std::shared_ptr<Data::CloudImageView> &ensureUserpicView();
87 
88 	[[nodiscard]] virtual QString generateName();
89 	[[nodiscard]] virtual QString generateShortName();
90 	[[nodiscard]] virtual auto generatePaintUserpicCallback()
91 		-> PaintRoundImageCallback;
92 
93 	void setCustomStatus(const QString &status, bool active = false);
94 	void clearCustomStatus();
95 
96 	// Box interface.
97 	virtual int nameIconWidth() const;
98 	virtual void paintNameIcon(
99 		Painter &p,
100 		int x,
101 		int y,
102 		int outerWidth,
103 		bool selected);
104 
rightActionSize()105 	virtual QSize rightActionSize() const {
106 		return QSize();
107 	}
rightActionMargins()108 	virtual QMargins rightActionMargins() const {
109 		return QMargins();
110 	}
rightActionDisabled()111 	virtual bool rightActionDisabled() const {
112 		return false;
113 	}
rightActionPaint(Painter & p,int x,int y,int outerWidth,bool selected,bool actionSelected)114 	virtual void rightActionPaint(
115 		Painter &p,
116 		int x,
117 		int y,
118 		int outerWidth,
119 		bool selected,
120 		bool actionSelected) {
121 	}
rightActionAddRipple(QPoint point,Fn<void ()> updateCallback)122 	virtual void rightActionAddRipple(
123 		QPoint point,
124 		Fn<void()> updateCallback) {
125 	}
rightActionStopLastRipple()126 	virtual void rightActionStopLastRipple() {
127 	}
128 
129 	// By default elements code falls back to a simple right action code.
130 	virtual int elementsCount() const;
131 	virtual QRect elementGeometry(int element, int outerWidth) const;
132 	virtual bool elementDisabled(int element) const;
133 	virtual bool elementOnlySelect(int element) const;
134 	virtual void elementAddRipple(
135 		int element,
136 		QPoint point,
137 		Fn<void()> updateCallback);
138 	virtual void elementsStopLastRipple();
139 	virtual void elementsPaint(
140 		Painter &p,
141 		int outerWidth,
142 		bool selected,
143 		int selectedElement);
144 
145 	virtual void refreshName(const style::PeerListItem &st);
name()146 	const Ui::Text::String &name() const {
147 		return _name;
148 	}
149 
150 	enum class StatusType {
151 		Online,
152 		LastSeen,
153 		Custom,
154 		CustomActive,
155 	};
156 	virtual void refreshStatus();
157 	crl::time refreshStatusTime() const;
158 
setAbsoluteIndex(int index)159 	void setAbsoluteIndex(int index) {
160 		_absoluteIndex = index;
161 	}
absoluteIndex()162 	int absoluteIndex() const {
163 		return _absoluteIndex;
164 	}
disabled()165 	bool disabled() const {
166 		return (_disabledState != State::Active);
167 	}
isSearchResult()168 	bool isSearchResult() const {
169 		return _isSearchResult;
170 	}
setIsSearchResult(bool isSearchResult)171 	void setIsSearchResult(bool isSearchResult) {
172 		_isSearchResult = isSearchResult;
173 	}
setIsSavedMessagesChat(bool isSavedMessagesChat)174 	void setIsSavedMessagesChat(bool isSavedMessagesChat) {
175 		_isSavedMessagesChat = isSavedMessagesChat;
176 	}
setIsRepliesMessagesChat(bool isRepliesMessagesChat)177 	void setIsRepliesMessagesChat(bool isRepliesMessagesChat) {
178 		_isRepliesMessagesChat = isRepliesMessagesChat;
179 	}
180 
181 	template <typename UpdateCallback>
setChecked(bool checked,const style::RoundImageCheckbox & st,anim::type animated,UpdateCallback callback)182 	void setChecked(
183 			bool checked,
184 			const style::RoundImageCheckbox &st,
185 			anim::type animated,
186 			UpdateCallback callback) {
187 		if (checked && !_checkbox) {
188 			createCheckbox(st, std::move(callback));
189 		}
190 		setCheckedInternal(checked, animated);
191 	}
setHidden(bool hidden)192 	void setHidden(bool hidden) {
193 		_hidden = hidden;
194 	}
hidden()195 	[[nodiscard]] bool hidden() const {
196 		return _hidden;
197 	}
198 	void finishCheckedAnimation();
199 	void invalidatePixmapsCache();
200 
201 	template <typename MaskGenerator, typename UpdateCallback>
202 	void addRipple(
203 		const style::PeerListItem &st,
204 		MaskGenerator &&maskGenerator,
205 		QPoint point,
206 		UpdateCallback &&updateCallback);
207 	void stopLastRipple();
208 	void paintRipple(Painter &p, int x, int y, int outerWidth);
209 	void paintUserpic(
210 		Painter &p,
211 		const style::PeerListItem &st,
212 		int x,
213 		int y,
214 		int outerWidth);
215 	float64 checkedRatio();
216 
setNameFirstLetters(const base::flat_set<QChar> & firstLetters)217 	void setNameFirstLetters(const base::flat_set<QChar> &firstLetters) {
218 		_nameFirstLetters = firstLetters;
219 	}
nameFirstLetters()220 	const base::flat_set<QChar> &nameFirstLetters() const {
221 		return _nameFirstLetters;
222 	}
223 
224 	virtual void lazyInitialize(const style::PeerListItem &st);
225 	virtual void paintStatusText(
226 		Painter &p,
227 		const style::PeerListItem &st,
228 		int x,
229 		int y,
230 		int availableWidth,
231 		int outerWidth,
232 		bool selected);
233 
234 protected:
isInitialized()235 	bool isInitialized() const {
236 		return _initialized;
237 	}
238 
239 	explicit PeerListRow(PeerListRowId id);
240 
241 private:
242 	void createCheckbox(
243 		const style::RoundImageCheckbox &st,
244 		Fn<void()> updateCallback);
245 	void setCheckedInternal(bool checked, anim::type animated);
246 	void paintDisabledCheckUserpic(
247 		Painter &p,
248 		const style::PeerListItem &st,
249 		int x,
250 		int y,
251 		int outerWidth) const;
252 	void setStatusText(const QString &text);
253 
254 	PeerListRowId _id = 0;
255 	PeerData *_peer = nullptr;
256 	mutable std::shared_ptr<Data::CloudImageView> _userpic;
257 	std::unique_ptr<Ui::RippleAnimation> _ripple;
258 	std::unique_ptr<Ui::RoundImageCheckbox> _checkbox;
259 	Ui::Text::String _name;
260 	Ui::Text::String _status;
261 	StatusType _statusType = StatusType::Online;
262 	crl::time _statusValidTill = 0;
263 	base::flat_set<QChar> _nameFirstLetters;
264 	int _absoluteIndex = -1;
265 	State _disabledState = State::Active;
266 	bool _hidden : 1;
267 	bool _initialized : 1;
268 	bool _isSearchResult : 1;
269 	bool _isSavedMessagesChat : 1;
270 	bool _isRepliesMessagesChat : 1;
271 
272 };
273 
274 enum class PeerListSearchMode {
275 	Disabled,
276 	Enabled,
277 };
278 
279 struct PeerListState;
280 
281 class PeerListDelegate {
282 public:
283 	virtual void peerListSetTitle(rpl::producer<QString> title) = 0;
284 	virtual void peerListSetAdditionalTitle(rpl::producer<QString> title) = 0;
285 	virtual void peerListSetHideEmpty(bool hide) = 0;
286 	virtual void peerListSetDescription(object_ptr<Ui::FlatLabel> description) = 0;
287 	virtual void peerListSetSearchLoading(object_ptr<Ui::FlatLabel> loading) = 0;
288 	virtual void peerListSetSearchNoResults(object_ptr<Ui::FlatLabel> noResults) = 0;
289 	virtual void peerListSetAboveWidget(object_ptr<TWidget> aboveWidget) = 0;
290 	virtual void peerListSetAboveSearchWidget(object_ptr<TWidget> aboveWidget) = 0;
291 	virtual void peerListSetBelowWidget(object_ptr<TWidget> belowWidget) = 0;
292 	virtual void peerListMouseLeftGeometry() = 0;
293 	virtual void peerListSetSearchMode(PeerListSearchMode mode) = 0;
294 	virtual void peerListAppendRow(std::unique_ptr<PeerListRow> row) = 0;
295 	virtual void peerListAppendSearchRow(std::unique_ptr<PeerListRow> row) = 0;
296 	virtual void peerListAppendFoundRow(not_null<PeerListRow*> row) = 0;
297 	virtual void peerListPrependRow(std::unique_ptr<PeerListRow> row) = 0;
298 	virtual void peerListPrependRowFromSearchResult(not_null<PeerListRow*> row) = 0;
299 	virtual void peerListUpdateRow(not_null<PeerListRow*> row) = 0;
300 	virtual void peerListRemoveRow(not_null<PeerListRow*> row) = 0;
301 	virtual void peerListConvertRowToSearchResult(not_null<PeerListRow*> row) = 0;
302 	virtual bool peerListIsRowChecked(not_null<PeerListRow*> row) = 0;
303 	virtual void peerListSetRowChecked(not_null<PeerListRow*> row, bool checked) = 0;
304 	virtual void peerListSetRowHidden(not_null<PeerListRow*> row, bool hidden) = 0;
305 	virtual void peerListSetForeignRowChecked(
306 		not_null<PeerListRow*> row,
307 		bool checked,
308 		anim::type animated) = 0;
309 	virtual not_null<PeerListRow*> peerListRowAt(int index) = 0;
310 	virtual void peerListRefreshRows() = 0;
311 	virtual void peerListScrollToTop() = 0;
312 	virtual int peerListFullRowsCount() = 0;
313 	virtual PeerListRow *peerListFindRow(PeerListRowId id) = 0;
314 	virtual void peerListSortRows(Fn<bool(const PeerListRow &a, const PeerListRow &b)> compare) = 0;
315 	virtual int peerListPartitionRows(Fn<bool(const PeerListRow &a)> border) = 0;
316 
317 	template <typename PeerDataRange>
peerListAddSelectedPeers(PeerDataRange && range)318 	void peerListAddSelectedPeers(PeerDataRange &&range) {
319 		for (const auto peer : range) {
320 			peerListAddSelectedPeerInBunch(peer);
321 		}
322 		peerListFinishSelectedRowsBunch();
323 	}
324 
325 	template <typename PeerListRowRange>
peerListAddSelectedRows(PeerListRowRange && range)326 	void peerListAddSelectedRows(PeerListRowRange &&range) {
327 		for (const auto row : range) {
328 			peerListAddSelectedRowInBunch(row);
329 		}
330 		peerListFinishSelectedRowsBunch();
331 	}
332 
333 	virtual void peerListShowRowMenu(
334 		not_null<PeerListRow*> row,
335 		bool highlightRow,
336 		Fn<void(not_null<Ui::PopupMenu*>)> destroyed = nullptr) = 0;
337 	virtual int peerListSelectedRowsCount() = 0;
338 	virtual std::unique_ptr<PeerListState> peerListSaveState() const = 0;
339 	virtual void peerListRestoreState(
340 		std::unique_ptr<PeerListState> state) = 0;
341 	virtual ~PeerListDelegate() = default;
342 
343 private:
344 	virtual void peerListAddSelectedPeerInBunch(not_null<PeerData*> peer) = 0;
345 	virtual void peerListAddSelectedRowInBunch(not_null<PeerListRow*> row) = 0;
346 	virtual void peerListFinishSelectedRowsBunch() = 0;
347 
348 };
349 
350 class PeerListSearchDelegate {
351 public:
352 	virtual void peerListSearchAddRow(not_null<PeerData*> peer) = 0;
353 	virtual void peerListSearchRefreshRows() = 0;
354 	virtual ~PeerListSearchDelegate() = default;
355 
356 };
357 
358 class PeerListSearchController {
359 public:
360 	struct SavedStateBase {
361 		virtual ~SavedStateBase() = default;
362 	};
363 
364 	virtual void searchQuery(const QString &query) = 0;
365 	virtual bool isLoading() = 0;
366 	virtual bool loadMoreRows() = 0;
367 	virtual ~PeerListSearchController() = default;
368 
setDelegate(not_null<PeerListSearchDelegate * > delegate)369 	void setDelegate(not_null<PeerListSearchDelegate*> delegate) {
370 		_delegate = delegate;
371 	}
372 
saveState()373 	virtual std::unique_ptr<SavedStateBase> saveState() const {
374 		return nullptr;
375 	}
restoreState(std::unique_ptr<SavedStateBase> state)376 	virtual void restoreState(
377 		std::unique_ptr<SavedStateBase> state) {
378 	}
379 
lifetime()380 	rpl::lifetime &lifetime() {
381 		return _lifetime;
382 	}
383 
384 protected:
delegate()385 	not_null<PeerListSearchDelegate*> delegate() const {
386 		return _delegate;
387 	}
388 
389 private:
390 	PeerListSearchDelegate *_delegate = nullptr;
391 	rpl::lifetime _lifetime;
392 
393 };
394 
395 class PeerListController : public PeerListSearchDelegate {
396 public:
397 	struct SavedStateBase {
398 		virtual ~SavedStateBase() = default;
399 	};
400 
401 	// Search works only with RowId == peer->id.
402 	PeerListController(
403 		std::unique_ptr<PeerListSearchController> searchController = {});
404 
setDelegate(not_null<PeerListDelegate * > delegate)405 	void setDelegate(not_null<PeerListDelegate*> delegate) {
406 		_delegate = delegate;
407 		prepare();
408 	}
delegate()409 	[[nodiscard]] not_null<PeerListDelegate*> delegate() const {
410 		return _delegate;
411 	}
412 
413 	void setStyleOverrides(
414 			const style::PeerList *listSt,
415 			const style::MultiSelect *selectSt = nullptr) {
416 		_listSt = listSt;
417 		_selectSt = selectSt;
418 	}
listSt()419 	const style::PeerList *listSt() const {
420 		return _listSt;
421 	}
selectSt()422 	const style::MultiSelect *selectSt() const {
423 		return _selectSt;
424 	}
425 	const style::PeerList &computeListSt() const;
426 	const style::MultiSelect &computeSelectSt() const;
427 
428 	virtual Main::Session &session() const = 0;
429 
430 	virtual void prepare() = 0;
431 
432 	virtual void rowClicked(not_null<PeerListRow*> row) = 0;
rowRightActionClicked(not_null<PeerListRow * > row)433 	virtual void rowRightActionClicked(not_null<PeerListRow*> row) {
434 	}
435 
436 	// By default elements code falls back to a simple right action code.
rowElementClicked(not_null<PeerListRow * > row,int element)437 	virtual void rowElementClicked(not_null<PeerListRow*> row, int element) {
438 		if (element == 1) {
439 			rowRightActionClicked(row);
440 		}
441 	}
442 
loadMoreRows()443 	virtual void loadMoreRows() {
444 	}
itemDeselectedHook(not_null<PeerData * > peer)445 	virtual void itemDeselectedHook(not_null<PeerData*> peer) {
446 	}
isForeignRow(PeerListRowId itemId)447 	virtual bool isForeignRow(PeerListRowId itemId) {
448 		return false;
449 	}
handleDeselectForeignRow(PeerListRowId itemId)450 	virtual bool handleDeselectForeignRow(PeerListRowId itemId) {
451 		return false;
452 	}
453 	virtual base::unique_qptr<Ui::PopupMenu> rowContextMenu(
454 		QWidget *parent,
455 		not_null<PeerListRow*> row);
isSearchLoading()456 	bool isSearchLoading() const {
457 		return _searchController ? _searchController->isLoading() : false;
458 	}
createSearchRow(not_null<PeerData * > peer)459 	virtual std::unique_ptr<PeerListRow> createSearchRow(
460 			not_null<PeerData*> peer) {
461 		return nullptr;
462 	}
createRestoredRow(not_null<PeerData * > peer)463 	virtual std::unique_ptr<PeerListRow> createRestoredRow(
464 			not_null<PeerData*> peer) {
465 		return nullptr;
466 	}
467 
468 	virtual std::unique_ptr<PeerListState> saveState() const;
469 	virtual void restoreState(
470 		std::unique_ptr<PeerListState> state);
471 
472 	[[nodiscard]] virtual int contentWidth() const;
473 	[[nodiscard]] virtual rpl::producer<int> boxHeightValue() const;
474 	[[nodiscard]] virtual int descriptionTopSkipMin() const;
475 
isRowSelected(not_null<PeerListRow * > row)476 	[[nodiscard]] bool isRowSelected(not_null<PeerListRow*> row) {
477 		return delegate()->peerListIsRowChecked(row);
478 	}
479 
searchInLocal()480 	virtual bool searchInLocal() {
481 		return true;
482 	}
483 	[[nodiscard]] bool hasComplexSearch() const;
484 	void search(const QString &query);
485 
486 	void peerListSearchAddRow(not_null<PeerData*> peer) override;
487 	void peerListSearchRefreshRows() override;
488 
respectSavedMessagesChat()489 	[[nodiscard]] virtual bool respectSavedMessagesChat() const {
490 		return false;
491 	}
customRowHeight()492 	[[nodiscard]] virtual int customRowHeight() {
493 		Unexpected("PeerListController::customRowHeight.");
494 	}
customRowPaint(Painter & p,crl::time now,not_null<PeerListRow * > row,bool selected)495 	virtual void customRowPaint(
496 			Painter &p,
497 			crl::time now,
498 			not_null<PeerListRow*> row,
499 			bool selected) {
500 		Unexpected("PeerListController::customRowPaint.");
501 	}
customRowSelectionPoint(not_null<PeerListRow * > row,int x,int y)502 	[[nodiscard]] virtual bool customRowSelectionPoint(
503 			not_null<PeerListRow*> row,
504 			int x,
505 			int y) {
506 		Unexpected("PeerListController::customRowSelectionPoint.");
507 	}
customRowRippleMaskGenerator()508 	[[nodiscard]] virtual Fn<QImage()> customRowRippleMaskGenerator() {
509 		Unexpected("PeerListController::customRowRippleMaskGenerator.");
510 	}
511 
512 	[[nodiscard]] virtual rpl::producer<int> onlineCountValue() const;
513 
lifetime()514 	[[nodiscard]] rpl::lifetime &lifetime() {
515 		return _lifetime;
516 	}
517 
518 	virtual ~PeerListController() = default;
519 
520 protected:
searchController()521 	PeerListSearchController *searchController() const {
522 		return _searchController.get();
523 	}
524 
525 	void setDescriptionText(const QString &text);
526 	void setSearchLoadingText(const QString &text);
527 	void setSearchNoResultsText(const QString &text);
setDescription(object_ptr<Ui::FlatLabel> description)528 	void setDescription(object_ptr<Ui::FlatLabel> description) {
529 		delegate()->peerListSetDescription(std::move(description));
530 	}
setSearchLoading(object_ptr<Ui::FlatLabel> loading)531 	void setSearchLoading(object_ptr<Ui::FlatLabel> loading) {
532 		delegate()->peerListSetSearchLoading(std::move(loading));
533 	}
setSearchNoResults(object_ptr<Ui::FlatLabel> noResults)534 	void setSearchNoResults(object_ptr<Ui::FlatLabel> noResults) {
535 		delegate()->peerListSetSearchNoResults(std::move(noResults));
536 	}
537 
538 private:
539 	PeerListDelegate *_delegate = nullptr;
540 	std::unique_ptr<PeerListSearchController> _searchController = nullptr;
541 
542 	const style::PeerList *_listSt = nullptr;
543 	const style::MultiSelect *_selectSt = nullptr;
544 
545 	rpl::lifetime _lifetime;
546 
547 };
548 
549 struct PeerListState {
550 	PeerListState() = default;
551 	PeerListState(PeerListState &&other) = delete;
552 	PeerListState &operator=(PeerListState &&other) = delete;
553 
554 	std::unique_ptr<PeerListController::SavedStateBase> controllerState;
555 	std::vector<not_null<PeerData*>> list;
556 	std::vector<not_null<PeerData*>> filterResults;
557 	QString searchQuery;
558 };
559 
560 class PeerListContent : public Ui::RpWidget {
561 public:
562 	PeerListContent(
563 		QWidget *parent,
564 		not_null<PeerListController*> controller);
565 
566 	struct SkipResult {
567 		int shouldMoveTo = 0;
568 		int reallyMovedTo = 0;
569 	};
570 	SkipResult selectSkip(int direction);
571 	void selectSkipPage(int height, int direction);
572 
573 	enum class Mode {
574 		Default,
575 		Custom,
576 	};
577 	void setMode(Mode mode);
578 
579 	[[nodiscard]] rpl::producer<int> selectedIndexValue() const;
580 	[[nodiscard]] bool hasSelection() const;
581 	[[nodiscard]] bool hasPressed() const;
582 	void clearSelection();
583 
584 	void searchQueryChanged(QString query);
585 	bool submitted();
586 
587 	// Interface for the controller.
588 	void appendRow(std::unique_ptr<PeerListRow> row);
589 	void appendSearchRow(std::unique_ptr<PeerListRow> row);
590 	void appendFoundRow(not_null<PeerListRow*> row);
591 	void prependRow(std::unique_ptr<PeerListRow> row);
592 	void prependRowFromSearchResult(not_null<PeerListRow*> row);
593 	PeerListRow *findRow(PeerListRowId id);
updateRow(not_null<PeerListRow * > row)594 	void updateRow(not_null<PeerListRow*> row) {
595 		updateRow(row, RowIndex());
596 	}
597 	void removeRow(not_null<PeerListRow*> row);
598 	void convertRowToSearchResult(not_null<PeerListRow*> row);
599 	int fullRowsCount() const;
600 	not_null<PeerListRow*> rowAt(int index) const;
601 	void setDescription(object_ptr<Ui::FlatLabel> description);
602 	void setSearchLoading(object_ptr<Ui::FlatLabel> loading);
603 	void setSearchNoResults(object_ptr<Ui::FlatLabel> noResults);
604 	void setAboveWidget(object_ptr<TWidget> widget);
605 	void setAboveSearchWidget(object_ptr<TWidget> widget);
606 	void setBelowWidget(object_ptr<TWidget> width);
607 	void setHideEmpty(bool hide);
608 	void refreshRows();
609 
610 	void mouseLeftGeometry();
611 
612 	void setSearchMode(PeerListSearchMode mode);
613 	void changeCheckState(
614 		not_null<PeerListRow*> row,
615 		bool checked,
616 		anim::type animated);
617 	void setRowHidden(
618 		not_null<PeerListRow*> row,
619 		bool hidden);
620 
621 	template <typename ReorderCallback>
reorderRows(ReorderCallback && callback)622 	void reorderRows(ReorderCallback &&callback) {
623 		callback(_rows.begin(), _rows.end());
624 		for (auto &searchEntity : _searchIndex) {
625 			callback(searchEntity.second.begin(), searchEntity.second.end());
626 		}
627 		refreshIndices();
628 		if (!_hiddenRows.empty()) {
629 			callback(_filterResults.begin(), _filterResults.end());
630 		}
631 		update();
632 	}
633 
634 	std::unique_ptr<PeerListState> saveState() const;
635 	void restoreState(std::unique_ptr<PeerListState> state);
636 
637 	void showRowMenu(
638 		not_null<PeerListRow*> row,
639 		bool highlightRow,
640 		Fn<void(not_null<Ui::PopupMenu*>)> destroyed);
641 
scrollToRequests()642 	auto scrollToRequests() const {
643 		return _scrollToRequests.events();
644 	}
645 
646 	~PeerListContent();
647 
648 protected:
649 	int resizeGetHeight(int newWidth) override;
650 	void visibleTopBottomUpdated(
651 		int visibleTop,
652 		int visibleBottom) override;
653 
654 	void paintEvent(QPaintEvent *e) override;
655 	void enterEventHook(QEnterEvent *e) override;
656 	void leaveEventHook(QEvent *e) override;
657 	void mouseMoveEvent(QMouseEvent *e) override;
658 	void mousePressEvent(QMouseEvent *e) override;
659 	void mouseReleaseEvent(QMouseEvent *e) override;
660 	void contextMenuEvent(QContextMenuEvent *e) override;
661 
662 private:
663 	void refreshIndices();
664 	void removeRowAtIndex(std::vector<std::unique_ptr<PeerListRow>> &from, int index);
665 	void handleNameChanged(not_null<PeerData*> peer);
666 
667 	void invalidatePixmapsCache();
668 
669 	struct RowIndex {
RowIndexRowIndex670 		RowIndex() {
671 		}
RowIndexRowIndex672 		explicit RowIndex(int value) : value(value) {
673 		}
674 		int value = -1;
675 	};
676 	friend inline bool operator==(RowIndex a, RowIndex b) {
677 		return (a.value == b.value);
678 	}
679 	friend inline bool operator!=(RowIndex a, RowIndex b) {
680 		return !(a == b);
681 	}
682 
683 	struct Selected {
SelectedSelected684 		Selected() {
685 		}
SelectedSelected686 		Selected(RowIndex index, int element)
687 		: index(index)
688 		, element(element) {
689 		}
SelectedSelected690 		Selected(int index, int element)
691 		: index(index)
692 		, element(element) {
693 		}
694 
695 		RowIndex index;
696 		int element = 0;
697 	};
698 	friend inline bool operator==(Selected a, Selected b) {
699 		return (a.index == b.index) && (a.element == b.element);
700 	}
701 	friend inline bool operator!=(Selected a, Selected b) {
702 		return !(a == b);
703 	}
704 	struct SelectedSaved {
SelectedSavedSelectedSaved705 		SelectedSaved(PeerListRowId id, Selected old)
706 		: id(id), old(old) {
707 		}
708 		PeerListRowId id = 0;
709 		Selected old;
710 	};
711 
712 	void setSelected(Selected selected);
713 	void setPressed(Selected pressed);
714 	void setContexted(Selected contexted);
715 	void restoreSelection();
716 	SelectedSaved saveSelectedData(Selected from);
717 	Selected restoreSelectedData(SelectedSaved from);
718 
719 	void selectByMouse(QPoint globalPosition);
720 	void loadProfilePhotos();
721 	void checkScrollForPreload();
722 
723 	void updateRow(not_null<PeerListRow*> row, RowIndex hint);
724 	void updateRow(RowIndex row);
725 	int getRowTop(RowIndex row) const;
726 	PeerListRow *getRow(RowIndex element);
727 	RowIndex findRowIndex(
728 		not_null<PeerListRow*> row,
729 		RowIndex hint = RowIndex());
730 	QRect getElementRect(
731 		not_null<PeerListRow*> row,
732 		RowIndex index,
733 		int element) const;
734 
735 	bool showRowMenu(
736 		RowIndex index,
737 		PeerListRow *row,
738 		QPoint globalPos,
739 		bool highlightRow,
740 		Fn<void(not_null<Ui::PopupMenu*>)> destroyed = nullptr);
741 
742 	crl::time paintRow(Painter &p, crl::time now, RowIndex index);
743 
744 	void addRowEntry(not_null<PeerListRow*> row);
745 	void addToSearchIndex(not_null<PeerListRow*> row);
746 	bool addingToSearchIndex() const;
747 	void removeFromSearchIndex(not_null<PeerListRow*> row);
748 	void setSearchQuery(const QString &query, const QString &normalizedQuery);
showingSearch()749 	bool showingSearch() const {
750 		return !_hiddenRows.empty() || !_searchQuery.isEmpty();
751 	}
shownRowsCount()752 	int shownRowsCount() const {
753 		return showingSearch() ? _filterResults.size() : _rows.size();
754 	}
755 	template <typename Callback>
756 	bool enumerateShownRows(Callback callback);
757 	template <typename Callback>
758 	bool enumerateShownRows(int from, int to, Callback callback);
759 
760 	int rowsTop() const;
761 	int labelHeight() const;
762 
763 	void clearSearchRows();
764 	void clearAllContent();
765 	void handleMouseMove(QPoint globalPosition);
766 	void mousePressReleased(Qt::MouseButton button);
767 
768 	const style::PeerList &_st;
769 	not_null<PeerListController*> _controller;
770 	PeerListSearchMode _searchMode = PeerListSearchMode::Disabled;
771 
772 	Mode _mode = Mode::Default;
773 	int _rowHeight = 0;
774 	int _visibleTop = 0;
775 	int _visibleBottom = 0;
776 
777 	Selected _selected;
778 	Selected _pressed;
779 	Selected _contexted;
780 	rpl::variable<int> _selectedIndex = -1;
781 	bool _mouseSelection = false;
782 	std::optional<QPoint> _lastMousePosition;
783 	Qt::MouseButton _pressButton = Qt::LeftButton;
784 
785 	rpl::event_stream<Ui::ScrollToRequest> _scrollToRequests;
786 
787 	std::vector<std::unique_ptr<PeerListRow>> _rows;
788 	std::map<PeerListRowId, not_null<PeerListRow*>> _rowsById;
789 	std::map<PeerData*, std::vector<not_null<PeerListRow*>>> _rowsByPeer;
790 
791 	std::map<QChar, std::vector<not_null<PeerListRow*>>> _searchIndex;
792 	QString _searchQuery;
793 	QString _normalizedSearchQuery;
794 	QString _mentionHighlight;
795 	std::vector<not_null<PeerListRow*>> _filterResults;
796 	base::flat_set<not_null<PeerListRow*>> _hiddenRows;
797 
798 	int _aboveHeight = 0;
799 	int _belowHeight = 0;
800 	bool _hideEmpty = false;
801 	object_ptr<TWidget> _aboveWidget = { nullptr };
802 	object_ptr<TWidget> _aboveSearchWidget = { nullptr };
803 	object_ptr<TWidget> _belowWidget = { nullptr };
804 	object_ptr<Ui::FlatLabel> _description = { nullptr };
805 	object_ptr<Ui::FlatLabel> _searchNoResults = { nullptr };
806 	object_ptr<Ui::FlatLabel> _searchLoading = { nullptr };
807 
808 	std::vector<std::unique_ptr<PeerListRow>> _searchRows;
809 	base::Timer _repaintByStatus;
810 	base::unique_qptr<Ui::PopupMenu> _contextMenu;
811 
812 };
813 
814 class PeerListContentDelegate : public PeerListDelegate {
815 public:
setContent(PeerListContent * content)816 	void setContent(PeerListContent *content) {
817 		_content = content;
818 	}
819 
peerListSetHideEmpty(bool hide)820 	void peerListSetHideEmpty(bool hide) override {
821 		_content->setHideEmpty(hide);
822 	}
peerListAppendRow(std::unique_ptr<PeerListRow> row)823 	void peerListAppendRow(
824 			std::unique_ptr<PeerListRow> row) override {
825 		_content->appendRow(std::move(row));
826 	}
peerListAppendSearchRow(std::unique_ptr<PeerListRow> row)827 	void peerListAppendSearchRow(
828 			std::unique_ptr<PeerListRow> row) override {
829 		_content->appendSearchRow(std::move(row));
830 	}
peerListAppendFoundRow(not_null<PeerListRow * > row)831 	void peerListAppendFoundRow(
832 			not_null<PeerListRow*> row) override {
833 		_content->appendFoundRow(row);
834 	}
peerListPrependRow(std::unique_ptr<PeerListRow> row)835 	void peerListPrependRow(
836 			std::unique_ptr<PeerListRow> row) override {
837 		_content->prependRow(std::move(row));
838 	}
peerListPrependRowFromSearchResult(not_null<PeerListRow * > row)839 	void peerListPrependRowFromSearchResult(
840 			not_null<PeerListRow*> row) override {
841 		_content->prependRowFromSearchResult(row);
842 	}
peerListFindRow(PeerListRowId id)843 	PeerListRow *peerListFindRow(PeerListRowId id) override {
844 		return _content->findRow(id);
845 	}
peerListUpdateRow(not_null<PeerListRow * > row)846 	void peerListUpdateRow(not_null<PeerListRow*> row) override {
847 		_content->updateRow(row);
848 	}
peerListRemoveRow(not_null<PeerListRow * > row)849 	void peerListRemoveRow(not_null<PeerListRow*> row) override {
850 		_content->removeRow(row);
851 	}
peerListConvertRowToSearchResult(not_null<PeerListRow * > row)852 	void peerListConvertRowToSearchResult(
853 			not_null<PeerListRow*> row) override {
854 		_content->convertRowToSearchResult(row);
855 	}
peerListSetRowChecked(not_null<PeerListRow * > row,bool checked)856 	void peerListSetRowChecked(
857 			not_null<PeerListRow*> row,
858 			bool checked) override {
859 		_content->changeCheckState(row, checked, anim::type::normal);
860 	}
peerListSetRowHidden(not_null<PeerListRow * > row,bool hidden)861 	void peerListSetRowHidden(
862 			not_null<PeerListRow*> row,
863 			bool hidden) override {
864 		_content->setRowHidden(row, hidden);
865 	}
peerListSetForeignRowChecked(not_null<PeerListRow * > row,bool checked,anim::type animated)866 	void peerListSetForeignRowChecked(
867 		not_null<PeerListRow*> row,
868 		bool checked,
869 		anim::type animated) override {
870 	}
peerListFullRowsCount()871 	int peerListFullRowsCount() override {
872 		return _content->fullRowsCount();
873 	}
peerListRowAt(int index)874 	not_null<PeerListRow*> peerListRowAt(int index) override {
875 		return _content->rowAt(index);
876 	}
peerListRefreshRows()877 	void peerListRefreshRows() override {
878 		_content->refreshRows();
879 	}
peerListSetDescription(object_ptr<Ui::FlatLabel> description)880 	void peerListSetDescription(object_ptr<Ui::FlatLabel> description) override {
881 		_content->setDescription(std::move(description));
882 	}
peerListSetSearchLoading(object_ptr<Ui::FlatLabel> loading)883 	void peerListSetSearchLoading(object_ptr<Ui::FlatLabel> loading) override {
884 		_content->setSearchLoading(std::move(loading));
885 	}
peerListSetSearchNoResults(object_ptr<Ui::FlatLabel> noResults)886 	void peerListSetSearchNoResults(object_ptr<Ui::FlatLabel> noResults) override {
887 		_content->setSearchNoResults(std::move(noResults));
888 	}
peerListSetAboveWidget(object_ptr<TWidget> aboveWidget)889 	void peerListSetAboveWidget(object_ptr<TWidget> aboveWidget) override {
890 		_content->setAboveWidget(std::move(aboveWidget));
891 	}
peerListSetAboveSearchWidget(object_ptr<TWidget> aboveWidget)892 	void peerListSetAboveSearchWidget(object_ptr<TWidget> aboveWidget) override {
893 		_content->setAboveSearchWidget(std::move(aboveWidget));
894 	}
peerListSetBelowWidget(object_ptr<TWidget> belowWidget)895 	void peerListSetBelowWidget(object_ptr<TWidget> belowWidget) override {
896 		_content->setBelowWidget(std::move(belowWidget));
897 	}
peerListSetSearchMode(PeerListSearchMode mode)898 	void peerListSetSearchMode(PeerListSearchMode mode) override {
899 		_content->setSearchMode(mode);
900 	}
peerListMouseLeftGeometry()901 	void peerListMouseLeftGeometry() override {
902 		_content->mouseLeftGeometry();
903 	}
peerListSortRows(Fn<bool (const PeerListRow & a,const PeerListRow & b)> compare)904 	void peerListSortRows(
905 			Fn<bool(const PeerListRow &a, const PeerListRow &b)> compare) override {
906 		_content->reorderRows([&](
907 				auto &&begin,
908 				auto &&end) {
909 			std::stable_sort(begin, end, [&](auto &&a, auto &&b) {
910 				return compare(*a, *b);
911 			});
912 		});
913 	}
peerListPartitionRows(Fn<bool (const PeerListRow & a)> border)914 	int peerListPartitionRows(
915 			Fn<bool(const PeerListRow &a)> border) override {
916 		auto result = 0;
917 		_content->reorderRows([&](
918 				auto &&begin,
919 				auto &&end) {
920 			auto edge = std::stable_partition(begin, end, [&](
921 					auto &&current) {
922 				return border(*current);
923 			});
924 			result = (edge - begin);
925 		});
926 		return result;
927 	}
peerListSaveState()928 	std::unique_ptr<PeerListState> peerListSaveState() const override {
929 		return _content->saveState();
930 	}
peerListRestoreState(std::unique_ptr<PeerListState> state)931 	void peerListRestoreState(
932 			std::unique_ptr<PeerListState> state) override {
933 		_content->restoreState(std::move(state));
934 	}
935 	void peerListShowRowMenu(
936 		not_null<PeerListRow*> row,
937 		bool highlightRow,
938 		Fn<void(not_null<Ui::PopupMenu*>)> destroyed = nullptr) override;
939 
940 protected:
content()941 	not_null<PeerListContent*> content() const {
942 		return _content;
943 	}
944 
945 private:
946 	PeerListContent *_content = nullptr;
947 
948 };
949 
950 class PeerListContentDelegateSimple : public PeerListContentDelegate {
951 public:
peerListSetTitle(rpl::producer<QString> title)952 	void peerListSetTitle(rpl::producer<QString> title) override {
953 	}
peerListSetAdditionalTitle(rpl::producer<QString> title)954 	void peerListSetAdditionalTitle(rpl::producer<QString> title) override {
955 	}
peerListIsRowChecked(not_null<PeerListRow * > row)956 	bool peerListIsRowChecked(not_null<PeerListRow*> row) override {
957 		return false;
958 	}
peerListSelectedRowsCount()959 	int peerListSelectedRowsCount() override {
960 		return 0;
961 	}
peerListScrollToTop()962 	void peerListScrollToTop() override {
963 	}
peerListAddSelectedPeerInBunch(not_null<PeerData * > peer)964 	void peerListAddSelectedPeerInBunch(
965 			not_null<PeerData*> peer) override {
966 		Unexpected("...DelegateSimple::peerListAddSelectedPeerInBunch");
967 	}
peerListAddSelectedRowInBunch(not_null<PeerListRow * > row)968 	void peerListAddSelectedRowInBunch(not_null<PeerListRow*> row) override {
969 		Unexpected("...DelegateSimple::peerListAddSelectedRowInBunch");
970 	}
peerListFinishSelectedRowsBunch()971 	void peerListFinishSelectedRowsBunch() override {
972 		Unexpected("...DelegateSimple::peerListFinishSelectedRowsBunch");
973 	}
peerListSetDescription(object_ptr<Ui::FlatLabel> description)974 	void peerListSetDescription(
975 			object_ptr<Ui::FlatLabel> description) override {
976 		description.destroy();
977 	}
978 
979 };
980 
981 
982 class PeerListBox
983 	: public Ui::BoxContent
984 	, public PeerListContentDelegate {
985 public:
986 	PeerListBox(
987 		QWidget*,
988 		std::unique_ptr<PeerListController> controller,
989 		Fn<void(not_null<PeerListBox*>)> init);
990 
991 	[[nodiscard]] std::vector<not_null<PeerData*>> collectSelectedRows();
992 
peerListSetTitle(rpl::producer<QString> title)993 	void peerListSetTitle(rpl::producer<QString> title) override {
994 		setTitle(std::move(title));
995 	}
peerListSetAdditionalTitle(rpl::producer<QString> title)996 	void peerListSetAdditionalTitle(rpl::producer<QString> title) override {
997 		setAdditionalTitle(std::move(title));
998 	}
999 	void peerListSetSearchMode(PeerListSearchMode mode) override;
1000 	void peerListSetRowChecked(
1001 		not_null<PeerListRow*> row,
1002 		bool checked) override;
1003 	void peerListSetForeignRowChecked(
1004 		not_null<PeerListRow*> row,
1005 		bool checked,
1006 		anim::type animated) override;
1007 	bool peerListIsRowChecked(not_null<PeerListRow*> row) override;
1008 	int peerListSelectedRowsCount() override;
1009 	void peerListScrollToTop() override;
1010 
1011 protected:
1012 	void prepare() override;
1013 	void setInnerFocus() override;
1014 
1015 	void keyPressEvent(QKeyEvent *e) override;
1016 	void resizeEvent(QResizeEvent *e) override;
1017 	void paintEvent(QPaintEvent *e) override;
1018 
1019 private:
peerListAddSelectedPeerInBunch(not_null<PeerData * > peer)1020 	void peerListAddSelectedPeerInBunch(
1021 			not_null<PeerData*> peer) override {
1022 		addSelectItem(peer, anim::type::instant);
1023 	}
peerListAddSelectedRowInBunch(not_null<PeerListRow * > row)1024 	void peerListAddSelectedRowInBunch(not_null<PeerListRow*> row) override {
1025 		addSelectItem(row, anim::type::instant);
1026 	}
1027 	void peerListFinishSelectedRowsBunch() override;
1028 
1029 	void addSelectItem(
1030 		not_null<PeerData*> peer,
1031 		anim::type animated);
1032 	void addSelectItem(
1033 		not_null<PeerListRow*> row,
1034 		anim::type animated);
1035 	void addSelectItem(
1036 		uint64 itemId,
1037 		const QString &text,
1038 		PaintRoundImageCallback paintUserpic,
1039 		anim::type animated);
1040 	void createMultiSelect();
1041 	int getTopScrollSkip() const;
1042 	void updateScrollSkips();
1043 	void searchQueryChanged(const QString &query);
1044 
1045 	object_ptr<Ui::SlideWrap<Ui::MultiSelect>> _select = { nullptr };
1046 
1047 	std::unique_ptr<PeerListController> _controller;
1048 	Fn<void(PeerListBox*)> _init;
1049 	bool _scrollBottomFixed = false;
1050 
1051 };
1052