/* 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 "menu/menu_ttl.h" #include "base/event_filter.h" #include "lang/lang_keys.h" #include "ui/boxes/choose_time.h" #include "ui/layers/generic_box.h" #include "ui/text/format_values.h" #include "ui/ui_utility.h" #include "ui/widgets/labels.h" #include "ui/widgets/menu/menu_action.h" #include "ui/widgets/popup_menu.h" #include "ui/widgets/vertical_drum_picker.h" #include "styles/style_chat.h" #include "styles/style_dialogs.h" // dialogsScamFont #include "styles/style_layers.h" #include "styles/style_menu_icons.h" namespace TTLMenu { namespace { constexpr auto kTTLDurHours1 = crl::time(1); constexpr auto kTTLDurSeconds1 = kTTLDurHours1 * 3600; constexpr auto kTTLDurHours2 = crl::time(24); constexpr auto kTTLDurSeconds2 = kTTLDurHours2 * 3600; constexpr auto kTTLDurHours3 = crl::time(24 * 7); constexpr auto kTTLDurSeconds3 = kTTLDurHours3 * 3600; constexpr auto kTTLDurHours4 = crl::time(24 * 30); constexpr auto kTTLDurSeconds4 = kTTLDurHours4 * 3600; void SetupPickerAndConfirm( not_null box, Fn callback, TimeId startTtlPeriod) { const auto ttls = std::vector{ (86400 * 1), (86400 * 2), (86400 * 3), (86400 * 4), (86400 * 5), (86400 * 6), (86400 * 7 * 1), (86400 * 7 * 2), (86400 * 7 * 3), (86400 * 30 * 1), (86400 * 30 * 2), (86400 * 30 * 3), (86400 * 30 * 4), (86400 * 30 * 5), (86400 * 30 * 6), (86400 * 30 * 12), }; const auto phrases = ranges::views::all( ttls ) | ranges::views::transform(Ui::FormatTTL) | ranges::to_vector; const auto startIndex = [&] { const auto it = ranges::find(ttls, startTtlPeriod); return (it == end(ttls)) ? 0 : std::distance(begin(ttls), it); }(); const auto content = box->addRow(object_ptr( box, st::historyMessagesTTLPickerHeight)); const auto font = st::boxTextFont; const auto maxPhraseWidth = [&] { const auto maxPhrase = ranges::max_element( phrases, std::less<>(), [&](const QString &s) { return font->width(s); }); return font->width(*maxPhrase); }(); const auto itemHeight = st::historyMessagesTTLPickerItemHeight; auto paintCallback = [=]( Painter &p, int index, float64 y, float64 distanceFromCenter, int outerWidth) { const auto r = QRectF(0, y, outerWidth, itemHeight); const auto progress = std::abs(distanceFromCenter); p.setOpacity(1. - progress); p.setFont(font); p.setPen(st::defaultFlatLabel.textFg); p.drawText(r, phrases[index], style::al_center); }; const auto picker = Ui::CreateChild( content, std::move(paintCallback), phrases.size(), itemHeight, startIndex); content->sizeValue( ) | rpl::start_with_next([=](const QSize &s) { picker->resize(maxPhraseWidth, s.height()); picker->moveToLeft((s.width() - picker->width()) / 2, 0); }, content->lifetime()); content->paintRequest( ) | rpl::start_with_next([=](const QRect &r) { Painter p(content); p.fillRect(r, Qt::transparent); const auto lineRect = QRect( 0, content->height() / 2, content->width(), st::defaultInputField.borderActive); p.fillRect(lineRect.translated(0, itemHeight / 2), st::activeLineFg); p.fillRect(lineRect.translated(0, -itemHeight / 2), st::activeLineFg); }, content->lifetime()); base::install_event_filter(content, [=](not_null e) { if ((e->type() == QEvent::MouseButtonPress) || (e->type() == QEvent::MouseButtonRelease) || (e->type() == QEvent::MouseMove)) { picker->handleMouseEvent(static_cast(e.get())); } else if (e->type() == QEvent::Wheel) { picker->handleWheelEvent(static_cast(e.get())); } return base::EventFilterResult::Continue; }); base::install_event_filter(box, [=](not_null e) { if (e->type() == QEvent::KeyPress) { picker->handleKeyEvent(static_cast(e.get())); } return base::EventFilterResult::Continue; }); box->addButton(tr::lng_settings_save(), [=] { callback(ttls[picker->index()]); box->getDelegate()->hideLayer(); }); } class IconWithText final : public Ui::Menu::Action { public: using Ui::Menu::Action::Action; void setData(const QString &text, const QPoint &iconPosition); protected: void paintEvent(QPaintEvent *e) override; private: QPoint _iconPosition; QString _text; }; void IconWithText::setData(const QString &text, const QPoint &iconPosition) { _iconPosition = iconPosition; _text = text; } void IconWithText::paintEvent(QPaintEvent *e) { Ui::Menu::Action::paintEvent(e); Painter p(this); p.setFont(st::dialogsScamFont); p.setPen(st::menuIconColor); p.drawText(_iconPosition, _text); } class TextItem final : public Ui::Menu::ItemBase { public: TextItem( not_null parent, const style::Menu &st, rpl::producer &&text); not_null action() const override; bool isEnabled() const override; protected: int contentHeight() const override; private: const base::unique_qptr _label; const not_null _dummyAction; }; TextItem::TextItem( not_null parent, const style::Menu &st, rpl::producer &&text) : ItemBase(parent, st) , _label(base::make_unique_q( this, std::move(text), st::historyMessagesTTLLabel)) , _dummyAction(Ui::CreateChild(parent.get())) { setAttribute(Qt::WA_TransparentForMouseEvents); setMinWidth(st::historyMessagesTTLLabel.minWidth + st.itemIconPosition.x()); sizeValue( ) | rpl::start_with_next([=](const QSize &s) { _label->moveToLeft( st.itemIconPosition.x(), (s.height() - _label->height()) / 2); }, lifetime()); initResizeHook(parent->sizeValue()); } not_null TextItem::action() const { return _dummyAction; } bool TextItem::isEnabled() const { return false; } int TextItem::contentHeight() const { return _label->height(); } void TTLBoxOld( not_null box, Fn callback, TimeId startTtlPeriod) { struct State { int lastSeconds = 0; }; const auto startTtl = startTtlPeriod ? startTtlPeriod : kTTLDurSeconds2; auto chooseTimeResult = ChooseTimeWidget(box, startTtl); box->addRow(std::move(chooseTimeResult.widget)); const auto state = box->lifetime().make_state(); box->setTitle(tr::lng_manage_messages_ttl_title()); auto confirmText = std::move( chooseTimeResult.secondsValue ) | rpl::map([=](int seconds) { state->lastSeconds = seconds; return !seconds ? tr::lng_manage_messages_ttl_disable() : tr::lng_enable_auto_delete(); }) | rpl::flatten_latest(); const auto confirm = box->addButton(std::move(confirmText), [=] { callback(state->lastSeconds); box->closeBox(); }); box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); } void TTLBox( not_null box, rpl::producer &&about, Fn callback, TimeId startTtlPeriod) { box->addRow(object_ptr( box, std::move(about), st::boxLabel)); SetupPickerAndConfirm(box, callback, startTtlPeriod); box->setTitle(tr::lng_manage_messages_ttl_title()); box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); if (startTtlPeriod) { box->addLeftButton(tr::lng_manage_messages_ttl_disable(), [=] { callback(0); }); } } } // namespace void FillTTLMenu(not_null menu, Args args) { const auto &st = menu->st().menu; const auto iconTextPosition = st.itemIconPosition + st::menuIconTTLAnyTextPosition; const auto addAction = [&](const QString &text, TimeId ttl) { auto item = base::make_unique_q( menu, st, Ui::Menu::CreateAction( menu->menu().get(), text, [=] { args.callback(ttl); }), &st::menuIconTTLAny, &st::menuIconTTLAny); item->setData(Ui::FormatTTLTiny(ttl), iconTextPosition); menu->addAction(std::move(item)); }; addAction(tr::lng_manage_messages_ttl_after1(tr::now), kTTLDurSeconds1); addAction(tr::lng_manage_messages_ttl_after2(tr::now), kTTLDurSeconds2); addAction(tr::lng_manage_messages_ttl_after3(tr::now), kTTLDurSeconds3); addAction(tr::lng_manage_messages_ttl_after4(tr::now), kTTLDurSeconds4); menu->addAction( tr::lng_manage_messages_ttl_after_custom(tr::now), [a = args] { a.show->showBox( Box(TTLBox, std::move(a.about), a.callback, a.startTtl)); }, &st::menuIconCustomize); if (args.startTtl) { const auto disable = menu->addAction( tr::lng_manage_messages_ttl_disable(tr::now), [=] { args.callback(0); }, &st::menuIconDisableAttention); disable->setData(st::menuIconAttentionColor->c); } menu->addSeparator(); menu->addAction(base::make_unique_q( menu, menu->st().menu, std::move(args.about))); } void SetupTTLMenu( not_null parent, rpl::producer<> triggers, Args args) { struct State { base::unique_qptr menu; }; const auto state = parent->lifetime().make_state(); std::move( triggers ) | rpl::start_with_next([=] { if (state->menu) { return; } state->menu = base::make_unique_q( parent, st::popupMenuExpandedSeparator); FillTTLMenu(state->menu.get(), args); state->menu->popup(QCursor::pos()); }, parent->lifetime()); } } // namespace TTLMenu