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