diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 2decd8466..b2b7236a3 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -647,6 +647,8 @@ PRIVATE inline_bots/inline_bot_result.h inline_bots/inline_bot_send_data.cpp inline_bots/inline_bot_send_data.h + inline_bots/inline_results_inner.cpp + inline_bots/inline_results_inner.h inline_bots/inline_results_widget.cpp inline_bots/inline_results_widget.h intro/intro_code.cpp diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h index 2c1fa9f96..d20c67c1d 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h @@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/object_ptr.h" namespace InlineBots { -class Result; +struct ResultSelected; } // namespace InlineBots namespace Main { @@ -60,11 +60,7 @@ public: not_null photo; Api::SendOptions options; }; - struct InlineChosen { - not_null result; - not_null bot; - Api::SendOptions options; - }; + using InlineChosen = InlineBots::ResultSelected; enum class Mode { Full, EmojiOnly diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 8ae6619a8..f5fe90f92 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -89,7 +89,7 @@ void CheckForSwitchInlineButton(not_null item) { return; } if (const auto user = item->history()->peer->asUser()) { - if (!user->isBot() || !user->botInfo->inlineReturnPeerId) { + if (!user->isBot() || !user->botInfo->inlineReturnTo.key) { return; } if (const auto markup = item->Get()) { diff --git a/Telegram/SourceFiles/data/data_user.h b/Telegram/SourceFiles/data/data_user.h index bff3be7ec..39665a968 100644 --- a/Telegram/SourceFiles/data/data_user.h +++ b/Telegram/SourceFiles/data/data_user.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "data/data_peer.h" +#include "dialogs/dialogs_key.h" class BotCommand { public: @@ -34,7 +35,7 @@ struct BotInfo { Ui::Text::String text = { int(st::msgMinWidth) }; // description QString startToken, startGroupToken, shareGameShortName; - PeerId inlineReturnPeerId = 0; + Dialogs::EntryState inlineReturnTo; }; class UserData : public PeerData { diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 86066c2e0..169d13e83 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -1814,24 +1814,17 @@ void InnerWidget::contextMenuEvent(QContextMenuEvent *e) { } else { fillArchiveSearchMenu(_menu.get()); } - } else if (const auto history = row.key.history()) { - Window::FillPeerMenu( + } else { + Window::FillDialogsEntryMenu( _controller, - Window::PeerMenuRequest{ - .peer = history->peer, - .source = Window::PeerMenuRequest::Source::ChatsList, + Dialogs::EntryState{ + .key = row.key, + .section = Dialogs::EntryState::Section::ChatsList, .filterId = _filterId, }, [&](const QString &text, Fn callback) { return _menu->addAction(text, std::move(callback)); }); - } else if (const auto folder = row.key.folder()) { - Window::FillFolderMenu( - _controller, - Window::FolderMenuRequest{ folder }, - [&](const QString &text, Fn callback) { - return _menu->addAction(text, std::move(callback)); - }); } connect(_menu.get(), &QObject::destroyed, [=] { if (_menuRow.key) { diff --git a/Telegram/SourceFiles/dialogs/dialogs_key.h b/Telegram/SourceFiles/dialogs/dialogs_key.h index 223488d21..2f5967b45 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_key.h +++ b/Telegram/SourceFiles/dialogs/dialogs_key.h @@ -109,4 +109,21 @@ inline bool operator>=(const RowDescriptor &a, const RowDescriptor &b) { return !(a < b); } +struct EntryState { + enum class Section { + History, + Profile, + ChatsList, + Scheduled, + Pinned, + Replies, + }; + + Key key; + Section section = Section::History; + FilterId filterId = 0; + MsgId rootId = 0; + MsgId currentReplyToId = 0; +}; + } // namespace Dialogs diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index c41aeb2d3..624e67f6c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -525,7 +525,7 @@ void Widget::refreshFolderTopBar() { _folderTopBar->setActiveChat( HistoryView::TopBarWidget::ActiveChat{ .key = _openedFolder, - .section = HistoryView::TopBarWidget::Section::Dialogs, + .section = Dialogs::EntryState::Section::ChatsList, }, nullptr); } else { diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index c672f9072..bc542e29e 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -191,7 +191,7 @@ void activateBotCommand( if (samePeer) { Notify::switchInlineBotButtonReceived(session, QString::fromUtf8(button->data), bot, msg->id); return true; - } else if (bot->isBot() && bot->botInfo->inlineReturnPeerId) { + } else if (bot->isBot() && bot->botInfo->inlineReturnTo.key) { if (Notify::switchInlineBotButtonReceived(session, QString::fromUtf8(button->data))) { return true; } diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 122792c35..6de4d1f96 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -707,14 +707,20 @@ void HistoryWidget::setGeometryWithTopMoved( _topDelta = 0; } +Dialogs::EntryState HistoryWidget::computeDialogsEntryState() const { + return Dialogs::EntryState{ + .key = _history, + .section = Dialogs::EntryState::Section::History, + .currentReplyToId = replyToId(), + }; +} + void HistoryWidget::refreshTopBarActiveChat() { - _topBar->setActiveChat( - HistoryView::TopBarWidget::ActiveChat{ - .key = _history, - .section = HistoryView::TopBarWidget::Section::History, - .currentReplyToId = replyToId(), - }, - _history->sendActionPainter()); + const auto state = computeDialogsEntryState(); + _topBar->setActiveChat(state, _history->sendActionPainter()); + if (_inlineResults) { + _inlineResults->setCurrentDialogsEntryState(state); + } } void HistoryWidget::refreshTabbedPanel() { @@ -830,7 +836,7 @@ void HistoryWidget::initTabbedSelector() { ) | rpl::filter([=] { return !isHidden(); }) | rpl::start_with_next([=](TabbedSelector::InlineChosen data) { - sendInlineResult(data.result, data.bot, data.options); + sendInlineResult(data); }, lifetime()); selector->setSendMenuType([=] { return sendMenuType(); }); @@ -1195,11 +1201,11 @@ void HistoryWidget::applyInlineBotQuery(UserData *bot, const QString &query) { if (!_inlineResults) { _inlineResults.create(this, controller()); _inlineResults->setResultSelectedCallback([=]( - InlineBots::Result *result, - UserData *bot, - Api::SendOptions options) { - sendInlineResult(result, bot, options); + InlineBots::ResultSelected result) { + sendInlineResult(result); }); + _inlineResults->setCurrentDialogsEntryState( + computeDialogsEntryState()); _inlineResults->requesting( ) | rpl::start_with_next([=](bool requesting) { _tabbedSelectorToggle->setLoading(requesting); @@ -1460,22 +1466,37 @@ bool HistoryWidget::notify_switchInlineBotButtonReceived(const QString &query, U applyDraft(); return true; } - } else if (auto bot = _peer ? _peer->asUser() : nullptr) { - const auto toPeerId = bot->isBot() - ? bot->botInfo->inlineReturnPeerId - : PeerId(0); - if (!toPeerId) { + } else if (const auto bot = _peer ? _peer->asUser() : nullptr) { + const auto to = bot->isBot() + ? bot->botInfo->inlineReturnTo + : Dialogs::EntryState(); + const auto history = to.key.history(); + if (!history) { return false; } - bot->botInfo->inlineReturnPeerId = 0; - const auto h = bot->owner().history(toPeerId); + bot->botInfo->inlineReturnTo = Dialogs::EntryState(); + using Section = Dialogs::EntryState::Section; + TextWithTags textWithTags = { '@' + bot->username + ' ' + query, TextWithTags::Tags() }; MessageCursor cursor = { textWithTags.text.size(), textWithTags.text.size(), QFIXED_MAX }; - h->setLocalDraft(std::make_unique(textWithTags, 0, cursor, false)); - if (h == _history) { - applyDraft(); + auto draft = std::make_unique( + textWithTags, + to.currentReplyToId, + cursor, + false); + + if (to.section == Section::Replies) { + controller()->showRepliesForMessage(history, to.rootId); } else { - Ui::showPeerHistory(h->peer, ShowAtUnreadMsgId); + history->setLocalDraft(std::move(draft)); + if (to.section == Section::Scheduled) { + controller()->showSection( + HistoryView::ScheduledMemento(history)); + } else if (history == _history) { + applyDraft(); + } else { + Ui::showPeerHistory(history->peer, ShowAtUnreadMsgId); + } } return true; } @@ -1657,9 +1678,7 @@ void HistoryWidget::showHistory( _pinnedClickedId = FullMsgId(); _minPinnedId = std::nullopt; - MsgId wasMsgId = _showAtMsgId; - History *wasHistory = _history; - + const auto wasDialogsEntryState = computeDialogsEntryState(); const auto startBot = (showAtMsgId == ShowAndStartBotMsgId); if (startBot) { showAtMsgId = ShowAtTheEndMsgId; @@ -1719,8 +1738,8 @@ void HistoryWidget::showHistory( if (const auto user = _peer->asUser()) { if (const auto &info = user->botInfo) { if (startBot) { - if (wasHistory) { - info->inlineReturnPeerId = wasHistory->peer->id; + if (wasDialogsEntryState.key) { + info->inlineReturnTo = wasDialogsEntryState; } sendBotStartCommand(); _history->clearLocalDraft(); @@ -1894,8 +1913,8 @@ void HistoryWidget::showHistory( if (const auto user = _peer->asUser()) { if (const auto &info = user->botInfo) { if (startBot) { - if (wasHistory) { - info->inlineReturnPeerId = wasHistory->peer->id; + if (wasDialogsEntryState.key) { + info->inlineReturnTo = wasDialogsEntryState; } sendBotStartCommand(); } @@ -5078,17 +5097,14 @@ void HistoryWidget::onFieldTabbed() { } } -void HistoryWidget::sendInlineResult( - not_null result, - not_null bot, - Api::SendOptions options) { +void HistoryWidget::sendInlineResult(InlineBots::ResultSelected result) { if (!_peer || !_peer->canWrite()) { return; } else if (showSlowmodeError()) { return; } - auto errorText = result->getErrorOnSend(_history); + auto errorText = result.result->getErrorOnSend(_history); if (!errorText.isEmpty()) { Ui::show(Box(errorText)); return; @@ -5096,9 +5112,9 @@ void HistoryWidget::sendInlineResult( auto action = Api::SendAction(_history); action.replyTo = replyToId(); - action.options = std::move(options); + action.options = std::move(result.options); action.generateLocal = true; - session().api().sendInlineResult(bot, result, action); + session().api().sendInlineResult(result.bot, result.result, action); clearFieldText(); _saveDraftText = true; @@ -5106,14 +5122,14 @@ void HistoryWidget::sendInlineResult( onDraftSave(); auto &bots = cRefRecentInlineBots(); - const auto index = bots.indexOf(bot); + const auto index = bots.indexOf(result.bot); if (index) { if (index > 0) { bots.removeAt(index); } else if (bots.size() >= RecentInlineBotsLimit) { bots.resize(RecentInlineBotsLimit - 1); } - bots.push_front(bot); + bots.push_front(result.bot); session().local().writeRecentHashtagsAndBots(); } diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index b05e6fb36..bad3b769d 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -38,7 +38,7 @@ namespace Layout { class ItemBase; class Widget; } // namespace Layout -class Result; +struct ResultSelected; } // namespace InlineBots namespace Data { @@ -353,6 +353,8 @@ private: void createTabbedPanel(); void setTabbedPanel(std::unique_ptr panel); void updateField(); + + [[nodiscard]] Dialogs::EntryState computeDialogsEntryState() const; void refreshTopBarActiveChat(); void requestMessageData(MsgId msgId); @@ -485,10 +487,7 @@ private: int wasScrollTop, int nowScrollTop); - void sendInlineResult( - not_null result, - not_null bot, - Api::SendOptions options); + void sendInlineResult(InlineBots::ResultSelected result); void drawField(Painter &p, const QRect &rect); void paintEditHeader( diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index 97189d361..fd852a95d 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -35,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/controls/history_view_voice_record_bar.h" #include "history/view/history_view_webpage_preview.h" #include "inline_bots/inline_results_widget.h" +#include "inline_bots/inline_bot_result.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "media/audio/media_audio_capture.h" @@ -583,6 +584,13 @@ void ComposeControls::setHistory(SetHistoryArgs &&args) { } } +void ComposeControls::setCurrentDialogsEntryState(Dialogs::EntryState state) { + _currentDialogsEntryState = state; + if (_inlineResults) { + _inlineResults->setCurrentDialogsEntryState(state); + } +} + void ComposeControls::move(int x, int y) { _wrap->move(x, y); _writeRestricted->move(x, y); @@ -1523,6 +1531,7 @@ bool ComposeControls::handleCancelRequest() { void ComposeControls::initWebpageProcess() { Expects(_history); + const auto peer = _history->peer; auto &lifetime = _wrap->lifetime(); const auto requestRepaint = crl::guard(_header.get(), [=] { @@ -1787,15 +1796,11 @@ void ComposeControls::applyInlineBotQuery( _inlineResults = std::make_unique( _parent, _window); + _inlineResults->setCurrentDialogsEntryState( + _currentDialogsEntryState); _inlineResults->setResultSelectedCallback([=]( - InlineBots::Result *result, - UserData *bot, - Api::SendOptions options) { - _inlineResultChosen.fire(InlineChosen{ - .result = result, - .bot = bot, - .options = options, - }); + InlineBots::ResultSelected result) { + _inlineResultChosen.fire_copy(result); }); _inlineResults->requesting( ) | rpl::start_with_next([=](bool requesting) { diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index b04a68b31..5b6200862 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/required.h" #include "api/api_common.h" #include "base/unique_qptr.h" +#include "dialogs/dialogs_key.h" #include "history/view/controls/compose_controls_common.h" #include "ui/rp_widget.h" #include "ui/effects/animations.h" @@ -87,6 +88,8 @@ public: [[nodiscard]] Main::Session &session() const; void setHistory(SetHistoryArgs &&args); + void setCurrentDialogsEntryState(Dialogs::EntryState state); + void finishAnimating(); void move(int x, int y); @@ -237,6 +240,7 @@ private: TextWithTags _localSavedText; TextUpdateEvents _textUpdateEvents; + Dialogs::EntryState _currentDialogsEntryState; //bool _inReplyEditForward = false; //bool _inClickable = false; diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp index 2d8d2fa92..8ea4d512f 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp @@ -104,7 +104,7 @@ PinnedWidget::PinnedWidget( _topBar->setActiveChat( TopBarWidget::ActiveChat{ .key = _history, - .section = TopBarWidget::Section::Pinned, + .section = Dialogs::EntryState::Section::Pinned, }, nullptr); diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 18fe048d0..b7135f1be 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -1137,14 +1137,14 @@ SendMenu::Type RepliesWidget::sendMenuType() const { } void RepliesWidget::refreshTopBarActiveChat() { - _topBar->setActiveChat( - TopBarWidget::ActiveChat{ - .key = _history, - .section = TopBarWidget::Section::Replies, - .rootId = _rootId, - .currentReplyToId = replyToId(), - }, - _sendAction.get()); + const auto state = Dialogs::EntryState{ + .key = _history, + .section = Dialogs::EntryState::Section::Replies, + .rootId = _rootId, + .currentReplyToId = replyToId(), + }; + _topBar->setActiveChat(state, _sendAction.get()); + _composeControls->setCurrentDialogsEntryState(state); } MsgId RepliesWidget::replyToId() const { diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index 9c5f32ca5..15741b1d8 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -99,12 +99,12 @@ ScheduledWidget::ScheduledWidget( controller, ComposeControls::Mode::Scheduled)) , _scrollDown(_scroll, st::historyToDown) { - _topBar->setActiveChat( - TopBarWidget::ActiveChat{ - .key = _history, - .section = TopBarWidget::Section::Scheduled, - }, - nullptr); + const auto state = Dialogs::EntryState{ + .key = _history, + .section = Dialogs::EntryState::Section::Scheduled, + }; + _topBar->setActiveChat(state, nullptr); + _composeControls->setCurrentDialogsEntryState(state); _topBar->move(0, 0); _topBar->resizeToWidth(width()); diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp index 432694aaa..c5eee7f76 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -232,30 +232,10 @@ void TopBarWidget::showMenu() { Fn callback) { return _menu->addAction(text, std::move(callback)); }; - if (const auto peer = _activeChat.key.peer()) { - using Source = Window::PeerMenuRequest::Source; - const auto source = (_activeChat.section == Section::Scheduled) - ? Source::ScheduledSection - : (_activeChat.section == Section::Replies) - ? Source::RepliesSection - : Source::History; - Window::FillPeerMenu( - _controller, - Window::PeerMenuRequest{ - .peer = peer, - .source = source, - .rootId = _activeChat.rootId, - .currentReplyToId = _activeChat.currentReplyToId, - }, - addAction); - } else if (const auto folder = _activeChat.key.folder()) { - Window::FillFolderMenu( - _controller, - Window::FolderMenuRequest{ folder }, - addAction); - } else { - Unexpected("Empty active chat in TopBarWidget::showMenu."); - } + Window::FillDialogsEntryMenu( + _controller, + _activeChat, + addAction); if (_menu->actions().empty()) { _menu.destroy(); } else { diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h index bff509316..988758b46 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h @@ -43,19 +43,8 @@ public: int canForwardCount = 0; int canSendNowCount = 0; }; - enum class Section { - History, - Dialogs, // For folder view in dialogs list. - Scheduled, - Pinned, - Replies, - }; - struct ActiveChat { - Dialogs::Key key; - Section section = Section::History; - MsgId rootId = 0; - MsgId currentReplyToId = 0; - }; + using ActiveChat = Dialogs::EntryState; + using Section = ActiveChat::Section; TopBarWidget( QWidget *parent, diff --git a/Telegram/SourceFiles/info/info_wrap_widget.cpp b/Telegram/SourceFiles/info/info_wrap_widget.cpp index 023742cec..22ab43419 100644 --- a/Telegram/SourceFiles/info/info_wrap_widget.cpp +++ b/Telegram/SourceFiles/info/info_wrap_widget.cpp @@ -575,11 +575,11 @@ void WrapWidget::showTopBarMenu() { return _topBarMenu->addAction(text, std::move(callback)); }; if (const auto peer = key().peer()) { - Window::FillPeerMenu( + Window::FillDialogsEntryMenu( _controller->parentController(), - Window::PeerMenuRequest{ - .peer = peer, - .source = Window::PeerMenuRequest::Source::Profile, + Dialogs::EntryState{ + .key = peer->owner().history(peer), + .section = Dialogs::EntryState::Section::Profile, }, addAction); //} else if (const auto feed = key().feed()) { // #feed diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_result.h b/Telegram/SourceFiles/inline_bots/inline_bot_result.h index 8a086f47c..4969959dd 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_result.h +++ b/Telegram/SourceFiles/inline_bots/inline_bot_result.h @@ -8,9 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "data/data_cloud_file.h" +#include "api/api_common.h" class FileLoader; class History; +class UserData; namespace Data { class LocationPoint; @@ -125,4 +127,10 @@ private: }; +struct ResultSelected { + not_null result; + not_null bot; + Api::SendOptions options; +}; + } // namespace InlineBots diff --git a/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp b/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp new file mode 100644 index 000000000..6074ab1f3 --- /dev/null +++ b/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp @@ -0,0 +1,769 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "inline_bots/inline_results_inner.h" + +#include "api/api_common.h" +#include "chat_helpers/send_context_menu.h" // SendMenu::FillSendMenu +#include "data/data_file_origin.h" +#include "data/data_user.h" +#include "data/data_changes.h" +#include "inline_bots/inline_bot_result.h" +#include "inline_bots/inline_bot_layout_item.h" +#include "lang/lang_keys.h" +#include "mainwindow.h" +#include "facades.h" +#include "main/main_session.h" +#include "window/window_session_controller.h" +#include "ui/widgets/popup_menu.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/labels.h" +#include "history/view/history_view_cursor_state.h" +#include "styles/style_chat_helpers.h" + +#include + +namespace InlineBots { +namespace Layout { + +Inner::Inner( + QWidget *parent, + not_null controller) +: RpWidget(parent) +, _controller(controller) +, _updateInlineItems([=] { updateInlineItems(); }) +, _previewTimer([=] { showPreview(); }) { + resize(st::emojiPanWidth - st::emojiScroll.width - st::buttonRadius, st::inlineResultsMinHeight); + + setMouseTracking(true); + setAttribute(Qt::WA_OpaquePaintEvent); + + _controller->session().downloaderTaskFinished( + ) | rpl::start_with_next([=] { + update(); + }, lifetime()); + + subscribe(controller->gifPauseLevelChanged(), [this] { + if (!_controller->isGifPausedAtLeastFor(Window::GifPauseReason::InlineResults)) { + update(); + } + }); + + _controller->session().changes().peerUpdates( + Data::PeerUpdate::Flag::Rights + ) | rpl::filter([=](const Data::PeerUpdate &update) { + return (update.peer.get() == _inlineQueryPeer); + }) | rpl::start_with_next([=] { + auto isRestricted = (_restrictedLabel != nullptr); + if (isRestricted != isRestrictedView()) { + auto h = countHeight(); + if (h != height()) resize(width(), h); + } + }, lifetime()); +} + +void Inner::visibleTopBottomUpdated( + int visibleTop, + int visibleBottom) { + _visibleBottom = visibleBottom; + if (_visibleTop != visibleTop) { + _visibleTop = visibleTop; + _lastScrolled = crl::now(); + } +} + +void Inner::checkRestrictedPeer() { + if (_inlineQueryPeer) { + const auto error = Data::RestrictionError( + _inlineQueryPeer, + ChatRestriction::f_send_inline); + if (error) { + if (!_restrictedLabel) { + _restrictedLabel.create(this, *error, st::stickersRestrictedLabel); + _restrictedLabel->show(); + _restrictedLabel->move(st::inlineResultsLeft - st::buttonRadius, st::stickerPanPadding); + _restrictedLabel->resizeToNaturalWidth(width() - (st::inlineResultsLeft - st::buttonRadius) * 2); + if (_switchPmButton) { + _switchPmButton->hide(); + } + update(); + } + return; + } + } + if (_restrictedLabel) { + _restrictedLabel.destroy(); + if (_switchPmButton) { + _switchPmButton->show(); + } + update(); + } +} + +bool Inner::isRestrictedView() { + checkRestrictedPeer(); + return (_restrictedLabel != nullptr); +} + +int Inner::countHeight() { + if (isRestrictedView()) { + return st::stickerPanPadding + _restrictedLabel->height() + st::stickerPanPadding; + } else if (_rows.isEmpty() && !_switchPmButton) { + return st::stickerPanPadding + st::normalFont->height + st::stickerPanPadding; + } + auto result = st::stickerPanPadding; + if (_switchPmButton) { + result += _switchPmButton->height() + st::inlineResultsSkip; + } + for (int i = 0, l = _rows.count(); i < l; ++i) { + result += _rows[i].height; + } + return result + st::stickerPanPadding; +} + +QString Inner::tooltipText() const { + if (const auto lnk = ClickHandler::getActive()) { + return lnk->tooltip(); + } + return QString(); +} + +QPoint Inner::tooltipPos() const { + return _lastMousePos; +} + +bool Inner::tooltipWindowActive() const { + return Ui::AppInFocus() && Ui::InFocusChain(window()); +} + +rpl::producer<> Inner::inlineRowsCleared() const { + return _inlineRowsCleared.events(); +} + +Inner::~Inner() = default; + +void Inner::paintEvent(QPaintEvent *e) { + Painter p(this); + QRect r = e ? e->rect() : rect(); + if (r != rect()) { + p.setClipRect(r); + } + p.fillRect(r, st::emojiPanBg); + + paintInlineItems(p, r); +} + +void Inner::paintInlineItems(Painter &p, const QRect &r) { + if (_restrictedLabel) { + return; + } + if (_rows.isEmpty() && !_switchPmButton) { + p.setFont(st::normalFont); + p.setPen(st::noContactsColor); + p.drawText(QRect(0, 0, width(), (height() / 3) * 2 + st::normalFont->height), tr::lng_inline_bot_no_results(tr::now), style::al_center); + return; + } + auto gifPaused = _controller->isGifPausedAtLeastFor(Window::GifPauseReason::InlineResults); + InlineBots::Layout::PaintContext context(crl::now(), false, gifPaused, false); + + auto top = st::stickerPanPadding; + if (_switchPmButton) { + top += _switchPmButton->height() + st::inlineResultsSkip; + } + + auto fromx = rtl() ? (width() - r.x() - r.width()) : r.x(); + auto tox = rtl() ? (width() - r.x()) : (r.x() + r.width()); + for (auto row = 0, rows = _rows.size(); row != rows; ++row) { + auto &inlineRow = _rows[row]; + if (top >= r.top() + r.height()) break; + if (top + inlineRow.height > r.top()) { + auto left = st::inlineResultsLeft - st::buttonRadius; + if (row == rows - 1) context.lastRow = true; + for (int col = 0, cols = inlineRow.items.size(); col < cols; ++col) { + if (left >= tox) break; + + auto item = inlineRow.items.at(col); + auto w = item->width(); + if (left + w > fromx) { + p.translate(left, top); + item->paint(p, r.translated(-left, -top), &context); + p.translate(-left, -top); + } + left += w; + if (item->hasRightSkip()) { + left += st::inlineResultsSkip; + } + } + } + top += inlineRow.height; + } +} + +void Inner::mousePressEvent(QMouseEvent *e) { + if (e->button() != Qt::LeftButton) { + return; + } + _lastMousePos = e->globalPos(); + updateSelected(); + + _pressed = _selected; + ClickHandler::pressed(); + _previewTimer.callOnce(QApplication::startDragTime()); +} + +void Inner::mouseReleaseEvent(QMouseEvent *e) { + _previewTimer.cancel(); + + auto pressed = std::exchange(_pressed, -1); + auto activated = ClickHandler::unpressed(); + + if (_previewShown) { + _previewShown = false; + return; + } + + _lastMousePos = e->globalPos(); + updateSelected(); + + if (_selected < 0 || _selected != pressed || !activated) { + return; + } + + if (dynamic_cast(activated.get())) { + int row = _selected / MatrixRowShift, column = _selected % MatrixRowShift; + selectInlineResult(row, column); + } else { + ActivateClickHandler(window(), activated, e->button()); + } +} + +void Inner::selectInlineResult(int row, int column) { + selectInlineResult(row, column, Api::SendOptions()); +} + +void Inner::selectInlineResult( + int row, + int column, + Api::SendOptions options) { + if (row >= _rows.size() || column >= _rows.at(row).items.size()) { + return; + } + + auto item = _rows[row].items[column]; + if (const auto inlineResult = item->getResult()) { + if (inlineResult->onChoose(item)) { + _resultSelectedCallback({ + .result = inlineResult, + .bot = _inlineBot, + .options = std::move(options) + }); + } + } +} + +void Inner::mouseMoveEvent(QMouseEvent *e) { + _lastMousePos = e->globalPos(); + updateSelected(); +} + +void Inner::leaveEventHook(QEvent *e) { + clearSelection(); + Ui::Tooltip::Hide(); +} + +void Inner::leaveToChildEvent(QEvent *e, QWidget *child) { + clearSelection(); +} + +void Inner::enterFromChildEvent(QEvent *e, QWidget *child) { + _lastMousePos = QCursor::pos(); + updateSelected(); +} + +void Inner::contextMenuEvent(QContextMenuEvent *e) { + if (_selected < 0 || _pressed >= 0) { + return; + } + const auto row = _selected / MatrixRowShift; + const auto column = _selected % MatrixRowShift; + const auto type = SendMenu::Type::Scheduled; + + _menu = base::make_unique_q(this); + + const auto send = [=](Api::SendOptions options) { + selectInlineResult(row, column, options); + }; + SendMenu::FillSendMenu( + _menu, + [&] { return type; }, + SendMenu::DefaultSilentCallback(send), + SendMenu::DefaultScheduleCallback(this, type, send)); + + if (!_menu->actions().empty()) { + _menu->popup(QCursor::pos()); + } +} + +void Inner::clearSelection() { + if (_selected >= 0) { + int srow = _selected / MatrixRowShift, scol = _selected % MatrixRowShift; + Assert(srow >= 0 && srow < _rows.size() && scol >= 0 && scol < _rows.at(srow).items.size()); + ClickHandler::clearActive(_rows.at(srow).items.at(scol)); + setCursor(style::cur_default); + } + _selected = _pressed = -1; + update(); +} + +void Inner::hideFinished() { + clearHeavyData(); +} + +void Inner::clearHeavyData() { + clearInlineRows(false); + for (const auto &[result, layout] : _inlineLayouts) { + layout->unloadHeavyPart(); + } +} + +bool Inner::inlineRowsAddItem(Result *result, Row &row, int32 &sumWidth) { + auto layout = layoutPrepareInlineResult(result, (_rows.size() * MatrixRowShift) + row.items.size()); + if (!layout) return false; + + layout->preload(); + if (inlineRowFinalize(row, sumWidth, layout->isFullLine())) { + layout->setPosition(_rows.size() * MatrixRowShift); + } + + sumWidth += layout->maxWidth(); + if (!row.items.isEmpty() && row.items.back()->hasRightSkip()) { + sumWidth += st::inlineResultsSkip; + } + + row.items.push_back(layout); + return true; +} + +bool Inner::inlineRowFinalize(Row &row, int32 &sumWidth, bool force) { + if (row.items.isEmpty()) return false; + + auto full = (row.items.size() >= kInlineItemsMaxPerRow); + auto big = (sumWidth >= st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft); + if (full || big || force) { + _rows.push_back(layoutInlineRow(row, (full || big) ? sumWidth : 0)); + row = Row(); + row.items.reserve(kInlineItemsMaxPerRow); + sumWidth = 0; + return true; + } + return false; +} + +void Inner::inlineBotChanged() { + refreshInlineRows(nullptr, nullptr, nullptr, true); +} + +void Inner::clearInlineRows(bool resultsDeleted) { + if (resultsDeleted) { + _selected = _pressed = -1; + } else { + clearSelection(); + for_const (auto &row, _rows) { + for_const (auto &item, row.items) { + item->setPosition(-1); + } + } + } + _rows.clear(); +} + +ItemBase *Inner::layoutPrepareInlineResult(Result *result, int32 position) { + auto it = _inlineLayouts.find(result); + if (it == _inlineLayouts.cend()) { + if (auto layout = ItemBase::createLayout(this, result, _inlineWithThumb)) { + it = _inlineLayouts.emplace(result, std::move(layout)).first; + it->second->initDimensions(); + } else { + return nullptr; + } + } + if (!it->second->maxWidth()) { + return nullptr; + } + + it->second->setPosition(position); + return it->second.get(); +} + +void Inner::deleteUnusedInlineLayouts() { + if (_rows.isEmpty()) { // delete all + _inlineLayouts.clear(); + } else { + for (auto i = _inlineLayouts.begin(); i != _inlineLayouts.cend();) { + if (i->second->position() < 0) { + i = _inlineLayouts.erase(i); + } else { + ++i; + } + } + } +} + +Inner::Row &Inner::layoutInlineRow(Row &row, int32 sumWidth) { + auto count = int(row.items.size()); + Assert(count <= kInlineItemsMaxPerRow); + + // enumerate items in the order of growing maxWidth() + // for that sort item indices by maxWidth() + int indices[kInlineItemsMaxPerRow]; + for (auto i = 0; i != count; ++i) { + indices[i] = i; + } + std::sort(indices, indices + count, [&row](int a, int b) -> bool { + return row.items.at(a)->maxWidth() < row.items.at(b)->maxWidth(); + }); + + row.height = 0; + int availw = width() - (st::inlineResultsLeft - st::buttonRadius); + for (int i = 0; i < count; ++i) { + int index = indices[i]; + int w = sumWidth ? (row.items.at(index)->maxWidth() * availw / sumWidth) : row.items.at(index)->maxWidth(); + int actualw = qMax(w, int(st::inlineResultsMinWidth)); + row.height = qMax(row.height, row.items.at(index)->resizeGetHeight(actualw)); + if (sumWidth) { + availw -= actualw; + sumWidth -= row.items.at(index)->maxWidth(); + if (index > 0 && row.items.at(index - 1)->hasRightSkip()) { + availw -= st::inlineResultsSkip; + sumWidth -= st::inlineResultsSkip; + } + } + } + return row; +} + +void Inner::preloadImages() { + for (auto row = 0, rows = _rows.size(); row != rows; ++row) { + for (auto col = 0, cols = _rows[row].items.size(); col != cols; ++col) { + _rows[row].items[col]->preload(); + } + } +} + +void Inner::hideInlineRowsPanel() { + clearInlineRows(false); +} + +void Inner::clearInlineRowsPanel() { + clearInlineRows(false); +} + +void Inner::refreshSwitchPmButton(const CacheEntry *entry) { + if (!entry || entry->switchPmText.isEmpty()) { + _switchPmButton.destroy(); + _switchPmStartToken.clear(); + } else { + if (!_switchPmButton) { + _switchPmButton.create(this, nullptr, st::switchPmButton); + _switchPmButton->show(); + _switchPmButton->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); + _switchPmButton->addClickHandler([=] { switchPm(); }); + } + _switchPmButton->setText(rpl::single(entry->switchPmText)); + _switchPmStartToken = entry->switchPmStartToken; + const auto buttonTop = st::stickerPanPadding; + _switchPmButton->move(st::inlineResultsLeft - st::buttonRadius, buttonTop); + if (isRestrictedView()) { + _switchPmButton->hide(); + } + } + update(); +} + +int Inner::refreshInlineRows(PeerData *queryPeer, UserData *bot, const CacheEntry *entry, bool resultsDeleted) { + _inlineBot = bot; + _inlineQueryPeer = queryPeer; + refreshSwitchPmButton(entry); + auto clearResults = [&] { + if (!entry) { + return true; + } + if (entry->results.empty() && entry->switchPmText.isEmpty()) { + return true; + } + return false; + }; + auto clearResultsResult = clearResults(); // Clang workaround. + if (clearResultsResult) { + if (resultsDeleted) { + clearInlineRows(true); + deleteUnusedInlineLayouts(); + } + _inlineRowsCleared.fire({}); + return 0; + } + + clearSelection(); + + Assert(_inlineBot != 0); + + auto count = int(entry->results.size()); + auto from = validateExistingInlineRows(entry->results); + auto added = 0; + + if (count) { + _rows.reserve(count); + auto row = Row(); + row.items.reserve(kInlineItemsMaxPerRow); + auto sumWidth = 0; + for (auto i = from; i != count; ++i) { + if (inlineRowsAddItem(entry->results[i].get(), row, sumWidth)) { + ++added; + } + } + inlineRowFinalize(row, sumWidth, true); + } + + auto h = countHeight(); + if (h != height()) resize(width(), h); + update(); + + _lastMousePos = QCursor::pos(); + updateSelected(); + + return added; +} + +int Inner::validateExistingInlineRows(const Results &results) { + int count = results.size(), until = 0, untilrow = 0, untilcol = 0; + for (; until < count;) { + if (untilrow >= _rows.size() || _rows[untilrow].items[untilcol]->getResult() != results[until].get()) { + break; + } + ++until; + if (++untilcol == _rows[untilrow].items.size()) { + ++untilrow; + untilcol = 0; + } + } + if (until == count) { // all items are layed out + if (untilrow == _rows.size()) { // nothing changed + return until; + } + + for (int i = untilrow, l = _rows.size(), skip = untilcol; i < l; ++i) { + for (int j = 0, s = _rows[i].items.size(); j < s; ++j) { + if (skip) { + --skip; + } else { + _rows[i].items[j]->setPosition(-1); + } + } + } + if (!untilcol) { // all good rows are filled + _rows.resize(untilrow); + return until; + } + _rows.resize(untilrow + 1); + _rows[untilrow].items.resize(untilcol); + _rows[untilrow] = layoutInlineRow(_rows[untilrow]); + return until; + } + if (untilrow && !untilcol) { // remove last row, maybe it is not full + --untilrow; + untilcol = _rows[untilrow].items.size(); + } + until -= untilcol; + + for (int i = untilrow, l = _rows.size(); i < l; ++i) { + for (int j = 0, s = _rows[i].items.size(); j < s; ++j) { + _rows[i].items[j]->setPosition(-1); + } + } + _rows.resize(untilrow); + + if (_rows.isEmpty()) { + _inlineWithThumb = false; + for (int i = until; i < count; ++i) { + if (results.at(i)->hasThumbDisplay()) { + _inlineWithThumb = true; + break; + } + } + } + return until; +} + +void Inner::inlineItemLayoutChanged(const ItemBase *layout) { + if (_selected < 0 || !isVisible()) { + return; + } + + int row = _selected / MatrixRowShift, col = _selected % MatrixRowShift; + if (row < _rows.size() && col < _rows.at(row).items.size()) { + if (layout == _rows.at(row).items.at(col)) { + updateSelected(); + } + } +} + +void Inner::inlineItemRepaint(const ItemBase *layout) { + auto ms = crl::now(); + if (_lastScrolled + 100 <= ms) { + update(); + } else { + _updateInlineItems.callOnce(_lastScrolled + 100 - ms); + } +} + +bool Inner::inlineItemVisible(const ItemBase *layout) { + int32 position = layout->position(); + if (position < 0 || !isVisible()) { + return false; + } + + int row = position / MatrixRowShift, col = position % MatrixRowShift; + Assert((row < _rows.size()) && (col < _rows[row].items.size())); + + auto &inlineItems = _rows[row].items; + int top = st::stickerPanPadding; + for (int32 i = 0; i < row; ++i) { + top += _rows.at(i).height; + } + + return (top < _visibleBottom) && (top + _rows[row].items[col]->height() > _visibleTop); +} + +Data::FileOrigin Inner::inlineItemFileOrigin() { + return Data::FileOrigin(); +} + +void Inner::updateSelected() { + if (_pressed >= 0 && !_previewShown) { + return; + } + + auto newSelected = -1; + auto p = mapFromGlobal(_lastMousePos); + + int sx = (rtl() ? width() - p.x() : p.x()) - (st::inlineResultsLeft - st::buttonRadius); + int sy = p.y() - st::stickerPanPadding; + if (_switchPmButton) { + sy -= _switchPmButton->height() + st::inlineResultsSkip; + } + int row = -1, col = -1, sel = -1; + ClickHandlerPtr lnk; + ClickHandlerHost *lnkhost = nullptr; + HistoryView::CursorState cursor = HistoryView::CursorState::None; + if (sy >= 0) { + row = 0; + for (int rows = _rows.size(); row < rows; ++row) { + if (sy < _rows[row].height) { + break; + } + sy -= _rows[row].height; + } + } + if (sx >= 0 && row >= 0 && row < _rows.size()) { + auto &inlineItems = _rows[row].items; + col = 0; + for (int cols = inlineItems.size(); col < cols; ++col) { + int width = inlineItems.at(col)->width(); + if (sx < width) { + break; + } + sx -= width; + if (inlineItems.at(col)->hasRightSkip()) { + sx -= st::inlineResultsSkip; + } + } + if (col < inlineItems.size()) { + sel = row * MatrixRowShift + col; + auto result = inlineItems[col]->getState( + QPoint(sx, sy), + HistoryView::StateRequest()); + lnk = result.link; + cursor = result.cursor; + lnkhost = inlineItems[col]; + } else { + row = col = -1; + } + } else { + row = col = -1; + } + int srow = (_selected >= 0) ? (_selected / MatrixRowShift) : -1; + int scol = (_selected >= 0) ? (_selected % MatrixRowShift) : -1; + if (_selected != sel) { + if (srow >= 0 && scol >= 0) { + Assert(srow >= 0 && srow < _rows.size() && scol >= 0 && scol < _rows.at(srow).items.size()); + _rows[srow].items[scol]->update(); + } + _selected = sel; + if (row >= 0 && col >= 0) { + Assert(row >= 0 && row < _rows.size() && col >= 0 && col < _rows.at(row).items.size()); + _rows[row].items[col]->update(); + } + if (_previewShown && _selected >= 0 && _pressed != _selected) { + _pressed = _selected; + if (row >= 0 && col >= 0) { + auto layout = _rows.at(row).items.at(col); + if (const auto w = App::wnd()) { + if (const auto previewDocument = layout->getPreviewDocument()) { + w->showMediaPreview( + Data::FileOrigin(), + previewDocument); + } else if (auto previewPhoto = layout->getPreviewPhoto()) { + w->showMediaPreview(Data::FileOrigin(), previewPhoto); + } + } + } + } + } + if (ClickHandler::setActive(lnk, lnkhost)) { + setCursor(lnk ? style::cur_pointer : style::cur_default); + Ui::Tooltip::Hide(); + } + if (lnk) { + Ui::Tooltip::Show(1000, this); + } +} + +void Inner::showPreview() { + if (_pressed < 0) return; + + int row = _pressed / MatrixRowShift, col = _pressed % MatrixRowShift; + if (row < _rows.size() && col < _rows.at(row).items.size()) { + auto layout = _rows.at(row).items.at(col); + if (const auto w = App::wnd()) { + if (const auto previewDocument = layout->getPreviewDocument()) { + _previewShown = w->showMediaPreview(Data::FileOrigin(), previewDocument); + } else if (const auto previewPhoto = layout->getPreviewPhoto()) { + _previewShown = w->showMediaPreview(Data::FileOrigin(), previewPhoto); + } + } + } +} + +void Inner::updateInlineItems() { + auto ms = crl::now(); + if (_lastScrolled + 100 <= ms) { + update(); + } else { + _updateInlineItems.callOnce(_lastScrolled + 100 - ms); + } +} + +void Inner::switchPm() { + if (_inlineBot && _inlineBot->isBot()) { + _inlineBot->botInfo->startToken = _switchPmStartToken; + _inlineBot->botInfo->inlineReturnTo = _currentDialogsEntryState; + Ui::showPeerHistory(_inlineBot, ShowAndStartBotMsgId); + } +} + +} // namespace Layout +} // namespace InlineBots diff --git a/Telegram/SourceFiles/inline_bots/inline_results_inner.h b/Telegram/SourceFiles/inline_bots/inline_results_inner.h new file mode 100644 index 000000000..6f2e1ab3a --- /dev/null +++ b/Telegram/SourceFiles/inline_bots/inline_results_inner.h @@ -0,0 +1,186 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "ui/rp_widget.h" +#include "ui/abstract_button.h" +#include "ui/widgets/tooltip.h" +#include "ui/effects/animations.h" +#include "ui/effects/panel_animation.h" +#include "dialogs/dialogs_key.h" +#include "base/timer.h" +#include "mtproto/sender.h" +#include "inline_bots/inline_bot_layout_item.h" + +namespace Api { +struct SendOptions; +} // namespace Api + +namespace Ui { +class ScrollArea; +class IconButton; +class LinkButton; +class RoundButton; +class FlatLabel; +class RippleAnimation; +class PopupMenu; +} // namespace Ui + +namespace Window { +class SessionController; +} // namespace Window + +namespace InlineBots { +class Result; +struct ResultSelected; +} // namespace InlineBots + +namespace InlineBots { +namespace Layout { + +constexpr int kInlineItemsMaxPerRow = 5; + +class ItemBase; +using Results = std::vector>; + +struct CacheEntry { + QString nextOffset; + QString switchPmText, switchPmStartToken; + Results results; +}; + +class Inner + : public Ui::RpWidget + , public Ui::AbstractTooltipShower + , public Context + , private base::Subscriber { + +public: + Inner(QWidget *parent, not_null controller); + + void hideFinished(); + + void clearSelection(); + + int refreshInlineRows(PeerData *queryPeer, UserData *bot, const CacheEntry *results, bool resultsDeleted); + void inlineBotChanged(); + void hideInlineRowsPanel(); + void clearInlineRowsPanel(); + + void preloadImages(); + + void inlineItemLayoutChanged(const ItemBase *layout) override; + void inlineItemRepaint(const ItemBase *layout) override; + bool inlineItemVisible(const ItemBase *layout) override; + Data::FileOrigin inlineItemFileOrigin() override; + + int countHeight(); + + void setResultSelectedCallback(Fn callback) { + _resultSelectedCallback = std::move(callback); + } + void setCurrentDialogsEntryState(Dialogs::EntryState state) { + _currentDialogsEntryState = state; + } + + // Ui::AbstractTooltipShower interface. + QString tooltipText() const override; + QPoint tooltipPos() const override; + bool tooltipWindowActive() const override; + + rpl::producer<> inlineRowsCleared() const; + + ~Inner(); + +protected: + void visibleTopBottomUpdated( + int visibleTop, + int visibleBottom) override; + + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void paintEvent(QPaintEvent *e) override; + void leaveEventHook(QEvent *e) override; + void leaveToChildEvent(QEvent *e, QWidget *child) override; + void enterFromChildEvent(QEvent *e, QWidget *child) override; + void contextMenuEvent(QContextMenuEvent *e) override; + +private: + static constexpr bool kRefreshIconsScrollAnimation = true; + static constexpr bool kRefreshIconsNoAnimation = false; + + struct Row { + int height = 0; + QVector items; + }; + + void switchPm(); + + void updateSelected(); + void checkRestrictedPeer(); + bool isRestrictedView(); + void clearHeavyData(); + + void paintInlineItems(Painter &p, const QRect &r); + + void refreshSwitchPmButton(const CacheEntry *entry); + + void showPreview(); + void updateInlineItems(); + void clearInlineRows(bool resultsDeleted); + ItemBase *layoutPrepareInlineResult(Result *result, int32 position); + + bool inlineRowsAddItem(Result *result, Row &row, int32 &sumWidth); + bool inlineRowFinalize(Row &row, int32 &sumWidth, bool force = false); + + Row &layoutInlineRow(Row &row, int32 sumWidth = 0); + void deleteUnusedInlineLayouts(); + + int validateExistingInlineRows(const Results &results); + void selectInlineResult(int row, int column); + void selectInlineResult(int row, int column, Api::SendOptions options); + + not_null _controller; + + int _visibleTop = 0; + int _visibleBottom = 0; + + UserData *_inlineBot = nullptr; + PeerData *_inlineQueryPeer = nullptr; + crl::time _lastScrolled = 0; + base::Timer _updateInlineItems; + bool _inlineWithThumb = false; + + object_ptr _switchPmButton = { nullptr }; + QString _switchPmStartToken; + Dialogs::EntryState _currentDialogsEntryState; + + object_ptr _restrictedLabel = { nullptr }; + + base::unique_qptr _menu; + + QVector _rows; + + std::map> _inlineLayouts; + + rpl::event_stream<> _inlineRowsCleared; + + int _selected = -1; + int _pressed = -1; + QPoint _lastMousePos; + + base::Timer _previewTimer; + bool _previewShown = false; + + Fn _resultSelectedCallback; + +}; + +} // namespace Layout +} // namespace InlineBots diff --git a/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp b/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp index c3184fbb9..7f94c480a 100644 --- a/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp @@ -7,783 +7,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "inline_bots/inline_results_widget.h" -#include "api/api_common.h" -#include "chat_helpers/send_context_menu.h" // SendMenu::FillSendMenu -#include "data/data_photo.h" -#include "data/data_document.h" -#include "data/data_channel.h" #include "data/data_user.h" #include "data/data_session.h" -#include "data/data_file_origin.h" -#include "data/data_changes.h" -#include "ui/widgets/buttons.h" -#include "ui/widgets/popup_menu.h" -#include "ui/widgets/shadow.h" -#include "ui/effects/ripple_animation.h" -#include "ui/image/image_prepare.h" -#include "boxes/confirm_box.h" #include "inline_bots/inline_bot_result.h" -#include "inline_bots/inline_bot_layout_item.h" -#include "dialogs/dialogs_layout.h" -#include "storage/localstorage.h" -#include "lang/lang_keys.h" -#include "mainwindow.h" -#include "mainwidget.h" +#include "inline_bots/inline_results_inner.h" #include "main/main_session.h" #include "window/window_session_controller.h" +#include "ui/widgets/shadow.h" #include "ui/widgets/scroll_area.h" -#include "ui/widgets/labels.h" +#include "ui/image/image_prepare.h" #include "ui/cached_round_corners.h" -#include "history/view/history_view_cursor_state.h" -#include "facades.h" #include "styles/style_chat_helpers.h" -#include - namespace InlineBots { namespace Layout { -namespace internal { namespace { constexpr auto kInlineBotRequestDelay = 400; } // namespace -Inner::Inner( - QWidget *parent, - not_null controller) -: RpWidget(parent) -, _controller(controller) -, _updateInlineItems([=] { updateInlineItems(); }) -, _previewTimer([=] { showPreview(); }) { - resize(st::emojiPanWidth - st::emojiScroll.width - st::buttonRadius, st::inlineResultsMinHeight); - - setMouseTracking(true); - setAttribute(Qt::WA_OpaquePaintEvent); - - _controller->session().downloaderTaskFinished( - ) | rpl::start_with_next([=] { - update(); - }, lifetime()); - - subscribe(controller->gifPauseLevelChanged(), [this] { - if (!_controller->isGifPausedAtLeastFor(Window::GifPauseReason::InlineResults)) { - update(); - } - }); - - _controller->session().changes().peerUpdates( - Data::PeerUpdate::Flag::Rights - ) | rpl::filter([=](const Data::PeerUpdate &update) { - return (update.peer.get() == _inlineQueryPeer); - }) | rpl::start_with_next([=] { - auto isRestricted = (_restrictedLabel != nullptr); - if (isRestricted != isRestrictedView()) { - auto h = countHeight(); - if (h != height()) resize(width(), h); - } - }, lifetime()); -} - -void Inner::visibleTopBottomUpdated( - int visibleTop, - int visibleBottom) { - _visibleBottom = visibleBottom; - if (_visibleTop != visibleTop) { - _visibleTop = visibleTop; - _lastScrolled = crl::now(); - } -} - -void Inner::checkRestrictedPeer() { - if (_inlineQueryPeer) { - const auto error = Data::RestrictionError( - _inlineQueryPeer, - ChatRestriction::f_send_inline); - if (error) { - if (!_restrictedLabel) { - _restrictedLabel.create(this, *error, st::stickersRestrictedLabel); - _restrictedLabel->show(); - _restrictedLabel->move(st::inlineResultsLeft - st::buttonRadius, st::stickerPanPadding); - _restrictedLabel->resizeToNaturalWidth(width() - (st::inlineResultsLeft - st::buttonRadius) * 2); - if (_switchPmButton) { - _switchPmButton->hide(); - } - update(); - } - return; - } - } - if (_restrictedLabel) { - _restrictedLabel.destroy(); - if (_switchPmButton) { - _switchPmButton->show(); - } - update(); - } -} - -bool Inner::isRestrictedView() { - checkRestrictedPeer(); - return (_restrictedLabel != nullptr); -} - -int Inner::countHeight() { - if (isRestrictedView()) { - return st::stickerPanPadding + _restrictedLabel->height() + st::stickerPanPadding; - } else if (_rows.isEmpty() && !_switchPmButton) { - return st::stickerPanPadding + st::normalFont->height + st::stickerPanPadding; - } - auto result = st::stickerPanPadding; - if (_switchPmButton) { - result += _switchPmButton->height() + st::inlineResultsSkip; - } - for (int i = 0, l = _rows.count(); i < l; ++i) { - result += _rows[i].height; - } - return result + st::stickerPanPadding; -} - -QString Inner::tooltipText() const { - if (const auto lnk = ClickHandler::getActive()) { - return lnk->tooltip(); - } - return QString(); -} - -QPoint Inner::tooltipPos() const { - return _lastMousePos; -} - -bool Inner::tooltipWindowActive() const { - return Ui::AppInFocus() && Ui::InFocusChain(window()); -} - -rpl::producer<> Inner::inlineRowsCleared() const { - return _inlineRowsCleared.events(); -} - -Inner::~Inner() = default; - -void Inner::paintEvent(QPaintEvent *e) { - Painter p(this); - QRect r = e ? e->rect() : rect(); - if (r != rect()) { - p.setClipRect(r); - } - p.fillRect(r, st::emojiPanBg); - - paintInlineItems(p, r); -} - -void Inner::paintInlineItems(Painter &p, const QRect &r) { - if (_restrictedLabel) { - return; - } - if (_rows.isEmpty() && !_switchPmButton) { - p.setFont(st::normalFont); - p.setPen(st::noContactsColor); - p.drawText(QRect(0, 0, width(), (height() / 3) * 2 + st::normalFont->height), tr::lng_inline_bot_no_results(tr::now), style::al_center); - return; - } - auto gifPaused = _controller->isGifPausedAtLeastFor(Window::GifPauseReason::InlineResults); - InlineBots::Layout::PaintContext context(crl::now(), false, gifPaused, false); - - auto top = st::stickerPanPadding; - if (_switchPmButton) { - top += _switchPmButton->height() + st::inlineResultsSkip; - } - - auto fromx = rtl() ? (width() - r.x() - r.width()) : r.x(); - auto tox = rtl() ? (width() - r.x()) : (r.x() + r.width()); - for (auto row = 0, rows = _rows.size(); row != rows; ++row) { - auto &inlineRow = _rows[row]; - if (top >= r.top() + r.height()) break; - if (top + inlineRow.height > r.top()) { - auto left = st::inlineResultsLeft - st::buttonRadius; - if (row == rows - 1) context.lastRow = true; - for (int col = 0, cols = inlineRow.items.size(); col < cols; ++col) { - if (left >= tox) break; - - auto item = inlineRow.items.at(col); - auto w = item->width(); - if (left + w > fromx) { - p.translate(left, top); - item->paint(p, r.translated(-left, -top), &context); - p.translate(-left, -top); - } - left += w; - if (item->hasRightSkip()) { - left += st::inlineResultsSkip; - } - } - } - top += inlineRow.height; - } -} - -void Inner::mousePressEvent(QMouseEvent *e) { - if (e->button() != Qt::LeftButton) { - return; - } - _lastMousePos = e->globalPos(); - updateSelected(); - - _pressed = _selected; - ClickHandler::pressed(); - _previewTimer.callOnce(QApplication::startDragTime()); -} - -void Inner::mouseReleaseEvent(QMouseEvent *e) { - _previewTimer.cancel(); - - auto pressed = std::exchange(_pressed, -1); - auto activated = ClickHandler::unpressed(); - - if (_previewShown) { - _previewShown = false; - return; - } - - _lastMousePos = e->globalPos(); - updateSelected(); - - if (_selected < 0 || _selected != pressed || !activated) { - return; - } - - if (dynamic_cast(activated.get())) { - int row = _selected / MatrixRowShift, column = _selected % MatrixRowShift; - selectInlineResult(row, column); - } else { - ActivateClickHandler(window(), activated, e->button()); - } -} - -void Inner::selectInlineResult(int row, int column) { - selectInlineResult(row, column, Api::SendOptions()); -} - -void Inner::selectInlineResult( - int row, - int column, - Api::SendOptions options) { - if (row >= _rows.size() || column >= _rows.at(row).items.size()) { - return; - } - - auto item = _rows[row].items[column]; - if (const auto inlineResult = item->getResult()) { - if (inlineResult->onChoose(item)) { - _resultSelectedCallback( - inlineResult, - _inlineBot, - std::move(options)); - } - } -} - -void Inner::mouseMoveEvent(QMouseEvent *e) { - _lastMousePos = e->globalPos(); - updateSelected(); -} - -void Inner::leaveEventHook(QEvent *e) { - clearSelection(); - Ui::Tooltip::Hide(); -} - -void Inner::leaveToChildEvent(QEvent *e, QWidget *child) { - clearSelection(); -} - -void Inner::enterFromChildEvent(QEvent *e, QWidget *child) { - _lastMousePos = QCursor::pos(); - updateSelected(); -} - -void Inner::contextMenuEvent(QContextMenuEvent *e) { - if (_selected < 0 || _pressed >= 0) { - return; - } - const auto row = _selected / MatrixRowShift; - const auto column = _selected % MatrixRowShift; - const auto type = SendMenu::Type::Scheduled; - - _menu = base::make_unique_q(this); - - const auto send = [=](Api::SendOptions options) { - selectInlineResult(row, column, options); - }; - SendMenu::FillSendMenu( - _menu, - [&] { return type; }, - SendMenu::DefaultSilentCallback(send), - SendMenu::DefaultScheduleCallback(this, type, send)); - - if (!_menu->actions().empty()) { - _menu->popup(QCursor::pos()); - } -} - -void Inner::clearSelection() { - if (_selected >= 0) { - int srow = _selected / MatrixRowShift, scol = _selected % MatrixRowShift; - Assert(srow >= 0 && srow < _rows.size() && scol >= 0 && scol < _rows.at(srow).items.size()); - ClickHandler::clearActive(_rows.at(srow).items.at(scol)); - setCursor(style::cur_default); - } - _selected = _pressed = -1; - update(); -} - -void Inner::hideFinished() { - clearHeavyData(); -} - -void Inner::clearHeavyData() { - clearInlineRows(false); - for (const auto &[result, layout] : _inlineLayouts) { - layout->unloadHeavyPart(); - } -} - -bool Inner::inlineRowsAddItem(Result *result, Row &row, int32 &sumWidth) { - auto layout = layoutPrepareInlineResult(result, (_rows.size() * MatrixRowShift) + row.items.size()); - if (!layout) return false; - - layout->preload(); - if (inlineRowFinalize(row, sumWidth, layout->isFullLine())) { - layout->setPosition(_rows.size() * MatrixRowShift); - } - - sumWidth += layout->maxWidth(); - if (!row.items.isEmpty() && row.items.back()->hasRightSkip()) { - sumWidth += st::inlineResultsSkip; - } - - row.items.push_back(layout); - return true; -} - -bool Inner::inlineRowFinalize(Row &row, int32 &sumWidth, bool force) { - if (row.items.isEmpty()) return false; - - auto full = (row.items.size() >= kInlineItemsMaxPerRow); - auto big = (sumWidth >= st::emojiPanWidth - st::emojiScroll.width - st::inlineResultsLeft); - if (full || big || force) { - _rows.push_back(layoutInlineRow(row, (full || big) ? sumWidth : 0)); - row = Row(); - row.items.reserve(kInlineItemsMaxPerRow); - sumWidth = 0; - return true; - } - return false; -} - -void Inner::inlineBotChanged() { - refreshInlineRows(nullptr, nullptr, nullptr, true); -} - -void Inner::clearInlineRows(bool resultsDeleted) { - if (resultsDeleted) { - _selected = _pressed = -1; - } else { - clearSelection(); - for_const (auto &row, _rows) { - for_const (auto &item, row.items) { - item->setPosition(-1); - } - } - } - _rows.clear(); -} - -ItemBase *Inner::layoutPrepareInlineResult(Result *result, int32 position) { - auto it = _inlineLayouts.find(result); - if (it == _inlineLayouts.cend()) { - if (auto layout = ItemBase::createLayout(this, result, _inlineWithThumb)) { - it = _inlineLayouts.emplace(result, std::move(layout)).first; - it->second->initDimensions(); - } else { - return nullptr; - } - } - if (!it->second->maxWidth()) { - return nullptr; - } - - it->second->setPosition(position); - return it->second.get(); -} - -void Inner::deleteUnusedInlineLayouts() { - if (_rows.isEmpty()) { // delete all - _inlineLayouts.clear(); - } else { - for (auto i = _inlineLayouts.begin(); i != _inlineLayouts.cend();) { - if (i->second->position() < 0) { - i = _inlineLayouts.erase(i); - } else { - ++i; - } - } - } -} - -Inner::Row &Inner::layoutInlineRow(Row &row, int32 sumWidth) { - auto count = int(row.items.size()); - Assert(count <= kInlineItemsMaxPerRow); - - // enumerate items in the order of growing maxWidth() - // for that sort item indices by maxWidth() - int indices[kInlineItemsMaxPerRow]; - for (auto i = 0; i != count; ++i) { - indices[i] = i; - } - std::sort(indices, indices + count, [&row](int a, int b) -> bool { - return row.items.at(a)->maxWidth() < row.items.at(b)->maxWidth(); - }); - - row.height = 0; - int availw = width() - (st::inlineResultsLeft - st::buttonRadius); - for (int i = 0; i < count; ++i) { - int index = indices[i]; - int w = sumWidth ? (row.items.at(index)->maxWidth() * availw / sumWidth) : row.items.at(index)->maxWidth(); - int actualw = qMax(w, int(st::inlineResultsMinWidth)); - row.height = qMax(row.height, row.items.at(index)->resizeGetHeight(actualw)); - if (sumWidth) { - availw -= actualw; - sumWidth -= row.items.at(index)->maxWidth(); - if (index > 0 && row.items.at(index - 1)->hasRightSkip()) { - availw -= st::inlineResultsSkip; - sumWidth -= st::inlineResultsSkip; - } - } - } - return row; -} - -void Inner::preloadImages() { - for (auto row = 0, rows = _rows.size(); row != rows; ++row) { - for (auto col = 0, cols = _rows[row].items.size(); col != cols; ++col) { - _rows[row].items[col]->preload(); - } - } -} - -void Inner::hideInlineRowsPanel() { - clearInlineRows(false); -} - -void Inner::clearInlineRowsPanel() { - clearInlineRows(false); -} - -void Inner::refreshSwitchPmButton(const CacheEntry *entry) { - if (!entry || entry->switchPmText.isEmpty()) { - _switchPmButton.destroy(); - _switchPmStartToken.clear(); - } else { - if (!_switchPmButton) { - _switchPmButton.create(this, nullptr, st::switchPmButton); - _switchPmButton->show(); - _switchPmButton->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); - _switchPmButton->addClickHandler([=] { onSwitchPm(); }); - } - _switchPmButton->setText(rpl::single(entry->switchPmText)); - _switchPmStartToken = entry->switchPmStartToken; - const auto buttonTop = st::stickerPanPadding; - _switchPmButton->move(st::inlineResultsLeft - st::buttonRadius, buttonTop); - if (isRestrictedView()) { - _switchPmButton->hide(); - } - } - update(); -} - -int Inner::refreshInlineRows(PeerData *queryPeer, UserData *bot, const CacheEntry *entry, bool resultsDeleted) { - _inlineBot = bot; - _inlineQueryPeer = queryPeer; - refreshSwitchPmButton(entry); - auto clearResults = [&] { - if (!entry) { - return true; - } - if (entry->results.empty() && entry->switchPmText.isEmpty()) { - return true; - } - return false; - }; - auto clearResultsResult = clearResults(); // Clang workaround. - if (clearResultsResult) { - if (resultsDeleted) { - clearInlineRows(true); - deleteUnusedInlineLayouts(); - } - _inlineRowsCleared.fire({}); - return 0; - } - - clearSelection(); - - Assert(_inlineBot != 0); - - auto count = int(entry->results.size()); - auto from = validateExistingInlineRows(entry->results); - auto added = 0; - - if (count) { - _rows.reserve(count); - auto row = Row(); - row.items.reserve(kInlineItemsMaxPerRow); - auto sumWidth = 0; - for (auto i = from; i != count; ++i) { - if (inlineRowsAddItem(entry->results[i].get(), row, sumWidth)) { - ++added; - } - } - inlineRowFinalize(row, sumWidth, true); - } - - auto h = countHeight(); - if (h != height()) resize(width(), h); - update(); - - _lastMousePos = QCursor::pos(); - updateSelected(); - - return added; -} - -int Inner::validateExistingInlineRows(const Results &results) { - int count = results.size(), until = 0, untilrow = 0, untilcol = 0; - for (; until < count;) { - if (untilrow >= _rows.size() || _rows[untilrow].items[untilcol]->getResult() != results[until].get()) { - break; - } - ++until; - if (++untilcol == _rows[untilrow].items.size()) { - ++untilrow; - untilcol = 0; - } - } - if (until == count) { // all items are layed out - if (untilrow == _rows.size()) { // nothing changed - return until; - } - - for (int i = untilrow, l = _rows.size(), skip = untilcol; i < l; ++i) { - for (int j = 0, s = _rows[i].items.size(); j < s; ++j) { - if (skip) { - --skip; - } else { - _rows[i].items[j]->setPosition(-1); - } - } - } - if (!untilcol) { // all good rows are filled - _rows.resize(untilrow); - return until; - } - _rows.resize(untilrow + 1); - _rows[untilrow].items.resize(untilcol); - _rows[untilrow] = layoutInlineRow(_rows[untilrow]); - return until; - } - if (untilrow && !untilcol) { // remove last row, maybe it is not full - --untilrow; - untilcol = _rows[untilrow].items.size(); - } - until -= untilcol; - - for (int i = untilrow, l = _rows.size(); i < l; ++i) { - for (int j = 0, s = _rows[i].items.size(); j < s; ++j) { - _rows[i].items[j]->setPosition(-1); - } - } - _rows.resize(untilrow); - - if (_rows.isEmpty()) { - _inlineWithThumb = false; - for (int i = until; i < count; ++i) { - if (results.at(i)->hasThumbDisplay()) { - _inlineWithThumb = true; - break; - } - } - } - return until; -} - -void Inner::inlineItemLayoutChanged(const ItemBase *layout) { - if (_selected < 0 || !isVisible()) { - return; - } - - int row = _selected / MatrixRowShift, col = _selected % MatrixRowShift; - if (row < _rows.size() && col < _rows.at(row).items.size()) { - if (layout == _rows.at(row).items.at(col)) { - updateSelected(); - } - } -} - -void Inner::inlineItemRepaint(const ItemBase *layout) { - auto ms = crl::now(); - if (_lastScrolled + 100 <= ms) { - update(); - } else { - _updateInlineItems.callOnce(_lastScrolled + 100 - ms); - } -} - -bool Inner::inlineItemVisible(const ItemBase *layout) { - int32 position = layout->position(); - if (position < 0 || !isVisible()) { - return false; - } - - int row = position / MatrixRowShift, col = position % MatrixRowShift; - Assert((row < _rows.size()) && (col < _rows[row].items.size())); - - auto &inlineItems = _rows[row].items; - int top = st::stickerPanPadding; - for (int32 i = 0; i < row; ++i) { - top += _rows.at(i).height; - } - - return (top < _visibleBottom) && (top + _rows[row].items[col]->height() > _visibleTop); -} - -Data::FileOrigin Inner::inlineItemFileOrigin() { - return Data::FileOrigin(); -} - -void Inner::updateSelected() { - if (_pressed >= 0 && !_previewShown) { - return; - } - - auto newSelected = -1; - auto p = mapFromGlobal(_lastMousePos); - - int sx = (rtl() ? width() - p.x() : p.x()) - (st::inlineResultsLeft - st::buttonRadius); - int sy = p.y() - st::stickerPanPadding; - if (_switchPmButton) { - sy -= _switchPmButton->height() + st::inlineResultsSkip; - } - int row = -1, col = -1, sel = -1; - ClickHandlerPtr lnk; - ClickHandlerHost *lnkhost = nullptr; - HistoryView::CursorState cursor = HistoryView::CursorState::None; - if (sy >= 0) { - row = 0; - for (int rows = _rows.size(); row < rows; ++row) { - if (sy < _rows[row].height) { - break; - } - sy -= _rows[row].height; - } - } - if (sx >= 0 && row >= 0 && row < _rows.size()) { - auto &inlineItems = _rows[row].items; - col = 0; - for (int cols = inlineItems.size(); col < cols; ++col) { - int width = inlineItems.at(col)->width(); - if (sx < width) { - break; - } - sx -= width; - if (inlineItems.at(col)->hasRightSkip()) { - sx -= st::inlineResultsSkip; - } - } - if (col < inlineItems.size()) { - sel = row * MatrixRowShift + col; - auto result = inlineItems[col]->getState( - QPoint(sx, sy), - HistoryView::StateRequest()); - lnk = result.link; - cursor = result.cursor; - lnkhost = inlineItems[col]; - } else { - row = col = -1; - } - } else { - row = col = -1; - } - int srow = (_selected >= 0) ? (_selected / MatrixRowShift) : -1; - int scol = (_selected >= 0) ? (_selected % MatrixRowShift) : -1; - if (_selected != sel) { - if (srow >= 0 && scol >= 0) { - Assert(srow >= 0 && srow < _rows.size() && scol >= 0 && scol < _rows.at(srow).items.size()); - _rows[srow].items[scol]->update(); - } - _selected = sel; - if (row >= 0 && col >= 0) { - Assert(row >= 0 && row < _rows.size() && col >= 0 && col < _rows.at(row).items.size()); - _rows[row].items[col]->update(); - } - if (_previewShown && _selected >= 0 && _pressed != _selected) { - _pressed = _selected; - if (row >= 0 && col >= 0) { - auto layout = _rows.at(row).items.at(col); - if (const auto w = App::wnd()) { - if (const auto previewDocument = layout->getPreviewDocument()) { - w->showMediaPreview( - Data::FileOrigin(), - previewDocument); - } else if (auto previewPhoto = layout->getPreviewPhoto()) { - w->showMediaPreview(Data::FileOrigin(), previewPhoto); - } - } - } - } - } - if (ClickHandler::setActive(lnk, lnkhost)) { - setCursor(lnk ? style::cur_pointer : style::cur_default); - Ui::Tooltip::Hide(); - } - if (lnk) { - Ui::Tooltip::Show(1000, this); - } -} - -void Inner::showPreview() { - if (_pressed < 0) return; - - int row = _pressed / MatrixRowShift, col = _pressed % MatrixRowShift; - if (row < _rows.size() && col < _rows.at(row).items.size()) { - auto layout = _rows.at(row).items.at(col); - if (const auto w = App::wnd()) { - if (const auto previewDocument = layout->getPreviewDocument()) { - _previewShown = w->showMediaPreview(Data::FileOrigin(), previewDocument); - } else if (const auto previewPhoto = layout->getPreviewPhoto()) { - _previewShown = w->showMediaPreview(Data::FileOrigin(), previewPhoto); - } - } - } -} - -void Inner::updateInlineItems() { - auto ms = crl::now(); - if (_lastScrolled + 100 <= ms) { - update(); - } else { - _updateInlineItems.callOnce(_lastScrolled + 100 - ms); - } -} - -void Inner::onSwitchPm() { - if (_inlineBot && _inlineBot->isBot()) { - _inlineBot->botInfo->startToken = _switchPmStartToken; - Ui::showPeerHistory(_inlineBot, ShowAndStartBotMsgId); - } -} - -} // namespace internal - Widget::Widget( QWidget *parent, not_null controller) @@ -801,7 +44,7 @@ Widget::Widget( _scroll->resize(st::emojiPanWidth - st::buttonRadius, _contentHeight); _scroll->move(verticalRect().topLeft()); - _inner = _scroll->setOwnedWidget(object_ptr(this, controller)); + _inner = _scroll->setOwnedWidget(object_ptr(this, controller)); _inner->moveToLeft(0, 0, _scroll->width()); @@ -990,6 +233,14 @@ QImage Widget::grabForPanelAnimation() { return result; } +void Widget::setResultSelectedCallback(Fn callback) { + _inner->setResultSelectedCallback(std::move(callback)); +} + +void Widget::setCurrentDialogsEntryState(Dialogs::EntryState state) { + _inner->setCurrentDialogsEntryState(state); +} + void Widget::hideAnimated() { if (isHidden()) return; if (_hiding) return; @@ -1103,7 +354,7 @@ void Widget::inlineResultsDone(const MTPmessages_BotResults &result) { if (it == _inlineCache.cend()) { it = _inlineCache.emplace( _inlineQuery, - std::make_unique()).first; + std::make_unique()).first; } auto entry = it->second.get(); entry->nextOffset = qs(d.vnext_offset().value_or_empty()); @@ -1163,7 +414,7 @@ void Widget::queryInlineBot(UserData *bot, PeerData *peer, QString query) { showInlineRows(true); } else { _inlineNextQuery = query; - _inlineRequestTimer.callOnce(internal::kInlineBotRequestDelay); + _inlineRequestTimer.callOnce(kInlineBotRequestDelay); } } } @@ -1199,7 +450,7 @@ void Widget::onInlineRequest() { bool Widget::refreshInlineRows(int *added) { auto it = _inlineCache.find(_inlineQuery); - const internal::CacheEntry *entry = nullptr; + const CacheEntry *entry = nullptr; if (it != _inlineCache.cend()) { if (!it->second->results.empty() || !it->second->switchPmText.isEmpty()) { entry = it->second.get(); diff --git a/Telegram/SourceFiles/inline_bots/inline_results_widget.h b/Telegram/SourceFiles/inline_bots/inline_results_widget.h index e7369759e..62c4acd4f 100644 --- a/Telegram/SourceFiles/inline_bots/inline_results_widget.h +++ b/Telegram/SourceFiles/inline_bots/inline_results_widget.h @@ -30,161 +30,29 @@ class RippleAnimation; class PopupMenu; } // namespace Ui +namespace Dialogs { +struct EntryState; +} // namespace Dialogs + namespace Window { class SessionController; } // namespace Window namespace InlineBots { - class Result; +struct ResultSelected; +} // namespace InlineBots +namespace InlineBots { namespace Layout { -class ItemBase; - -namespace internal { - -constexpr int kInlineItemsMaxPerRow = 5; - -using Results = std::vector>; -using ResultSelected = Fn; - -struct CacheEntry { - QString nextOffset; - QString switchPmText, switchPmStartToken; - Results results; -}; - -class Inner - : public Ui::RpWidget - , public Ui::AbstractTooltipShower - , public Context - , private base::Subscriber { - -public: - Inner(QWidget *parent, not_null controller); - - void hideFinished(); - - void clearSelection(); - - int refreshInlineRows(PeerData *queryPeer, UserData *bot, const CacheEntry *results, bool resultsDeleted); - void inlineBotChanged(); - void hideInlineRowsPanel(); - void clearInlineRowsPanel(); - - void preloadImages(); - - void inlineItemLayoutChanged(const ItemBase *layout) override; - void inlineItemRepaint(const ItemBase *layout) override; - bool inlineItemVisible(const ItemBase *layout) override; - Data::FileOrigin inlineItemFileOrigin() override; - - int countHeight(); - - void setResultSelectedCallback(ResultSelected callback) { - _resultSelectedCallback = std::move(callback); - } - - // Ui::AbstractTooltipShower interface. - QString tooltipText() const override; - QPoint tooltipPos() const override; - bool tooltipWindowActive() const override; - - rpl::producer<> inlineRowsCleared() const; - - ~Inner(); - -protected: - void visibleTopBottomUpdated( - int visibleTop, - int visibleBottom) override; - - void mousePressEvent(QMouseEvent *e) override; - void mouseReleaseEvent(QMouseEvent *e) override; - void mouseMoveEvent(QMouseEvent *e) override; - void paintEvent(QPaintEvent *e) override; - void leaveEventHook(QEvent *e) override; - void leaveToChildEvent(QEvent *e, QWidget *child) override; - void enterFromChildEvent(QEvent *e, QWidget *child) override; - void contextMenuEvent(QContextMenuEvent *e) override; - -private: - static constexpr bool kRefreshIconsScrollAnimation = true; - static constexpr bool kRefreshIconsNoAnimation = false; - - struct Row { - int height = 0; - QVector items; - }; - - void onSwitchPm(); - - void updateSelected(); - void checkRestrictedPeer(); - bool isRestrictedView(); - void clearHeavyData(); - - void paintInlineItems(Painter &p, const QRect &r); - - void refreshSwitchPmButton(const CacheEntry *entry); - - void showPreview(); - void updateInlineItems(); - void clearInlineRows(bool resultsDeleted); - ItemBase *layoutPrepareInlineResult(Result *result, int32 position); - - bool inlineRowsAddItem(Result *result, Row &row, int32 &sumWidth); - bool inlineRowFinalize(Row &row, int32 &sumWidth, bool force = false); - - Row &layoutInlineRow(Row &row, int32 sumWidth = 0); - void deleteUnusedInlineLayouts(); - - int validateExistingInlineRows(const Results &results); - void selectInlineResult(int row, int column); - void selectInlineResult(int row, int column, Api::SendOptions options); - - not_null _controller; - - int _visibleTop = 0; - int _visibleBottom = 0; - - UserData *_inlineBot = nullptr; - PeerData *_inlineQueryPeer = nullptr; - crl::time _lastScrolled = 0; - base::Timer _updateInlineItems; - bool _inlineWithThumb = false; - - object_ptr _switchPmButton = { nullptr }; - QString _switchPmStartToken; - - object_ptr _restrictedLabel = { nullptr }; - - base::unique_qptr _menu; - - QVector _rows; - - std::map> _inlineLayouts; - - rpl::event_stream<> _inlineRowsCleared; - - int _selected = -1; - int _pressed = -1; - QPoint _lastMousePos; - - base::Timer _previewTimer; - bool _previewShown = false; - - ResultSelected _resultSelectedCallback; - -}; - -} // namespace internal +struct CacheEntry; +class Inner; class Widget : public Ui::RpWidget { - public: Widget(QWidget *parent, not_null controller); + ~Widget(); void moveBottom(int bottom); @@ -201,16 +69,13 @@ public: void showAnimated(); void hideAnimated(); - void setResultSelectedCallback(internal::ResultSelected callback) { - _inner->setResultSelectedCallback(std::move(callback)); - } + void setResultSelectedCallback(Fn callback); + void setCurrentDialogsEntryState(Dialogs::EntryState state); [[nodiscard]] rpl::producer requesting() const { return _requesting.events(); } - ~Widget(); - protected: void paintEvent(QPaintEvent *e) override; @@ -273,9 +138,9 @@ private: bool _inPanelGrab = false; object_ptr _scroll; - QPointer _inner; + QPointer _inner; - std::map> _inlineCache; + std::map> _inlineCache; base::Timer _inlineRequestTimer; UserData *_inlineBot = nullptr; diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index fd25686f4..660026281 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -107,12 +107,12 @@ class Filler { public: Filler( not_null controller, - PeerMenuRequest request, + Dialogs::EntryState request, const PeerMenuCallback &addAction); void fill(); private: - using Source = PeerMenuRequest::Source; + using Section = Dialogs::EntryState::Section; [[nodiscard]] bool showInfo(); [[nodiscard]] bool showHidePromotion(); @@ -128,36 +128,14 @@ private: void addBlockUser(not_null user); void addChatActions(not_null chat); void addChannelActions(not_null channel); + void addTogglesForArchive(); void addPollAction(not_null peer); not_null _controller; - PeerMenuRequest _request; - not_null _peer; - const PeerMenuCallback &_addAction; - -}; - -class FolderFiller { -public: - FolderFiller( - not_null controller, - FolderMenuRequest request, - const PeerMenuCallback &addAction); - void fill(); - -private: - void addTogglesForArchive(); - //bool showInfo(); - //void addTogglePin(); - //void addInfo(); - //void addSearch(); - //void addNotifications(); - //void addUngroup(); - - not_null _controller; - FolderMenuRequest _request; - not_null _folder; + Dialogs::EntryState _request; + PeerData *_peer = nullptr; + Data::Folder *_folder = nullptr; const PeerMenuCallback &_addAction; }; @@ -277,16 +255,17 @@ void TogglePinnedDialog( Filler::Filler( not_null controller, - PeerMenuRequest request, + Dialogs::EntryState request, const PeerMenuCallback &addAction) : _controller(controller) , _request(request) -, _peer(request.peer) +, _peer(request.key.peer()) +, _folder(request.key.folder()) , _addAction(addAction) { } bool Filler::showInfo() { - if (_request.source == Source::Profile + if (_request.section == Section::Profile || _peer->isSelf() || _peer->isRepliesChat()) { return false; @@ -302,7 +281,7 @@ bool Filler::showInfo() { } bool Filler::showHidePromotion() { - if (_request.source != Source::ChatsList) { + if (_request.section != Section::ChatsList) { return false; } const auto history = _peer->owner().historyLoaded(_peer); @@ -312,7 +291,7 @@ bool Filler::showHidePromotion() { } bool Filler::showToggleArchived() { - if (_request.source != Source::ChatsList) { + if (_request.section != Section::ChatsList) { return false; } const auto history = _peer->owner().historyLoaded(_peer); @@ -325,7 +304,7 @@ bool Filler::showToggleArchived() { } bool Filler::showTogglePin() { - if (_request.source != Source::ChatsList) { + if (_request.section != Section::ChatsList) { return false; } const auto history = _peer->owner().historyLoaded(_peer); @@ -474,7 +453,7 @@ void Filler::addBlockUser(not_null user) { void Filler::addUserActions(not_null user) { const auto controller = _controller; const auto window = &_controller->window(); - if (_request.source != Source::ChatsList) { + if (_request.section != Section::ChatsList) { if (user->session().supportMode()) { _addAction("Edit support info", [=] { user->session().supportHelper().editInfo(controller, user); @@ -522,13 +501,13 @@ void Filler::addUserActions(not_null user) { if (!user->isInaccessible() && user != user->session().user() && !user->isRepliesChat() - && _request.source != Source::ChatsList) { + && _request.section != Section::ChatsList) { addBlockUser(user); } } void Filler::addChatActions(not_null chat) { - if (_request.source != Source::ChatsList) { + if (_request.section != Section::ChatsList) { const auto controller = _controller; if (EditPeerInfoBox::Available(chat)) { const auto text = tr::lng_manage_group_title(tr::now); @@ -568,7 +547,7 @@ void Filler::addChannelActions(not_null channel) { // [=] { ToggleChannelGrouping(channel, !grouped); }); // } //} - if (_request.source != Source::ChatsList) { + if (_request.section != Section::ChatsList) { if (channel->isBroadcast()) { if (const auto chat = channel->linkedChat()) { _addAction(tr::lng_profile_view_discussion(tr::now), [=] { @@ -618,7 +597,7 @@ void Filler::addChannelActions(not_null channel) { text, [=] { channel->session().api().joinChannel(channel); }); } - if (_request.source != Source::ChatsList) { + if (_request.section != Section::ChatsList) { const auto needReport = !channel->amCreator() && (!isGroup || channel->isPublic()); if (needReport) { @@ -634,7 +613,7 @@ void Filler::addPollAction(not_null peer) { return; } const auto controller = _controller; - const auto source = (_request.source == Source::ScheduledSection) + const auto source = (_request.section == Section::Scheduled) ? Api::SendType::Scheduled : Api::SendType::Normal; const auto flag = PollData::Flags(); @@ -652,8 +631,11 @@ void Filler::addPollAction(not_null peer) { } void Filler::fill() { - if (_request.source == Source::ScheduledSection - || _request.source == Source::RepliesSection) { + if (_folder) { + addTogglesForArchive(); + return; + } else if (_request.section == Section::Scheduled + || _request.section == Section::Replies) { addPollAction(_peer); return; } @@ -669,10 +651,10 @@ void Filler::fill() { if (showInfo()) { addInfo(); } - if (_request.source != Source::Profile && !_peer->isSelf()) { + if (_request.section != Section::Profile && !_peer->isSelf()) { PeerMenuAddMuteAction(_peer, _addAction); } - if (_request.source == Source::ChatsList) { + if (_request.section == Section::ChatsList) { //addSearch(); addToggleUnreadMark(); } @@ -686,21 +668,9 @@ void Filler::fill() { } } -FolderFiller::FolderFiller( - not_null controller, - FolderMenuRequest request, - const PeerMenuCallback &addAction) -: _controller(controller) -, _request(request) -, _folder(request.folder) -, _addAction(addAction) { -} +void Filler::addTogglesForArchive() { + Expects(_folder != nullptr); -void FolderFiller::fill() { - addTogglesForArchive(); -} - -void FolderFiller::addTogglesForArchive() { if (_folder->id() != Data::Folder::kId) { return; } @@ -1310,20 +1280,11 @@ Fn DeleteAndLeaveHandler(not_null peer) { }; } -void FillPeerMenu( +void FillDialogsEntryMenu( not_null controller, - PeerMenuRequest request, + Dialogs::EntryState request, const PeerMenuCallback &callback) { - Filler filler(controller, request, callback); - filler.fill(); -} - -void FillFolderMenu( - not_null controller, - FolderMenuRequest request, - const PeerMenuCallback &callback) { - FolderFiller filler(controller, request, callback); - filler.fill(); + Filler(controller, request, callback).fill(); } } // namespace Window diff --git a/Telegram/SourceFiles/window/window_peer_menu.h b/Telegram/SourceFiles/window/window_peer_menu.h index a7944da05..fdaf60363 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.h +++ b/Telegram/SourceFiles/window/window_peer_menu.h @@ -24,6 +24,7 @@ class Session; namespace Dialogs { class MainList; +struct EntryState; } // namespace Dialogs namespace Window { @@ -32,37 +33,13 @@ class Controller; class SessionController; class SessionNavigation; -struct PeerMenuRequest { - enum class Source { - ChatsList, - History, - Profile, - ScheduledSection, - RepliesSection, - }; - - not_null peer; - Source source = Source::ChatsList; - FilterId filterId = 0; - MsgId rootId = 0; - MsgId currentReplyToId = 0; -}; - -struct FolderMenuRequest { - not_null folder; -}; - using PeerMenuCallback = Fn handler)>; -void FillPeerMenu( +void FillDialogsEntryMenu( not_null controller, - PeerMenuRequest request, - const PeerMenuCallback &addAction); -void FillFolderMenu( - not_null controller, - FolderMenuRequest request, + Dialogs::EntryState request, const PeerMenuCallback &addAction); void PeerMenuAddMuteAction(