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