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 #include "boxes/peer_lists_box.h"
9 
10 #include "lang/lang_keys.h"
11 #include "ui/wrap/slide_wrap.h"
12 #include "ui/wrap/vertical_layout.h"
13 #include "ui/widgets/multi_select.h"
14 #include "ui/widgets/scroll_area.h"
15 #include "main/main_session.h"
16 #include "data/data_session.h"
17 #include "data/data_peer.h"
18 #include "styles/style_boxes.h"
19 #include "styles/style_layers.h"
20 
PeerListsBox(QWidget *,std::vector<std::unique_ptr<PeerListController>> controllers,Fn<void (not_null<PeerListsBox * >)> init)21 PeerListsBox::PeerListsBox(
22 	QWidget*,
23 	std::vector<std::unique_ptr<PeerListController>> controllers,
24 	Fn<void(not_null<PeerListsBox*>)> init)
25 : _lists(makeLists(std::move(controllers)))
26 , _init(std::move(init)) {
27 	Expects(!_lists.empty());
28 }
29 
collectSelectedRows()30 auto PeerListsBox::collectSelectedRows()
31 -> std::vector<not_null<PeerData*>> {
32 	auto result = std::vector<not_null<PeerData*>>();
33 	auto items = _select
34 		? _select->entity()->getItems()
35 		: QVector<uint64>();
36 	if (!items.empty()) {
37 		result.reserve(items.size());
38 		const auto session = &firstController()->session();
39 		for (const auto itemId : items) {
40 			const auto foreign = [&] {
41 				for (const auto &list : _lists) {
42 					if (list.controller->isForeignRow(itemId)) {
43 						return true;
44 					}
45 				}
46 				return false;
47 			}();
48 			if (!foreign) {
49 				result.push_back(session->data().peer(PeerId(itemId)));
50 			}
51 		}
52 	}
53 	return result;
54 }
55 
56 
makeList(std::unique_ptr<PeerListController> controller)57 PeerListsBox::List PeerListsBox::makeList(
58 		std::unique_ptr<PeerListController> controller) {
59 	auto delegate = std::make_unique<Delegate>(this, controller.get());
60 	return {
61 		std::move(controller),
62 		std::move(delegate),
63 	};
64 }
65 
makeLists(std::vector<std::unique_ptr<PeerListController>> controllers)66 std::vector<PeerListsBox::List> PeerListsBox::makeLists(
67 		std::vector<std::unique_ptr<PeerListController>> controllers) {
68 	auto result = std::vector<List>();
69 	result.reserve(controllers.size());
70 	for (auto &controller : controllers) {
71 		result.push_back(makeList(std::move(controller)));
72 	}
73 	return result;
74 }
75 
firstController() const76 not_null<PeerListController*> PeerListsBox::firstController() const {
77 	return _lists.front().controller.get();
78 }
79 
createMultiSelect()80 void PeerListsBox::createMultiSelect() {
81 	Expects(_select == nullptr);
82 
83 	auto entity = object_ptr<Ui::MultiSelect>(
84 		this,
85 		(firstController()->selectSt()
86 			? *firstController()->selectSt()
87 			: st::defaultMultiSelect),
88 		tr::lng_participant_filter());
89 	_select.create(this, std::move(entity));
90 	_select->heightValue(
91 	) | rpl::start_with_next(
92 		[this] { updateScrollSkips(); },
93 		lifetime());
94 	_select->entity()->setSubmittedCallback([=](Qt::KeyboardModifiers) {
95 		for (const auto &list : _lists) {
96 			if (list.content->submitted()) {
97 				break;
98 			}
99 		}
100 	});
101 	_select->entity()->setQueryChangedCallback([=](const QString &query) {
102 		searchQueryChanged(query);
103 	});
104 	_select->entity()->setItemRemovedCallback([=](uint64 itemId) {
105 		for (const auto &list : _lists) {
106 			if (list.controller->handleDeselectForeignRow(itemId)) {
107 				return;
108 			}
109 		}
110 		const auto session = &firstController()->session();
111 		if (const auto peer = session->data().peerLoaded(PeerId(itemId))) {
112 			const auto id = peer->id;
113 			for (const auto &list : _lists) {
114 				if (const auto row = list.delegate->peerListFindRow(id.value)) {
115 					list.content->changeCheckState(
116 						row,
117 						false,
118 						anim::type::normal);
119 					update();
120 				}
121 				list.controller->itemDeselectedHook(peer);
122 			}
123 		}
124 	});
125 	_select->resizeToWidth(firstController()->contentWidth());
126 	_select->moveToLeft(0, 0);
127 }
128 
getTopScrollSkip() const129 int PeerListsBox::getTopScrollSkip() const {
130 	auto result = 0;
131 	if (_select && !_select->isHidden()) {
132 		result += _select->height();
133 	}
134 	return result;
135 }
136 
updateScrollSkips()137 void PeerListsBox::updateScrollSkips() {
138 	// If we show / hide the search field scroll top is fixed.
139 	// If we resize search field by bubbles scroll bottom is fixed.
140 	setInnerTopSkip(getTopScrollSkip(), _scrollBottomFixed);
141 	if (!_select->animating()) {
142 		_scrollBottomFixed = true;
143 	}
144 }
145 
prepare()146 void PeerListsBox::prepare() {
147 	auto rows = setInnerWidget(
148 		object_ptr<Ui::VerticalLayout>(this),
149 		st::boxScroll);
150 	for (auto &list : _lists) {
151 		const auto content = rows->add(object_ptr<PeerListContent>(
152 			rows,
153 			list.controller.get()));
154 		list.content = content;
155 		list.delegate->setContent(content);
156 		list.controller->setDelegate(list.delegate.get());
157 
158 		content->scrollToRequests(
159 		) | rpl::start_with_next([=](Ui::ScrollToRequest request) {
160 			const auto skip = content->y();
161 			onScrollToY(
162 				skip + request.ymin,
163 				(request.ymax >= 0) ? (skip + request.ymax) : request.ymax);
164 		}, lifetime());
165 
166 		content->selectedIndexValue(
167 		) | rpl::filter([=](int index) {
168 			return (index >= 0);
169 		}) | rpl::start_with_next([=] {
170 			for (const auto &list : _lists) {
171 				if (list.content && list.content != content) {
172 					list.content->clearSelection();
173 				}
174 			}
175 		}, lifetime());
176 	}
177 	rows->resizeToWidth(firstController()->contentWidth());
178 
179 	setDimensions(firstController()->contentWidth(), st::boxMaxListHeight);
180 	if (_select) {
181 		_select->finishAnimating();
182 		Ui::SendPendingMoveResizeEvents(_select);
183 		_scrollBottomFixed = true;
184 		onScrollToY(0);
185 	}
186 
187 	if (_init) {
188 		_init(this);
189 	}
190 }
191 
keyPressEvent(QKeyEvent * e)192 void PeerListsBox::keyPressEvent(QKeyEvent *e) {
193 	const auto skipRows = [&](int rows) {
194 		if (rows == 0) {
195 			return;
196 		}
197 		for (const auto &list : _lists) {
198 			if (list.content->hasPressed()) {
199 				return;
200 			}
201 		}
202 		const auto from = begin(_lists), till = end(_lists);
203 		auto i = from;
204 		for (; i != till; ++i) {
205 			if (i->content->hasSelection()) {
206 				break;
207 			}
208 		}
209 		if (i == till && rows < 0) {
210 			return;
211 		}
212 		if (rows > 0) {
213 			if (i == till) {
214 				i = from;
215 			}
216 			for (; i != till; ++i) {
217 				const auto result = i->content->selectSkip(rows);
218 				if (result.shouldMoveTo - result.reallyMovedTo >= rows) {
219 					continue;
220 				} else if (result.reallyMovedTo >= result.shouldMoveTo) {
221 					return;
222 				} else {
223 					rows = result.shouldMoveTo - result.reallyMovedTo;
224 				}
225 			}
226 		} else {
227 			for (++i; i != from;) {
228 				const auto result = (--i)->content->selectSkip(rows);
229 				if (result.shouldMoveTo - result.reallyMovedTo <= rows) {
230 					continue;
231 				} else if (result.reallyMovedTo <= result.shouldMoveTo) {
232 					return;
233 				} else {
234 					rows = result.shouldMoveTo - result.reallyMovedTo;
235 				}
236 			}
237 		}
238 	};
239 	const auto rowsInPage = [&] {
240 		const auto rowHeight = firstController()->computeListSt().item.height;
241 		return height() / rowHeight;
242 	};
243 	if (e->key() == Qt::Key_Down) {
244 		skipRows(1);
245 	} else if (e->key() == Qt::Key_Up) {
246 		skipRows(-1);
247 	} else if (e->key() == Qt::Key_PageDown) {
248 		skipRows(rowsInPage());
249 	} else if (e->key() == Qt::Key_PageUp) {
250 		skipRows(-rowsInPage());
251 	} else if (e->key() == Qt::Key_Escape && _select && !_select->entity()->getQuery().isEmpty()) {
252 		_select->entity()->clearQuery();
253 	} else {
254 		BoxContent::keyPressEvent(e);
255 	}
256 }
257 
searchQueryChanged(const QString & query)258 void PeerListsBox::searchQueryChanged(const QString &query) {
259 	onScrollToY(0);
260 	for (const auto &list : _lists) {
261 		list.content->searchQueryChanged(query);
262 	}
263 }
264 
resizeEvent(QResizeEvent * e)265 void PeerListsBox::resizeEvent(QResizeEvent *e) {
266 	BoxContent::resizeEvent(e);
267 
268 	if (_select) {
269 		_select->resizeToWidth(width());
270 		_select->moveToLeft(0, 0);
271 
272 		updateScrollSkips();
273 	}
274 
275 	for (const auto &list : _lists) {
276 		list.content->resizeToWidth(width());
277 	}
278 }
279 
paintEvent(QPaintEvent * e)280 void PeerListsBox::paintEvent(QPaintEvent *e) {
281 	Painter p(this);
282 
283 	const auto &bg = (firstController()->listSt()
284 		? *firstController()->listSt()
285 		: st::peerListBox).bg;
286 	for (const auto &rect : e->region()) {
287 		p.fillRect(rect, bg);
288 	}
289 }
290 
setInnerFocus()291 void PeerListsBox::setInnerFocus() {
292 	if (!_select || !_select->toggled()) {
293 		_lists.front().content->setFocus();
294 	} else {
295 		_select->entity()->setInnerFocus();
296 	}
297 }
298 
Delegate(not_null<PeerListsBox * > box,not_null<PeerListController * > controller)299 PeerListsBox::Delegate::Delegate(
300 	not_null<PeerListsBox*> box,
301 	not_null<PeerListController*> controller)
302 : _box(box)
303 , _controller(controller) {
304 }
305 
peerListSetTitle(rpl::producer<QString> title)306 void PeerListsBox::Delegate::peerListSetTitle(rpl::producer<QString> title) {
307 }
308 
peerListSetAdditionalTitle(rpl::producer<QString> title)309 void PeerListsBox::Delegate::peerListSetAdditionalTitle(
310 	rpl::producer<QString> title) {
311 }
312 
peerListSetRowChecked(not_null<PeerListRow * > row,bool checked)313 void PeerListsBox::Delegate::peerListSetRowChecked(
314 		not_null<PeerListRow*> row,
315 		bool checked) {
316 	if (checked) {
317 		_box->addSelectItem(row, anim::type::normal);
318 		PeerListContentDelegate::peerListSetRowChecked(row, checked);
319 		peerListUpdateRow(row);
320 
321 		// This call deletes row from _searchRows.
322 		_box->_select->entity()->clearQuery();
323 	} else {
324 		// The itemRemovedCallback will call changeCheckState() here.
325 		_box->_select->entity()->removeItem(row->id());
326 		peerListUpdateRow(row);
327 	}
328 }
329 
peerListSetForeignRowChecked(not_null<PeerListRow * > row,bool checked,anim::type animated)330 void PeerListsBox::Delegate::peerListSetForeignRowChecked(
331 		not_null<PeerListRow*> row,
332 		bool checked,
333 		anim::type animated) {
334 	if (checked) {
335 		_box->addSelectItem(row, animated);
336 
337 		// This call deletes row from _searchRows.
338 		_box->_select->entity()->clearQuery();
339 	} else {
340 		// The itemRemovedCallback will call changeCheckState() here.
341 		_box->_select->entity()->removeItem(row->id());
342 	}
343 }
344 
peerListScrollToTop()345 void PeerListsBox::Delegate::peerListScrollToTop() {
346 	_box->onScrollToY(0);
347 }
348 
peerListSetSearchMode(PeerListSearchMode mode)349 void PeerListsBox::Delegate::peerListSetSearchMode(PeerListSearchMode mode) {
350 	PeerListContentDelegate::peerListSetSearchMode(mode);
351 	_box->setSearchMode(mode);
352 }
353 
setSearchMode(PeerListSearchMode mode)354 void PeerListsBox::setSearchMode(PeerListSearchMode mode) {
355 	auto selectVisible = (mode != PeerListSearchMode::Disabled);
356 	if (selectVisible && !_select) {
357 		createMultiSelect();
358 		_select->toggle(!selectVisible, anim::type::instant);
359 	}
360 	if (_select) {
361 		_select->toggle(selectVisible, anim::type::normal);
362 		_scrollBottomFixed = false;
363 		setInnerFocus();
364 	}
365 }
366 
peerListFinishSelectedRowsBunch()367 void PeerListsBox::Delegate::peerListFinishSelectedRowsBunch() {
368 	Expects(_box->_select != nullptr);
369 
370 	_box->_select->entity()->finishItemsBunch();
371 }
372 
peerListIsRowChecked(not_null<PeerListRow * > row)373 bool PeerListsBox::Delegate::peerListIsRowChecked(
374 		not_null<PeerListRow*> row) {
375 	return _box->_select
376 		? _box->_select->entity()->hasItem(row->id())
377 		: false;
378 }
379 
peerListSelectedRowsCount()380 int PeerListsBox::Delegate::peerListSelectedRowsCount() {
381 	return _box->_select ? _box->_select->entity()->getItemsCount() : 0;
382 }
383 
addSelectItem(not_null<PeerData * > peer,anim::type animated)384 void PeerListsBox::addSelectItem(
385 		not_null<PeerData*> peer,
386 		anim::type animated) {
387 	addSelectItem(
388 		peer->id.value,
389 		peer->shortName(),
390 		PaintUserpicCallback(peer, false),
391 		animated);
392 }
393 
addSelectItem(not_null<PeerListRow * > row,anim::type animated)394 void PeerListsBox::addSelectItem(
395 		not_null<PeerListRow*> row,
396 		anim::type animated) {
397 	addSelectItem(
398 		row->id(),
399 		row->generateShortName(),
400 		row->generatePaintUserpicCallback(),
401 		animated);
402 }
403 
addSelectItem(uint64 itemId,const QString & text,Ui::MultiSelect::PaintRoundImage paintUserpic,anim::type animated)404 void PeerListsBox::addSelectItem(
405 		uint64 itemId,
406 		const QString &text,
407 		Ui::MultiSelect::PaintRoundImage paintUserpic,
408 		anim::type animated) {
409 	if (!_select) {
410 		createMultiSelect();
411 		_select->hide(anim::type::instant);
412 	}
413 	const auto &activeBg = (firstController()->selectSt()
414 		? *firstController()->selectSt()
415 		: st::defaultMultiSelect).item.textActiveBg;
416 	if (animated == anim::type::instant) {
417 		_select->entity()->addItemInBunch(
418 			itemId,
419 			text,
420 			activeBg,
421 			std::move(paintUserpic));
422 	} else {
423 		_select->entity()->addItem(
424 			itemId,
425 			text,
426 			activeBg,
427 			std::move(paintUserpic));
428 	}
429 }
430