delete_message_context_action.cpp 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  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 "ui/controls/delete_message_context_action.h"
  8. #include "ui/widgets/menu/menu_action.h"
  9. #include "ui/effects/ripple_animation.h"
  10. #include "ui/painter.h"
  11. #include "lang/lang_keys.h"
  12. #include "base/call_delayed.h"
  13. #include "base/unixtime.h"
  14. #include "base/timer.h"
  15. #include "styles/style_chat.h"
  16. #include "styles/style_menu_icons.h"
  17. namespace Ui {
  18. namespace {
  19. class ActionWithTimer final : public Menu::ItemBase {
  20. public:
  21. ActionWithTimer(
  22. not_null<RpWidget*> parent,
  23. const style::Menu &st,
  24. TimeId destroyAt,
  25. Fn<void()> callback,
  26. Fn<void()> destroyByTimerCallback);
  27. bool isEnabled() const override;
  28. not_null<QAction*> action() const override;
  29. void handleKeyPress(not_null<QKeyEvent*> e) override;
  30. private:
  31. QPoint prepareRippleStartPosition() const override;
  32. QImage prepareRippleMask() const override;
  33. int contentHeight() const override;
  34. void prepare();
  35. void refreshAutoDeleteText();
  36. void paint(Painter &p);
  37. const not_null<QAction*> _dummyAction;
  38. const style::Menu &_st;
  39. const TimeId _destroyAt = 0;
  40. const Fn<void()> _destroyByTimerCallback;
  41. const crl::time _startedAt = 0;
  42. base::Timer _refreshTimer;
  43. Text::String _text;
  44. int _textWidth = 0;
  45. QString _autoDeleteText;
  46. const int _height;
  47. };
  48. TextParseOptions MenuTextOptions = {
  49. TextParseLinks, // flags
  50. 0, // maxw
  51. 0, // maxh
  52. Qt::LayoutDirectionAuto, // dir
  53. };
  54. ActionWithTimer::ActionWithTimer(
  55. not_null<RpWidget*> parent,
  56. const style::Menu &st,
  57. TimeId destroyAt,
  58. Fn<void()> callback,
  59. Fn<void()> destroyByTimerCallback)
  60. : ItemBase(parent, st)
  61. , _dummyAction(new QAction(parent))
  62. , _st(st)
  63. , _destroyAt(destroyAt)
  64. , _destroyByTimerCallback(destroyByTimerCallback)
  65. , _startedAt(crl::now())
  66. , _refreshTimer([=] { refreshAutoDeleteText(); })
  67. , _height(st::ttlItemPadding.top()
  68. + _st.itemStyle.font->height
  69. + st::ttlItemTimerFont->height
  70. + st::ttlItemPadding.bottom()) {
  71. setAcceptBoth(true);
  72. initResizeHook(parent->sizeValue());
  73. setClickedCallback(std::move(callback));
  74. paintRequest(
  75. ) | rpl::start_with_next([=] {
  76. Painter p(this);
  77. paint(p);
  78. }, lifetime());
  79. enableMouseSelecting();
  80. prepare();
  81. }
  82. void ActionWithTimer::paint(Painter &p) {
  83. const auto selected = isSelected();
  84. if (selected && _st.itemBgOver->c.alpha() < 255) {
  85. p.fillRect(0, 0, width(), _height, _st.itemBg);
  86. }
  87. p.fillRect(0, 0, width(), _height, selected ? _st.itemBgOver : _st.itemBg);
  88. if (isEnabled()) {
  89. paintRipple(p, 0, 0);
  90. }
  91. const auto normalHeight = _st.itemPadding.top()
  92. + _st.itemStyle.font->height
  93. + _st.itemPadding.bottom();
  94. const auto deltaHeight = _height - normalHeight;
  95. st::menuIconDelete.paint(
  96. p,
  97. _st.itemIconPosition + QPoint(0, deltaHeight / 2),
  98. width());
  99. p.setPen(selected ? _st.itemFgOver : _st.itemFg);
  100. _text.drawLeftElided(
  101. p,
  102. _st.itemPadding.left(),
  103. st::ttlItemPadding.top(),
  104. _textWidth,
  105. width());
  106. p.setFont(st::ttlItemTimerFont);
  107. p.setPen(selected ? _st.itemFgShortcutOver : _st.itemFgShortcut);
  108. p.drawTextLeft(
  109. _st.itemPadding.left(),
  110. st::ttlItemPadding.top() + _st.itemStyle.font->height,
  111. width(),
  112. _autoDeleteText);
  113. }
  114. void ActionWithTimer::refreshAutoDeleteText() {
  115. const auto now = base::unixtime::now();
  116. const auto left = (_destroyAt > now) ? (_destroyAt - now) : 0;
  117. const auto text = [&] {
  118. const auto duration = (left >= 86400)
  119. ? tr::lng_days(tr::now, lt_count, ((left + 43200) / 86400))
  120. : (left >= 3600)
  121. ? QString("%1:%2:%3"
  122. ).arg(left / 3600
  123. ).arg((left % 3600) / 60, 2, 10, QChar('0')
  124. ).arg(left % 60, 2, 10, QChar('0'))
  125. : QString("%1:%2"
  126. ).arg(left / 60
  127. ).arg(left % 60, 2, 10, QChar('0'));
  128. return tr::lng_context_auto_delete_in(
  129. tr::now,
  130. lt_duration,
  131. duration);
  132. }();
  133. if (_autoDeleteText != text) {
  134. _autoDeleteText = text;
  135. update();
  136. }
  137. if (!left) {
  138. base::call_delayed(crl::time(100), this, _destroyByTimerCallback);
  139. return;
  140. }
  141. const auto nextCall = (left >= 86400)
  142. ? ((left % 43200) + 1) * crl::time(1000)
  143. : crl::time(500) - ((crl::now() - _startedAt) % 500);
  144. _refreshTimer.callOnce(nextCall);
  145. }
  146. void ActionWithTimer::prepare() {
  147. refreshAutoDeleteText();
  148. _text.setMarkedText(
  149. _st.itemStyle,
  150. { tr::lng_context_delete_msg(tr::now) },
  151. MenuTextOptions);
  152. const auto textWidth = _text.maxWidth();
  153. const auto &padding = _st.itemPadding;
  154. const auto goodWidth = padding.left()
  155. + textWidth
  156. + padding.right();
  157. const auto ttlMaxWidth = [&](const QString &duration) {
  158. return padding.left()
  159. + st::ttlItemTimerFont->width(tr::lng_context_auto_delete_in(
  160. tr::now,
  161. lt_duration,
  162. duration))
  163. + padding.right();
  164. };
  165. const auto maxWidth1 = ttlMaxWidth("23:59:59");
  166. const auto maxWidth2 = ttlMaxWidth(tr::lng_days(tr::now, lt_count, 7));
  167. const auto w = std::clamp(
  168. std::max({ goodWidth, maxWidth1, maxWidth2 }),
  169. _st.widthMin,
  170. _st.widthMax);
  171. _textWidth = w - (goodWidth - textWidth);
  172. setMinWidth(w);
  173. update();
  174. }
  175. bool ActionWithTimer::isEnabled() const {
  176. return true;
  177. }
  178. not_null<QAction*> ActionWithTimer::action() const {
  179. return _dummyAction;
  180. }
  181. QPoint ActionWithTimer::prepareRippleStartPosition() const {
  182. return mapFromGlobal(QCursor::pos());
  183. }
  184. QImage ActionWithTimer::prepareRippleMask() const {
  185. return Ui::RippleAnimation::RectMask(size());
  186. }
  187. int ActionWithTimer::contentHeight() const {
  188. return _height;
  189. }
  190. void ActionWithTimer::handleKeyPress(not_null<QKeyEvent*> e) {
  191. if (!isSelected()) {
  192. return;
  193. }
  194. const auto key = e->key();
  195. if (key == Qt::Key_Enter || key == Qt::Key_Return) {
  196. setClicked(Menu::TriggeredSource::Keyboard);
  197. }
  198. }
  199. } // namespace
  200. base::unique_qptr<Menu::ItemBase> DeleteMessageContextAction(
  201. not_null<Menu::Menu*> menu,
  202. Fn<void()> callback,
  203. TimeId destroyAt,
  204. Fn<void()> destroyByTimerCallback) {
  205. if (destroyAt <= 0) {
  206. return base::make_unique_q<Menu::Action>(
  207. menu,
  208. menu->st(),
  209. Menu::CreateAction(
  210. menu,
  211. tr::lng_context_delete_msg(tr::now),
  212. std::move(callback)),
  213. &st::menuIconDelete,
  214. &st::menuIconDelete);
  215. }
  216. return base::make_unique_q<ActionWithTimer>(
  217. menu,
  218. menu->st(),
  219. destroyAt,
  220. std::move(callback),
  221. std::move(destroyByTimerCallback));
  222. }
  223. } // namespace Ui