bot_keyboard.cpp 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "chat_helpers/bot_keyboard.h"
  8. #include "api/api_bot.h"
  9. #include "core/click_handler_types.h"
  10. #include "data/data_session.h"
  11. #include "data/data_user.h"
  12. #include "history/history.h"
  13. #include "history/history_item_components.h"
  14. #include "main/main_session.h"
  15. #include "ui/cached_round_corners.h"
  16. #include "ui/painter.h"
  17. #include "ui/ui_utility.h"
  18. #include "window/window_session_controller.h"
  19. #include "styles/style_chat.h"
  20. #include "styles/style_widgets.h"
  21. namespace {
  22. class Style : public ReplyKeyboard::Style {
  23. public:
  24. Style(
  25. not_null<BotKeyboard*> parent,
  26. const style::BotKeyboardButton &st);
  27. Images::CornersMaskRef buttonRounding(
  28. Ui::BubbleRounding outer,
  29. RectParts sides) const override;
  30. void startPaint(QPainter &p, const Ui::ChatStyle *st) const override;
  31. const style::TextStyle &textStyle() const override;
  32. void repaint(not_null<const HistoryItem*> item) const override;
  33. protected:
  34. void paintButtonBg(
  35. QPainter &p,
  36. const Ui::ChatStyle *st,
  37. const QRect &rect,
  38. Ui::BubbleRounding rounding,
  39. float64 howMuchOver) const override;
  40. void paintButtonIcon(
  41. QPainter &p,
  42. const Ui::ChatStyle *st,
  43. const QRect &rect,
  44. int outerWidth,
  45. HistoryMessageMarkupButton::Type type) const override;
  46. void paintButtonLoading(
  47. QPainter &p,
  48. const Ui::ChatStyle *st,
  49. const QRect &rect,
  50. int outerWidth,
  51. Ui::BubbleRounding rounding) const override;
  52. int minButtonWidth(HistoryMessageMarkupButton::Type type) const override;
  53. private:
  54. not_null<BotKeyboard*> _parent;
  55. };
  56. Style::Style(
  57. not_null<BotKeyboard*> parent,
  58. const style::BotKeyboardButton &st)
  59. : ReplyKeyboard::Style(st), _parent(parent) {
  60. }
  61. void Style::startPaint(QPainter &p, const Ui::ChatStyle *st) const {
  62. p.setPen(st::botKbColor);
  63. p.setFont(st::botKbStyle.font);
  64. }
  65. const style::TextStyle &Style::textStyle() const {
  66. return st::botKbStyle;
  67. }
  68. void Style::repaint(not_null<const HistoryItem*> item) const {
  69. _parent->update();
  70. }
  71. Images::CornersMaskRef Style::buttonRounding(
  72. Ui::BubbleRounding outer,
  73. RectParts sides) const {
  74. using namespace Images;
  75. return CornersMaskRef(CornersMask(ImageRoundRadius::Small));
  76. }
  77. void Style::paintButtonBg(
  78. QPainter &p,
  79. const Ui::ChatStyle *st,
  80. const QRect &rect,
  81. Ui::BubbleRounding rounding,
  82. float64 howMuchOver) const {
  83. Ui::FillRoundRect(p, rect, st::botKbBg, Ui::BotKeyboardCorners);
  84. }
  85. void Style::paintButtonIcon(
  86. QPainter &p,
  87. const Ui::ChatStyle *st,
  88. const QRect &rect,
  89. int outerWidth,
  90. HistoryMessageMarkupButton::Type type) const {
  91. // Buttons with icons should not appear here.
  92. }
  93. void Style::paintButtonLoading(
  94. QPainter &p,
  95. const Ui::ChatStyle *st,
  96. const QRect &rect,
  97. int outerWidth,
  98. Ui::BubbleRounding rounding) const {
  99. // Buttons with loading progress should not appear here.
  100. }
  101. int Style::minButtonWidth(HistoryMessageMarkupButton::Type type) const {
  102. int result = 2 * buttonPadding();
  103. return result;
  104. }
  105. } // namespace
  106. BotKeyboard::BotKeyboard(
  107. not_null<Window::SessionController*> controller,
  108. QWidget *parent)
  109. : TWidget(parent)
  110. , _controller(controller)
  111. , _st(&st::botKbButton) {
  112. setGeometry(0, 0, _st->margin, st::botKbScroll.deltat);
  113. _height = st::botKbScroll.deltat;
  114. setMouseTracking(true);
  115. }
  116. void BotKeyboard::paintEvent(QPaintEvent *e) {
  117. Painter p(this);
  118. auto clip = e->rect();
  119. p.fillRect(clip, st::historyComposeAreaBg);
  120. if (_impl) {
  121. int x = rtl() ? st::botKbScroll.width : _st->margin;
  122. p.translate(x, st::botKbScroll.deltat);
  123. _impl->paint(
  124. p,
  125. nullptr,
  126. Ui::BubbleRounding(),
  127. width(),
  128. clip.translated(-x, -st::botKbScroll.deltat));
  129. }
  130. }
  131. void BotKeyboard::mousePressEvent(QMouseEvent *e) {
  132. _lastMousePos = e->globalPos();
  133. updateSelected();
  134. ClickHandler::pressed();
  135. }
  136. void BotKeyboard::mouseMoveEvent(QMouseEvent *e) {
  137. _lastMousePos = e->globalPos();
  138. updateSelected();
  139. }
  140. void BotKeyboard::mouseReleaseEvent(QMouseEvent *e) {
  141. _lastMousePos = e->globalPos();
  142. updateSelected();
  143. if (ClickHandlerPtr activated = ClickHandler::unpressed()) {
  144. ActivateClickHandler(window(), activated, {
  145. e->button(),
  146. QVariant::fromValue(ClickHandlerContext{
  147. .sessionWindow = base::make_weak(_controller),
  148. })
  149. });
  150. }
  151. }
  152. void BotKeyboard::enterEventHook(QEnterEvent *e) {
  153. _lastMousePos = QCursor::pos();
  154. updateSelected();
  155. }
  156. void BotKeyboard::leaveEventHook(QEvent *e) {
  157. clearSelection();
  158. }
  159. bool BotKeyboard::moderateKeyActivate(
  160. int key,
  161. Fn<ClickContext(FullMsgId)> context) {
  162. const auto &data = _controller->session().data();
  163. const auto botCommand = [](int key) {
  164. if (key == Qt::Key_Q || key == Qt::Key_6) {
  165. return u"/translate"_q;
  166. } else if (key == Qt::Key_W || key == Qt::Key_5) {
  167. return u"/eng"_q;
  168. } else if (key == Qt::Key_3) {
  169. return u"/pattern"_q;
  170. } else if (key == Qt::Key_4) {
  171. return u"/abuse"_q;
  172. } else if (key == Qt::Key_0 || key == Qt::Key_E || key == Qt::Key_9) {
  173. return u"/undo"_q;
  174. } else if (key == Qt::Key_Plus
  175. || key == Qt::Key_QuoteLeft
  176. || key == Qt::Key_7) {
  177. return u"/next"_q;
  178. } else if (key == Qt::Key_Period
  179. || key == Qt::Key_S
  180. || key == Qt::Key_8) {
  181. return u"/stats"_q;
  182. }
  183. return QString();
  184. };
  185. if (const auto item = data.message(_wasForMsgId)) {
  186. if (const auto markup = item->Get<HistoryMessageReplyMarkup>()) {
  187. if (key >= Qt::Key_1 && key <= Qt::Key_2) {
  188. const auto index = int(key - Qt::Key_1);
  189. if (!markup->data.rows.empty()
  190. && index >= 0
  191. && index < int(markup->data.rows.front().size())) {
  192. Api::ActivateBotCommand(
  193. context(
  194. _wasForMsgId).other.value<ClickHandlerContext>(),
  195. 0,
  196. index);
  197. return true;
  198. }
  199. } else if (const auto user = item->history()->peer->asUser()) {
  200. if (user->isBot() && item->from() == user) {
  201. const auto command = botCommand(key);
  202. if (!command.isEmpty()) {
  203. _sendCommandRequests.fire({
  204. .peer = user,
  205. .command = command,
  206. .context = item->fullId(),
  207. });
  208. }
  209. return true;
  210. }
  211. }
  212. }
  213. }
  214. return false;
  215. }
  216. void BotKeyboard::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
  217. if (!_impl) return;
  218. _impl->clickHandlerActiveChanged(p, active);
  219. }
  220. void BotKeyboard::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
  221. if (!_impl) return;
  222. _impl->clickHandlerPressedChanged(p, pressed, Ui::BubbleRounding());
  223. }
  224. bool BotKeyboard::updateMarkup(HistoryItem *to, bool force) {
  225. if (!to || !to->definesReplyKeyboard()) {
  226. if (_wasForMsgId.msg) {
  227. _maximizeSize = _singleUse = _forceReply = _persistent = false;
  228. _wasForMsgId = FullMsgId();
  229. _placeholder = QString();
  230. _impl = nullptr;
  231. return true;
  232. }
  233. return false;
  234. }
  235. const auto peerId = to->history()->peer->id;
  236. if (_wasForMsgId == FullMsgId(peerId, to->id) && !force) {
  237. return false;
  238. }
  239. _wasForMsgId = FullMsgId(peerId, to->id);
  240. auto markupFlags = to->replyKeyboardFlags();
  241. _forceReply = markupFlags & ReplyMarkupFlag::ForceReply;
  242. _maximizeSize = !(markupFlags & ReplyMarkupFlag::Resize);
  243. _singleUse = _forceReply || (markupFlags & ReplyMarkupFlag::SingleUse);
  244. _persistent = (markupFlags & ReplyMarkupFlag::Persistent);
  245. if (const auto markup = to->Get<HistoryMessageReplyMarkup>()) {
  246. _placeholder = markup->data.placeholder;
  247. } else {
  248. _placeholder = QString();
  249. }
  250. _impl = nullptr;
  251. if (auto markup = to->Get<HistoryMessageReplyMarkup>()) {
  252. if (!markup->data.rows.empty()) {
  253. _impl = std::make_unique<ReplyKeyboard>(
  254. to,
  255. std::make_unique<Style>(this, *_st));
  256. }
  257. }
  258. resizeToWidth(width(), _maxOuterHeight);
  259. return true;
  260. }
  261. bool BotKeyboard::hasMarkup() const {
  262. return _impl != nullptr;
  263. }
  264. bool BotKeyboard::forceReply() const {
  265. return _forceReply;
  266. }
  267. int BotKeyboard::resizeGetHeight(int newWidth) {
  268. updateStyle(newWidth);
  269. _height = st::botKbScroll.deltat + st::botKbScroll.deltab + (_impl ? _impl->naturalHeight() : 0);
  270. if (_maximizeSize) {
  271. accumulate_max(_height, _maxOuterHeight);
  272. }
  273. if (_impl) {
  274. int implWidth = newWidth - _st->margin - st::botKbScroll.width;
  275. int implHeight = _height - (st::botKbScroll.deltat + st::botKbScroll.deltab);
  276. _impl->resize(implWidth, implHeight);
  277. }
  278. return _height;
  279. }
  280. bool BotKeyboard::maximizeSize() const {
  281. return _maximizeSize;
  282. }
  283. bool BotKeyboard::singleUse() const {
  284. return _singleUse;
  285. }
  286. bool BotKeyboard::persistent() const {
  287. return _persistent;
  288. }
  289. void BotKeyboard::updateStyle(int newWidth) {
  290. if (!_impl) return;
  291. int implWidth = newWidth - st::botKbButton.margin - st::botKbScroll.width;
  292. _st = _impl->isEnoughSpace(implWidth, st::botKbButton) ? &st::botKbButton : &st::botKbTinyButton;
  293. _impl->setStyle(std::make_unique<Style>(this, *_st));
  294. }
  295. void BotKeyboard::clearSelection() {
  296. if (_impl) {
  297. if (ClickHandler::setActive(ClickHandlerPtr(), this)) {
  298. Ui::Tooltip::Hide();
  299. setCursor(style::cur_default);
  300. }
  301. }
  302. }
  303. QPoint BotKeyboard::tooltipPos() const {
  304. return _lastMousePos;
  305. }
  306. bool BotKeyboard::tooltipWindowActive() const {
  307. return Ui::AppInFocus() && Ui::InFocusChain(window());
  308. }
  309. QString BotKeyboard::tooltipText() const {
  310. if (ClickHandlerPtr lnk = ClickHandler::getActive()) {
  311. return lnk->tooltip();
  312. }
  313. return QString();
  314. }
  315. void BotKeyboard::updateSelected() {
  316. Ui::Tooltip::Show(1000, this);
  317. if (!_impl) return;
  318. auto p = mapFromGlobal(_lastMousePos);
  319. auto x = rtl() ? st::botKbScroll.width : _st->margin;
  320. auto link = _impl->getLink(p - QPoint(x, _st->margin));
  321. if (ClickHandler::setActive(link, this)) {
  322. Ui::Tooltip::Hide();
  323. setCursor(link ? style::cur_pointer : style::cur_default);
  324. }
  325. }
  326. auto BotKeyboard::sendCommandRequests() const
  327. -> rpl::producer<Bot::SendCommandRequest> {
  328. return _sendCommandRequests.events();
  329. }
  330. BotKeyboard::~BotKeyboard() = default;