| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392 |
- /*
- 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 "chat_helpers/bot_keyboard.h"
- #include "api/api_bot.h"
- #include "core/click_handler_types.h"
- #include "data/data_session.h"
- #include "data/data_user.h"
- #include "history/history.h"
- #include "history/history_item_components.h"
- #include "main/main_session.h"
- #include "ui/cached_round_corners.h"
- #include "ui/painter.h"
- #include "ui/ui_utility.h"
- #include "window/window_session_controller.h"
- #include "styles/style_chat.h"
- #include "styles/style_widgets.h"
- namespace {
- class Style : public ReplyKeyboard::Style {
- public:
- Style(
- not_null<BotKeyboard*> parent,
- const style::BotKeyboardButton &st);
- Images::CornersMaskRef buttonRounding(
- Ui::BubbleRounding outer,
- RectParts sides) const override;
- void startPaint(QPainter &p, const Ui::ChatStyle *st) const override;
- const style::TextStyle &textStyle() const override;
- void repaint(not_null<const HistoryItem*> item) const override;
- protected:
- void paintButtonBg(
- QPainter &p,
- const Ui::ChatStyle *st,
- const QRect &rect,
- Ui::BubbleRounding rounding,
- float64 howMuchOver) const override;
- void paintButtonIcon(
- QPainter &p,
- const Ui::ChatStyle *st,
- const QRect &rect,
- int outerWidth,
- HistoryMessageMarkupButton::Type type) const override;
- void paintButtonLoading(
- QPainter &p,
- const Ui::ChatStyle *st,
- const QRect &rect,
- int outerWidth,
- Ui::BubbleRounding rounding) const override;
- int minButtonWidth(HistoryMessageMarkupButton::Type type) const override;
- private:
- not_null<BotKeyboard*> _parent;
- };
- Style::Style(
- not_null<BotKeyboard*> parent,
- const style::BotKeyboardButton &st)
- : ReplyKeyboard::Style(st), _parent(parent) {
- }
- void Style::startPaint(QPainter &p, const Ui::ChatStyle *st) const {
- p.setPen(st::botKbColor);
- p.setFont(st::botKbStyle.font);
- }
- const style::TextStyle &Style::textStyle() const {
- return st::botKbStyle;
- }
- void Style::repaint(not_null<const HistoryItem*> item) const {
- _parent->update();
- }
- Images::CornersMaskRef Style::buttonRounding(
- Ui::BubbleRounding outer,
- RectParts sides) const {
- using namespace Images;
- return CornersMaskRef(CornersMask(ImageRoundRadius::Small));
- }
- void Style::paintButtonBg(
- QPainter &p,
- const Ui::ChatStyle *st,
- const QRect &rect,
- Ui::BubbleRounding rounding,
- float64 howMuchOver) const {
- Ui::FillRoundRect(p, rect, st::botKbBg, Ui::BotKeyboardCorners);
- }
- void Style::paintButtonIcon(
- QPainter &p,
- const Ui::ChatStyle *st,
- const QRect &rect,
- int outerWidth,
- HistoryMessageMarkupButton::Type type) const {
- // Buttons with icons should not appear here.
- }
- void Style::paintButtonLoading(
- QPainter &p,
- const Ui::ChatStyle *st,
- const QRect &rect,
- int outerWidth,
- Ui::BubbleRounding rounding) const {
- // Buttons with loading progress should not appear here.
- }
- int Style::minButtonWidth(HistoryMessageMarkupButton::Type type) const {
- int result = 2 * buttonPadding();
- return result;
- }
- } // namespace
- BotKeyboard::BotKeyboard(
- not_null<Window::SessionController*> controller,
- QWidget *parent)
- : TWidget(parent)
- , _controller(controller)
- , _st(&st::botKbButton) {
- setGeometry(0, 0, _st->margin, st::botKbScroll.deltat);
- _height = st::botKbScroll.deltat;
- setMouseTracking(true);
- }
- void BotKeyboard::paintEvent(QPaintEvent *e) {
- Painter p(this);
- auto clip = e->rect();
- p.fillRect(clip, st::historyComposeAreaBg);
- if (_impl) {
- int x = rtl() ? st::botKbScroll.width : _st->margin;
- p.translate(x, st::botKbScroll.deltat);
- _impl->paint(
- p,
- nullptr,
- Ui::BubbleRounding(),
- width(),
- clip.translated(-x, -st::botKbScroll.deltat));
- }
- }
- void BotKeyboard::mousePressEvent(QMouseEvent *e) {
- _lastMousePos = e->globalPos();
- updateSelected();
- ClickHandler::pressed();
- }
- void BotKeyboard::mouseMoveEvent(QMouseEvent *e) {
- _lastMousePos = e->globalPos();
- updateSelected();
- }
- void BotKeyboard::mouseReleaseEvent(QMouseEvent *e) {
- _lastMousePos = e->globalPos();
- updateSelected();
- if (ClickHandlerPtr activated = ClickHandler::unpressed()) {
- ActivateClickHandler(window(), activated, {
- e->button(),
- QVariant::fromValue(ClickHandlerContext{
- .sessionWindow = base::make_weak(_controller),
- })
- });
- }
- }
- void BotKeyboard::enterEventHook(QEnterEvent *e) {
- _lastMousePos = QCursor::pos();
- updateSelected();
- }
- void BotKeyboard::leaveEventHook(QEvent *e) {
- clearSelection();
- }
- bool BotKeyboard::moderateKeyActivate(
- int key,
- Fn<ClickContext(FullMsgId)> context) {
- const auto &data = _controller->session().data();
- const auto botCommand = [](int key) {
- if (key == Qt::Key_Q || key == Qt::Key_6) {
- return u"/translate"_q;
- } else if (key == Qt::Key_W || key == Qt::Key_5) {
- return u"/eng"_q;
- } else if (key == Qt::Key_3) {
- return u"/pattern"_q;
- } else if (key == Qt::Key_4) {
- return u"/abuse"_q;
- } else if (key == Qt::Key_0 || key == Qt::Key_E || key == Qt::Key_9) {
- return u"/undo"_q;
- } else if (key == Qt::Key_Plus
- || key == Qt::Key_QuoteLeft
- || key == Qt::Key_7) {
- return u"/next"_q;
- } else if (key == Qt::Key_Period
- || key == Qt::Key_S
- || key == Qt::Key_8) {
- return u"/stats"_q;
- }
- return QString();
- };
- if (const auto item = data.message(_wasForMsgId)) {
- if (const auto markup = item->Get<HistoryMessageReplyMarkup>()) {
- if (key >= Qt::Key_1 && key <= Qt::Key_2) {
- const auto index = int(key - Qt::Key_1);
- if (!markup->data.rows.empty()
- && index >= 0
- && index < int(markup->data.rows.front().size())) {
- Api::ActivateBotCommand(
- context(
- _wasForMsgId).other.value<ClickHandlerContext>(),
- 0,
- index);
- return true;
- }
- } else if (const auto user = item->history()->peer->asUser()) {
- if (user->isBot() && item->from() == user) {
- const auto command = botCommand(key);
- if (!command.isEmpty()) {
- _sendCommandRequests.fire({
- .peer = user,
- .command = command,
- .context = item->fullId(),
- });
- }
- return true;
- }
- }
- }
- }
- return false;
- }
- void BotKeyboard::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
- if (!_impl) return;
- _impl->clickHandlerActiveChanged(p, active);
- }
- void BotKeyboard::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
- if (!_impl) return;
- _impl->clickHandlerPressedChanged(p, pressed, Ui::BubbleRounding());
- }
- bool BotKeyboard::updateMarkup(HistoryItem *to, bool force) {
- if (!to || !to->definesReplyKeyboard()) {
- if (_wasForMsgId.msg) {
- _maximizeSize = _singleUse = _forceReply = _persistent = false;
- _wasForMsgId = FullMsgId();
- _placeholder = QString();
- _impl = nullptr;
- return true;
- }
- return false;
- }
- const auto peerId = to->history()->peer->id;
- if (_wasForMsgId == FullMsgId(peerId, to->id) && !force) {
- return false;
- }
- _wasForMsgId = FullMsgId(peerId, to->id);
- auto markupFlags = to->replyKeyboardFlags();
- _forceReply = markupFlags & ReplyMarkupFlag::ForceReply;
- _maximizeSize = !(markupFlags & ReplyMarkupFlag::Resize);
- _singleUse = _forceReply || (markupFlags & ReplyMarkupFlag::SingleUse);
- _persistent = (markupFlags & ReplyMarkupFlag::Persistent);
- if (const auto markup = to->Get<HistoryMessageReplyMarkup>()) {
- _placeholder = markup->data.placeholder;
- } else {
- _placeholder = QString();
- }
- _impl = nullptr;
- if (auto markup = to->Get<HistoryMessageReplyMarkup>()) {
- if (!markup->data.rows.empty()) {
- _impl = std::make_unique<ReplyKeyboard>(
- to,
- std::make_unique<Style>(this, *_st));
- }
- }
- resizeToWidth(width(), _maxOuterHeight);
- return true;
- }
- bool BotKeyboard::hasMarkup() const {
- return _impl != nullptr;
- }
- bool BotKeyboard::forceReply() const {
- return _forceReply;
- }
- int BotKeyboard::resizeGetHeight(int newWidth) {
- updateStyle(newWidth);
- _height = st::botKbScroll.deltat + st::botKbScroll.deltab + (_impl ? _impl->naturalHeight() : 0);
- if (_maximizeSize) {
- accumulate_max(_height, _maxOuterHeight);
- }
- if (_impl) {
- int implWidth = newWidth - _st->margin - st::botKbScroll.width;
- int implHeight = _height - (st::botKbScroll.deltat + st::botKbScroll.deltab);
- _impl->resize(implWidth, implHeight);
- }
- return _height;
- }
- bool BotKeyboard::maximizeSize() const {
- return _maximizeSize;
- }
- bool BotKeyboard::singleUse() const {
- return _singleUse;
- }
- bool BotKeyboard::persistent() const {
- return _persistent;
- }
- void BotKeyboard::updateStyle(int newWidth) {
- if (!_impl) return;
- int implWidth = newWidth - st::botKbButton.margin - st::botKbScroll.width;
- _st = _impl->isEnoughSpace(implWidth, st::botKbButton) ? &st::botKbButton : &st::botKbTinyButton;
- _impl->setStyle(std::make_unique<Style>(this, *_st));
- }
- void BotKeyboard::clearSelection() {
- if (_impl) {
- if (ClickHandler::setActive(ClickHandlerPtr(), this)) {
- Ui::Tooltip::Hide();
- setCursor(style::cur_default);
- }
- }
- }
- QPoint BotKeyboard::tooltipPos() const {
- return _lastMousePos;
- }
- bool BotKeyboard::tooltipWindowActive() const {
- return Ui::AppInFocus() && Ui::InFocusChain(window());
- }
- QString BotKeyboard::tooltipText() const {
- if (ClickHandlerPtr lnk = ClickHandler::getActive()) {
- return lnk->tooltip();
- }
- return QString();
- }
- void BotKeyboard::updateSelected() {
- Ui::Tooltip::Show(1000, this);
- if (!_impl) return;
- auto p = mapFromGlobal(_lastMousePos);
- auto x = rtl() ? st::botKbScroll.width : _st->margin;
- auto link = _impl->getLink(p - QPoint(x, _st->margin));
- if (ClickHandler::setActive(link, this)) {
- Ui::Tooltip::Hide();
- setCursor(link ? style::cur_pointer : style::cur_default);
- }
- }
- auto BotKeyboard::sendCommandRequests() const
- -> rpl::producer<Bot::SendCommandRequest> {
- return _sendCommandRequests.events();
- }
- BotKeyboard::~BotKeyboard() = default;
|