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_list_box.h"
9
10 #include "main/main_session.h"
11 #include "mainwidget.h"
12 #include "ui/widgets/multi_select.h"
13 #include "ui/widgets/labels.h"
14 #include "ui/widgets/scroll_area.h"
15 #include "ui/widgets/popup_menu.h"
16 #include "ui/effects/round_checkbox.h"
17 #include "ui/effects/ripple_animation.h"
18 #include "ui/empty_userpic.h"
19 #include "ui/wrap/slide_wrap.h"
20 #include "ui/text/text_options.h"
21 #include "lang/lang_keys.h"
22 #include "storage/file_download.h"
23 #include "data/data_peer_values.h"
24 #include "data/data_chat.h"
25 #include "data/data_session.h"
26 #include "data/data_changes.h"
27 #include "base/unixtime.h"
28 #include "styles/style_layers.h"
29 #include "styles/style_boxes.h"
30 #include "styles/style_dialogs.h"
31 #include "styles/style_widgets.h"
32
33 #include <rpl/range.h>
34
PaintUserpicCallback(not_null<PeerData * > peer,bool respectSavedMessagesChat)35 PaintRoundImageCallback PaintUserpicCallback(
36 not_null<PeerData*> peer,
37 bool respectSavedMessagesChat) {
38 if (respectSavedMessagesChat) {
39 if (peer->isSelf()) {
40 return [](Painter &p, int x, int y, int outerWidth, int size) {
41 Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
42 };
43 } else if (peer->isRepliesChat()) {
44 return [](Painter &p, int x, int y, int outerWidth, int size) {
45 Ui::EmptyUserpic::PaintRepliesMessages(p, x, y, outerWidth, size);
46 };
47 }
48 }
49 auto userpic = std::shared_ptr<Data::CloudImageView>();
50 return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
51 peer->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
52 };
53 }
54
PeerListBox(QWidget *,std::unique_ptr<PeerListController> controller,Fn<void (not_null<PeerListBox * >)> init)55 PeerListBox::PeerListBox(
56 QWidget*,
57 std::unique_ptr<PeerListController> controller,
58 Fn<void(not_null<PeerListBox*>)> init)
59 : _controller(std::move(controller))
60 , _init(std::move(init)) {
61 Expects(_controller != nullptr);
62 }
63
createMultiSelect()64 void PeerListBox::createMultiSelect() {
65 Expects(_select == nullptr);
66
67 auto entity = object_ptr<Ui::MultiSelect>(
68 this,
69 (_controller->selectSt()
70 ? *_controller->selectSt()
71 : st::defaultMultiSelect),
72 tr::lng_participant_filter());
73 _select.create(this, std::move(entity));
74 _select->heightValue(
75 ) | rpl::start_with_next(
76 [this] { updateScrollSkips(); },
77 lifetime());
78 _select->entity()->setSubmittedCallback([=](Qt::KeyboardModifiers) {
79 content()->submitted();
80 });
81 _select->entity()->setQueryChangedCallback([=](const QString &query) {
82 searchQueryChanged(query);
83 });
84 _select->entity()->setItemRemovedCallback([=](uint64 itemId) {
85 if (_controller->handleDeselectForeignRow(itemId)) {
86 return;
87 }
88 if (const auto peer = _controller->session().data().peerLoaded(PeerId(itemId))) {
89 if (const auto row = peerListFindRow(itemId)) {
90 content()->changeCheckState(row, false, anim::type::normal);
91 update();
92 }
93 _controller->itemDeselectedHook(peer);
94 }
95 });
96 _select->resizeToWidth(_controller->contentWidth());
97 _select->moveToLeft(0, 0);
98 }
99
getTopScrollSkip() const100 int PeerListBox::getTopScrollSkip() const {
101 auto result = 0;
102 if (_select && !_select->isHidden()) {
103 result += _select->height();
104 }
105 return result;
106 }
107
updateScrollSkips()108 void PeerListBox::updateScrollSkips() {
109 // If we show / hide the search field scroll top is fixed.
110 // If we resize search field by bubbles scroll bottom is fixed.
111 setInnerTopSkip(getTopScrollSkip(), _scrollBottomFixed);
112 if (!_select->animating()) {
113 _scrollBottomFixed = true;
114 }
115 }
116
prepare()117 void PeerListBox::prepare() {
118 setContent(setInnerWidget(
119 object_ptr<PeerListContent>(
120 this,
121 _controller.get()),
122 st::boxScroll));
123 content()->resizeToWidth(_controller->contentWidth());
124
125 _controller->setDelegate(this);
126
127 _controller->boxHeightValue(
128 ) | rpl::start_with_next([=](int height) {
129 setDimensions(_controller->contentWidth(), height);
130 }, lifetime());
131
132 if (_select) {
133 _select->finishAnimating();
134 Ui::SendPendingMoveResizeEvents(_select);
135 _scrollBottomFixed = true;
136 onScrollToY(0);
137 }
138
139 content()->scrollToRequests(
140 ) | rpl::start_with_next([this](Ui::ScrollToRequest request) {
141 onScrollToY(request.ymin, request.ymax);
142 }, lifetime());
143
144 if (_init) {
145 _init(this);
146 }
147 }
148
keyPressEvent(QKeyEvent * e)149 void PeerListBox::keyPressEvent(QKeyEvent *e) {
150 if (e->key() == Qt::Key_Down) {
151 content()->selectSkip(1);
152 } else if (e->key() == Qt::Key_Up) {
153 content()->selectSkip(-1);
154 } else if (e->key() == Qt::Key_PageDown) {
155 content()->selectSkipPage(height(), 1);
156 } else if (e->key() == Qt::Key_PageUp) {
157 content()->selectSkipPage(height(), -1);
158 } else if (e->key() == Qt::Key_Escape && _select && !_select->entity()->getQuery().isEmpty()) {
159 _select->entity()->clearQuery();
160 } else {
161 BoxContent::keyPressEvent(e);
162 }
163 }
164
searchQueryChanged(const QString & query)165 void PeerListBox::searchQueryChanged(const QString &query) {
166 onScrollToY(0);
167 content()->searchQueryChanged(query);
168 }
169
resizeEvent(QResizeEvent * e)170 void PeerListBox::resizeEvent(QResizeEvent *e) {
171 BoxContent::resizeEvent(e);
172
173 if (_select) {
174 _select->resizeToWidth(width());
175 _select->moveToLeft(0, 0);
176
177 updateScrollSkips();
178 }
179
180 content()->resizeToWidth(width());
181 }
182
paintEvent(QPaintEvent * e)183 void PeerListBox::paintEvent(QPaintEvent *e) {
184 Painter p(this);
185
186 const auto &bg = (_controller->listSt()
187 ? *_controller->listSt()
188 : st::peerListBox).bg;
189 for (const auto &rect : e->region()) {
190 p.fillRect(rect, bg);
191 }
192 }
193
setInnerFocus()194 void PeerListBox::setInnerFocus() {
195 if (!_select || !_select->toggled()) {
196 content()->setFocus();
197 } else {
198 _select->entity()->setInnerFocus();
199 }
200 }
201
peerListSetRowChecked(not_null<PeerListRow * > row,bool checked)202 void PeerListBox::peerListSetRowChecked(
203 not_null<PeerListRow*> row,
204 bool checked) {
205 if (checked) {
206 addSelectItem(row, anim::type::normal);
207 PeerListContentDelegate::peerListSetRowChecked(row, checked);
208 peerListUpdateRow(row);
209
210 // This call deletes row from _searchRows.
211 _select->entity()->clearQuery();
212 } else {
213 // The itemRemovedCallback will call changeCheckState() here.
214 _select->entity()->removeItem(row->id());
215 peerListUpdateRow(row);
216 }
217 }
218
peerListSetForeignRowChecked(not_null<PeerListRow * > row,bool checked,anim::type animated)219 void PeerListBox::peerListSetForeignRowChecked(
220 not_null<PeerListRow*> row,
221 bool checked,
222 anim::type animated) {
223 if (checked) {
224 addSelectItem(row, animated);
225
226 // This call deletes row from _searchRows.
227 _select->entity()->clearQuery();
228 } else {
229 // The itemRemovedCallback will call changeCheckState() here.
230 _select->entity()->removeItem(row->id());
231 }
232 }
233
peerListScrollToTop()234 void PeerListBox::peerListScrollToTop() {
235 onScrollToY(0);
236 }
237
peerListSetSearchMode(PeerListSearchMode mode)238 void PeerListBox::peerListSetSearchMode(PeerListSearchMode mode) {
239 PeerListContentDelegate::peerListSetSearchMode(mode);
240
241 auto selectVisible = (mode != PeerListSearchMode::Disabled);
242 if (selectVisible && !_select) {
243 createMultiSelect();
244 _select->toggle(!selectVisible, anim::type::instant);
245 }
246 if (_select) {
247 _select->toggle(selectVisible, anim::type::normal);
248 _scrollBottomFixed = false;
249 setInnerFocus();
250 }
251 }
252
PeerListController(std::unique_ptr<PeerListSearchController> searchController)253 PeerListController::PeerListController(std::unique_ptr<PeerListSearchController> searchController) : _searchController(std::move(searchController)) {
254 if (_searchController) {
255 _searchController->setDelegate(this);
256 }
257 }
258
computeListSt() const259 const style::PeerList &PeerListController::computeListSt() const {
260 return _listSt ? *_listSt : st::peerListBox;
261 }
262
computeSelectSt() const263 const style::MultiSelect &PeerListController::computeSelectSt() const {
264 return _selectSt ? *_selectSt : st::defaultMultiSelect;
265 }
266
hasComplexSearch() const267 bool PeerListController::hasComplexSearch() const {
268 return (_searchController != nullptr);
269 }
270
search(const QString & query)271 void PeerListController::search(const QString &query) {
272 Expects(hasComplexSearch());
273
274 _searchController->searchQuery(query);
275 }
276
peerListSearchAddRow(not_null<PeerData * > peer)277 void PeerListController::peerListSearchAddRow(not_null<PeerData*> peer) {
278 if (auto row = delegate()->peerListFindRow(peer->id.value)) {
279 Assert(row->id() == row->peer()->id.value);
280 delegate()->peerListAppendFoundRow(row);
281 } else if (auto row = createSearchRow(peer)) {
282 Assert(row->id() == row->peer()->id.value);
283 delegate()->peerListAppendSearchRow(std::move(row));
284 }
285 }
286
peerListSearchRefreshRows()287 void PeerListController::peerListSearchRefreshRows() {
288 delegate()->peerListRefreshRows();
289 }
290
onlineCountValue() const291 rpl::producer<int> PeerListController::onlineCountValue() const {
292 return rpl::single(0);
293 }
294
setDescriptionText(const QString & text)295 void PeerListController::setDescriptionText(const QString &text) {
296 if (text.isEmpty()) {
297 setDescription(nullptr);
298 } else {
299 setDescription(object_ptr<Ui::FlatLabel>(nullptr, text, computeListSt().about));
300 }
301 }
302
setSearchLoadingText(const QString & text)303 void PeerListController::setSearchLoadingText(const QString &text) {
304 if (text.isEmpty()) {
305 setSearchLoading(nullptr);
306 } else {
307 setSearchLoading(object_ptr<Ui::FlatLabel>(nullptr, text, st::membersAbout));
308 }
309 }
310
setSearchNoResultsText(const QString & text)311 void PeerListController::setSearchNoResultsText(const QString &text) {
312 if (text.isEmpty()) {
313 setSearchNoResults(nullptr);
314 } else {
315 setSearchNoResults(object_ptr<Ui::FlatLabel>(nullptr, text, st::membersAbout));
316 }
317 }
318
rowContextMenu(QWidget * parent,not_null<PeerListRow * > row)319 base::unique_qptr<Ui::PopupMenu> PeerListController::rowContextMenu(
320 QWidget *parent,
321 not_null<PeerListRow*> row) {
322 return nullptr;
323 }
324
saveState() const325 std::unique_ptr<PeerListState> PeerListController::saveState() const {
326 return delegate()->peerListSaveState();
327 }
328
restoreState(std::unique_ptr<PeerListState> state)329 void PeerListController::restoreState(
330 std::unique_ptr<PeerListState> state) {
331 delegate()->peerListRestoreState(std::move(state));
332 }
333
contentWidth() const334 int PeerListController::contentWidth() const {
335 return st::boxWideWidth;
336 }
337
boxHeightValue() const338 rpl::producer<int> PeerListController::boxHeightValue() const {
339 return rpl::single(st::boxMaxListHeight);
340 }
341
descriptionTopSkipMin() const342 int PeerListController::descriptionTopSkipMin() const {
343 return computeListSt().item.height;
344 }
345
addSelectItem(not_null<PeerData * > peer,anim::type animated)346 void PeerListBox::addSelectItem(
347 not_null<PeerData*> peer,
348 anim::type animated) {
349 const auto respect = _controller->respectSavedMessagesChat();
350 const auto text = (respect && peer->isSelf())
351 ? tr::lng_saved_short(tr::now)
352 : (respect && peer->isRepliesChat())
353 ? tr::lng_replies_messages(tr::now)
354 : peer->shortName();
355 addSelectItem(
356 peer->id.value,
357 text,
358 PaintUserpicCallback(peer, respect),
359 animated);
360 }
361
addSelectItem(not_null<PeerListRow * > row,anim::type animated)362 void PeerListBox::addSelectItem(
363 not_null<PeerListRow*> row,
364 anim::type animated) {
365 addSelectItem(
366 row->id(),
367 row->generateShortName(),
368 row->generatePaintUserpicCallback(),
369 animated);
370 }
371
addSelectItem(uint64 itemId,const QString & text,Ui::MultiSelect::PaintRoundImage paintUserpic,anim::type animated)372 void PeerListBox::addSelectItem(
373 uint64 itemId,
374 const QString &text,
375 Ui::MultiSelect::PaintRoundImage paintUserpic,
376 anim::type animated) {
377 if (!_select) {
378 createMultiSelect();
379 _select->hide(anim::type::instant);
380 }
381 const auto &activeBg = (_controller->selectSt()
382 ? *_controller->selectSt()
383 : st::defaultMultiSelect).item.textActiveBg;
384 if (animated == anim::type::instant) {
385 _select->entity()->addItemInBunch(
386 itemId,
387 text,
388 activeBg,
389 std::move(paintUserpic));
390 } else {
391 _select->entity()->addItem(
392 itemId,
393 text,
394 activeBg,
395 std::move(paintUserpic));
396 }
397 }
398
peerListFinishSelectedRowsBunch()399 void PeerListBox::peerListFinishSelectedRowsBunch() {
400 Expects(_select != nullptr);
401
402 _select->entity()->finishItemsBunch();
403 }
404
peerListIsRowChecked(not_null<PeerListRow * > row)405 bool PeerListBox::peerListIsRowChecked(not_null<PeerListRow*> row) {
406 return _select ? _select->entity()->hasItem(row->id()) : false;
407 }
408
peerListSelectedRowsCount()409 int PeerListBox::peerListSelectedRowsCount() {
410 return _select ? _select->entity()->getItemsCount() : 0;
411 }
412
collectSelectedRows()413 auto PeerListBox::collectSelectedRows()
414 -> std::vector<not_null<PeerData*>> {
415 auto result = std::vector<not_null<PeerData*>>();
416 auto items = _select
417 ? _select->entity()->getItems()
418 : QVector<uint64>();
419 if (!items.empty()) {
420 result.reserve(items.size());
421 for (const auto itemId : items) {
422 if (!_controller->isForeignRow(itemId)) {
423 result.push_back(_controller->session().data().peer(PeerId(itemId)));
424 }
425 }
426 }
427 return result;
428 }
429
PeerListRow(not_null<PeerData * > peer)430 PeerListRow::PeerListRow(not_null<PeerData*> peer)
431 : PeerListRow(peer, peer->id.value) {
432 }
433
PeerListRow(not_null<PeerData * > peer,PeerListRowId id)434 PeerListRow::PeerListRow(not_null<PeerData*> peer, PeerListRowId id)
435 : _id(id)
436 , _peer(peer)
437 , _hidden(false)
438 , _initialized(false)
439 , _isSearchResult(false)
440 , _isSavedMessagesChat(false)
441 , _isRepliesMessagesChat(false) {
442 }
443
PeerListRow(PeerListRowId id)444 PeerListRow::PeerListRow(PeerListRowId id)
445 : _id(id)
446 , _hidden(false)
447 , _initialized(false)
448 , _isSearchResult(false)
449 , _isSavedMessagesChat(false)
450 , _isRepliesMessagesChat(false) {
451 }
452
453 PeerListRow::~PeerListRow() = default;
454
checked() const455 bool PeerListRow::checked() const {
456 return _checkbox && _checkbox->checked();
457 }
458
setCustomStatus(const QString & status,bool active)459 void PeerListRow::setCustomStatus(const QString &status, bool active) {
460 setStatusText(status);
461 _statusType = active ? StatusType::CustomActive : StatusType::Custom;
462 _statusValidTill = 0;
463 }
464
clearCustomStatus()465 void PeerListRow::clearCustomStatus() {
466 _statusType = StatusType::Online;
467 refreshStatus();
468 }
469
refreshStatus()470 void PeerListRow::refreshStatus() {
471 if (!_initialized
472 || special()
473 || _statusType == StatusType::Custom
474 || _statusType == StatusType::CustomActive) {
475 return;
476 }
477 _statusType = StatusType::LastSeen;
478 _statusValidTill = 0;
479 if (auto user = peer()->asUser()) {
480 if (_isSavedMessagesChat) {
481 setStatusText(tr::lng_saved_forward_here(tr::now));
482 } else {
483 auto time = base::unixtime::now();
484 setStatusText(Data::OnlineText(user, time));
485 if (Data::OnlineTextActive(user, time)) {
486 _statusType = StatusType::Online;
487 }
488 _statusValidTill = crl::now()
489 + Data::OnlineChangeTimeout(user, time);
490 }
491 } else if (auto chat = peer()->asChat()) {
492 if (!chat->amIn()) {
493 setStatusText(tr::lng_chat_status_unaccessible(tr::now));
494 } else if (chat->count > 0) {
495 setStatusText(tr::lng_chat_status_members(tr::now, lt_count_decimal, chat->count));
496 } else {
497 setStatusText(tr::lng_group_status(tr::now));
498 }
499 } else if (peer()->isMegagroup()) {
500 setStatusText(tr::lng_group_status(tr::now));
501 } else if (peer()->isChannel()) {
502 setStatusText(tr::lng_channel_status(tr::now));
503 }
504 }
505
refreshStatusTime() const506 crl::time PeerListRow::refreshStatusTime() const {
507 return _statusValidTill;
508 }
509
refreshName(const style::PeerListItem & st)510 void PeerListRow::refreshName(const style::PeerListItem &st) {
511 if (!_initialized) {
512 return;
513 }
514 const auto text = _isSavedMessagesChat
515 ? tr::lng_saved_messages(tr::now)
516 : _isRepliesMessagesChat
517 ? tr::lng_replies_messages(tr::now)
518 : generateName();
519 _name.setText(st.nameStyle, text, Ui::NameTextOptions());
520 }
521
elementsCount() const522 int PeerListRow::elementsCount() const {
523 return 1;
524 }
525
elementGeometry(int element,int outerWidth) const526 QRect PeerListRow::elementGeometry(int element, int outerWidth) const {
527 if (element != 1) {
528 return QRect();
529 }
530 const auto size = rightActionSize();
531 if (size.isEmpty()) {
532 return QRect();
533 }
534 const auto margins = rightActionMargins();
535 const auto right = margins.right();
536 const auto top = margins.top();
537 const auto left = outerWidth - right - size.width();
538 return QRect(QPoint(left, top), size);
539 }
540
elementDisabled(int element) const541 bool PeerListRow::elementDisabled(int element) const {
542 return (element == 1) && rightActionDisabled();
543 }
544
elementOnlySelect(int element) const545 bool PeerListRow::elementOnlySelect(int element) const {
546 return false;
547 }
548
elementAddRipple(int element,QPoint point,Fn<void ()> updateCallback)549 void PeerListRow::elementAddRipple(
550 int element,
551 QPoint point,
552 Fn<void()> updateCallback) {
553 if (element == 1) {
554 rightActionAddRipple(point, std::move(updateCallback));
555 }
556 }
557
elementsStopLastRipple()558 void PeerListRow::elementsStopLastRipple() {
559 rightActionStopLastRipple();
560 }
561
elementsPaint(Painter & p,int outerWidth,bool selected,int selectedElement)562 void PeerListRow::elementsPaint(
563 Painter &p,
564 int outerWidth,
565 bool selected,
566 int selectedElement) {
567 const auto geometry = elementGeometry(1, outerWidth);
568 if (!geometry.isEmpty()) {
569 rightActionPaint(
570 p,
571 geometry.x(),
572 geometry.y(),
573 outerWidth,
574 selected,
575 (selectedElement == 1));
576 }
577 }
578
generateName()579 QString PeerListRow::generateName() {
580 return peer()->name;
581 }
582
generateShortName()583 QString PeerListRow::generateShortName() {
584 return _isSavedMessagesChat
585 ? tr::lng_saved_short(tr::now)
586 : _isRepliesMessagesChat
587 ? tr::lng_replies_messages(tr::now)
588 : peer()->shortName();
589 }
590
ensureUserpicView()591 std::shared_ptr<Data::CloudImageView> &PeerListRow::ensureUserpicView() {
592 if (!_userpic) {
593 _userpic = peer()->createUserpicView();
594 }
595 return _userpic;
596 }
597
generatePaintUserpicCallback()598 PaintRoundImageCallback PeerListRow::generatePaintUserpicCallback() {
599 const auto saved = _isSavedMessagesChat;
600 const auto replies = _isRepliesMessagesChat;
601 const auto peer = this->peer();
602 auto userpic = saved ? nullptr : ensureUserpicView();
603 return [=](Painter &p, int x, int y, int outerWidth, int size) mutable {
604 if (saved) {
605 Ui::EmptyUserpic::PaintSavedMessages(p, x, y, outerWidth, size);
606 } else if (replies) {
607 Ui::EmptyUserpic::PaintRepliesMessages(p, x, y, outerWidth, size);
608 } else {
609 peer->paintUserpicLeft(p, userpic, x, y, outerWidth, size);
610 }
611 };
612 }
613
invalidatePixmapsCache()614 void PeerListRow::invalidatePixmapsCache() {
615 if (_checkbox) {
616 _checkbox->invalidateCache();
617 }
618 }
619
nameIconWidth() const620 int PeerListRow::nameIconWidth() const {
621 return (special() || !_peer->isVerified())
622 ? 0
623 : st::dialogsVerifiedIcon.width();
624 }
625
paintNameIcon(Painter & p,int x,int y,int outerWidth,bool selected)626 void PeerListRow::paintNameIcon(
627 Painter &p,
628 int x,
629 int y,
630 int outerWidth,
631 bool selected) {
632 st::dialogsVerifiedIcon.paint(p, x, y, outerWidth);
633 }
634
paintStatusText(Painter & p,const style::PeerListItem & st,int x,int y,int availableWidth,int outerWidth,bool selected)635 void PeerListRow::paintStatusText(
636 Painter &p,
637 const style::PeerListItem &st,
638 int x,
639 int y,
640 int availableWidth,
641 int outerWidth,
642 bool selected) {
643 auto statusHasOnlineColor = (_statusType == PeerListRow::StatusType::Online)
644 || (_statusType == PeerListRow::StatusType::CustomActive);
645 p.setFont(st::contactsStatusFont);
646 p.setPen(statusHasOnlineColor ? st.statusFgActive : (selected ? st.statusFgOver : st.statusFg));
647 _status.drawLeftElided(p, x, y, availableWidth, outerWidth);
648 }
649
650 template <typename MaskGenerator, typename UpdateCallback>
addRipple(const style::PeerListItem & st,MaskGenerator && maskGenerator,QPoint point,UpdateCallback && updateCallback)651 void PeerListRow::addRipple(const style::PeerListItem &st, MaskGenerator &&maskGenerator, QPoint point, UpdateCallback &&updateCallback) {
652 if (!_ripple) {
653 auto mask = maskGenerator();
654 if (mask.isNull()) {
655 return;
656 }
657 _ripple = std::make_unique<Ui::RippleAnimation>(st.button.ripple, std::move(mask), std::forward<UpdateCallback>(updateCallback));
658 }
659 _ripple->add(point);
660 }
661
stopLastRipple()662 void PeerListRow::stopLastRipple() {
663 if (_ripple) {
664 _ripple->lastStop();
665 }
666 }
667
paintRipple(Painter & p,int x,int y,int outerWidth)668 void PeerListRow::paintRipple(Painter &p, int x, int y, int outerWidth) {
669 if (_ripple) {
670 _ripple->paint(p, x, y, outerWidth);
671 if (_ripple->empty()) {
672 _ripple.reset();
673 }
674 }
675 }
676
paintUserpic(Painter & p,const style::PeerListItem & st,int x,int y,int outerWidth)677 void PeerListRow::paintUserpic(
678 Painter &p,
679 const style::PeerListItem &st,
680 int x,
681 int y,
682 int outerWidth) {
683 if (_disabledState == State::DisabledChecked) {
684 paintDisabledCheckUserpic(p, st, x, y, outerWidth);
685 } else if (_checkbox) {
686 _checkbox->paint(p, x, y, outerWidth);
687 } else if (const auto callback = generatePaintUserpicCallback()) {
688 callback(p, x, y, outerWidth, st.photoSize);
689 }
690 }
691
692 // Emulates Ui::RoundImageCheckbox::paint() in a checked state.
paintDisabledCheckUserpic(Painter & p,const style::PeerListItem & st,int x,int y,int outerWidth) const693 void PeerListRow::paintDisabledCheckUserpic(
694 Painter &p,
695 const style::PeerListItem &st,
696 int x,
697 int y,
698 int outerWidth) const {
699 auto userpicRadius = st.checkbox.imageSmallRadius;
700 auto userpicShift = st.checkbox.imageRadius - userpicRadius;
701 auto userpicDiameter = st.checkbox.imageRadius * 2;
702 auto userpicLeft = x + userpicShift;
703 auto userpicTop = y + userpicShift;
704 auto userpicEllipse = style::rtlrect(x, y, userpicDiameter, userpicDiameter, outerWidth);
705 auto userpicBorderPen = st.disabledCheckFg->p;
706 userpicBorderPen.setWidth(st.checkbox.selectWidth);
707
708 auto iconDiameter = st.checkbox.check.size;
709 auto iconLeft = x + userpicDiameter + st.checkbox.selectWidth - iconDiameter;
710 auto iconTop = y + userpicDiameter + st.checkbox.selectWidth - iconDiameter;
711 auto iconEllipse = style::rtlrect(iconLeft, iconTop, iconDiameter, iconDiameter, outerWidth);
712 auto iconBorderPen = st.checkbox.check.border->p;
713 iconBorderPen.setWidth(st.checkbox.selectWidth);
714
715 if (_isSavedMessagesChat) {
716 Ui::EmptyUserpic::PaintSavedMessages(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
717 } else if (_isRepliesMessagesChat) {
718 Ui::EmptyUserpic::PaintRepliesMessages(p, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
719 } else {
720 peer()->paintUserpicLeft(p, _userpic, userpicLeft, userpicTop, outerWidth, userpicRadius * 2);
721 }
722
723 {
724 PainterHighQualityEnabler hq(p);
725
726 p.setPen(userpicBorderPen);
727 p.setBrush(Qt::NoBrush);
728 p.drawEllipse(userpicEllipse);
729
730 p.setPen(iconBorderPen);
731 p.setBrush(st.disabledCheckFg);
732 p.drawEllipse(iconEllipse);
733 }
734
735 st.checkbox.check.check.paint(p, iconEllipse.topLeft(), outerWidth);
736 }
737
setStatusText(const QString & text)738 void PeerListRow::setStatusText(const QString &text) {
739 _status.setText(st::defaultTextStyle, text, Ui::NameTextOptions());
740 }
741
checkedRatio()742 float64 PeerListRow::checkedRatio() {
743 return _checkbox ? _checkbox->checkedAnimationRatio() : 0.;
744 }
745
lazyInitialize(const style::PeerListItem & st)746 void PeerListRow::lazyInitialize(const style::PeerListItem &st) {
747 if (_initialized) {
748 return;
749 }
750 _initialized = true;
751 refreshName(st);
752 refreshStatus();
753 }
754
createCheckbox(const style::RoundImageCheckbox & st,Fn<void ()> updateCallback)755 void PeerListRow::createCheckbox(
756 const style::RoundImageCheckbox &st,
757 Fn<void()> updateCallback) {
758 _checkbox = std::make_unique<Ui::RoundImageCheckbox>(
759 st,
760 std::move(updateCallback),
761 generatePaintUserpicCallback());
762 }
763
setCheckedInternal(bool checked,anim::type animated)764 void PeerListRow::setCheckedInternal(bool checked, anim::type animated) {
765 Expects(_checkbox != nullptr);
766
767 _checkbox->setChecked(checked, animated);
768 }
769
finishCheckedAnimation()770 void PeerListRow::finishCheckedAnimation() {
771 _checkbox->setChecked(_checkbox->checked(), anim::type::instant);
772 }
773
PeerListContent(QWidget * parent,not_null<PeerListController * > controller)774 PeerListContent::PeerListContent(
775 QWidget *parent,
776 not_null<PeerListController*> controller)
777 : RpWidget(parent)
778 , _st(controller->computeListSt())
779 , _controller(controller)
780 , _rowHeight(_st.item.height) {
781 _controller->session().downloaderTaskFinished(
782 ) | rpl::start_with_next([=] {
783 update();
784 }, lifetime());
785
786 using UpdateFlag = Data::PeerUpdate::Flag;
787 _controller->session().changes().peerUpdates(
788 UpdateFlag::Name | UpdateFlag::Photo
789 ) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
790 if (update.flags & UpdateFlag::Name) {
791 handleNameChanged(update.peer);
792 }
793 if (update.flags & UpdateFlag::Photo) {
794 this->update();
795 }
796 }, lifetime());
797
798 style::PaletteChanged(
799 ) | rpl::start_with_next([=] {
800 invalidatePixmapsCache();
801 }, lifetime());
802
803 _repaintByStatus.setCallback([this] { update(); });
804 }
805
setMode(Mode mode)806 void PeerListContent::setMode(Mode mode) {
807 if (mode == Mode::Default && _mode == Mode::Default) {
808 return;
809 }
810 _mode = mode;
811 switch (_mode) {
812 case Mode::Default:
813 _rowHeight = _st.item.height;
814 break;
815 case Mode::Custom:
816 _rowHeight = _controller->customRowHeight();
817 break;
818 }
819 const auto wasMouseSelection = _mouseSelection;
820 const auto wasLastMousePosition = _lastMousePosition;
821 _contextMenu = nullptr;
822 if (wasMouseSelection) {
823 setSelected(Selected());
824 }
825 setPressed(Selected());
826 refreshRows();
827 if (wasMouseSelection && wasLastMousePosition) {
828 selectByMouse(*wasLastMousePosition);
829 }
830 }
831
appendRow(std::unique_ptr<PeerListRow> row)832 void PeerListContent::appendRow(std::unique_ptr<PeerListRow> row) {
833 Expects(row != nullptr);
834
835 if (_rowsById.find(row->id()) == _rowsById.cend()) {
836 row->setAbsoluteIndex(_rows.size());
837 addRowEntry(row.get());
838 if (!_hiddenRows.empty()) {
839 Assert(!row->hidden());
840 _filterResults.push_back(row.get());
841 }
842 _rows.push_back(std::move(row));
843 }
844 }
845
appendSearchRow(std::unique_ptr<PeerListRow> row)846 void PeerListContent::appendSearchRow(std::unique_ptr<PeerListRow> row) {
847 Expects(row != nullptr);
848 Expects(showingSearch());
849
850 if (_rowsById.find(row->id()) == _rowsById.cend()) {
851 row->setAbsoluteIndex(_searchRows.size());
852 row->setIsSearchResult(true);
853 addRowEntry(row.get());
854 _filterResults.push_back(row.get());
855 _searchRows.push_back(std::move(row));
856 }
857 }
858
appendFoundRow(not_null<PeerListRow * > row)859 void PeerListContent::appendFoundRow(not_null<PeerListRow*> row) {
860 Expects(showingSearch());
861
862 auto index = findRowIndex(row);
863 if (index.value < 0) {
864 _filterResults.push_back(row);
865 }
866 }
867
changeCheckState(not_null<PeerListRow * > row,bool checked,anim::type animated)868 void PeerListContent::changeCheckState(
869 not_null<PeerListRow*> row,
870 bool checked,
871 anim::type animated) {
872 row->setChecked(
873 checked,
874 _st.item.checkbox,
875 animated,
876 [=] { updateRow(row); });
877 }
878
setRowHidden(not_null<PeerListRow * > row,bool hidden)879 void PeerListContent::setRowHidden(not_null<PeerListRow*> row, bool hidden) {
880 Expects(!row->isSearchResult());
881
882 row->setHidden(hidden);
883 if (hidden) {
884 _hiddenRows.emplace(row);
885 } else {
886 _hiddenRows.remove(row);
887 }
888 }
889
addRowEntry(not_null<PeerListRow * > row)890 void PeerListContent::addRowEntry(not_null<PeerListRow*> row) {
891 if (_controller->respectSavedMessagesChat() && !row->special()) {
892 if (row->peer()->isSelf()) {
893 row->setIsSavedMessagesChat(true);
894 } else if (row->peer()->isRepliesChat()) {
895 row->setIsRepliesMessagesChat(true);
896 }
897 }
898 _rowsById.emplace(row->id(), row);
899 if (!row->special()) {
900 _rowsByPeer[row->peer()].push_back(row);
901 }
902 if (addingToSearchIndex()) {
903 addToSearchIndex(row);
904 }
905 if (_controller->isRowSelected(row)) {
906 Assert(row->special() || row->id() == row->peer()->id.value);
907 changeCheckState(row, true, anim::type::instant);
908 }
909 }
910
invalidatePixmapsCache()911 void PeerListContent::invalidatePixmapsCache() {
912 auto invalidate = [](auto &&row) { row->invalidatePixmapsCache(); };
913 ranges::for_each(_rows, invalidate);
914 ranges::for_each(_searchRows, invalidate);
915 }
916
addingToSearchIndex() const917 bool PeerListContent::addingToSearchIndex() const {
918 // If we started indexing already, we continue.
919 return (_searchMode != PeerListSearchMode::Disabled) || !_searchIndex.empty();
920 }
921
addToSearchIndex(not_null<PeerListRow * > row)922 void PeerListContent::addToSearchIndex(not_null<PeerListRow*> row) {
923 if (row->isSearchResult() || row->special()) {
924 return;
925 }
926
927 removeFromSearchIndex(row);
928 row->setNameFirstLetters(row->peer()->nameFirstLetters());
929 for (auto ch : row->nameFirstLetters()) {
930 _searchIndex[ch].push_back(row);
931 }
932 }
933
removeFromSearchIndex(not_null<PeerListRow * > row)934 void PeerListContent::removeFromSearchIndex(not_null<PeerListRow*> row) {
935 const auto &nameFirstLetters = row->nameFirstLetters();
936 if (!nameFirstLetters.empty()) {
937 for (auto ch : row->nameFirstLetters()) {
938 auto it = _searchIndex.find(ch);
939 if (it != _searchIndex.cend()) {
940 auto &entry = it->second;
941 entry.erase(ranges::remove(entry, row), end(entry));
942 if (entry.empty()) {
943 _searchIndex.erase(it);
944 }
945 }
946 }
947 row->setNameFirstLetters({});
948 }
949 }
950
prependRow(std::unique_ptr<PeerListRow> row)951 void PeerListContent::prependRow(std::unique_ptr<PeerListRow> row) {
952 Expects(row != nullptr);
953
954 if (_rowsById.find(row->id()) == _rowsById.cend()) {
955 addRowEntry(row.get());
956 if (!_hiddenRows.empty()) {
957 Assert(!row->hidden());
958 _filterResults.insert(_filterResults.begin(), row.get());
959 }
960 _rows.insert(_rows.begin(), std::move(row));
961 refreshIndices();
962 }
963 }
964
prependRowFromSearchResult(not_null<PeerListRow * > row)965 void PeerListContent::prependRowFromSearchResult(not_null<PeerListRow*> row) {
966 if (!row->isSearchResult()) {
967 return;
968 }
969 Assert(_rowsById.find(row->id()) != _rowsById.cend());
970 auto index = row->absoluteIndex();
971 Assert(index >= 0 && index < _searchRows.size());
972 Assert(_searchRows[index].get() == row);
973
974 row->setIsSearchResult(false);
975 if (!_hiddenRows.empty()) {
976 Assert(!row->hidden());
977 _filterResults.insert(_filterResults.begin(), row);
978 }
979 _rows.insert(_rows.begin(), std::move(_searchRows[index]));
980 refreshIndices();
981 removeRowAtIndex(_searchRows, index);
982
983 if (addingToSearchIndex()) {
984 addToSearchIndex(row);
985 }
986 }
987
refreshIndices()988 void PeerListContent::refreshIndices() {
989 auto index = 0;
990 for (auto &row : _rows) {
991 row->setAbsoluteIndex(index++);
992 }
993 }
994
removeRowAtIndex(std::vector<std::unique_ptr<PeerListRow>> & from,int index)995 void PeerListContent::removeRowAtIndex(
996 std::vector<std::unique_ptr<PeerListRow>> &from,
997 int index) {
998 from.erase(from.begin() + index);
999 for (auto i = index, count = int(from.size()); i != count; ++i) {
1000 from[i]->setAbsoluteIndex(i);
1001 }
1002 }
1003
findRow(PeerListRowId id)1004 PeerListRow *PeerListContent::findRow(PeerListRowId id) {
1005 auto it = _rowsById.find(id);
1006 return (it == _rowsById.cend()) ? nullptr : it->second.get();
1007 }
1008
removeRow(not_null<PeerListRow * > row)1009 void PeerListContent::removeRow(not_null<PeerListRow*> row) {
1010 auto index = row->absoluteIndex();
1011 auto isSearchResult = row->isSearchResult();
1012 auto &eraseFrom = isSearchResult ? _searchRows : _rows;
1013
1014 Assert(index >= 0 && index < eraseFrom.size());
1015 Assert(eraseFrom[index].get() == row);
1016
1017 auto pressedData = saveSelectedData(_pressed);
1018 auto contextedData = saveSelectedData(_contexted);
1019 setSelected(Selected());
1020 setPressed(Selected());
1021 setContexted(Selected());
1022
1023 _rowsById.erase(row->id());
1024 if (!row->special()) {
1025 auto &byPeer = _rowsByPeer[row->peer()];
1026 byPeer.erase(ranges::remove(byPeer, row), end(byPeer));
1027 }
1028 removeFromSearchIndex(row);
1029 _filterResults.erase(
1030 ranges::remove(_filterResults, row),
1031 end(_filterResults));
1032 _hiddenRows.remove(row);
1033 removeRowAtIndex(eraseFrom, index);
1034
1035 restoreSelection();
1036 setPressed(restoreSelectedData(pressedData));
1037 setContexted(restoreSelectedData(contextedData));
1038 }
1039
clearAllContent()1040 void PeerListContent::clearAllContent() {
1041 setSelected(Selected());
1042 setPressed(Selected());
1043 setContexted(Selected());
1044 _mouseSelection = false;
1045 _lastMousePosition = std::nullopt;
1046 _rowsById.clear();
1047 _rowsByPeer.clear();
1048 _filterResults.clear();
1049 _searchIndex.clear();
1050 _rows.clear();
1051 _searchRows.clear();
1052 _searchQuery
1053 = _normalizedSearchQuery
1054 = _mentionHighlight
1055 = QString();
1056 }
1057
convertRowToSearchResult(not_null<PeerListRow * > row)1058 void PeerListContent::convertRowToSearchResult(not_null<PeerListRow*> row) {
1059 if (row->isSearchResult()) {
1060 return;
1061 } else if (!showingSearch() || !_controller->hasComplexSearch()) {
1062 return removeRow(row);
1063 }
1064 auto index = row->absoluteIndex();
1065 Assert(index >= 0 && index < _rows.size());
1066 Assert(_rows[index].get() == row);
1067
1068 removeFromSearchIndex(row);
1069 row->setIsSearchResult(true);
1070 row->setHidden(false);
1071 row->setAbsoluteIndex(_searchRows.size());
1072 _hiddenRows.remove(row);
1073 _searchRows.push_back(std::move(_rows[index]));
1074 removeRowAtIndex(_rows, index);
1075 }
1076
fullRowsCount() const1077 int PeerListContent::fullRowsCount() const {
1078 return _rows.size();
1079 }
1080
rowAt(int index) const1081 not_null<PeerListRow*> PeerListContent::rowAt(int index) const {
1082 Expects(index >= 0 && index < _rows.size());
1083
1084 return _rows[index].get();
1085 }
1086
setDescription(object_ptr<Ui::FlatLabel> description)1087 void PeerListContent::setDescription(object_ptr<Ui::FlatLabel> description) {
1088 _description = std::move(description);
1089 if (_description) {
1090 _description->setParent(this);
1091 }
1092 }
1093
setSearchLoading(object_ptr<Ui::FlatLabel> loading)1094 void PeerListContent::setSearchLoading(object_ptr<Ui::FlatLabel> loading) {
1095 _searchLoading = std::move(loading);
1096 if (_searchLoading) {
1097 _searchLoading->setParent(this);
1098 }
1099 }
1100
setSearchNoResults(object_ptr<Ui::FlatLabel> noResults)1101 void PeerListContent::setSearchNoResults(object_ptr<Ui::FlatLabel> noResults) {
1102 _searchNoResults = std::move(noResults);
1103 if (_searchNoResults) {
1104 _searchNoResults->setParent(this);
1105 }
1106 }
1107
setAboveWidget(object_ptr<TWidget> widget)1108 void PeerListContent::setAboveWidget(object_ptr<TWidget> widget) {
1109 _aboveWidget = std::move(widget);
1110 if (_aboveWidget) {
1111 _aboveWidget->setParent(this);
1112 }
1113 }
1114
setAboveSearchWidget(object_ptr<TWidget> widget)1115 void PeerListContent::setAboveSearchWidget(object_ptr<TWidget> widget) {
1116 _aboveSearchWidget = std::move(widget);
1117 if (_aboveSearchWidget) {
1118 _aboveSearchWidget->setParent(this);
1119 }
1120 }
1121
setHideEmpty(bool hide)1122 void PeerListContent::setHideEmpty(bool hide) {
1123 _hideEmpty = hide;
1124 resizeToWidth(width());
1125 }
1126
setBelowWidget(object_ptr<TWidget> widget)1127 void PeerListContent::setBelowWidget(object_ptr<TWidget> widget) {
1128 _belowWidget = std::move(widget);
1129 if (_belowWidget) {
1130 _belowWidget->setParent(this);
1131 }
1132 }
1133
labelHeight() const1134 int PeerListContent::labelHeight() const {
1135 if (_hideEmpty && !shownRowsCount()) {
1136 return 0;
1137 }
1138 auto computeLabelHeight = [](auto &label) {
1139 if (!label) {
1140 return 0;
1141 }
1142 return st::membersAboutLimitPadding.top() + label->height() + st::membersAboutLimitPadding.bottom();
1143 };
1144 if (showingSearch()) {
1145 if (!_filterResults.empty()) {
1146 return 0;
1147 }
1148 if (_controller->isSearchLoading()) {
1149 return computeLabelHeight(_searchLoading);
1150 }
1151 return computeLabelHeight(_searchNoResults);
1152 }
1153 return computeLabelHeight(_description);
1154 }
1155
refreshRows()1156 void PeerListContent::refreshRows() {
1157 if (!_hiddenRows.empty()) {
1158 _filterResults.clear();
1159 for (const auto &row : _rows) {
1160 if (!row->hidden()) {
1161 _filterResults.push_back(row.get());
1162 }
1163 }
1164 }
1165 resizeToWidth(width());
1166 if (_visibleBottom > 0) {
1167 checkScrollForPreload();
1168 }
1169 if (_mouseSelection) {
1170 selectByMouse(QCursor::pos());
1171 }
1172 update();
1173 }
1174
setSearchMode(PeerListSearchMode mode)1175 void PeerListContent::setSearchMode(PeerListSearchMode mode) {
1176 if (_searchMode != mode) {
1177 if (!addingToSearchIndex()) {
1178 for (const auto &row : _rows) {
1179 addToSearchIndex(row.get());
1180 }
1181 }
1182 _searchMode = mode;
1183 if (_controller->hasComplexSearch()) {
1184 if (!_searchLoading) {
1185 setSearchLoading(object_ptr<Ui::FlatLabel>(
1186 this,
1187 tr::lng_contacts_loading(tr::now),
1188 st::membersAbout));
1189 }
1190 } else {
1191 clearSearchRows();
1192 }
1193 }
1194 }
1195
clearSearchRows()1196 void PeerListContent::clearSearchRows() {
1197 while (!_searchRows.empty()) {
1198 removeRow(_searchRows.back().get());
1199 }
1200 }
1201
paintEvent(QPaintEvent * e)1202 void PeerListContent::paintEvent(QPaintEvent *e) {
1203 Painter p(this);
1204
1205 const auto clip = e->rect();
1206 if (_mode != Mode::Custom) {
1207 p.fillRect(clip, _st.item.button.textBg);
1208 }
1209
1210 const auto repaintByStatusAfter = _repaintByStatus.remainingTime();
1211 auto repaintAfterMin = repaintByStatusAfter;
1212
1213 const auto rowsTopCached = rowsTop();
1214 const auto now = crl::now();
1215 const auto yFrom = clip.y() - rowsTopCached;
1216 const auto yTo = clip.y() + clip.height() - rowsTopCached;
1217 p.translate(0, rowsTopCached);
1218 const auto count = shownRowsCount();
1219 if (count > 0) {
1220 const auto from = floorclamp(yFrom, _rowHeight, 0, count);
1221 const auto to = ceilclamp(yTo, _rowHeight, 0, count);
1222 p.translate(0, from * _rowHeight);
1223 for (auto index = from; index != to; ++index) {
1224 const auto repaintAfter = paintRow(p, now, RowIndex(index));
1225 if (repaintAfter > 0
1226 && (repaintAfterMin < 0
1227 || repaintAfterMin > repaintAfter)) {
1228 repaintAfterMin = repaintAfter;
1229 }
1230 p.translate(0, _rowHeight);
1231 }
1232 }
1233 if (repaintAfterMin != repaintByStatusAfter) {
1234 Assert(repaintAfterMin >= 0);
1235 _repaintByStatus.callOnce(repaintAfterMin);
1236 }
1237 }
1238
resizeGetHeight(int newWidth)1239 int PeerListContent::resizeGetHeight(int newWidth) {
1240 const auto rowsCount = shownRowsCount();
1241 const auto hideAll = !rowsCount && _hideEmpty;
1242 _aboveHeight = 0;
1243 if (_aboveWidget) {
1244 _aboveWidget->resizeToWidth(newWidth);
1245 _aboveWidget->moveToLeft(0, 0, newWidth);
1246 if (hideAll || showingSearch()) {
1247 _aboveWidget->hide();
1248 } else {
1249 _aboveWidget->show();
1250 _aboveHeight = _aboveWidget->height();
1251 }
1252 }
1253 if (_aboveSearchWidget) {
1254 _aboveSearchWidget->resizeToWidth(newWidth);
1255 _aboveSearchWidget->moveToLeft(0, 0, newWidth);
1256 if (hideAll || !showingSearch()) {
1257 _aboveSearchWidget->hide();
1258 } else {
1259 _aboveSearchWidget->show();
1260 _aboveHeight = _aboveSearchWidget->height();
1261 }
1262 }
1263 const auto labelTop = rowsTop()
1264 + std::max(
1265 shownRowsCount() * _rowHeight,
1266 _controller->descriptionTopSkipMin());
1267 const auto labelWidth = newWidth - 2 * st::contactsPadding.left();
1268 if (_description) {
1269 _description->resizeToWidth(labelWidth);
1270 _description->moveToLeft(st::contactsPadding.left(), labelTop + st::membersAboutLimitPadding.top(), newWidth);
1271 _description->setVisible(!hideAll && !showingSearch());
1272 }
1273 if (_searchNoResults) {
1274 _searchNoResults->resizeToWidth(labelWidth);
1275 _searchNoResults->moveToLeft(st::contactsPadding.left(), labelTop + st::membersAboutLimitPadding.top(), newWidth);
1276 _searchNoResults->setVisible(!hideAll && showingSearch() && _filterResults.empty() && !_controller->isSearchLoading());
1277 }
1278 if (_searchLoading) {
1279 _searchLoading->resizeToWidth(labelWidth);
1280 _searchLoading->moveToLeft(st::contactsPadding.left(), labelTop + st::membersAboutLimitPadding.top(), newWidth);
1281 _searchLoading->setVisible(!hideAll && showingSearch() && _filterResults.empty() && _controller->isSearchLoading());
1282 }
1283 const auto label = labelHeight();
1284 const auto belowTop = (label > 0 || rowsCount > 0)
1285 ? (labelTop + label + _st.padding.bottom())
1286 : _aboveHeight;
1287 _belowHeight = 0;
1288 if (_belowWidget) {
1289 _belowWidget->resizeToWidth(newWidth);
1290 _belowWidget->moveToLeft(0, belowTop, newWidth);
1291 if (hideAll || showingSearch()) {
1292 _belowWidget->hide();
1293 } else {
1294 _belowWidget->show();
1295 _belowHeight = _belowWidget->height();
1296 }
1297 }
1298 return belowTop + _belowHeight;
1299 }
1300
enterEventHook(QEnterEvent * e)1301 void PeerListContent::enterEventHook(QEnterEvent *e) {
1302 setMouseTracking(true);
1303 }
1304
leaveEventHook(QEvent * e)1305 void PeerListContent::leaveEventHook(QEvent *e) {
1306 setMouseTracking(false);
1307 mouseLeftGeometry();
1308 }
1309
mouseMoveEvent(QMouseEvent * e)1310 void PeerListContent::mouseMoveEvent(QMouseEvent *e) {
1311 handleMouseMove(e->globalPos());
1312 }
1313
handleMouseMove(QPoint globalPosition)1314 void PeerListContent::handleMouseMove(QPoint globalPosition) {
1315 if (!_lastMousePosition) {
1316 _lastMousePosition = globalPosition;
1317 return;
1318 } else if (!_mouseSelection
1319 && *_lastMousePosition == globalPosition) {
1320 return;
1321 }
1322 selectByMouse(globalPosition);
1323 }
1324
mousePressEvent(QMouseEvent * e)1325 void PeerListContent::mousePressEvent(QMouseEvent *e) {
1326 _pressButton = e->button();
1327 selectByMouse(e->globalPos());
1328 setPressed(_selected);
1329 if (auto row = getRow(_selected.index)) {
1330 auto updateCallback = [this, row, hint = _selected.index] {
1331 updateRow(row, hint);
1332 };
1333 if (_selected.element) {
1334 const auto elementRect = getElementRect(
1335 row,
1336 _selected.index,
1337 _selected.element);
1338 if (!elementRect.isEmpty()) {
1339 row->elementAddRipple(
1340 _selected.element,
1341 mapFromGlobal(QCursor::pos()) - elementRect.topLeft(),
1342 std::move(updateCallback));
1343 }
1344 } else {
1345 auto point = mapFromGlobal(QCursor::pos()) - QPoint(0, getRowTop(_selected.index));
1346 if (_mode == Mode::Custom) {
1347 row->addRipple(_st.item, _controller->customRowRippleMaskGenerator(), point, std::move(updateCallback));
1348 } else {
1349 const auto maskGenerator = [&] {
1350 return Ui::RippleAnimation::rectMask(
1351 QSize(width(), _rowHeight));
1352 };
1353 row->addRipple(_st.item, maskGenerator, point, std::move(updateCallback));
1354 }
1355 }
1356 }
1357 if (anim::Disabled() && !_selected.element) {
1358 mousePressReleased(e->button());
1359 }
1360 }
1361
mouseReleaseEvent(QMouseEvent * e)1362 void PeerListContent::mouseReleaseEvent(QMouseEvent *e) {
1363 mousePressReleased(e->button());
1364 }
1365
mousePressReleased(Qt::MouseButton button)1366 void PeerListContent::mousePressReleased(Qt::MouseButton button) {
1367 updateRow(_pressed.index);
1368 updateRow(_selected.index);
1369
1370 auto pressed = _pressed;
1371 setPressed(Selected());
1372 if (button == Qt::LeftButton && pressed == _selected) {
1373 if (auto row = getRow(pressed.index)) {
1374 if (pressed.element) {
1375 _controller->rowElementClicked(row, pressed.element);
1376 } else {
1377 _controller->rowClicked(row);
1378 }
1379 }
1380 }
1381 }
1382
showRowMenu(not_null<PeerListRow * > row,bool highlightRow,Fn<void (not_null<Ui::PopupMenu * >)> destroyed)1383 void PeerListContent::showRowMenu(
1384 not_null<PeerListRow*> row,
1385 bool highlightRow,
1386 Fn<void(not_null<Ui::PopupMenu*>)> destroyed) {
1387 const auto index = findRowIndex(row);
1388 showRowMenu(
1389 index,
1390 row,
1391 QCursor::pos(),
1392 highlightRow,
1393 std::move(destroyed));
1394 }
1395
showRowMenu(RowIndex index,PeerListRow * row,QPoint globalPos,bool highlightRow,Fn<void (not_null<Ui::PopupMenu * >)> destroyed)1396 bool PeerListContent::showRowMenu(
1397 RowIndex index,
1398 PeerListRow *row,
1399 QPoint globalPos,
1400 bool highlightRow,
1401 Fn<void(not_null<Ui::PopupMenu*>)> destroyed) {
1402 if (_contextMenu) {
1403 _contextMenu->setDestroyedCallback(nullptr);
1404 _contextMenu = nullptr;
1405 }
1406 setContexted(Selected());
1407 if (_pressButton != Qt::LeftButton) {
1408 mousePressReleased(_pressButton);
1409 }
1410
1411 if (highlightRow) {
1412 row = getRow(index);
1413 }
1414 if (!row) {
1415 return false;
1416 }
1417
1418 _contextMenu = _controller->rowContextMenu(this, row);
1419 const auto raw = _contextMenu.get();
1420 if (!raw) {
1421 return false;
1422 }
1423
1424 if (highlightRow) {
1425 setContexted({ index, false });
1426 }
1427 raw->setDestroyedCallback(crl::guard(
1428 this,
1429 [=] {
1430 if (highlightRow) {
1431 setContexted(Selected());
1432 }
1433 handleMouseMove(QCursor::pos());
1434 if (destroyed) {
1435 destroyed(raw);
1436 }
1437 }));
1438 raw->popup(globalPos);
1439 return true;
1440 }
1441
contextMenuEvent(QContextMenuEvent * e)1442 void PeerListContent::contextMenuEvent(QContextMenuEvent *e) {
1443 if (e->reason() == QContextMenuEvent::Mouse) {
1444 handleMouseMove(e->globalPos());
1445 }
1446 if (showRowMenu(_selected.index, nullptr, e->globalPos(), true)) {
1447 e->accept();
1448 }
1449 }
1450
setPressed(Selected pressed)1451 void PeerListContent::setPressed(Selected pressed) {
1452 if (_pressed == pressed) {
1453 return;
1454 } else if (const auto row = getRow(_pressed.index)) {
1455 row->stopLastRipple();
1456 row->elementsStopLastRipple();
1457 }
1458 _pressed = pressed;
1459 }
1460
paintRow(Painter & p,crl::time now,RowIndex index)1461 crl::time PeerListContent::paintRow(
1462 Painter &p,
1463 crl::time now,
1464 RowIndex index) {
1465 const auto row = getRow(index);
1466 Assert(row != nullptr);
1467
1468 row->lazyInitialize(_st.item);
1469 const auto outerWidth = width();
1470
1471 auto refreshStatusAt = row->refreshStatusTime();
1472 if (refreshStatusAt > 0 && now >= refreshStatusAt) {
1473 row->refreshStatus();
1474 refreshStatusAt = row->refreshStatusTime();
1475 }
1476 const auto refreshStatusIn = (refreshStatusAt > 0)
1477 ? std::max(refreshStatusAt - now, crl::time(1))
1478 : 0;
1479
1480 const auto peer = row->special() ? nullptr : row->peer().get();
1481 const auto active = (_contexted.index.value >= 0)
1482 ? _contexted
1483 : (_pressed.index.value >= 0)
1484 ? _pressed
1485 : _selected;
1486 const auto selected = (active.index == index)
1487 && (!active.element || !row->elementOnlySelect(active.element));
1488
1489 if (_mode == Mode::Custom) {
1490 _controller->customRowPaint(p, now, row, selected);
1491 return refreshStatusIn;
1492 }
1493
1494 const auto &bg = selected
1495 ? _st.item.button.textBgOver
1496 : _st.item.button.textBg;
1497 p.fillRect(0, 0, outerWidth, _rowHeight, bg);
1498 row->paintRipple(p, 0, 0, outerWidth);
1499 row->paintUserpic(
1500 p,
1501 _st.item,
1502 _st.item.photoPosition.x(),
1503 _st.item.photoPosition.y(),
1504 outerWidth);
1505
1506 p.setPen(st::contactsNameFg);
1507
1508 auto skipRight = _st.item.photoPosition.x();
1509 auto rightActionSize = row->rightActionSize();
1510 auto rightActionMargins = rightActionSize.isEmpty()
1511 ? QMargins()
1512 : row->rightActionMargins();
1513 auto &name = row->name();
1514 auto namex = _st.item.namePosition.x();
1515 auto namew = outerWidth - namex - skipRight;
1516 if (!rightActionSize.isEmpty()) {
1517 namew -= rightActionMargins.left()
1518 + rightActionSize.width()
1519 + rightActionMargins.right()
1520 - skipRight;
1521 }
1522 auto statusw = namew;
1523 if (auto iconWidth = row->nameIconWidth()) {
1524 namew -= iconWidth;
1525 row->paintNameIcon(
1526 p,
1527 namex + qMin(name.maxWidth(), namew),
1528 _st.item.namePosition.y(),
1529 width(),
1530 selected);
1531 }
1532 auto nameCheckedRatio = row->disabled() ? 0. : row->checkedRatio();
1533 p.setPen(anim::pen(_st.item.nameFg, _st.item.nameFgChecked, nameCheckedRatio));
1534 name.drawLeftElided(p, namex, _st.item.namePosition.y(), namew, width());
1535
1536 p.setFont(st::contactsStatusFont);
1537 if (row->isSearchResult()
1538 && !_mentionHighlight.isEmpty()
1539 && peer
1540 && peer->userName().startsWith(
1541 _mentionHighlight,
1542 Qt::CaseInsensitive)) {
1543 const auto username = peer->userName();
1544 const auto availableWidth = statusw;
1545 auto highlightedPart = '@' + username.mid(0, _mentionHighlight.size());
1546 auto grayedPart = username.mid(_mentionHighlight.size());
1547 const auto highlightedWidth = st::contactsStatusFont->width(highlightedPart);
1548 if (highlightedWidth >= availableWidth || grayedPart.isEmpty()) {
1549 if (highlightedWidth > availableWidth) {
1550 highlightedPart = st::contactsStatusFont->elided(highlightedPart, availableWidth);
1551 }
1552 p.setPen(_st.item.statusFgActive);
1553 p.drawTextLeft(_st.item.statusPosition.x(), _st.item.statusPosition.y(), width(), highlightedPart);
1554 } else {
1555 grayedPart = st::contactsStatusFont->elided(grayedPart, availableWidth - highlightedWidth);
1556 p.setPen(_st.item.statusFgActive);
1557 p.drawTextLeft(_st.item.statusPosition.x(), _st.item.statusPosition.y(), width(), highlightedPart);
1558 p.setPen(selected ? _st.item.statusFgOver : _st.item.statusFg);
1559 p.drawTextLeft(_st.item.statusPosition.x() + highlightedWidth, _st.item.statusPosition.y(), width(), grayedPart);
1560 }
1561 } else {
1562 row->paintStatusText(p, _st.item, _st.item.statusPosition.x(), _st.item.statusPosition.y(), statusw, width(), selected);
1563 }
1564
1565 row->elementsPaint(
1566 p,
1567 width(),
1568 selected,
1569 (active.index == index) ? active.element : 0);
1570
1571 return refreshStatusIn;
1572 }
1573
selectSkip(int direction)1574 PeerListContent::SkipResult PeerListContent::selectSkip(int direction) {
1575 if (hasPressed()) {
1576 return { _selected.index.value, _selected.index.value };
1577 }
1578 _mouseSelection = false;
1579 _lastMousePosition = std::nullopt;
1580
1581 auto newSelectedIndex = _selected.index.value + direction;
1582
1583 auto result = SkipResult();
1584 result.shouldMoveTo = newSelectedIndex;
1585
1586 auto rowsCount = shownRowsCount();
1587 auto index = 0;
1588 auto firstEnabled = -1, lastEnabled = -1;
1589 enumerateShownRows([&firstEnabled, &lastEnabled, &index](not_null<PeerListRow*> row) {
1590 if (!row->disabled()) {
1591 if (firstEnabled < 0) {
1592 firstEnabled = index;
1593 }
1594 lastEnabled = index;
1595 }
1596 ++index;
1597 return true;
1598 });
1599 if (firstEnabled < 0) {
1600 firstEnabled = rowsCount;
1601 lastEnabled = firstEnabled - 1;
1602 }
1603
1604 Assert(lastEnabled < rowsCount);
1605 Assert(firstEnabled - 1 <= lastEnabled);
1606
1607 // Always pass through the first enabled item when changing from / to none selected.
1608 if ((_selected.index.value > firstEnabled && newSelectedIndex < firstEnabled)
1609 || (_selected.index.value < firstEnabled && newSelectedIndex > firstEnabled)) {
1610 newSelectedIndex = firstEnabled;
1611 }
1612
1613 // Snap the index.
1614 newSelectedIndex = std::clamp(
1615 newSelectedIndex,
1616 firstEnabled - 1,
1617 lastEnabled);
1618
1619 // Skip the disabled rows.
1620 if (newSelectedIndex < firstEnabled) {
1621 newSelectedIndex = -1;
1622 } else if (newSelectedIndex > lastEnabled) {
1623 newSelectedIndex = lastEnabled;
1624 } else if (getRow(RowIndex(newSelectedIndex))->disabled()) {
1625 auto delta = (direction > 0) ? 1 : -1;
1626 for (newSelectedIndex += delta; ; newSelectedIndex += delta) {
1627 // We must find an enabled row, firstEnabled <= us <= lastEnabled.
1628 Assert(newSelectedIndex >= 0 && newSelectedIndex < rowsCount);
1629 if (!getRow(RowIndex(newSelectedIndex))->disabled()) {
1630 break;
1631 }
1632 }
1633 }
1634
1635 _selected.index.value = newSelectedIndex;
1636 _selected.element = 0;
1637 if (newSelectedIndex >= 0) {
1638 auto top = (newSelectedIndex > 0) ? getRowTop(RowIndex(newSelectedIndex)) : 0;
1639 auto bottom = (newSelectedIndex + 1 < rowsCount) ? getRowTop(RowIndex(newSelectedIndex + 1)) : height();
1640 _scrollToRequests.fire({ top, bottom });
1641 }
1642
1643 update();
1644
1645 _selectedIndex = _selected.index.value;
1646 result.reallyMovedTo = _selected.index.value;
1647 return result;
1648 }
1649
selectSkipPage(int height,int direction)1650 void PeerListContent::selectSkipPage(int height, int direction) {
1651 auto rowsToSkip = height / _rowHeight;
1652 if (!rowsToSkip) {
1653 return;
1654 }
1655 selectSkip(rowsToSkip * direction);
1656 }
1657
selectedIndexValue() const1658 rpl::producer<int> PeerListContent::selectedIndexValue() const {
1659 return _selectedIndex.value();
1660 }
1661
hasSelection() const1662 bool PeerListContent::hasSelection() const {
1663 return _selected.index.value >= 0;
1664 }
1665
hasPressed() const1666 bool PeerListContent::hasPressed() const {
1667 return _pressed.index.value >= 0;
1668 }
1669
clearSelection()1670 void PeerListContent::clearSelection() {
1671 setSelected(Selected());
1672 }
1673
mouseLeftGeometry()1674 void PeerListContent::mouseLeftGeometry() {
1675 if (_mouseSelection) {
1676 setSelected(Selected());
1677 _mouseSelection = false;
1678 _lastMousePosition = std::nullopt;
1679 }
1680 }
1681
loadProfilePhotos()1682 void PeerListContent::loadProfilePhotos() {
1683 if (_visibleTop >= _visibleBottom) return;
1684
1685 auto yFrom = _visibleTop;
1686 auto yTo = _visibleBottom + (_visibleBottom - _visibleTop) * PreloadHeightsCount;
1687
1688 if (yTo < 0) return;
1689 if (yFrom < 0) yFrom = 0;
1690
1691 auto rowsCount = shownRowsCount();
1692 if (rowsCount > 0) {
1693 auto from = yFrom / _rowHeight;
1694 if (from < 0) from = 0;
1695 if (from < rowsCount) {
1696 auto to = (yTo / _rowHeight) + 1;
1697 if (to > rowsCount) to = rowsCount;
1698
1699 for (auto index = from; index != to; ++index) {
1700 const auto row = getRow(RowIndex(index));
1701 if (!row->special()) {
1702 row->peer()->loadUserpic();
1703 }
1704 }
1705 }
1706 }
1707 }
1708
checkScrollForPreload()1709 void PeerListContent::checkScrollForPreload() {
1710 if (_visibleBottom + PreloadHeightsCount * (_visibleBottom - _visibleTop) >= height()) {
1711 _controller->loadMoreRows();
1712 }
1713 }
1714
searchQueryChanged(QString query)1715 void PeerListContent::searchQueryChanged(QString query) {
1716 const auto searchWordsList = TextUtilities::PrepareSearchWords(query);
1717 const auto normalizedQuery = searchWordsList.join(' ');
1718 if (_normalizedSearchQuery != normalizedQuery) {
1719 setSearchQuery(query, normalizedQuery);
1720 if (_controller->searchInLocal() && !searchWordsList.isEmpty()) {
1721 Assert(_hiddenRows.empty());
1722
1723 auto minimalList = (const std::vector<not_null<PeerListRow*>>*)nullptr;
1724 for (const auto &searchWord : searchWordsList) {
1725 auto searchWordStart = searchWord[0].toLower();
1726 auto it = _searchIndex.find(searchWordStart);
1727 if (it == _searchIndex.cend()) {
1728 // Some word can't be found in any row.
1729 minimalList = nullptr;
1730 break;
1731 } else if (!minimalList || minimalList->size() > it->second.size()) {
1732 minimalList = &it->second;
1733 }
1734 }
1735 if (minimalList) {
1736 auto searchWordInNames = [](
1737 not_null<PeerData*> peer,
1738 const QString &searchWord) {
1739 for (auto &nameWord : peer->nameWords()) {
1740 if (nameWord.startsWith(searchWord)) {
1741 return true;
1742 }
1743 }
1744 return false;
1745 };
1746 auto allSearchWordsInNames = [&](
1747 not_null<PeerData*> peer) {
1748 for (const auto &searchWord : searchWordsList) {
1749 if (!searchWordInNames(peer, searchWord)) {
1750 return false;
1751 }
1752 }
1753 return true;
1754 };
1755
1756 _filterResults.reserve(minimalList->size());
1757 for (const auto &row : *minimalList) {
1758 if (!row->special() && allSearchWordsInNames(row->peer())) {
1759 _filterResults.push_back(row);
1760 }
1761 }
1762 }
1763 }
1764 if (_controller->hasComplexSearch()) {
1765 _controller->search(_searchQuery);
1766 }
1767 refreshRows();
1768 }
1769 }
1770
saveState() const1771 std::unique_ptr<PeerListState> PeerListContent::saveState() const {
1772 Expects(_hiddenRows.empty());
1773
1774 auto result = std::make_unique<PeerListState>();
1775 result->controllerState
1776 = std::make_unique<PeerListController::SavedStateBase>();
1777 result->list.reserve(_rows.size());
1778 for (const auto &row : _rows) {
1779 result->list.push_back(row->peer());
1780 }
1781 result->filterResults.reserve(_filterResults.size());
1782 for (const auto &row : _filterResults) {
1783 result->filterResults.push_back(row->peer());
1784 }
1785 result->searchQuery = _searchQuery;
1786 return result;
1787 }
1788
restoreState(std::unique_ptr<PeerListState> state)1789 void PeerListContent::restoreState(
1790 std::unique_ptr<PeerListState> state) {
1791 if (!state || !state->controllerState) {
1792 return;
1793 }
1794
1795 clearAllContent();
1796
1797 for (auto peer : state->list) {
1798 if (auto row = _controller->createRestoredRow(peer)) {
1799 appendRow(std::move(row));
1800 }
1801 }
1802 auto query = state->searchQuery;
1803 auto searchWords = TextUtilities::PrepareSearchWords(query);
1804 setSearchQuery(query, searchWords.join(' '));
1805 for (auto peer : state->filterResults) {
1806 if (auto existingRow = findRow(peer->id.value)) {
1807 _filterResults.push_back(existingRow);
1808 } else if (auto row = _controller->createSearchRow(peer)) {
1809 appendSearchRow(std::move(row));
1810 }
1811 }
1812 refreshRows();
1813 }
1814
setSearchQuery(const QString & query,const QString & normalizedQuery)1815 void PeerListContent::setSearchQuery(
1816 const QString &query,
1817 const QString &normalizedQuery) {
1818 setSelected(Selected());
1819 setPressed(Selected());
1820 setContexted(Selected());
1821 _mouseSelection = false;
1822 _lastMousePosition = std::nullopt;
1823 _searchQuery = query;
1824 _normalizedSearchQuery = normalizedQuery;
1825 _mentionHighlight = _searchQuery.startsWith('@')
1826 ? _searchQuery.mid(1)
1827 : _searchQuery;
1828 _filterResults.clear();
1829 clearSearchRows();
1830 }
1831
submitted()1832 bool PeerListContent::submitted() {
1833 if (const auto row = getRow(_selected.index)) {
1834 _controller->rowClicked(row);
1835 return true;
1836 } else if (showingSearch()) {
1837 if (const auto row = getRow(RowIndex(0))) {
1838 _controller->rowClicked(row);
1839 return true;
1840 }
1841 }
1842 return false;
1843 }
1844
visibleTopBottomUpdated(int visibleTop,int visibleBottom)1845 void PeerListContent::visibleTopBottomUpdated(
1846 int visibleTop,
1847 int visibleBottom) {
1848 _visibleTop = visibleTop;
1849 _visibleBottom = visibleBottom;
1850 loadProfilePhotos();
1851 checkScrollForPreload();
1852 }
1853
setSelected(Selected selected)1854 void PeerListContent::setSelected(Selected selected) {
1855 updateRow(_selected.index);
1856 if (_selected == selected) {
1857 return;
1858 }
1859 _selected = selected;
1860 updateRow(_selected.index);
1861 setCursor(_selected.element ? style::cur_pointer : style::cur_default);
1862
1863 _selectedIndex = _selected.index.value;
1864 }
1865
setContexted(Selected contexted)1866 void PeerListContent::setContexted(Selected contexted) {
1867 updateRow(_contexted.index);
1868 if (_contexted != contexted) {
1869 _contexted = contexted;
1870 updateRow(_contexted.index);
1871 }
1872 }
1873
restoreSelection()1874 void PeerListContent::restoreSelection() {
1875 if (_mouseSelection) {
1876 selectByMouse(QCursor::pos());
1877 }
1878 }
1879
saveSelectedData(Selected from)1880 auto PeerListContent::saveSelectedData(Selected from)
1881 -> SelectedSaved {
1882 if (auto row = getRow(from.index)) {
1883 return { row->id(), from };
1884 }
1885 return { PeerListRowId(0), from };
1886 }
1887
restoreSelectedData(SelectedSaved from)1888 auto PeerListContent::restoreSelectedData(SelectedSaved from)
1889 -> Selected {
1890 auto result = from.old;
1891 if (auto row = findRow(from.id)) {
1892 result.index = findRowIndex(row, result.index);
1893 } else {
1894 result.index.value = -1;
1895 }
1896 return result;
1897 }
1898
selectByMouse(QPoint globalPosition)1899 void PeerListContent::selectByMouse(QPoint globalPosition) {
1900 _mouseSelection = true;
1901 _lastMousePosition = globalPosition;
1902 const auto point = mapFromGlobal(globalPosition);
1903 const auto customMode = (_mode == Mode::Custom);
1904 auto in = parentWidget()->rect().contains(parentWidget()->mapFromGlobal(globalPosition));
1905 auto selected = Selected();
1906 auto rowsPointY = point.y() - rowsTop();
1907 selected.index.value = (in
1908 && rowsPointY >= 0
1909 && rowsPointY < shownRowsCount() * _rowHeight)
1910 ? (rowsPointY / _rowHeight)
1911 : -1;
1912 if (selected.index.value >= 0) {
1913 const auto row = getRow(selected.index);
1914 if (row->disabled()
1915 || (customMode
1916 && !_controller->customRowSelectionPoint(
1917 row,
1918 point.x(),
1919 rowsPointY - (selected.index.value * _rowHeight)))) {
1920 selected = Selected();
1921 } else if (!customMode) {
1922 for (auto i = 0, count = row->elementsCount(); i != count; ++i) {
1923 const auto rect = getElementRect(row, selected.index, i + 1);
1924 if (rect.contains(point)) {
1925 selected.element = i + 1;
1926 break;
1927 }
1928 }
1929 }
1930 }
1931 setSelected(selected);
1932 }
1933
getElementRect(not_null<PeerListRow * > row,RowIndex index,int element) const1934 QRect PeerListContent::getElementRect(
1935 not_null<PeerListRow*> row,
1936 RowIndex index,
1937 int element) const {
1938 if (row->elementDisabled(element)) {
1939 return QRect();
1940 }
1941 const auto geometry = row->elementGeometry(element, width());
1942 if (geometry.isEmpty()) {
1943 return QRect();
1944 }
1945 return geometry.translated(0, getRowTop(index));
1946 }
1947
rowsTop() const1948 int PeerListContent::rowsTop() const {
1949 return _aboveHeight + _st.padding.top();
1950 }
1951
getRowTop(RowIndex index) const1952 int PeerListContent::getRowTop(RowIndex index) const {
1953 if (index.value >= 0) {
1954 return rowsTop() + index.value * _rowHeight;
1955 }
1956 return -1;
1957 }
1958
updateRow(not_null<PeerListRow * > row,RowIndex hint)1959 void PeerListContent::updateRow(not_null<PeerListRow*> row, RowIndex hint) {
1960 updateRow(findRowIndex(row, hint));
1961 }
1962
updateRow(RowIndex index)1963 void PeerListContent::updateRow(RowIndex index) {
1964 if (index.value < 0) {
1965 return;
1966 }
1967 if (const auto row = getRow(index); row && row->disabled()) {
1968 if (index == _selected.index) {
1969 setSelected(Selected());
1970 }
1971 if (index == _pressed.index) {
1972 setPressed(Selected());
1973 }
1974 if (index == _contexted.index) {
1975 setContexted(Selected());
1976 }
1977 }
1978 update(0, getRowTop(index), width(), _rowHeight);
1979 }
1980
1981 template <typename Callback>
enumerateShownRows(Callback callback)1982 bool PeerListContent::enumerateShownRows(Callback callback) {
1983 return enumerateShownRows(0, shownRowsCount(), std::move(callback));
1984 }
1985
1986 template <typename Callback>
enumerateShownRows(int from,int to,Callback callback)1987 bool PeerListContent::enumerateShownRows(int from, int to, Callback callback) {
1988 Assert(0 <= from);
1989 Assert(from <= to);
1990 if (showingSearch()) {
1991 Assert(to <= _filterResults.size());
1992 for (auto i = from; i != to; ++i) {
1993 if (!callback(_filterResults[i])) {
1994 return false;
1995 }
1996 }
1997 } else {
1998 Assert(to <= _rows.size());
1999 for (auto i = from; i != to; ++i) {
2000 if (!callback(_rows[i].get())) {
2001 return false;
2002 }
2003 }
2004 }
2005 return true;
2006 }
2007
getRow(RowIndex index)2008 PeerListRow *PeerListContent::getRow(RowIndex index) {
2009 if (index.value >= 0) {
2010 if (showingSearch()) {
2011 if (index.value < _filterResults.size()) {
2012 return _filterResults[index.value];
2013 }
2014 } else if (index.value < _rows.size()) {
2015 return _rows[index.value].get();
2016 }
2017 }
2018 return nullptr;
2019 }
2020
findRowIndex(not_null<PeerListRow * > row,RowIndex hint)2021 PeerListContent::RowIndex PeerListContent::findRowIndex(
2022 not_null<PeerListRow*> row,
2023 RowIndex hint) {
2024 if (!showingSearch()) {
2025 Assert(!row->isSearchResult());
2026 return RowIndex(row->absoluteIndex());
2027 }
2028
2029 auto result = hint;
2030 if (getRow(result) == row) {
2031 return result;
2032 }
2033
2034 auto count = shownRowsCount();
2035 for (result.value = 0; result.value != count; ++result.value) {
2036 if (getRow(result) == row) {
2037 return result;
2038 }
2039 }
2040 result.value = -1;
2041 return result;
2042 }
2043
handleNameChanged(not_null<PeerData * > peer)2044 void PeerListContent::handleNameChanged(not_null<PeerData*> peer) {
2045 auto byPeer = _rowsByPeer.find(peer);
2046 if (byPeer != _rowsByPeer.cend()) {
2047 for (auto row : byPeer->second) {
2048 if (addingToSearchIndex()) {
2049 addToSearchIndex(row);
2050 }
2051 row->refreshName(_st.item);
2052 updateRow(row);
2053 }
2054 }
2055 }
2056
~PeerListContent()2057 PeerListContent::~PeerListContent() {
2058 if (_contextMenu) {
2059 _contextMenu->setDestroyedCallback(nullptr);
2060 }
2061 }
2062
peerListShowRowMenu(not_null<PeerListRow * > row,bool highlightRow,Fn<void (not_null<Ui::PopupMenu * >)> destroyed)2063 void PeerListContentDelegate::peerListShowRowMenu(
2064 not_null<PeerListRow*> row,
2065 bool highlightRow,
2066 Fn<void(not_null<Ui::PopupMenu *>)> destroyed) {
2067 _content->showRowMenu(row, highlightRow, std::move(destroyed));
2068 }
2069