peer_lists_box.cpp 11 KB

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