Add return from bot switch_pm to Scheduled/Replies.

This commit is contained in:
John Preston 2020-11-12 18:46:17 +03:00
parent 4a8b59b788
commit f04b3da76a
25 changed files with 1158 additions and 1139 deletions

View File

@ -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

View File

@ -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

View File

@ -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>()) {

View File

@ -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 {

View File

@ -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) {

View File

@ -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

View File

@ -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 {

View File

@ -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;
}

View File

@ -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();
}

View File

@ -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(

View File

@ -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) {

View File

@ -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;

View File

@ -104,7 +104,7 @@ PinnedWidget::PinnedWidget(
_topBar->setActiveChat(
TopBarWidget::ActiveChat{
.key = _history,
.section = TopBarWidget::Section::Pinned,
.section = Dialogs::EntryState::Section::Pinned,
},
nullptr);

View File

@ -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 {

View File

@ -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());

View File

@ -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 {

View File

@ -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,

View File

@ -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

View File

@ -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

View 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

View 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

View File

@ -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();

View File

@ -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;

View File

@ -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

View File

@ -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(