peer_list_box.cpp 64 KB

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