mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Add return from bot switch_pm to Scheduled/Replies.
This commit is contained in:
parent
4a8b59b788
commit
f04b3da76a
25 changed files with 1158 additions and 1139 deletions
|
@ -647,6 +647,8 @@ PRIVATE
|
||||||
inline_bots/inline_bot_result.h
|
inline_bots/inline_bot_result.h
|
||||||
inline_bots/inline_bot_send_data.cpp
|
inline_bots/inline_bot_send_data.cpp
|
||||||
inline_bots/inline_bot_send_data.h
|
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.cpp
|
||||||
inline_bots/inline_results_widget.h
|
inline_bots/inline_results_widget.h
|
||||||
intro/intro_code.cpp
|
intro/intro_code.cpp
|
||||||
|
|
|
@ -15,7 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "base/object_ptr.h"
|
#include "base/object_ptr.h"
|
||||||
|
|
||||||
namespace InlineBots {
|
namespace InlineBots {
|
||||||
class Result;
|
struct ResultSelected;
|
||||||
} // namespace InlineBots
|
} // namespace InlineBots
|
||||||
|
|
||||||
namespace Main {
|
namespace Main {
|
||||||
|
@ -60,11 +60,7 @@ public:
|
||||||
not_null<PhotoData*> photo;
|
not_null<PhotoData*> photo;
|
||||||
Api::SendOptions options;
|
Api::SendOptions options;
|
||||||
};
|
};
|
||||||
struct InlineChosen {
|
using InlineChosen = InlineBots::ResultSelected;
|
||||||
not_null<InlineBots::Result*> result;
|
|
||||||
not_null<UserData*> bot;
|
|
||||||
Api::SendOptions options;
|
|
||||||
};
|
|
||||||
enum class Mode {
|
enum class Mode {
|
||||||
Full,
|
Full,
|
||||||
EmojiOnly
|
EmojiOnly
|
||||||
|
|
|
@ -89,7 +89,7 @@ void CheckForSwitchInlineButton(not_null<HistoryItem*> item) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (const auto user = item->history()->peer->asUser()) {
|
if (const auto user = item->history()->peer->asUser()) {
|
||||||
if (!user->isBot() || !user->botInfo->inlineReturnPeerId) {
|
if (!user->isBot() || !user->botInfo->inlineReturnTo.key) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (const auto markup = item->Get<HistoryMessageReplyMarkup>()) {
|
if (const auto markup = item->Get<HistoryMessageReplyMarkup>()) {
|
||||||
|
|
|
@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "data/data_peer.h"
|
#include "data/data_peer.h"
|
||||||
|
#include "dialogs/dialogs_key.h"
|
||||||
|
|
||||||
class BotCommand {
|
class BotCommand {
|
||||||
public:
|
public:
|
||||||
|
@ -34,7 +35,7 @@ struct BotInfo {
|
||||||
Ui::Text::String text = { int(st::msgMinWidth) }; // description
|
Ui::Text::String text = { int(st::msgMinWidth) }; // description
|
||||||
|
|
||||||
QString startToken, startGroupToken, shareGameShortName;
|
QString startToken, startGroupToken, shareGameShortName;
|
||||||
PeerId inlineReturnPeerId = 0;
|
Dialogs::EntryState inlineReturnTo;
|
||||||
};
|
};
|
||||||
|
|
||||||
class UserData : public PeerData {
|
class UserData : public PeerData {
|
||||||
|
|
|
@ -1814,24 +1814,17 @@ void InnerWidget::contextMenuEvent(QContextMenuEvent *e) {
|
||||||
} else {
|
} else {
|
||||||
fillArchiveSearchMenu(_menu.get());
|
fillArchiveSearchMenu(_menu.get());
|
||||||
}
|
}
|
||||||
} else if (const auto history = row.key.history()) {
|
} else {
|
||||||
Window::FillPeerMenu(
|
Window::FillDialogsEntryMenu(
|
||||||
_controller,
|
_controller,
|
||||||
Window::PeerMenuRequest{
|
Dialogs::EntryState{
|
||||||
.peer = history->peer,
|
.key = row.key,
|
||||||
.source = Window::PeerMenuRequest::Source::ChatsList,
|
.section = Dialogs::EntryState::Section::ChatsList,
|
||||||
.filterId = _filterId,
|
.filterId = _filterId,
|
||||||
},
|
},
|
||||||
[&](const QString &text, Fn<void()> callback) {
|
[&](const QString &text, Fn<void()> callback) {
|
||||||
return _menu->addAction(text, std::move(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<void()> callback) {
|
|
||||||
return _menu->addAction(text, std::move(callback));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
connect(_menu.get(), &QObject::destroyed, [=] {
|
connect(_menu.get(), &QObject::destroyed, [=] {
|
||||||
if (_menuRow.key) {
|
if (_menuRow.key) {
|
||||||
|
|
|
@ -109,4 +109,21 @@ inline bool operator>=(const RowDescriptor &a, const RowDescriptor &b) {
|
||||||
return !(a < 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
|
} // namespace Dialogs
|
||||||
|
|
|
@ -525,7 +525,7 @@ void Widget::refreshFolderTopBar() {
|
||||||
_folderTopBar->setActiveChat(
|
_folderTopBar->setActiveChat(
|
||||||
HistoryView::TopBarWidget::ActiveChat{
|
HistoryView::TopBarWidget::ActiveChat{
|
||||||
.key = _openedFolder,
|
.key = _openedFolder,
|
||||||
.section = HistoryView::TopBarWidget::Section::Dialogs,
|
.section = Dialogs::EntryState::Section::ChatsList,
|
||||||
},
|
},
|
||||||
nullptr);
|
nullptr);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -191,7 +191,7 @@ void activateBotCommand(
|
||||||
if (samePeer) {
|
if (samePeer) {
|
||||||
Notify::switchInlineBotButtonReceived(session, QString::fromUtf8(button->data), bot, msg->id);
|
Notify::switchInlineBotButtonReceived(session, QString::fromUtf8(button->data), bot, msg->id);
|
||||||
return true;
|
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))) {
|
if (Notify::switchInlineBotButtonReceived(session, QString::fromUtf8(button->data))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -707,14 +707,20 @@ void HistoryWidget::setGeometryWithTopMoved(
|
||||||
_topDelta = 0;
|
_topDelta = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Dialogs::EntryState HistoryWidget::computeDialogsEntryState() const {
|
||||||
|
return Dialogs::EntryState{
|
||||||
|
.key = _history,
|
||||||
|
.section = Dialogs::EntryState::Section::History,
|
||||||
|
.currentReplyToId = replyToId(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
void HistoryWidget::refreshTopBarActiveChat() {
|
void HistoryWidget::refreshTopBarActiveChat() {
|
||||||
_topBar->setActiveChat(
|
const auto state = computeDialogsEntryState();
|
||||||
HistoryView::TopBarWidget::ActiveChat{
|
_topBar->setActiveChat(state, _history->sendActionPainter());
|
||||||
.key = _history,
|
if (_inlineResults) {
|
||||||
.section = HistoryView::TopBarWidget::Section::History,
|
_inlineResults->setCurrentDialogsEntryState(state);
|
||||||
.currentReplyToId = replyToId(),
|
}
|
||||||
},
|
|
||||||
_history->sendActionPainter());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryWidget::refreshTabbedPanel() {
|
void HistoryWidget::refreshTabbedPanel() {
|
||||||
|
@ -830,7 +836,7 @@ void HistoryWidget::initTabbedSelector() {
|
||||||
) | rpl::filter([=] {
|
) | rpl::filter([=] {
|
||||||
return !isHidden();
|
return !isHidden();
|
||||||
}) | rpl::start_with_next([=](TabbedSelector::InlineChosen data) {
|
}) | rpl::start_with_next([=](TabbedSelector::InlineChosen data) {
|
||||||
sendInlineResult(data.result, data.bot, data.options);
|
sendInlineResult(data);
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
|
||||||
selector->setSendMenuType([=] { return sendMenuType(); });
|
selector->setSendMenuType([=] { return sendMenuType(); });
|
||||||
|
@ -1195,11 +1201,11 @@ void HistoryWidget::applyInlineBotQuery(UserData *bot, const QString &query) {
|
||||||
if (!_inlineResults) {
|
if (!_inlineResults) {
|
||||||
_inlineResults.create(this, controller());
|
_inlineResults.create(this, controller());
|
||||||
_inlineResults->setResultSelectedCallback([=](
|
_inlineResults->setResultSelectedCallback([=](
|
||||||
InlineBots::Result *result,
|
InlineBots::ResultSelected result) {
|
||||||
UserData *bot,
|
sendInlineResult(result);
|
||||||
Api::SendOptions options) {
|
|
||||||
sendInlineResult(result, bot, options);
|
|
||||||
});
|
});
|
||||||
|
_inlineResults->setCurrentDialogsEntryState(
|
||||||
|
computeDialogsEntryState());
|
||||||
_inlineResults->requesting(
|
_inlineResults->requesting(
|
||||||
) | rpl::start_with_next([=](bool requesting) {
|
) | rpl::start_with_next([=](bool requesting) {
|
||||||
_tabbedSelectorToggle->setLoading(requesting);
|
_tabbedSelectorToggle->setLoading(requesting);
|
||||||
|
@ -1460,22 +1466,37 @@ bool HistoryWidget::notify_switchInlineBotButtonReceived(const QString &query, U
|
||||||
applyDraft();
|
applyDraft();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (auto bot = _peer ? _peer->asUser() : nullptr) {
|
} else if (const auto bot = _peer ? _peer->asUser() : nullptr) {
|
||||||
const auto toPeerId = bot->isBot()
|
const auto to = bot->isBot()
|
||||||
? bot->botInfo->inlineReturnPeerId
|
? bot->botInfo->inlineReturnTo
|
||||||
: PeerId(0);
|
: Dialogs::EntryState();
|
||||||
if (!toPeerId) {
|
const auto history = to.key.history();
|
||||||
|
if (!history) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
bot->botInfo->inlineReturnPeerId = 0;
|
bot->botInfo->inlineReturnTo = Dialogs::EntryState();
|
||||||
const auto h = bot->owner().history(toPeerId);
|
using Section = Dialogs::EntryState::Section;
|
||||||
|
|
||||||
TextWithTags textWithTags = { '@' + bot->username + ' ' + query, TextWithTags::Tags() };
|
TextWithTags textWithTags = { '@' + bot->username + ' ' + query, TextWithTags::Tags() };
|
||||||
MessageCursor cursor = { textWithTags.text.size(), textWithTags.text.size(), QFIXED_MAX };
|
MessageCursor cursor = { textWithTags.text.size(), textWithTags.text.size(), QFIXED_MAX };
|
||||||
h->setLocalDraft(std::make_unique<Data::Draft>(textWithTags, 0, cursor, false));
|
auto draft = std::make_unique<Data::Draft>(
|
||||||
if (h == _history) {
|
textWithTags,
|
||||||
applyDraft();
|
to.currentReplyToId,
|
||||||
|
cursor,
|
||||||
|
false);
|
||||||
|
|
||||||
|
if (to.section == Section::Replies) {
|
||||||
|
controller()->showRepliesForMessage(history, to.rootId);
|
||||||
} else {
|
} 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;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1657,9 +1678,7 @@ void HistoryWidget::showHistory(
|
||||||
_pinnedClickedId = FullMsgId();
|
_pinnedClickedId = FullMsgId();
|
||||||
_minPinnedId = std::nullopt;
|
_minPinnedId = std::nullopt;
|
||||||
|
|
||||||
MsgId wasMsgId = _showAtMsgId;
|
const auto wasDialogsEntryState = computeDialogsEntryState();
|
||||||
History *wasHistory = _history;
|
|
||||||
|
|
||||||
const auto startBot = (showAtMsgId == ShowAndStartBotMsgId);
|
const auto startBot = (showAtMsgId == ShowAndStartBotMsgId);
|
||||||
if (startBot) {
|
if (startBot) {
|
||||||
showAtMsgId = ShowAtTheEndMsgId;
|
showAtMsgId = ShowAtTheEndMsgId;
|
||||||
|
@ -1719,8 +1738,8 @@ void HistoryWidget::showHistory(
|
||||||
if (const auto user = _peer->asUser()) {
|
if (const auto user = _peer->asUser()) {
|
||||||
if (const auto &info = user->botInfo) {
|
if (const auto &info = user->botInfo) {
|
||||||
if (startBot) {
|
if (startBot) {
|
||||||
if (wasHistory) {
|
if (wasDialogsEntryState.key) {
|
||||||
info->inlineReturnPeerId = wasHistory->peer->id;
|
info->inlineReturnTo = wasDialogsEntryState;
|
||||||
}
|
}
|
||||||
sendBotStartCommand();
|
sendBotStartCommand();
|
||||||
_history->clearLocalDraft();
|
_history->clearLocalDraft();
|
||||||
|
@ -1894,8 +1913,8 @@ void HistoryWidget::showHistory(
|
||||||
if (const auto user = _peer->asUser()) {
|
if (const auto user = _peer->asUser()) {
|
||||||
if (const auto &info = user->botInfo) {
|
if (const auto &info = user->botInfo) {
|
||||||
if (startBot) {
|
if (startBot) {
|
||||||
if (wasHistory) {
|
if (wasDialogsEntryState.key) {
|
||||||
info->inlineReturnPeerId = wasHistory->peer->id;
|
info->inlineReturnTo = wasDialogsEntryState;
|
||||||
}
|
}
|
||||||
sendBotStartCommand();
|
sendBotStartCommand();
|
||||||
}
|
}
|
||||||
|
@ -5078,17 +5097,14 @@ void HistoryWidget::onFieldTabbed() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryWidget::sendInlineResult(
|
void HistoryWidget::sendInlineResult(InlineBots::ResultSelected result) {
|
||||||
not_null<InlineBots::Result*> result,
|
|
||||||
not_null<UserData*> bot,
|
|
||||||
Api::SendOptions options) {
|
|
||||||
if (!_peer || !_peer->canWrite()) {
|
if (!_peer || !_peer->canWrite()) {
|
||||||
return;
|
return;
|
||||||
} else if (showSlowmodeError()) {
|
} else if (showSlowmodeError()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto errorText = result->getErrorOnSend(_history);
|
auto errorText = result.result->getErrorOnSend(_history);
|
||||||
if (!errorText.isEmpty()) {
|
if (!errorText.isEmpty()) {
|
||||||
Ui::show(Box<InformBox>(errorText));
|
Ui::show(Box<InformBox>(errorText));
|
||||||
return;
|
return;
|
||||||
|
@ -5096,9 +5112,9 @@ void HistoryWidget::sendInlineResult(
|
||||||
|
|
||||||
auto action = Api::SendAction(_history);
|
auto action = Api::SendAction(_history);
|
||||||
action.replyTo = replyToId();
|
action.replyTo = replyToId();
|
||||||
action.options = std::move(options);
|
action.options = std::move(result.options);
|
||||||
action.generateLocal = true;
|
action.generateLocal = true;
|
||||||
session().api().sendInlineResult(bot, result, action);
|
session().api().sendInlineResult(result.bot, result.result, action);
|
||||||
|
|
||||||
clearFieldText();
|
clearFieldText();
|
||||||
_saveDraftText = true;
|
_saveDraftText = true;
|
||||||
|
@ -5106,14 +5122,14 @@ void HistoryWidget::sendInlineResult(
|
||||||
onDraftSave();
|
onDraftSave();
|
||||||
|
|
||||||
auto &bots = cRefRecentInlineBots();
|
auto &bots = cRefRecentInlineBots();
|
||||||
const auto index = bots.indexOf(bot);
|
const auto index = bots.indexOf(result.bot);
|
||||||
if (index) {
|
if (index) {
|
||||||
if (index > 0) {
|
if (index > 0) {
|
||||||
bots.removeAt(index);
|
bots.removeAt(index);
|
||||||
} else if (bots.size() >= RecentInlineBotsLimit) {
|
} else if (bots.size() >= RecentInlineBotsLimit) {
|
||||||
bots.resize(RecentInlineBotsLimit - 1);
|
bots.resize(RecentInlineBotsLimit - 1);
|
||||||
}
|
}
|
||||||
bots.push_front(bot);
|
bots.push_front(result.bot);
|
||||||
session().local().writeRecentHashtagsAndBots();
|
session().local().writeRecentHashtagsAndBots();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ namespace Layout {
|
||||||
class ItemBase;
|
class ItemBase;
|
||||||
class Widget;
|
class Widget;
|
||||||
} // namespace Layout
|
} // namespace Layout
|
||||||
class Result;
|
struct ResultSelected;
|
||||||
} // namespace InlineBots
|
} // namespace InlineBots
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
|
@ -353,6 +353,8 @@ private:
|
||||||
void createTabbedPanel();
|
void createTabbedPanel();
|
||||||
void setTabbedPanel(std::unique_ptr<TabbedPanel> panel);
|
void setTabbedPanel(std::unique_ptr<TabbedPanel> panel);
|
||||||
void updateField();
|
void updateField();
|
||||||
|
|
||||||
|
[[nodiscard]] Dialogs::EntryState computeDialogsEntryState() const;
|
||||||
void refreshTopBarActiveChat();
|
void refreshTopBarActiveChat();
|
||||||
|
|
||||||
void requestMessageData(MsgId msgId);
|
void requestMessageData(MsgId msgId);
|
||||||
|
@ -485,10 +487,7 @@ private:
|
||||||
int wasScrollTop,
|
int wasScrollTop,
|
||||||
int nowScrollTop);
|
int nowScrollTop);
|
||||||
|
|
||||||
void sendInlineResult(
|
void sendInlineResult(InlineBots::ResultSelected result);
|
||||||
not_null<InlineBots::Result*> result,
|
|
||||||
not_null<UserData*> bot,
|
|
||||||
Api::SendOptions options);
|
|
||||||
|
|
||||||
void drawField(Painter &p, const QRect &rect);
|
void drawField(Painter &p, const QRect &rect);
|
||||||
void paintEditHeader(
|
void paintEditHeader(
|
||||||
|
|
|
@ -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/controls/history_view_voice_record_bar.h"
|
||||||
#include "history/view/history_view_webpage_preview.h"
|
#include "history/view/history_view_webpage_preview.h"
|
||||||
#include "inline_bots/inline_results_widget.h"
|
#include "inline_bots/inline_results_widget.h"
|
||||||
|
#include "inline_bots/inline_bot_result.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "media/audio/media_audio_capture.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) {
|
void ComposeControls::move(int x, int y) {
|
||||||
_wrap->move(x, y);
|
_wrap->move(x, y);
|
||||||
_writeRestricted->move(x, y);
|
_writeRestricted->move(x, y);
|
||||||
|
@ -1523,6 +1531,7 @@ bool ComposeControls::handleCancelRequest() {
|
||||||
|
|
||||||
void ComposeControls::initWebpageProcess() {
|
void ComposeControls::initWebpageProcess() {
|
||||||
Expects(_history);
|
Expects(_history);
|
||||||
|
|
||||||
const auto peer = _history->peer;
|
const auto peer = _history->peer;
|
||||||
auto &lifetime = _wrap->lifetime();
|
auto &lifetime = _wrap->lifetime();
|
||||||
const auto requestRepaint = crl::guard(_header.get(), [=] {
|
const auto requestRepaint = crl::guard(_header.get(), [=] {
|
||||||
|
@ -1787,15 +1796,11 @@ void ComposeControls::applyInlineBotQuery(
|
||||||
_inlineResults = std::make_unique<InlineBots::Layout::Widget>(
|
_inlineResults = std::make_unique<InlineBots::Layout::Widget>(
|
||||||
_parent,
|
_parent,
|
||||||
_window);
|
_window);
|
||||||
|
_inlineResults->setCurrentDialogsEntryState(
|
||||||
|
_currentDialogsEntryState);
|
||||||
_inlineResults->setResultSelectedCallback([=](
|
_inlineResults->setResultSelectedCallback([=](
|
||||||
InlineBots::Result *result,
|
InlineBots::ResultSelected result) {
|
||||||
UserData *bot,
|
_inlineResultChosen.fire_copy(result);
|
||||||
Api::SendOptions options) {
|
|
||||||
_inlineResultChosen.fire(InlineChosen{
|
|
||||||
.result = result,
|
|
||||||
.bot = bot,
|
|
||||||
.options = options,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
_inlineResults->requesting(
|
_inlineResults->requesting(
|
||||||
) | rpl::start_with_next([=](bool requesting) {
|
) | rpl::start_with_next([=](bool requesting) {
|
||||||
|
|
|
@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "base/required.h"
|
#include "base/required.h"
|
||||||
#include "api/api_common.h"
|
#include "api/api_common.h"
|
||||||
#include "base/unique_qptr.h"
|
#include "base/unique_qptr.h"
|
||||||
|
#include "dialogs/dialogs_key.h"
|
||||||
#include "history/view/controls/compose_controls_common.h"
|
#include "history/view/controls/compose_controls_common.h"
|
||||||
#include "ui/rp_widget.h"
|
#include "ui/rp_widget.h"
|
||||||
#include "ui/effects/animations.h"
|
#include "ui/effects/animations.h"
|
||||||
|
@ -87,6 +88,8 @@ public:
|
||||||
|
|
||||||
[[nodiscard]] Main::Session &session() const;
|
[[nodiscard]] Main::Session &session() const;
|
||||||
void setHistory(SetHistoryArgs &&args);
|
void setHistory(SetHistoryArgs &&args);
|
||||||
|
void setCurrentDialogsEntryState(Dialogs::EntryState state);
|
||||||
|
|
||||||
void finishAnimating();
|
void finishAnimating();
|
||||||
|
|
||||||
void move(int x, int y);
|
void move(int x, int y);
|
||||||
|
@ -237,6 +240,7 @@ private:
|
||||||
|
|
||||||
TextWithTags _localSavedText;
|
TextWithTags _localSavedText;
|
||||||
TextUpdateEvents _textUpdateEvents;
|
TextUpdateEvents _textUpdateEvents;
|
||||||
|
Dialogs::EntryState _currentDialogsEntryState;
|
||||||
|
|
||||||
//bool _inReplyEditForward = false;
|
//bool _inReplyEditForward = false;
|
||||||
//bool _inClickable = false;
|
//bool _inClickable = false;
|
||||||
|
|
|
@ -104,7 +104,7 @@ PinnedWidget::PinnedWidget(
|
||||||
_topBar->setActiveChat(
|
_topBar->setActiveChat(
|
||||||
TopBarWidget::ActiveChat{
|
TopBarWidget::ActiveChat{
|
||||||
.key = _history,
|
.key = _history,
|
||||||
.section = TopBarWidget::Section::Pinned,
|
.section = Dialogs::EntryState::Section::Pinned,
|
||||||
},
|
},
|
||||||
nullptr);
|
nullptr);
|
||||||
|
|
||||||
|
|
|
@ -1137,14 +1137,14 @@ SendMenu::Type RepliesWidget::sendMenuType() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void RepliesWidget::refreshTopBarActiveChat() {
|
void RepliesWidget::refreshTopBarActiveChat() {
|
||||||
_topBar->setActiveChat(
|
const auto state = Dialogs::EntryState{
|
||||||
TopBarWidget::ActiveChat{
|
.key = _history,
|
||||||
.key = _history,
|
.section = Dialogs::EntryState::Section::Replies,
|
||||||
.section = TopBarWidget::Section::Replies,
|
.rootId = _rootId,
|
||||||
.rootId = _rootId,
|
.currentReplyToId = replyToId(),
|
||||||
.currentReplyToId = replyToId(),
|
};
|
||||||
},
|
_topBar->setActiveChat(state, _sendAction.get());
|
||||||
_sendAction.get());
|
_composeControls->setCurrentDialogsEntryState(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
MsgId RepliesWidget::replyToId() const {
|
MsgId RepliesWidget::replyToId() const {
|
||||||
|
|
|
@ -99,12 +99,12 @@ ScheduledWidget::ScheduledWidget(
|
||||||
controller,
|
controller,
|
||||||
ComposeControls::Mode::Scheduled))
|
ComposeControls::Mode::Scheduled))
|
||||||
, _scrollDown(_scroll, st::historyToDown) {
|
, _scrollDown(_scroll, st::historyToDown) {
|
||||||
_topBar->setActiveChat(
|
const auto state = Dialogs::EntryState{
|
||||||
TopBarWidget::ActiveChat{
|
.key = _history,
|
||||||
.key = _history,
|
.section = Dialogs::EntryState::Section::Scheduled,
|
||||||
.section = TopBarWidget::Section::Scheduled,
|
};
|
||||||
},
|
_topBar->setActiveChat(state, nullptr);
|
||||||
nullptr);
|
_composeControls->setCurrentDialogsEntryState(state);
|
||||||
|
|
||||||
_topBar->move(0, 0);
|
_topBar->move(0, 0);
|
||||||
_topBar->resizeToWidth(width());
|
_topBar->resizeToWidth(width());
|
||||||
|
|
|
@ -232,30 +232,10 @@ void TopBarWidget::showMenu() {
|
||||||
Fn<void()> callback) {
|
Fn<void()> callback) {
|
||||||
return _menu->addAction(text, std::move(callback));
|
return _menu->addAction(text, std::move(callback));
|
||||||
};
|
};
|
||||||
if (const auto peer = _activeChat.key.peer()) {
|
Window::FillDialogsEntryMenu(
|
||||||
using Source = Window::PeerMenuRequest::Source;
|
_controller,
|
||||||
const auto source = (_activeChat.section == Section::Scheduled)
|
_activeChat,
|
||||||
? Source::ScheduledSection
|
addAction);
|
||||||
: (_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.");
|
|
||||||
}
|
|
||||||
if (_menu->actions().empty()) {
|
if (_menu->actions().empty()) {
|
||||||
_menu.destroy();
|
_menu.destroy();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -43,19 +43,8 @@ public:
|
||||||
int canForwardCount = 0;
|
int canForwardCount = 0;
|
||||||
int canSendNowCount = 0;
|
int canSendNowCount = 0;
|
||||||
};
|
};
|
||||||
enum class Section {
|
using ActiveChat = Dialogs::EntryState;
|
||||||
History,
|
using Section = ActiveChat::Section;
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
TopBarWidget(
|
TopBarWidget(
|
||||||
QWidget *parent,
|
QWidget *parent,
|
||||||
|
|
|
@ -575,11 +575,11 @@ void WrapWidget::showTopBarMenu() {
|
||||||
return _topBarMenu->addAction(text, std::move(callback));
|
return _topBarMenu->addAction(text, std::move(callback));
|
||||||
};
|
};
|
||||||
if (const auto peer = key().peer()) {
|
if (const auto peer = key().peer()) {
|
||||||
Window::FillPeerMenu(
|
Window::FillDialogsEntryMenu(
|
||||||
_controller->parentController(),
|
_controller->parentController(),
|
||||||
Window::PeerMenuRequest{
|
Dialogs::EntryState{
|
||||||
.peer = peer,
|
.key = peer->owner().history(peer),
|
||||||
.source = Window::PeerMenuRequest::Source::Profile,
|
.section = Dialogs::EntryState::Section::Profile,
|
||||||
},
|
},
|
||||||
addAction);
|
addAction);
|
||||||
//} else if (const auto feed = key().feed()) { // #feed
|
//} else if (const auto feed = key().feed()) { // #feed
|
||||||
|
|
|
@ -8,9 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "data/data_cloud_file.h"
|
#include "data/data_cloud_file.h"
|
||||||
|
#include "api/api_common.h"
|
||||||
|
|
||||||
class FileLoader;
|
class FileLoader;
|
||||||
class History;
|
class History;
|
||||||
|
class UserData;
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
class LocationPoint;
|
class LocationPoint;
|
||||||
|
@ -125,4 +127,10 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ResultSelected {
|
||||||
|
not_null<Result*> result;
|
||||||
|
not_null<UserData*> bot;
|
||||||
|
Api::SendOptions options;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace InlineBots
|
} // namespace InlineBots
|
||||||
|
|
769
Telegram/SourceFiles/inline_bots/inline_results_inner.cpp
Normal file
769
Telegram/SourceFiles/inline_bots/inline_results_inner.cpp
Normal file
|
@ -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 <QtWidgets/QApplication>
|
||||||
|
|
||||||
|
namespace InlineBots {
|
||||||
|
namespace Layout {
|
||||||
|
|
||||||
|
Inner::Inner(
|
||||||
|
QWidget *parent,
|
||||||
|
not_null<Window::SessionController*> 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<InlineBots::Layout::SendClickHandler*>(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<Ui::PopupMenu>(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
|
186
Telegram/SourceFiles/inline_bots/inline_results_inner.h
Normal file
186
Telegram/SourceFiles/inline_bots/inline_results_inner.h
Normal file
|
@ -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<std::unique_ptr<Result>>;
|
||||||
|
|
||||||
|
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<Window::SessionController*> 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<void(ResultSelected)> 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<ItemBase*> 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<Window::SessionController*> _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<Ui::RoundButton> _switchPmButton = { nullptr };
|
||||||
|
QString _switchPmStartToken;
|
||||||
|
Dialogs::EntryState _currentDialogsEntryState;
|
||||||
|
|
||||||
|
object_ptr<Ui::FlatLabel> _restrictedLabel = { nullptr };
|
||||||
|
|
||||||
|
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||||
|
|
||||||
|
QVector<Row> _rows;
|
||||||
|
|
||||||
|
std::map<Result*, std::unique_ptr<ItemBase>> _inlineLayouts;
|
||||||
|
|
||||||
|
rpl::event_stream<> _inlineRowsCleared;
|
||||||
|
|
||||||
|
int _selected = -1;
|
||||||
|
int _pressed = -1;
|
||||||
|
QPoint _lastMousePos;
|
||||||
|
|
||||||
|
base::Timer _previewTimer;
|
||||||
|
bool _previewShown = false;
|
||||||
|
|
||||||
|
Fn<void(ResultSelected)> _resultSelectedCallback;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Layout
|
||||||
|
} // namespace InlineBots
|
|
@ -7,783 +7,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "inline_bots/inline_results_widget.h"
|
#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_user.h"
|
||||||
#include "data/data_session.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_result.h"
|
||||||
#include "inline_bots/inline_bot_layout_item.h"
|
#include "inline_bots/inline_results_inner.h"
|
||||||
#include "dialogs/dialogs_layout.h"
|
|
||||||
#include "storage/localstorage.h"
|
|
||||||
#include "lang/lang_keys.h"
|
|
||||||
#include "mainwindow.h"
|
|
||||||
#include "mainwidget.h"
|
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
|
#include "ui/widgets/shadow.h"
|
||||||
#include "ui/widgets/scroll_area.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 "ui/cached_round_corners.h"
|
||||||
#include "history/view/history_view_cursor_state.h"
|
|
||||||
#include "facades.h"
|
|
||||||
#include "styles/style_chat_helpers.h"
|
#include "styles/style_chat_helpers.h"
|
||||||
|
|
||||||
#include <QtWidgets/QApplication>
|
|
||||||
|
|
||||||
namespace InlineBots {
|
namespace InlineBots {
|
||||||
namespace Layout {
|
namespace Layout {
|
||||||
namespace internal {
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr auto kInlineBotRequestDelay = 400;
|
constexpr auto kInlineBotRequestDelay = 400;
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Inner::Inner(
|
|
||||||
QWidget *parent,
|
|
||||||
not_null<Window::SessionController*> 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<InlineBots::Layout::SendClickHandler*>(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<Ui::PopupMenu>(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(
|
Widget::Widget(
|
||||||
QWidget *parent,
|
QWidget *parent,
|
||||||
not_null<Window::SessionController*> controller)
|
not_null<Window::SessionController*> controller)
|
||||||
|
@ -801,7 +44,7 @@ Widget::Widget(
|
||||||
_scroll->resize(st::emojiPanWidth - st::buttonRadius, _contentHeight);
|
_scroll->resize(st::emojiPanWidth - st::buttonRadius, _contentHeight);
|
||||||
|
|
||||||
_scroll->move(verticalRect().topLeft());
|
_scroll->move(verticalRect().topLeft());
|
||||||
_inner = _scroll->setOwnedWidget(object_ptr<internal::Inner>(this, controller));
|
_inner = _scroll->setOwnedWidget(object_ptr<Inner>(this, controller));
|
||||||
|
|
||||||
_inner->moveToLeft(0, 0, _scroll->width());
|
_inner->moveToLeft(0, 0, _scroll->width());
|
||||||
|
|
||||||
|
@ -990,6 +233,14 @@ QImage Widget::grabForPanelAnimation() {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Widget::setResultSelectedCallback(Fn<void(ResultSelected)> callback) {
|
||||||
|
_inner->setResultSelectedCallback(std::move(callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Widget::setCurrentDialogsEntryState(Dialogs::EntryState state) {
|
||||||
|
_inner->setCurrentDialogsEntryState(state);
|
||||||
|
}
|
||||||
|
|
||||||
void Widget::hideAnimated() {
|
void Widget::hideAnimated() {
|
||||||
if (isHidden()) return;
|
if (isHidden()) return;
|
||||||
if (_hiding) return;
|
if (_hiding) return;
|
||||||
|
@ -1103,7 +354,7 @@ void Widget::inlineResultsDone(const MTPmessages_BotResults &result) {
|
||||||
if (it == _inlineCache.cend()) {
|
if (it == _inlineCache.cend()) {
|
||||||
it = _inlineCache.emplace(
|
it = _inlineCache.emplace(
|
||||||
_inlineQuery,
|
_inlineQuery,
|
||||||
std::make_unique<internal::CacheEntry>()).first;
|
std::make_unique<CacheEntry>()).first;
|
||||||
}
|
}
|
||||||
auto entry = it->second.get();
|
auto entry = it->second.get();
|
||||||
entry->nextOffset = qs(d.vnext_offset().value_or_empty());
|
entry->nextOffset = qs(d.vnext_offset().value_or_empty());
|
||||||
|
@ -1163,7 +414,7 @@ void Widget::queryInlineBot(UserData *bot, PeerData *peer, QString query) {
|
||||||
showInlineRows(true);
|
showInlineRows(true);
|
||||||
} else {
|
} else {
|
||||||
_inlineNextQuery = query;
|
_inlineNextQuery = query;
|
||||||
_inlineRequestTimer.callOnce(internal::kInlineBotRequestDelay);
|
_inlineRequestTimer.callOnce(kInlineBotRequestDelay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1199,7 +450,7 @@ void Widget::onInlineRequest() {
|
||||||
|
|
||||||
bool Widget::refreshInlineRows(int *added) {
|
bool Widget::refreshInlineRows(int *added) {
|
||||||
auto it = _inlineCache.find(_inlineQuery);
|
auto it = _inlineCache.find(_inlineQuery);
|
||||||
const internal::CacheEntry *entry = nullptr;
|
const CacheEntry *entry = nullptr;
|
||||||
if (it != _inlineCache.cend()) {
|
if (it != _inlineCache.cend()) {
|
||||||
if (!it->second->results.empty() || !it->second->switchPmText.isEmpty()) {
|
if (!it->second->results.empty() || !it->second->switchPmText.isEmpty()) {
|
||||||
entry = it->second.get();
|
entry = it->second.get();
|
||||||
|
|
|
@ -30,161 +30,29 @@ class RippleAnimation;
|
||||||
class PopupMenu;
|
class PopupMenu;
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
|
||||||
|
namespace Dialogs {
|
||||||
|
struct EntryState;
|
||||||
|
} // namespace Dialogs
|
||||||
|
|
||||||
namespace Window {
|
namespace Window {
|
||||||
class SessionController;
|
class SessionController;
|
||||||
} // namespace Window
|
} // namespace Window
|
||||||
|
|
||||||
namespace InlineBots {
|
namespace InlineBots {
|
||||||
|
|
||||||
class Result;
|
class Result;
|
||||||
|
struct ResultSelected;
|
||||||
|
} // namespace InlineBots
|
||||||
|
|
||||||
|
namespace InlineBots {
|
||||||
namespace Layout {
|
namespace Layout {
|
||||||
|
|
||||||
class ItemBase;
|
struct CacheEntry;
|
||||||
|
class Inner;
|
||||||
namespace internal {
|
|
||||||
|
|
||||||
constexpr int kInlineItemsMaxPerRow = 5;
|
|
||||||
|
|
||||||
using Results = std::vector<std::unique_ptr<Result>>;
|
|
||||||
using ResultSelected = Fn<void(Result *, UserData *, Api::SendOptions)>;
|
|
||||||
|
|
||||||
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<Window::SessionController*> 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<ItemBase*> 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<Window::SessionController*> _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<Ui::RoundButton> _switchPmButton = { nullptr };
|
|
||||||
QString _switchPmStartToken;
|
|
||||||
|
|
||||||
object_ptr<Ui::FlatLabel> _restrictedLabel = { nullptr };
|
|
||||||
|
|
||||||
base::unique_qptr<Ui::PopupMenu> _menu;
|
|
||||||
|
|
||||||
QVector<Row> _rows;
|
|
||||||
|
|
||||||
std::map<Result*, std::unique_ptr<ItemBase>> _inlineLayouts;
|
|
||||||
|
|
||||||
rpl::event_stream<> _inlineRowsCleared;
|
|
||||||
|
|
||||||
int _selected = -1;
|
|
||||||
int _pressed = -1;
|
|
||||||
QPoint _lastMousePos;
|
|
||||||
|
|
||||||
base::Timer _previewTimer;
|
|
||||||
bool _previewShown = false;
|
|
||||||
|
|
||||||
ResultSelected _resultSelectedCallback;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace internal
|
|
||||||
|
|
||||||
class Widget : public Ui::RpWidget {
|
class Widget : public Ui::RpWidget {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Widget(QWidget *parent, not_null<Window::SessionController*> controller);
|
Widget(QWidget *parent, not_null<Window::SessionController*> controller);
|
||||||
|
~Widget();
|
||||||
|
|
||||||
void moveBottom(int bottom);
|
void moveBottom(int bottom);
|
||||||
|
|
||||||
|
@ -201,16 +69,13 @@ public:
|
||||||
void showAnimated();
|
void showAnimated();
|
||||||
void hideAnimated();
|
void hideAnimated();
|
||||||
|
|
||||||
void setResultSelectedCallback(internal::ResultSelected callback) {
|
void setResultSelectedCallback(Fn<void(ResultSelected)> callback);
|
||||||
_inner->setResultSelectedCallback(std::move(callback));
|
void setCurrentDialogsEntryState(Dialogs::EntryState state);
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] rpl::producer<bool> requesting() const {
|
[[nodiscard]] rpl::producer<bool> requesting() const {
|
||||||
return _requesting.events();
|
return _requesting.events();
|
||||||
}
|
}
|
||||||
|
|
||||||
~Widget();
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void paintEvent(QPaintEvent *e) override;
|
void paintEvent(QPaintEvent *e) override;
|
||||||
|
|
||||||
|
@ -273,9 +138,9 @@ private:
|
||||||
bool _inPanelGrab = false;
|
bool _inPanelGrab = false;
|
||||||
|
|
||||||
object_ptr<Ui::ScrollArea> _scroll;
|
object_ptr<Ui::ScrollArea> _scroll;
|
||||||
QPointer<internal::Inner> _inner;
|
QPointer<Inner> _inner;
|
||||||
|
|
||||||
std::map<QString, std::unique_ptr<internal::CacheEntry>> _inlineCache;
|
std::map<QString, std::unique_ptr<CacheEntry>> _inlineCache;
|
||||||
base::Timer _inlineRequestTimer;
|
base::Timer _inlineRequestTimer;
|
||||||
|
|
||||||
UserData *_inlineBot = nullptr;
|
UserData *_inlineBot = nullptr;
|
||||||
|
|
|
@ -107,12 +107,12 @@ class Filler {
|
||||||
public:
|
public:
|
||||||
Filler(
|
Filler(
|
||||||
not_null<SessionController*> controller,
|
not_null<SessionController*> controller,
|
||||||
PeerMenuRequest request,
|
Dialogs::EntryState request,
|
||||||
const PeerMenuCallback &addAction);
|
const PeerMenuCallback &addAction);
|
||||||
void fill();
|
void fill();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using Source = PeerMenuRequest::Source;
|
using Section = Dialogs::EntryState::Section;
|
||||||
|
|
||||||
[[nodiscard]] bool showInfo();
|
[[nodiscard]] bool showInfo();
|
||||||
[[nodiscard]] bool showHidePromotion();
|
[[nodiscard]] bool showHidePromotion();
|
||||||
|
@ -128,36 +128,14 @@ private:
|
||||||
void addBlockUser(not_null<UserData*> user);
|
void addBlockUser(not_null<UserData*> user);
|
||||||
void addChatActions(not_null<ChatData*> chat);
|
void addChatActions(not_null<ChatData*> chat);
|
||||||
void addChannelActions(not_null<ChannelData*> channel);
|
void addChannelActions(not_null<ChannelData*> channel);
|
||||||
|
void addTogglesForArchive();
|
||||||
|
|
||||||
void addPollAction(not_null<PeerData*> peer);
|
void addPollAction(not_null<PeerData*> peer);
|
||||||
|
|
||||||
not_null<SessionController*> _controller;
|
not_null<SessionController*> _controller;
|
||||||
PeerMenuRequest _request;
|
Dialogs::EntryState _request;
|
||||||
not_null<PeerData*> _peer;
|
PeerData *_peer = nullptr;
|
||||||
const PeerMenuCallback &_addAction;
|
Data::Folder *_folder = nullptr;
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
class FolderFiller {
|
|
||||||
public:
|
|
||||||
FolderFiller(
|
|
||||||
not_null<SessionController*> 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<SessionController*> _controller;
|
|
||||||
FolderMenuRequest _request;
|
|
||||||
not_null<Data::Folder*> _folder;
|
|
||||||
const PeerMenuCallback &_addAction;
|
const PeerMenuCallback &_addAction;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -277,16 +255,17 @@ void TogglePinnedDialog(
|
||||||
|
|
||||||
Filler::Filler(
|
Filler::Filler(
|
||||||
not_null<SessionController*> controller,
|
not_null<SessionController*> controller,
|
||||||
PeerMenuRequest request,
|
Dialogs::EntryState request,
|
||||||
const PeerMenuCallback &addAction)
|
const PeerMenuCallback &addAction)
|
||||||
: _controller(controller)
|
: _controller(controller)
|
||||||
, _request(request)
|
, _request(request)
|
||||||
, _peer(request.peer)
|
, _peer(request.key.peer())
|
||||||
|
, _folder(request.key.folder())
|
||||||
, _addAction(addAction) {
|
, _addAction(addAction) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Filler::showInfo() {
|
bool Filler::showInfo() {
|
||||||
if (_request.source == Source::Profile
|
if (_request.section == Section::Profile
|
||||||
|| _peer->isSelf()
|
|| _peer->isSelf()
|
||||||
|| _peer->isRepliesChat()) {
|
|| _peer->isRepliesChat()) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -302,7 +281,7 @@ bool Filler::showInfo() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Filler::showHidePromotion() {
|
bool Filler::showHidePromotion() {
|
||||||
if (_request.source != Source::ChatsList) {
|
if (_request.section != Section::ChatsList) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const auto history = _peer->owner().historyLoaded(_peer);
|
const auto history = _peer->owner().historyLoaded(_peer);
|
||||||
|
@ -312,7 +291,7 @@ bool Filler::showHidePromotion() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Filler::showToggleArchived() {
|
bool Filler::showToggleArchived() {
|
||||||
if (_request.source != Source::ChatsList) {
|
if (_request.section != Section::ChatsList) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const auto history = _peer->owner().historyLoaded(_peer);
|
const auto history = _peer->owner().historyLoaded(_peer);
|
||||||
|
@ -325,7 +304,7 @@ bool Filler::showToggleArchived() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Filler::showTogglePin() {
|
bool Filler::showTogglePin() {
|
||||||
if (_request.source != Source::ChatsList) {
|
if (_request.section != Section::ChatsList) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const auto history = _peer->owner().historyLoaded(_peer);
|
const auto history = _peer->owner().historyLoaded(_peer);
|
||||||
|
@ -474,7 +453,7 @@ void Filler::addBlockUser(not_null<UserData*> user) {
|
||||||
void Filler::addUserActions(not_null<UserData*> user) {
|
void Filler::addUserActions(not_null<UserData*> user) {
|
||||||
const auto controller = _controller;
|
const auto controller = _controller;
|
||||||
const auto window = &_controller->window();
|
const auto window = &_controller->window();
|
||||||
if (_request.source != Source::ChatsList) {
|
if (_request.section != Section::ChatsList) {
|
||||||
if (user->session().supportMode()) {
|
if (user->session().supportMode()) {
|
||||||
_addAction("Edit support info", [=] {
|
_addAction("Edit support info", [=] {
|
||||||
user->session().supportHelper().editInfo(controller, user);
|
user->session().supportHelper().editInfo(controller, user);
|
||||||
|
@ -522,13 +501,13 @@ void Filler::addUserActions(not_null<UserData*> user) {
|
||||||
if (!user->isInaccessible()
|
if (!user->isInaccessible()
|
||||||
&& user != user->session().user()
|
&& user != user->session().user()
|
||||||
&& !user->isRepliesChat()
|
&& !user->isRepliesChat()
|
||||||
&& _request.source != Source::ChatsList) {
|
&& _request.section != Section::ChatsList) {
|
||||||
addBlockUser(user);
|
addBlockUser(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Filler::addChatActions(not_null<ChatData*> chat) {
|
void Filler::addChatActions(not_null<ChatData*> chat) {
|
||||||
if (_request.source != Source::ChatsList) {
|
if (_request.section != Section::ChatsList) {
|
||||||
const auto controller = _controller;
|
const auto controller = _controller;
|
||||||
if (EditPeerInfoBox::Available(chat)) {
|
if (EditPeerInfoBox::Available(chat)) {
|
||||||
const auto text = tr::lng_manage_group_title(tr::now);
|
const auto text = tr::lng_manage_group_title(tr::now);
|
||||||
|
@ -568,7 +547,7 @@ void Filler::addChannelActions(not_null<ChannelData*> channel) {
|
||||||
// [=] { ToggleChannelGrouping(channel, !grouped); });
|
// [=] { ToggleChannelGrouping(channel, !grouped); });
|
||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
if (_request.source != Source::ChatsList) {
|
if (_request.section != Section::ChatsList) {
|
||||||
if (channel->isBroadcast()) {
|
if (channel->isBroadcast()) {
|
||||||
if (const auto chat = channel->linkedChat()) {
|
if (const auto chat = channel->linkedChat()) {
|
||||||
_addAction(tr::lng_profile_view_discussion(tr::now), [=] {
|
_addAction(tr::lng_profile_view_discussion(tr::now), [=] {
|
||||||
|
@ -618,7 +597,7 @@ void Filler::addChannelActions(not_null<ChannelData*> channel) {
|
||||||
text,
|
text,
|
||||||
[=] { channel->session().api().joinChannel(channel); });
|
[=] { channel->session().api().joinChannel(channel); });
|
||||||
}
|
}
|
||||||
if (_request.source != Source::ChatsList) {
|
if (_request.section != Section::ChatsList) {
|
||||||
const auto needReport = !channel->amCreator()
|
const auto needReport = !channel->amCreator()
|
||||||
&& (!isGroup || channel->isPublic());
|
&& (!isGroup || channel->isPublic());
|
||||||
if (needReport) {
|
if (needReport) {
|
||||||
|
@ -634,7 +613,7 @@ void Filler::addPollAction(not_null<PeerData*> peer) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto controller = _controller;
|
const auto controller = _controller;
|
||||||
const auto source = (_request.source == Source::ScheduledSection)
|
const auto source = (_request.section == Section::Scheduled)
|
||||||
? Api::SendType::Scheduled
|
? Api::SendType::Scheduled
|
||||||
: Api::SendType::Normal;
|
: Api::SendType::Normal;
|
||||||
const auto flag = PollData::Flags();
|
const auto flag = PollData::Flags();
|
||||||
|
@ -652,8 +631,11 @@ void Filler::addPollAction(not_null<PeerData*> peer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Filler::fill() {
|
void Filler::fill() {
|
||||||
if (_request.source == Source::ScheduledSection
|
if (_folder) {
|
||||||
|| _request.source == Source::RepliesSection) {
|
addTogglesForArchive();
|
||||||
|
return;
|
||||||
|
} else if (_request.section == Section::Scheduled
|
||||||
|
|| _request.section == Section::Replies) {
|
||||||
addPollAction(_peer);
|
addPollAction(_peer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -669,10 +651,10 @@ void Filler::fill() {
|
||||||
if (showInfo()) {
|
if (showInfo()) {
|
||||||
addInfo();
|
addInfo();
|
||||||
}
|
}
|
||||||
if (_request.source != Source::Profile && !_peer->isSelf()) {
|
if (_request.section != Section::Profile && !_peer->isSelf()) {
|
||||||
PeerMenuAddMuteAction(_peer, _addAction);
|
PeerMenuAddMuteAction(_peer, _addAction);
|
||||||
}
|
}
|
||||||
if (_request.source == Source::ChatsList) {
|
if (_request.section == Section::ChatsList) {
|
||||||
//addSearch();
|
//addSearch();
|
||||||
addToggleUnreadMark();
|
addToggleUnreadMark();
|
||||||
}
|
}
|
||||||
|
@ -686,21 +668,9 @@ void Filler::fill() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FolderFiller::FolderFiller(
|
void Filler::addTogglesForArchive() {
|
||||||
not_null<SessionController*> controller,
|
Expects(_folder != nullptr);
|
||||||
FolderMenuRequest request,
|
|
||||||
const PeerMenuCallback &addAction)
|
|
||||||
: _controller(controller)
|
|
||||||
, _request(request)
|
|
||||||
, _folder(request.folder)
|
|
||||||
, _addAction(addAction) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void FolderFiller::fill() {
|
|
||||||
addTogglesForArchive();
|
|
||||||
}
|
|
||||||
|
|
||||||
void FolderFiller::addTogglesForArchive() {
|
|
||||||
if (_folder->id() != Data::Folder::kId) {
|
if (_folder->id() != Data::Folder::kId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1310,20 +1280,11 @@ Fn<void()> DeleteAndLeaveHandler(not_null<PeerData*> peer) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void FillPeerMenu(
|
void FillDialogsEntryMenu(
|
||||||
not_null<SessionController*> controller,
|
not_null<SessionController*> controller,
|
||||||
PeerMenuRequest request,
|
Dialogs::EntryState request,
|
||||||
const PeerMenuCallback &callback) {
|
const PeerMenuCallback &callback) {
|
||||||
Filler filler(controller, request, callback);
|
Filler(controller, request, callback).fill();
|
||||||
filler.fill();
|
|
||||||
}
|
|
||||||
|
|
||||||
void FillFolderMenu(
|
|
||||||
not_null<SessionController*> controller,
|
|
||||||
FolderMenuRequest request,
|
|
||||||
const PeerMenuCallback &callback) {
|
|
||||||
FolderFiller filler(controller, request, callback);
|
|
||||||
filler.fill();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Window
|
} // namespace Window
|
||||||
|
|
|
@ -24,6 +24,7 @@ class Session;
|
||||||
|
|
||||||
namespace Dialogs {
|
namespace Dialogs {
|
||||||
class MainList;
|
class MainList;
|
||||||
|
struct EntryState;
|
||||||
} // namespace Dialogs
|
} // namespace Dialogs
|
||||||
|
|
||||||
namespace Window {
|
namespace Window {
|
||||||
|
@ -32,37 +33,13 @@ class Controller;
|
||||||
class SessionController;
|
class SessionController;
|
||||||
class SessionNavigation;
|
class SessionNavigation;
|
||||||
|
|
||||||
struct PeerMenuRequest {
|
|
||||||
enum class Source {
|
|
||||||
ChatsList,
|
|
||||||
History,
|
|
||||||
Profile,
|
|
||||||
ScheduledSection,
|
|
||||||
RepliesSection,
|
|
||||||
};
|
|
||||||
|
|
||||||
not_null<PeerData*> peer;
|
|
||||||
Source source = Source::ChatsList;
|
|
||||||
FilterId filterId = 0;
|
|
||||||
MsgId rootId = 0;
|
|
||||||
MsgId currentReplyToId = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct FolderMenuRequest {
|
|
||||||
not_null<Data::Folder*> folder;
|
|
||||||
};
|
|
||||||
|
|
||||||
using PeerMenuCallback = Fn<QAction*(
|
using PeerMenuCallback = Fn<QAction*(
|
||||||
const QString &text,
|
const QString &text,
|
||||||
Fn<void()> handler)>;
|
Fn<void()> handler)>;
|
||||||
|
|
||||||
void FillPeerMenu(
|
void FillDialogsEntryMenu(
|
||||||
not_null<SessionController*> controller,
|
not_null<SessionController*> controller,
|
||||||
PeerMenuRequest request,
|
Dialogs::EntryState request,
|
||||||
const PeerMenuCallback &addAction);
|
|
||||||
void FillFolderMenu(
|
|
||||||
not_null<SessionController*> controller,
|
|
||||||
FolderMenuRequest request,
|
|
||||||
const PeerMenuCallback &addAction);
|
const PeerMenuCallback &addAction);
|
||||||
|
|
||||||
void PeerMenuAddMuteAction(
|
void PeerMenuAddMuteAction(
|
||||||
|
|
Loading…
Add table
Reference in a new issue