gifs_list_widget.cpp 26 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049
  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 "chat_helpers/gifs_list_widget.h"
  8. #include "api/api_toggling_media.h" // Api::ToggleSavedGif
  9. #include "base/const_string.h"
  10. #include "base/qt/qt_key_modifiers.h"
  11. #include "chat_helpers/stickers_list_footer.h"
  12. #include "data/data_photo.h"
  13. #include "data/data_document.h"
  14. #include "data/stickers/data_custom_emoji.h"
  15. #include "data/data_session.h"
  16. #include "data/data_user.h"
  17. #include "data/data_file_origin.h"
  18. #include "data/data_photo_media.h"
  19. #include "data/data_document_media.h"
  20. #include "data/stickers/data_stickers.h"
  21. #include "menu/menu_send.h" // SendMenu::FillSendMenu
  22. #include "mtproto/mtproto_config.h"
  23. #include "core/click_handler_types.h"
  24. #include "ui/controls/tabbed_search.h"
  25. #include "ui/layers/generic_box.h"
  26. #include "ui/widgets/buttons.h"
  27. #include "ui/widgets/fields/input_field.h"
  28. #include "ui/widgets/popup_menu.h"
  29. #include "ui/effects/ripple_animation.h"
  30. #include "ui/image/image.h"
  31. #include "ui/painter.h"
  32. #include "boxes/send_gif_with_caption_box.h"
  33. #include "boxes/stickers_box.h"
  34. #include "inline_bots/inline_bot_result.h"
  35. #include "storage/localstorage.h"
  36. #include "lang/lang_keys.h"
  37. #include "layout/layout_position.h"
  38. #include "mainwindow.h"
  39. #include "main/main_session.h"
  40. #include "window/window_session_controller.h"
  41. #include "history/view/history_view_cursor_state.h"
  42. #include "storage/storage_account.h" // Account::writeSavedGifs
  43. #include "styles/style_chat_helpers.h"
  44. #include "styles/style_menu_icons.h"
  45. #include <QtWidgets/QApplication>
  46. namespace ChatHelpers {
  47. namespace {
  48. constexpr auto kSearchRequestDelay = 400;
  49. constexpr auto kMinRepaintDelay = crl::time(33);
  50. constexpr auto kMinAfterScrollDelay = crl::time(33);
  51. } // namespace
  52. void AddGifAction(
  53. Fn<void(QString, Fn<void()> &&, const style::icon*)> callback,
  54. std::shared_ptr<Show> show,
  55. not_null<DocumentData*> document,
  56. const style::ComposeIcons *iconsOverride) {
  57. if (!document->isGifv()) {
  58. return;
  59. }
  60. auto &data = document->owner();
  61. const auto index = data.stickers().savedGifs().indexOf(document);
  62. const auto saved = (index >= 0);
  63. const auto text = (saved
  64. ? tr::lng_context_delete_gif
  65. : tr::lng_context_save_gif)(tr::now);
  66. const auto &icons = iconsOverride
  67. ? *iconsOverride
  68. : st::defaultComposeIcons;
  69. callback(text, [=] {
  70. Api::ToggleSavedGif(
  71. show,
  72. document,
  73. Data::FileOriginSavedGifs(),
  74. !saved);
  75. auto &data = document->owner();
  76. if (saved) {
  77. data.stickers().savedGifsRef().remove(index);
  78. document->session().local().writeSavedGifs();
  79. }
  80. data.stickers().notifySavedGifsUpdated();
  81. }, saved ? &icons.menuGifRemove : &icons.menuGifAdd);
  82. }
  83. GifsListWidget::GifsListWidget(
  84. QWidget *parent,
  85. not_null<Window::SessionController*> controller,
  86. PauseReason level)
  87. : GifsListWidget(parent, {
  88. .show = controller->uiShow(),
  89. .paused = Window::PausedIn(controller, level),
  90. }) {
  91. }
  92. GifsListWidget::GifsListWidget(
  93. QWidget *parent,
  94. GifsListDescriptor &&descriptor)
  95. : Inner(
  96. parent,
  97. descriptor.st ? *descriptor.st : st::defaultEmojiPan,
  98. descriptor.show,
  99. descriptor.paused)
  100. , _show(std::move(descriptor.show))
  101. , _api(&session().mtp())
  102. , _section(Section::Gifs)
  103. , _updateInlineItems([=] { updateInlineItems(); })
  104. , _mosaic(st::emojiPanWidth - st::inlineResultsLeft)
  105. , _previewTimer([=] { showPreview(); }) {
  106. setMouseTracking(true);
  107. setAttribute(Qt::WA_OpaquePaintEvent);
  108. setupSearch();
  109. _inlineRequestTimer.setSingleShot(true);
  110. connect(
  111. &_inlineRequestTimer,
  112. &QTimer::timeout,
  113. this,
  114. [=] { sendInlineRequest(); });
  115. session().data().stickers().savedGifsUpdated(
  116. ) | rpl::start_with_next([=] {
  117. refreshSavedGifs();
  118. }, lifetime());
  119. session().downloaderTaskFinished(
  120. ) | rpl::start_with_next([=] {
  121. updateInlineItems();
  122. }, lifetime());
  123. _show->pauseChanged(
  124. ) | rpl::start_with_next([=] {
  125. if (!paused()) {
  126. updateInlineItems();
  127. }
  128. }, lifetime());
  129. sizeValue(
  130. ) | rpl::start_with_next([=](const QSize &s) {
  131. _mosaic.setFullWidth(s.width());
  132. }, lifetime());
  133. _mosaic.setPadding(st::gifsPadding
  134. + QMargins(-st::emojiPanRadius, _search->height(), 0, 0));
  135. _mosaic.setRightSkip(st::inlineResultsSkip);
  136. }
  137. rpl::producer<FileChosen> GifsListWidget::fileChosen() const {
  138. return _fileChosen.events();
  139. }
  140. rpl::producer<PhotoChosen> GifsListWidget::photoChosen() const {
  141. return _photoChosen.events();
  142. }
  143. auto GifsListWidget::inlineResultChosen() const
  144. -> rpl::producer<InlineChosen> {
  145. return _inlineResultChosen.events();
  146. }
  147. object_ptr<TabbedSelector::InnerFooter> GifsListWidget::createFooter() {
  148. Expects(_footer == nullptr);
  149. using FooterDescriptor = StickersListFooter::Descriptor;
  150. auto result = object_ptr<StickersListFooter>(FooterDescriptor{
  151. .session = &session(),
  152. .paused = pausedMethod(),
  153. .parent = this,
  154. .st = &st(),
  155. .features = { .stickersSettings = false },
  156. });
  157. _footer = result;
  158. _chosenSetId = Data::Stickers::RecentSetId;
  159. GifSectionsValue(
  160. &session()
  161. ) | rpl::start_with_next([=](std::vector<GifSection> &&list) {
  162. _sections = std::move(list);
  163. refreshIcons();
  164. }, _footer->lifetime());
  165. _footer->setChosen(
  166. ) | rpl::start_with_next([=](uint64 setId) {
  167. if (_search) {
  168. _search->cancel();
  169. }
  170. _chosenSetId = setId;
  171. refreshIcons();
  172. const auto i = ranges::find(_sections, setId, [](GifSection value) {
  173. return value.document->id;
  174. });
  175. searchForGifs((i != end(_sections)) ? i->emoji->text() : QString());
  176. }, _footer->lifetime());
  177. return result;
  178. }
  179. void GifsListWidget::refreshIcons() {
  180. if (_footer) {
  181. _footer->refreshIcons(
  182. fillIcons(),
  183. _chosenSetId,
  184. nullptr,
  185. ValidateIconAnimations::None);
  186. }
  187. }
  188. std::vector<StickerIcon> GifsListWidget::fillIcons() {
  189. auto result = std::vector<StickerIcon>();
  190. result.reserve(_sections.size() + 1);
  191. result.emplace_back(Data::Stickers::RecentSetId);
  192. const auto side = StickersListFooter::IconFrameSize();
  193. for (const auto &section : _sections) {
  194. const auto s = section.document;
  195. const auto id = s->id;
  196. const auto size = s->hasThumbnail()
  197. ? QSize(
  198. s->thumbnailLocation().width(),
  199. s->thumbnailLocation().height())
  200. : QSize();
  201. const auto pix = size.scaled(side, side, Qt::KeepAspectRatio);
  202. const auto owner = &s->owner();
  203. const auto already = _fakeSets.find(id);
  204. const auto set = (already != end(_fakeSets))
  205. ? already
  206. : _fakeSets.emplace(
  207. id,
  208. std::make_unique<Data::StickersSet>(
  209. owner,
  210. id,
  211. 0,
  212. 0,
  213. QString(),
  214. QString(),
  215. 0,
  216. Data::StickersSetFlag::Special,
  217. 0)).first;
  218. result.emplace_back(set->second.get(), s, pix.width(), pix.height());
  219. }
  220. return result;
  221. }
  222. void GifsListWidget::visibleTopBottomUpdated(
  223. int visibleTop,
  224. int visibleBottom) {
  225. const auto top = getVisibleTop();
  226. Inner::visibleTopBottomUpdated(visibleTop, visibleBottom);
  227. if (top != getVisibleTop()) {
  228. _lastScrolledAt = crl::now();
  229. update();
  230. }
  231. checkLoadMore();
  232. }
  233. void GifsListWidget::checkLoadMore() {
  234. auto visibleHeight = (getVisibleBottom() - getVisibleTop());
  235. if (getVisibleBottom() + visibleHeight > height()) {
  236. sendInlineRequest();
  237. }
  238. }
  239. int GifsListWidget::countDesiredHeight(int newWidth) {
  240. return _mosaic.countDesiredHeight(newWidth);
  241. }
  242. GifsListWidget::~GifsListWidget() {
  243. clearInlineRows(true);
  244. deleteUnusedGifLayouts();
  245. deleteUnusedInlineLayouts();
  246. }
  247. void GifsListWidget::cancelGifsSearch() {
  248. _search->setLoading(false);
  249. if (_inlineRequestId) {
  250. _api.request(_inlineRequestId).cancel();
  251. _inlineRequestId = 0;
  252. }
  253. _inlineRequestTimer.stop();
  254. _inlineQuery = _inlineNextQuery = _inlineNextOffset = QString();
  255. _inlineCache.clear();
  256. refreshInlineRows(nullptr, true);
  257. }
  258. void GifsListWidget::inlineResultsDone(const MTPmessages_BotResults &result) {
  259. _search->setLoading(false);
  260. _inlineRequestId = 0;
  261. auto it = _inlineCache.find(_inlineQuery);
  262. auto adding = (it != _inlineCache.cend());
  263. if (result.type() == mtpc_messages_botResults) {
  264. auto &d = result.c_messages_botResults();
  265. session().data().processUsers(d.vusers());
  266. auto &v = d.vresults().v;
  267. auto queryId = d.vquery_id().v;
  268. if (it == _inlineCache.cend()) {
  269. it = _inlineCache.emplace(
  270. _inlineQuery,
  271. std::make_unique<InlineCacheEntry>()).first;
  272. }
  273. const auto entry = it->second.get();
  274. entry->nextOffset = qs(d.vnext_offset().value_or_empty());
  275. if (const auto count = v.size()) {
  276. entry->results.reserve(entry->results.size() + count);
  277. }
  278. auto added = 0;
  279. for (const auto &res : v) {
  280. auto result = InlineBots::Result::Create(
  281. &session(),
  282. queryId,
  283. res);
  284. if (result) {
  285. ++added;
  286. entry->results.push_back(std::move(result));
  287. }
  288. }
  289. if (!added) {
  290. entry->nextOffset = QString();
  291. }
  292. } else if (adding) {
  293. it->second->nextOffset = QString();
  294. }
  295. if (!showInlineRows(!adding)) {
  296. it->second->nextOffset = QString();
  297. }
  298. checkLoadMore();
  299. }
  300. void GifsListWidget::paintEvent(QPaintEvent *e) {
  301. Painter p(this);
  302. auto clip = e->rect();
  303. p.fillRect(clip, st().bg);
  304. paintInlineItems(p, clip);
  305. }
  306. void GifsListWidget::paintInlineItems(Painter &p, QRect clip) {
  307. if (_mosaic.empty()) {
  308. p.setFont(st::normalFont);
  309. p.setPen(st::noContactsColor);
  310. auto text = _inlineQuery.isEmpty()
  311. ? tr::lng_gifs_no_saved(tr::now)
  312. : tr::lng_inline_bot_no_results(tr::now);
  313. p.drawText(QRect(0, 0, width(), (height() / 3) * 2 + st::normalFont->height), text, style::al_center);
  314. return;
  315. }
  316. const auto gifPaused = paused();
  317. using namespace InlineBots::Layout;
  318. PaintContext context(crl::now(), false, gifPaused, false);
  319. auto paintItem = [&](not_null<const ItemBase*> item, QPoint point) {
  320. p.translate(point.x(), point.y());
  321. item->paint(
  322. p,
  323. clip.translated(-point),
  324. &context);
  325. p.translate(-point.x(), -point.y());
  326. };
  327. _mosaic.paint(std::move(paintItem), clip);
  328. }
  329. void GifsListWidget::mousePressEvent(QMouseEvent *e) {
  330. if (e->button() != Qt::LeftButton) {
  331. return;
  332. }
  333. _lastMousePos = e->globalPos();
  334. updateSelected();
  335. _pressed = _selected;
  336. ClickHandler::pressed();
  337. _previewTimer.callOnce(QApplication::startDragTime());
  338. }
  339. base::unique_qptr<Ui::PopupMenu> GifsListWidget::fillContextMenu(
  340. const SendMenu::Details &details) {
  341. if (_selected < 0 || _pressed >= 0) {
  342. return nullptr;
  343. }
  344. auto menu = base::make_unique_q<Ui::PopupMenu>(this, st().menu);
  345. const auto selected = _selected;
  346. const auto send = crl::guard(this, [=](Api::SendOptions options) {
  347. selectInlineResult(selected, options, true);
  348. });
  349. const auto item = _mosaic.maybeItemAt(_selected);
  350. const auto isInlineResult = !item->getPhoto()
  351. && !item->getDocument()
  352. && item->getResult();
  353. const auto icons = &st().icons;
  354. auto copyDetails = details;
  355. if (isInlineResult) {
  356. // inline results don't have effects
  357. copyDetails.effectAllowed = false;
  358. }
  359. // In case we're adding items after FillSendMenu we have
  360. // to pass nullptr for showForEffect and attach selector later.
  361. // Otherwise added items widths won't be respected in menu geometry.
  362. SendMenu::FillSendMenu(
  363. menu,
  364. nullptr, // showForMenu
  365. copyDetails,
  366. SendMenu::DefaultCallback(_show, send),
  367. icons);
  368. if (!isInlineResult && _inlineQueryPeer) {
  369. auto done = crl::guard(this, [=](
  370. Api::SendOptions options,
  371. TextWithTags text) {
  372. selectInlineResult(selected, options, true, std::move(text));
  373. });
  374. const auto show = _show;
  375. const auto peer = _inlineQueryPeer;
  376. menu->addAction(tr::lng_send_gif_with_caption(tr::now), [=] {
  377. show->show(Box(
  378. Ui::SendGifWithCaptionBox,
  379. item->getDocument(),
  380. peer,
  381. copyDetails,
  382. std::move(done)));
  383. }, &st::menuIconEdit);
  384. }
  385. if (const auto item = _mosaic.maybeItemAt(_selected)) {
  386. const auto document = item->getDocument()
  387. ? item->getDocument() // Saved GIF.
  388. : item->getPreviewDocument(); // Searched GIF.
  389. if (document) {
  390. auto callback = [&](
  391. const QString &text,
  392. Fn<void()> &&done,
  393. const style::icon *icon) {
  394. menu->addAction(text, std::move(done), icon);
  395. };
  396. AddGifAction(std::move(callback), _show, document, icons);
  397. }
  398. }
  399. SendMenu::AttachSendMenuEffect(
  400. menu,
  401. _show,
  402. copyDetails,
  403. SendMenu::DefaultCallback(_show, send));
  404. return menu;
  405. }
  406. void GifsListWidget::mouseReleaseEvent(QMouseEvent *e) {
  407. _previewTimer.cancel();
  408. auto pressed = std::exchange(_pressed, -1);
  409. auto activated = ClickHandler::unpressed();
  410. if (_previewShown) {
  411. _previewShown = false;
  412. return;
  413. }
  414. _lastMousePos = e->globalPos();
  415. updateSelected();
  416. if (_selected < 0 || _selected != pressed || !activated) {
  417. return;
  418. }
  419. if (dynamic_cast<InlineBots::Layout::SendClickHandler*>(activated.get())) {
  420. selectInlineResult(_selected, {});
  421. } else {
  422. ActivateClickHandler(window(), activated, {
  423. e->button(),
  424. QVariant::fromValue(ClickHandlerContext{
  425. .show = _show,
  426. })
  427. });
  428. }
  429. }
  430. void GifsListWidget::selectInlineResult(
  431. int index,
  432. Api::SendOptions options,
  433. bool forceSend,
  434. TextWithTags caption) {
  435. const auto item = _mosaic.maybeItemAt(index);
  436. if (!item) {
  437. return;
  438. }
  439. const auto messageSendingFrom = [&] {
  440. if (options.scheduled) {
  441. return Ui::MessageSendingAnimationFrom();
  442. }
  443. const auto rect = item->innerContentRect().translated(
  444. _mosaic.findRect(index).topLeft());
  445. return Ui::MessageSendingAnimationFrom{
  446. .type = Ui::MessageSendingAnimationFrom::Type::Gif,
  447. .localId = session().data().nextLocalMessageId(),
  448. .globalStartGeometry = mapToGlobal(rect),
  449. .crop = true,
  450. };
  451. };
  452. forceSend |= base::IsCtrlPressed();
  453. if (const auto photo = item->getPhoto()) {
  454. using Data::PhotoSize;
  455. const auto media = photo->activeMediaView();
  456. if (forceSend
  457. || (media && media->image(PhotoSize::Thumbnail))
  458. || (media && media->image(PhotoSize::Large))) {
  459. _photoChosen.fire({
  460. .photo = photo,
  461. .options = options });
  462. } else if (!photo->loading(PhotoSize::Thumbnail)) {
  463. photo->load(PhotoSize::Thumbnail, Data::FileOrigin());
  464. }
  465. } else if (const auto document = item->getDocument()) {
  466. const auto media = document->activeMediaView();
  467. const auto preview = Data::VideoPreviewState(media.get());
  468. if (forceSend || (media && preview.loaded())) {
  469. _fileChosen.fire({
  470. .document = document,
  471. .options = options,
  472. .messageSendingFrom = messageSendingFrom(),
  473. .caption = std::move(caption),
  474. });
  475. } else if (!preview.usingThumbnail()) {
  476. if (preview.loading()) {
  477. document->cancel();
  478. } else {
  479. document->save(
  480. document->stickerOrGifOrigin(),
  481. QString());
  482. }
  483. }
  484. } else if (const auto inlineResult = item->getResult()) {
  485. if (inlineResult->onChoose(item)) {
  486. options.hideViaBot = true;
  487. _inlineResultChosen.fire({
  488. .result = inlineResult,
  489. .bot = _searchBot,
  490. .options = options,
  491. .messageSendingFrom = messageSendingFrom(),
  492. });
  493. }
  494. }
  495. }
  496. void GifsListWidget::mouseMoveEvent(QMouseEvent *e) {
  497. _lastMousePos = e->globalPos();
  498. updateSelected();
  499. }
  500. void GifsListWidget::leaveEventHook(QEvent *e) {
  501. clearSelection();
  502. }
  503. void GifsListWidget::leaveToChildEvent(QEvent *e, QWidget *child) {
  504. clearSelection();
  505. }
  506. void GifsListWidget::enterFromChildEvent(QEvent *e, QWidget *child) {
  507. _lastMousePos = QCursor::pos();
  508. updateSelected();
  509. }
  510. void GifsListWidget::clearSelection() {
  511. if (_selected >= 0) {
  512. ClickHandler::clearActive(_mosaic.itemAt(_selected));
  513. setCursor(style::cur_default);
  514. }
  515. _selected = _pressed = -1;
  516. repaintItems();
  517. }
  518. TabbedSelector::InnerFooter *GifsListWidget::getFooter() const {
  519. return _footer;
  520. }
  521. void GifsListWidget::processHideFinished() {
  522. clearSelection();
  523. clearHeavyData();
  524. if (_footer) {
  525. _footer->clearHeavyData();
  526. }
  527. }
  528. void GifsListWidget::processPanelHideFinished() {
  529. clearHeavyData();
  530. if (_footer) {
  531. _footer->clearHeavyData();
  532. }
  533. }
  534. void GifsListWidget::clearHeavyData() {
  535. // Preserve panel state through visibility toggles.
  536. //clearInlineRows(false);
  537. for (const auto &[document, layout] : _gifLayouts) {
  538. layout->unloadHeavyPart();
  539. }
  540. for (const auto &[document, layout] : _inlineLayouts) {
  541. layout->unloadHeavyPart();
  542. }
  543. }
  544. void GifsListWidget::refreshSavedGifs() {
  545. if (_section == Section::Gifs) {
  546. clearInlineRows(false);
  547. const auto &saved = session().data().stickers().savedGifs();
  548. if (!saved.isEmpty()) {
  549. const auto layouts = ranges::views::all(
  550. saved
  551. ) | ranges::views::transform([&](not_null<DocumentData*> gif) {
  552. return layoutPrepareSavedGif(gif);
  553. }) | ranges::views::filter([](const LayoutItem *item) {
  554. return item != nullptr;
  555. }) | ranges::to<std::vector<not_null<LayoutItem*>>>;
  556. _mosaic.addItems(layouts);
  557. }
  558. deleteUnusedGifLayouts();
  559. resizeToWidth(width());
  560. repaintItems();
  561. }
  562. if (isVisible()) {
  563. updateSelected();
  564. } else {
  565. preloadImages();
  566. }
  567. }
  568. void GifsListWidget::clearInlineRows(bool resultsDeleted) {
  569. if (resultsDeleted) {
  570. _selected = _pressed = -1;
  571. } else {
  572. clearSelection();
  573. }
  574. _mosaic.clearRows(resultsDeleted);
  575. }
  576. GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareSavedGif(
  577. not_null<DocumentData*> document) {
  578. auto it = _gifLayouts.find(document);
  579. if (it == _gifLayouts.cend()) {
  580. if (auto layout = LayoutItem::createLayoutGif(this, document)) {
  581. it = _gifLayouts.emplace(document, std::move(layout)).first;
  582. it->second->initDimensions();
  583. } else {
  584. return nullptr;
  585. }
  586. }
  587. if (!it->second->maxWidth()) return nullptr;
  588. return it->second.get();
  589. }
  590. GifsListWidget::LayoutItem *GifsListWidget::layoutPrepareInlineResult(
  591. std::shared_ptr<InlineResult> result) {
  592. const auto raw = result.get();
  593. auto it = _inlineLayouts.find(raw);
  594. if (it == _inlineLayouts.cend()) {
  595. if (auto layout = LayoutItem::createLayout(
  596. this,
  597. std::move(result),
  598. _inlineWithThumb)) {
  599. it = _inlineLayouts.emplace(raw, std::move(layout)).first;
  600. it->second->initDimensions();
  601. } else {
  602. return nullptr;
  603. }
  604. }
  605. if (!it->second->maxWidth()) return nullptr;
  606. return it->second.get();
  607. }
  608. void GifsListWidget::deleteUnusedGifLayouts() {
  609. if (_mosaic.empty() || _section != Section::Gifs) { // delete all
  610. _gifLayouts.clear();
  611. } else {
  612. for (auto i = _gifLayouts.begin(); i != _gifLayouts.cend();) {
  613. if (i->second->position() < 0) {
  614. i = _gifLayouts.erase(i);
  615. } else {
  616. ++i;
  617. }
  618. }
  619. }
  620. }
  621. void GifsListWidget::deleteUnusedInlineLayouts() {
  622. if (_mosaic.empty() || _section == Section::Gifs) { // delete all
  623. _inlineLayouts.clear();
  624. } else {
  625. for (auto i = _inlineLayouts.begin(); i != _inlineLayouts.cend();) {
  626. if (i->second->position() < 0) {
  627. i = _inlineLayouts.erase(i);
  628. } else {
  629. ++i;
  630. }
  631. }
  632. }
  633. }
  634. void GifsListWidget::preloadImages() {
  635. _mosaic.forEach([](not_null<const LayoutItem*> item) {
  636. item->preload();
  637. });
  638. }
  639. void GifsListWidget::switchToSavedGifs() {
  640. clearInlineRows(false);
  641. _section = Section::Gifs;
  642. refreshSavedGifs();
  643. scrollTo(0);
  644. }
  645. int GifsListWidget::refreshInlineRows(const InlineCacheEntry *entry, bool resultsDeleted) {
  646. if (!entry) {
  647. if (resultsDeleted) {
  648. clearInlineRows(true);
  649. deleteUnusedInlineLayouts();
  650. }
  651. switchToSavedGifs();
  652. return 0;
  653. }
  654. clearSelection();
  655. _section = Section::Inlines;
  656. const auto count = int(entry->results.size());
  657. const auto from = validateExistingInlineRows(entry->results);
  658. auto added = 0;
  659. if (count) {
  660. const auto resultLayouts = entry->results | ranges::views::slice(
  661. from,
  662. count
  663. ) | ranges::views::transform([&](
  664. const std::shared_ptr<InlineBots::Result> &r) {
  665. return layoutPrepareInlineResult(r);
  666. }) | ranges::views::filter([](const LayoutItem *item) {
  667. return item != nullptr;
  668. }) | ranges::to<std::vector<not_null<LayoutItem*>>>;
  669. _mosaic.addItems(resultLayouts);
  670. added = resultLayouts.size();
  671. preloadImages();
  672. }
  673. resizeToWidth(width());
  674. repaintItems();
  675. _lastMousePos = QCursor::pos();
  676. updateSelected();
  677. return added;
  678. }
  679. int GifsListWidget::validateExistingInlineRows(const InlineResults &results) {
  680. const auto until = _mosaic.validateExistingRows([&](
  681. not_null<const LayoutItem*> item,
  682. int untilIndex) {
  683. return item->getResult().get() != results[untilIndex].get();
  684. }, results.size());
  685. if (_mosaic.empty()) {
  686. _inlineWithThumb = false;
  687. for (int i = until; i < results.size(); ++i) {
  688. if (results.at(i)->hasThumbDisplay()) {
  689. _inlineWithThumb = true;
  690. break;
  691. }
  692. }
  693. }
  694. return until;
  695. }
  696. void GifsListWidget::inlineItemLayoutChanged(const InlineBots::Layout::ItemBase *layout) {
  697. if (_selected < 0 || !isVisible()) {
  698. return;
  699. }
  700. if (const auto item = _mosaic.maybeItemAt(_selected)) {
  701. if (layout == item) {
  702. updateSelected();
  703. }
  704. }
  705. }
  706. void GifsListWidget::inlineItemRepaint(
  707. const InlineBots::Layout::ItemBase *layout) {
  708. updateInlineItems();
  709. }
  710. bool GifsListWidget::inlineItemVisible(
  711. const InlineBots::Layout::ItemBase *layout) {
  712. auto position = layout->position();
  713. if (position < 0 || !isVisible()) {
  714. return false;
  715. }
  716. const auto &[row, column] = Layout::IndexToPosition(position);
  717. auto top = 0;
  718. for (auto i = 0; i != row; ++i) {
  719. top += _mosaic.rowHeightAt(i);
  720. }
  721. return (top < getVisibleBottom())
  722. && (top + _mosaic.itemAt(row, column)->height() > getVisibleTop());
  723. }
  724. Data::FileOrigin GifsListWidget::inlineItemFileOrigin() {
  725. return _inlineQuery.isEmpty()
  726. ? Data::FileOriginSavedGifs()
  727. : Data::FileOrigin();
  728. }
  729. void GifsListWidget::afterShown() {
  730. if (_search) {
  731. _search->stealFocus();
  732. }
  733. }
  734. void GifsListWidget::beforeHiding() {
  735. if (_search) {
  736. _search->returnFocus();
  737. }
  738. }
  739. bool GifsListWidget::refreshInlineRows(int32 *added) {
  740. auto it = _inlineCache.find(_inlineQuery);
  741. const InlineCacheEntry *entry = nullptr;
  742. if (it != _inlineCache.cend()) {
  743. entry = it->second.get();
  744. _inlineNextOffset = it->second->nextOffset;
  745. }
  746. auto result = refreshInlineRows(entry, false);
  747. if (added) *added = result;
  748. return (entry != nullptr);
  749. }
  750. void GifsListWidget::setupSearch() {
  751. const auto session = &_show->session();
  752. _search = MakeSearch(this, st(), [=](std::vector<QString> &&query) {
  753. const auto accumulated = ranges::accumulate(query, QString(), [](
  754. QString a,
  755. QString b) {
  756. return a.isEmpty() ? b : (a + ' ' + b);
  757. });
  758. _chosenSetId = accumulated.isEmpty()
  759. ? Data::Stickers::RecentSetId
  760. : SearchEmojiSectionSetId();
  761. refreshIcons();
  762. searchForGifs(accumulated);
  763. }, session, TabbedSearchType::Emoji);
  764. }
  765. int32 GifsListWidget::showInlineRows(bool newResults) {
  766. auto added = 0;
  767. refreshInlineRows(&added);
  768. if (newResults) {
  769. scrollTo(0);
  770. }
  771. return added;
  772. }
  773. void GifsListWidget::searchForGifs(const QString &query) {
  774. if (query.isEmpty()) {
  775. cancelGifsSearch();
  776. return;
  777. }
  778. if (_inlineQuery != query) {
  779. _search->setLoading(false);
  780. if (_inlineRequestId) {
  781. _api.request(_inlineRequestId).cancel();
  782. _inlineRequestId = 0;
  783. }
  784. if (_inlineCache.find(query) != _inlineCache.cend()) {
  785. _inlineRequestTimer.stop();
  786. _inlineQuery = _inlineNextQuery = query;
  787. showInlineRows(true);
  788. } else {
  789. _inlineNextQuery = query;
  790. _inlineRequestTimer.start(kSearchRequestDelay);
  791. }
  792. }
  793. if (!_searchBot && !_searchBotRequestId) {
  794. const auto username = session().serverConfig().gifSearchUsername;
  795. _searchBotRequestId = _api.request(MTPcontacts_ResolveUsername(
  796. MTP_flags(0),
  797. MTP_string(username),
  798. MTP_string()
  799. )).done([=](const MTPcontacts_ResolvedPeer &result) {
  800. auto &data = result.data();
  801. session().data().processUsers(data.vusers());
  802. session().data().processChats(data.vchats());
  803. const auto peer = session().data().peerLoaded(
  804. peerFromMTP(data.vpeer()));
  805. if (const auto user = peer ? peer->asUser() : nullptr) {
  806. _searchBot = user;
  807. }
  808. }).send();
  809. }
  810. }
  811. void GifsListWidget::cancelled() {
  812. _cancelled.fire({});
  813. }
  814. rpl::producer<> GifsListWidget::cancelRequests() const {
  815. return _cancelled.events();
  816. }
  817. void GifsListWidget::sendInlineRequest() {
  818. if (_inlineRequestId || !_inlineQueryPeer || _inlineNextQuery.isEmpty()) {
  819. return;
  820. }
  821. if (!_searchBot) {
  822. // Wait for the bot being resolved.
  823. _search->setLoading(true);
  824. _inlineRequestTimer.start(kSearchRequestDelay);
  825. return;
  826. }
  827. _inlineRequestTimer.stop();
  828. _inlineQuery = _inlineNextQuery;
  829. auto nextOffset = QString();
  830. auto it = _inlineCache.find(_inlineQuery);
  831. if (it != _inlineCache.cend()) {
  832. nextOffset = it->second->nextOffset;
  833. if (nextOffset.isEmpty()) {
  834. _search->setLoading(false);
  835. return;
  836. }
  837. }
  838. _search->setLoading(true);
  839. _inlineRequestId = _api.request(MTPmessages_GetInlineBotResults(
  840. MTP_flags(0),
  841. _searchBot->inputUser,
  842. _inlineQueryPeer->input,
  843. MTPInputGeoPoint(),
  844. MTP_string(_inlineQuery),
  845. MTP_string(nextOffset)
  846. )).done([this](const MTPmessages_BotResults &result) {
  847. inlineResultsDone(result);
  848. }).fail([this] {
  849. // show error?
  850. _search->setLoading(false);
  851. _inlineRequestId = 0;
  852. }).handleAllErrors().send();
  853. }
  854. void GifsListWidget::refreshRecent() {
  855. if (_section == Section::Gifs) {
  856. refreshSavedGifs();
  857. }
  858. }
  859. void GifsListWidget::updateSelected() {
  860. if (_pressed >= 0 && !_previewShown) {
  861. return;
  862. }
  863. const auto p = mapFromGlobal(_lastMousePos);
  864. const auto sx = rtl() ? (width() - p.x()) : p.x();
  865. const auto sy = p.y();
  866. const auto &[index, exact, relative] = _mosaic.findByPoint({ sx, sy });
  867. const auto selected = exact ? index : -1;
  868. const auto item = exact ? _mosaic.itemAt(selected).get() : nullptr;
  869. const auto link = exact ? item->getState(relative, {}).link : nullptr;
  870. if (_selected != selected) {
  871. if (const auto s = _mosaic.maybeItemAt(_selected)) {
  872. s->update();
  873. }
  874. _selected = selected;
  875. if (item) {
  876. item->update();
  877. }
  878. if (_previewShown && _selected >= 0 && _pressed != _selected) {
  879. _pressed = _selected;
  880. if (item) {
  881. if (const auto preview = item->getPreviewDocument()) {
  882. _show->showMediaPreview(
  883. Data::FileOriginSavedGifs(),
  884. preview);
  885. } else if (const auto preview = item->getPreviewPhoto()) {
  886. _show->showMediaPreview(Data::FileOrigin(), preview);
  887. }
  888. }
  889. }
  890. }
  891. if (ClickHandler::setActive(link, item)) {
  892. setCursor(link ? style::cur_pointer : style::cur_default);
  893. }
  894. }
  895. void GifsListWidget::showPreview() {
  896. if (_pressed < 0) {
  897. return;
  898. }
  899. if (const auto layout = _mosaic.maybeItemAt(_pressed)) {
  900. if (const auto previewDocument = layout->getPreviewDocument()) {
  901. _previewShown = _show->showMediaPreview(
  902. Data::FileOriginSavedGifs(),
  903. previewDocument);
  904. } else if (const auto previewPhoto = layout->getPreviewPhoto()) {
  905. _previewShown = _show->showMediaPreview(
  906. Data::FileOrigin(),
  907. previewPhoto);
  908. }
  909. }
  910. }
  911. void GifsListWidget::updateInlineItems() {
  912. const auto now = crl::now();
  913. const auto delay = std::max(
  914. _lastScrolledAt + kMinAfterScrollDelay - now,
  915. _lastUpdatedAt + kMinRepaintDelay - now);
  916. if (delay <= 0) {
  917. repaintItems(now);
  918. } else if (!_updateInlineItems.isActive()
  919. || _updateInlineItems.remainingTime() > kMinRepaintDelay) {
  920. _updateInlineItems.callOnce(std::max(delay, kMinRepaintDelay));
  921. }
  922. }
  923. void GifsListWidget::repaintItems(crl::time now) {
  924. _lastUpdatedAt = now ? now : crl::now();
  925. update();
  926. }
  927. } // namespace ChatHelpers