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