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