/* 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 "settings/settings_shortcuts.h" #include "base/event_filter.h" #include "core/application.h" #include "core/shortcuts.h" #include "lang/lang_keys.h" #include "ui/text/text_utilities.h" #include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "ui/widgets/popup_menu.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" #include "ui/vertical_list.h" #include "styles/style_menu_icons.h" #include "styles/style_settings.h" #include namespace Settings { namespace { namespace S = ::Shortcuts; struct Labeled { S::Command command = {}; rpl::producer label; }; [[nodiscard]] std::vector Entries() { using C = S::Command; const auto pinned = [](int index) { return tr::lng_shortcuts_chat_pinned_n( lt_index, rpl::single(QString::number(index))); }; const auto account = [](int index) { return tr::lng_shortcuts_show_account_n( lt_index, rpl::single(QString::number(index))); }; const auto folder = [](int index) { return tr::lng_shortcuts_show_folder_n( lt_index, rpl::single(QString::number(index))); }; const auto separator = Labeled{ C(), nullptr }; return { { C::Close, tr::lng_shortcuts_close() }, { C::Lock, tr::lng_shortcuts_lock() }, { C::Minimize, tr::lng_shortcuts_minimize() }, { C::Quit, tr::lng_shortcuts_quit() }, separator, { C::Search, tr::lng_shortcuts_search() }, separator, { C::ChatPrevious, tr::lng_shortcuts_chat_previous() }, { C::ChatNext, tr::lng_shortcuts_chat_next() }, { C::ChatFirst, tr::lng_shortcuts_chat_first() }, { C::ChatLast, tr::lng_shortcuts_chat_last() }, { C::ChatSelf, tr::lng_shortcuts_chat_self() }, separator, { C::ChatPinned1, pinned(1) }, { C::ChatPinned2, pinned(2) }, { C::ChatPinned3, pinned(3) }, { C::ChatPinned4, pinned(4) }, { C::ChatPinned5, pinned(5) }, { C::ChatPinned6, pinned(6) }, { C::ChatPinned7, pinned(7) }, { C::ChatPinned8, pinned(8) }, separator, { C::ShowAccount1, account(1) }, { C::ShowAccount2, account(2) }, { C::ShowAccount3, account(3) }, { C::ShowAccount4, account(4) }, { C::ShowAccount5, account(5) }, { C::ShowAccount6, account(6) }, separator, { C::ShowAllChats, tr::lng_shortcuts_show_all_chats() }, { C::ShowFolder1, folder(1) }, { C::ShowFolder2, folder(2) }, { C::ShowFolder3, folder(3) }, { C::ShowFolder4, folder(4) }, { C::ShowFolder5, folder(5) }, { C::ShowFolder6, folder(6) }, { C::ShowFolderLast, tr::lng_shortcuts_show_folder_last() }, { C::FolderNext, tr::lng_shortcuts_folder_next() }, { C::FolderPrevious, tr::lng_shortcuts_folder_previous() }, { C::ShowArchive, tr::lng_shortcuts_archive() }, { C::ShowContacts, tr::lng_shortcuts_contacts() }, separator, { C::ReadChat, tr::lng_shortcuts_read_chat() }, { C::ArchiveChat, tr::lng_shortcuts_archive_chat() }, { C::ShowScheduled, tr::lng_shortcuts_scheduled() }, { C::ShowChatMenu, tr::lng_shortcuts_show_chat_menu() }, separator, { C::JustSendMessage, tr::lng_shortcuts_just_send() }, { C::SendSilentMessage, tr::lng_shortcuts_silent_send() }, { C::ScheduleMessage, tr::lng_shortcuts_schedule() }, separator, { C::MediaViewerFullscreen, tr::lng_shortcuts_media_fullscreen() }, separator, { C::MediaPlay, tr::lng_shortcuts_media_play() }, { C::MediaPause, tr::lng_shortcuts_media_pause() }, { C::MediaPlayPause, tr::lng_shortcuts_media_play_pause() }, { C::MediaStop, tr::lng_shortcuts_media_stop() }, { C::MediaPrevious, tr::lng_shortcuts_media_previous() }, { C::MediaNext, tr::lng_shortcuts_media_next() }, }; } [[nodiscard]] QString ToString(const QKeySequence &key) { auto result = key.toString(); #ifdef Q_OS_MAC result = result.replace(u"Ctrl+"_q, QString() + QChar(0x2318)); result = result.replace(u"Meta+"_q, QString() + QChar(0x2303)); result = result.replace(u"Alt+"_q, QString() + QChar(0x2325)); result = result.replace(u"Shift+"_q, QString() + QChar(0x21E7)); #endif // Q_OS_MAC return result; } [[nodiscard]] Fn SetupShortcutsContent( not_null controller, not_null content) { const auto &defaults = S::KeysDefaults(); const auto ¤ts = S::KeysCurrents(); struct Button { S::Command command; std::unique_ptr widget; rpl::variable key; rpl::variable removed; }; struct Entry { S::Command command; rpl::producer label; std::vector original; std::vector now; Ui::VerticalLayout *wrap = nullptr; std::vector> buttons; }; struct State { std::vector entries; rpl::variable modified; rpl::variable recording; rpl::variable lastKey; Fn showMenuFor; }; const auto state = content->lifetime().make_state(); const auto labeled = Entries(); auto &entries = state->entries = ranges::views::all( labeled ) | ranges::views::transform([](Labeled labeled) { return Entry{ labeled.command, std::move(labeled.label) }; }) | ranges::to_vector; for (const auto &[keys, commands] : defaults) { for (const auto command : commands) { const auto i = ranges::find(entries, command, &Entry::command); if (i != end(entries)) { i->original.push_back(keys); } } } for (const auto &[keys, commands] : currents) { for (const auto command : commands) { const auto i = ranges::find(entries, command, &Entry::command); if (i != end(entries)) { i->now.push_back(keys); } } } const auto checkModified = [=] { for (const auto &entry : state->entries) { auto original = entry.original; auto now = entry.now; ranges::sort(original); ranges::sort(now); if (original != now) { state->modified = true; return; } } state->modified = false; }; checkModified(); const auto menu = std::make_shared>(); const auto fill = [=](Entry &entry) { auto index = 0; if (entry.original.empty()) { entry.original.push_back(QKeySequence()); } if (entry.now.empty()) { entry.now.push_back(QKeySequence()); } for (const auto &now : entry.now) { if (index < entry.buttons.size()) { entry.buttons[index]->key = now; entry.buttons[index]->removed = false; } else { auto button = std::make_unique