/* 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_send.h" #include "api/api_common.h" #include "base/event_filter.h" #include "boxes/abstract_box.h" #include "chat_helpers/compose/compose_show.h" #include "core/shortcuts.h" #include "history/view/reactions/history_view_reactions_selector.h" #include "history/view/history_view_schedule_box.h" #include "lang/lang_keys.h" #include "ui/widgets/popup_menu.h" #include "data/data_peer.h" #include "data/data_forum.h" #include "data/data_forum_topic.h" #include "data/data_message_reactions.h" #include "data/data_session.h" #include "main/main_session.h" #include "history/history.h" #include "history/history_unread_things.h" #include "apiwrap.h" #include "styles/style_chat_helpers.h" #include "styles/style_menu_icons.h" #include namespace SendMenu { namespace { [[nodiscard]] Data::PossibleItemReactionsRef LookupPossibleEffects( not_null session) { auto result = Data::PossibleItemReactionsRef(); const auto reactions = &session->data().reactions(); const auto &full = reactions->list(Data::Reactions::Type::Active); const auto &top = reactions->list(Data::Reactions::Type::Top); const auto &recent = reactions->list(Data::Reactions::Type::Recent); const auto premiumPossible = session->premiumPossible(); auto added = base::flat_set(); result.recent.reserve(full.size()); for (const auto &reaction : ranges::views::concat(top, recent, full)) { if (premiumPossible || !reaction.id.custom()) { if (added.emplace(reaction.id).second) { result.recent.push_back(&reaction); } } } result.customAllowed = premiumPossible; const auto i = ranges::find( result.recent, reactions->favoriteId(), &Data::Reaction::id); if (i != end(result.recent) && i != begin(result.recent)) { std::rotate(begin(result.recent), i, i + 1); } return result; } } // namespace Fn DefaultSilentCallback(Fn send) { return [=] { send({ .silent = true }); }; } Fn DefaultScheduleCallback( std::shared_ptr show, Type type, Fn send) { return [=, weak = Ui::MakeWeak(show->toastParent())] { show->showBox( HistoryView::PrepareScheduleBox( weak, show, type, [=](Api::SendOptions options) { send(options); }), Ui::LayerOption::KeepOther); }; } Fn DefaultWhenOnlineCallback(Fn send) { return [=] { send(Api::DefaultSendWhenOnlineOptions()); }; } FillMenuResult FillSendMenu( not_null menu, Type type, Fn silent, Fn schedule, Fn whenOnline, const style::ComposeIcons *iconsOverride) { if (!silent && !schedule) { return FillMenuResult::None; } const auto &icons = iconsOverride ? *iconsOverride : st::defaultComposeIcons; const auto now = type; if (now == Type::Disabled || (!silent && now == Type::SilentOnly)) { return FillMenuResult::None; } if (silent && now != Type::Reminder) { menu->addAction( tr::lng_send_silent_message(tr::now), silent, &icons.menuMute); } if (schedule && now != Type::SilentOnly) { menu->addAction( (now == Type::Reminder ? tr::lng_reminder_message(tr::now) : tr::lng_schedule_message(tr::now)), schedule, &icons.menuSchedule); } if (whenOnline && now == Type::ScheduledToUser) { menu->addAction( tr::lng_scheduled_send_until_online(tr::now), whenOnline, &icons.menuWhenOnline); } return FillMenuResult::Success; } void SetupMenuAndShortcuts( not_null button, std::shared_ptr show, Fn type, Fn silent, Fn schedule, Fn whenOnline) { if (!silent && !schedule && !whenOnline) { return; } const auto menu = std::make_shared>(); const auto showMenu = [=] { *menu = base::make_unique_q( button, st::popupMenuWithIcons); const auto result = FillSendMenu( *menu, type(), silent, schedule, whenOnline); if (result != FillMenuResult::Success) { return false; } const auto desiredPosition = QCursor::pos(); using namespace HistoryView::Reactions; const auto selector = show ? AttachSelectorToMenu( menu->get(), desiredPosition, st::reactPanelEmojiPan, show, LookupPossibleEffects(&show->session()), { tr::lng_effect_add_title(tr::now) }) : base::make_unexpected(AttachSelectorResult::Skipped); if (selector) { //(*selector)->chosen(); (*menu)->popupPrepared(); } else if (selector.error() == AttachSelectorResult::Failed) { return false; } else { (*menu)->popup(desiredPosition); } return true; }; base::install_event_filter(button, [=](not_null e) { if (e->type() == QEvent::ContextMenu && showMenu()) { return base::EventFilterResult::Cancel; } return base::EventFilterResult::Continue; }); Shortcuts::Requests( ) | rpl::filter([=] { return button->isActiveWindow(); }) | rpl::start_with_next([=](not_null request) { using Command = Shortcuts::Command; const auto now = type(); if (now == Type::Disabled || (!silent && now == Type::SilentOnly)) { return; } (silent && (now != Type::Reminder) && request->check(Command::SendSilentMessage) && request->handle([=] { silent(); return true; })) || (schedule && (now != Type::SilentOnly) && request->check(Command::ScheduleMessage) && request->handle([=] { schedule(); return true; })) || (request->check(Command::JustSendMessage) && request->handle([=] { const auto post = [&](QEvent::Type type) { QApplication::postEvent( button, new QMouseEvent( type, QPointF(0, 0), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); }; post(QEvent::MouseButtonPress); post(QEvent::MouseButtonRelease); return true; })); }, button->lifetime()); } void SetupReadAllMenu( not_null button, Fn currentThread, const QString &text, Fn, Fn)> sendReadRequest) { struct State { base::unique_qptr menu; base::flat_set> sentForEntries; }; const auto state = std::make_shared(); const auto showMenu = [=] { const auto thread = base::make_weak(currentThread()); if (!thread) { return; } state->menu = base::make_unique_q( button, st::popupMenuWithIcons); state->menu->addAction(text, [=] { const auto strong = thread.get(); if (!strong || !state->sentForEntries.emplace(thread).second) { return; } sendReadRequest(strong, [=] { state->sentForEntries.remove(thread); }); }, &st::menuIconMarkRead); state->menu->popup(QCursor::pos()); }; base::install_event_filter(button, [=](not_null e) { if (e->type() == QEvent::ContextMenu) { showMenu(); return base::EventFilterResult::Cancel; } return base::EventFilterResult::Continue; }); } void SetupUnreadMentionsMenu( not_null button, Fn currentThread) { const auto text = tr::lng_context_mark_read_mentions_all(tr::now); const auto sendOne = [=]( base::weak_ptr weakThread, Fn done, auto resend) -> void { const auto thread = weakThread.get(); if (!thread) { done(); return; } const auto peer = thread->peer(); const auto topic = thread->asTopic(); const auto rootId = topic ? topic->rootId() : 0; using Flag = MTPmessages_ReadMentions::Flag; peer->session().api().request(MTPmessages_ReadMentions( MTP_flags(rootId ? Flag::f_top_msg_id : Flag()), peer->input, MTP_int(rootId) )).done([=](const MTPmessages_AffectedHistory &result) { const auto offset = peer->session().api().applyAffectedHistory( peer, result); if (offset > 0) { resend(weakThread, done, resend); } else { done(); peer->owner().history(peer)->clearUnreadMentionsFor(rootId); } }).fail(done).send(); }; const auto sendRequest = [=]( not_null thread, Fn done) { sendOne(base::make_weak(thread), std::move(done), sendOne); }; SetupReadAllMenu(button, currentThread, text, sendRequest); } void SetupUnreadReactionsMenu( not_null button, Fn currentThread) { const auto text = tr::lng_context_mark_read_reactions_all(tr::now); const auto sendOne = [=]( base::weak_ptr weakThread, Fn done, auto resend) -> void { const auto thread = weakThread.get(); if (!thread) { done(); return; } const auto topic = thread->asTopic(); const auto peer = thread->peer(); const auto rootId = topic ? topic->rootId() : 0; using Flag = MTPmessages_ReadReactions::Flag; peer->session().api().request(MTPmessages_ReadReactions( MTP_flags(rootId ? Flag::f_top_msg_id : Flag(0)), peer->input, MTP_int(rootId) )).done([=](const MTPmessages_AffectedHistory &result) { const auto offset = peer->session().api().applyAffectedHistory( peer, result); if (offset > 0) { resend(weakThread, done, resend); } else { done(); peer->owner().history(peer)->clearUnreadReactionsFor(rootId); } }).fail(done).send(); }; const auto sendRequest = [=]( not_null thread, Fn done) { sendOne(base::make_weak(thread), std::move(done), sendOne); }; SetupReadAllMenu(button, currentThread, text, sendRequest); } } // namespace SendMenu