diff --git a/Telegram/Resources/default_shortcuts-custom.json b/Telegram/Resources/default_shortcuts-custom.json index 42d412b15b..054ad70d07 100644 --- a/Telegram/Resources/default_shortcuts-custom.json +++ b/Telegram/Resources/default_shortcuts-custom.json @@ -1,6 +1,7 @@ // This is a list of your own shortcuts for Telegram Desktop // You can see full list of commands in the 'shortcuts-default.json' file // Place a null value instead of a command string to switch the shortcut off +// You can also edit them in Settings > Chat Settings > Keyboard Shortcuts. [ // { diff --git a/Telegram/Resources/icons/menu/shortcut.png b/Telegram/Resources/icons/menu/shortcut.png new file mode 100644 index 0000000000..e5e3389e97 Binary files /dev/null and b/Telegram/Resources/icons/menu/shortcut.png differ diff --git a/Telegram/Resources/icons/menu/shortcut@2x.png b/Telegram/Resources/icons/menu/shortcut@2x.png new file mode 100644 index 0000000000..f50a5dc710 Binary files /dev/null and b/Telegram/Resources/icons/menu/shortcut@2x.png differ diff --git a/Telegram/Resources/icons/menu/shortcut@3x.png b/Telegram/Resources/icons/menu/shortcut@3x.png new file mode 100644 index 0000000000..5eb87725c6 Binary files /dev/null and b/Telegram/Resources/icons/menu/shortcut@3x.png differ diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 0c66517822..37d2f97ca5 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -643,7 +643,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_chat_corner_reaction" = "Reaction button on messages"; "lng_settings_shortcuts" = "Keyboard shortcuts"; + "lng_shortcuts_reset" = "Reset to default"; +"lng_shortcuts_recording" = "Recording..."; +"lng_shortcuts_add_another" = "Add another"; + "lng_shortcuts_close" = "Close the window"; "lng_shortcuts_lock" = "Lock the application"; "lng_shortcuts_minimize" = "Minimize the window"; @@ -677,7 +681,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_shortcuts_archive_chat" = "Archive chat"; "lng_shortcuts_media_fullscreen" = "Toggle video fullscreen"; "lng_shortcuts_show_chat_menu" = "Show chat menu"; -"lng_shortcuts_recording" = "Recording..."; "lng_settings_chat_reactions_title" = "Quick Reaction"; "lng_settings_chat_reactions_subtitle" = "Choose your favorite reaction"; diff --git a/Telegram/SourceFiles/core/shortcuts.cpp b/Telegram/SourceFiles/core/shortcuts.cpp index 63f8957e72..fe7809b84b 100644 --- a/Telegram/SourceFiles/core/shortcuts.cpp +++ b/Telegram/SourceFiles/core/shortcuts.cpp @@ -113,45 +113,15 @@ const auto CommandByName = base::flat_map{ // }; -const auto CommandNames = base::flat_map{ - { Command::Close , u"close_telegram"_q }, - { Command::Lock , u"lock_telegram"_q }, - { Command::Minimize , u"minimize_telegram"_q }, - { Command::Quit , u"quit_telegram"_q }, - - { Command::MediaPlay , u"media_play"_q }, - { Command::MediaPause , u"media_pause"_q }, - { Command::MediaPlayPause , u"media_playpause"_q }, - { Command::MediaStop , u"media_stop"_q }, - { Command::MediaPrevious , u"media_previous"_q }, - { Command::MediaNext , u"media_next"_q }, - - { Command::Search , u"search"_q }, - - { Command::ChatPrevious , u"previous_chat"_q }, - { Command::ChatNext , u"next_chat"_q }, - { Command::ChatFirst , u"first_chat"_q }, - { Command::ChatLast , u"last_chat"_q }, - { Command::ChatSelf , u"self_chat"_q }, - - { Command::FolderPrevious , u"previous_folder"_q }, - { Command::FolderNext , u"next_folder"_q }, - { Command::ShowAllChats , u"all_chats"_q }, - - { Command::ShowFolder1 , u"folder1"_q }, - { Command::ShowFolder2 , u"folder2"_q }, - { Command::ShowFolder3 , u"folder3"_q }, - { Command::ShowFolder4 , u"folder4"_q }, - { Command::ShowFolder5 , u"folder5"_q }, - { Command::ShowFolder6 , u"folder6"_q }, - { Command::ShowFolderLast , u"last_folder"_q }, - - { Command::ShowArchive , u"show_archive"_q }, - { Command::ShowContacts , u"show_contacts"_q }, - - { Command::ReadChat , u"read_chat"_q }, - - { Command::ShowChatMenu , u"show_chat_menu"_q }, +const base::flat_map &CommandNames() { + static const auto result = [&] { + auto result = base::flat_map(); + for (const auto &[name, command] : CommandByName) { + result.emplace(command, name); + } + return result; + }(); + return result; }; [[maybe_unused]] constexpr auto kNoValue = { @@ -176,18 +146,22 @@ public: [[nodiscard]] const QStringList &errors() const; - [[nodiscard]] base::flat_map keysDefaults() const; - [[nodiscard]] base::flat_map keysCurrents() const; + [[nodiscard]] auto keysDefaults() const + -> base::flat_map>; + [[nodiscard]] auto keysCurrents() const + -> base::flat_map>; void change( QKeySequence was, QKeySequence now, Command command, std::optional restore); + void resetToDefaults(); private: void fillDefaults(); void writeDefaultFile(); + void writeCustomFile(); bool readCustomFile(); void set(const QString &keys, Command command, bool replace = false); @@ -204,7 +178,7 @@ private: base::flat_multi_map, Command> _commandByObject; std::vector> _listened; - base::flat_map _defaults; + base::flat_map> _defaults; base::flat_set _mediaShortcuts; base::flat_set _supportShortcuts; @@ -295,16 +269,19 @@ const QStringList &Manager::errors() const { return _errors; } -base::flat_map Manager::keysDefaults() const { +auto Manager::keysDefaults() const +-> base::flat_map> { return _defaults; } -base::flat_map Manager::keysCurrents() const { - auto result = base::flat_map(); +auto Manager::keysCurrents() const +-> base::flat_map> { + auto result = base::flat_map>(); for (const auto &[keys, command] : _shortcuts) { - const auto i = _commandByObject.findFirst(command); - if (i != _commandByObject.end()) { - result.emplace(keys, i->second); + auto i = _commandByObject.findFirst(command); + const auto end = _commandByObject.end(); + for (; i != end && (i->first == command); ++i) { + result[keys].emplace(i->second); } } return result; @@ -325,6 +302,19 @@ void Manager::change( Assert(!was.isEmpty()); set(was, *restore, true); } + writeCustomFile(); +} + +void Manager::resetToDefaults() { + while (!_shortcuts.empty()) { + remove(_shortcuts.begin()->first); + } + for (const auto &[sequence, commands] : _defaults) { + for (const auto command : commands) { + set(sequence, command, false); + } + } + writeCustomFile(); } std::vector Manager::lookup(not_null object) const { @@ -529,8 +519,8 @@ void Manager::writeDefaultFile() { auto i = _commandByObject.findFirst(object); const auto end = _commandByObject.end(); for (; i != end && i->first == object; ++i) { - const auto j = CommandNames.find(i->second); - if (j != CommandNames.end()) { + const auto j = CommandNames().find(i->second); + if (j != CommandNames().end()) { QJsonObject entry; entry.insert(u"keys"_q, sequence.toString().toLower()); entry.insert(u"command"_q, j->second); @@ -551,6 +541,55 @@ void Manager::writeDefaultFile() { } } + auto document = QJsonDocument(); + document.setArray(shortcuts); + file.write(document.toJson(QJsonDocument::Indented)); +} + +void Manager::writeCustomFile() { + auto shortcuts = QJsonArray(); + for (const auto &[sequence, shortcut] : _shortcuts) { + const auto object = shortcut.get(); + auto i = _commandByObject.findFirst(object); + const auto end = _commandByObject.end(); + for (; i != end && i->first == object; ++i) { + const auto d = _defaults.find(sequence); + if (d == _defaults.end() || !d->second.contains(i->second)) { + const auto j = CommandNames().find(i->second); + if (j != CommandNames().end()) { + QJsonObject entry; + entry.insert(u"keys"_q, sequence.toString().toLower()); + entry.insert(u"command"_q, j->second); + shortcuts.append(entry); + } + } + } + } + for (const auto &[sequence, command] : _defaults) { + if (!_shortcuts.contains(sequence)) { + QJsonObject entry; + entry.insert(u"keys"_q, sequence.toString().toLower()); + entry.insert(u"command"_q, QJsonValue()); + shortcuts.append(entry); + } + } + + if (shortcuts.isEmpty()) { + WriteDefaultCustomFile(); + return; + } + + auto file = QFile(CustomFilePath()); + if (!file.open(QIODevice::WriteOnly)) { + LOG(("Shortcut Warning: could not write custom shortcuts file.")); + return; + } + const char *customHeader = R"HEADER( +// This is a list of changed shortcuts for Telegram Desktop +// You can edit them in Settings > Chat Settings > Keyboard Shortcuts. + +)HEADER"; + file.write(customHeader); auto document = QJsonDocument(); document.setArray(shortcuts); @@ -632,7 +671,7 @@ void Manager::remove(const QKeySequence &keys) { void Manager::unregister(base::unique_qptr shortcut) { if (shortcut) { - _commandByObject.erase(shortcut.get()); + _commandByObject.removeAll(shortcut.get()); _mediaShortcuts.erase(shortcut.get()); _supportShortcuts.erase(shortcut.get()); } @@ -720,11 +759,13 @@ void Unpause() { Paused = false; } -base::flat_map KeysDefaults() { +auto KeysDefaults() +-> base::flat_map> { return Data.keysDefaults(); } -base::flat_map KeysCurrents() { +auto KeysCurrents() +-> base::flat_map> { return Data.keysCurrents(); } @@ -736,6 +777,10 @@ void Change( Data.change(was, now, command, restore); } +void ResetToDefaults() { + Data.resetToDefaults(); +} + bool AllowWithoutModifiers(int key) { const auto service = { Qt::Key_Escape, diff --git a/Telegram/SourceFiles/core/shortcuts.h b/Telegram/SourceFiles/core/shortcuts.h index eaad212497..70c8eb63af 100644 --- a/Telegram/SourceFiles/core/shortcuts.h +++ b/Telegram/SourceFiles/core/shortcuts.h @@ -142,14 +142,17 @@ void ToggleSupportShortcuts(bool toggled); void Pause(); void Unpause(); -[[nodiscard]] base::flat_map KeysDefaults(); -[[nodiscard]] base::flat_map KeysCurrents(); +[[nodiscard]] auto KeysDefaults() +-> base::flat_map>; +[[nodiscard]] auto KeysCurrents() +-> base::flat_map>; void Change( QKeySequence was, QKeySequence now, Command command, std::optional restore = {}); +void ResetToDefaults(); [[nodiscard]] bool AllowWithoutModifiers(int key); diff --git a/Telegram/SourceFiles/settings/settings_chat.cpp b/Telegram/SourceFiles/settings/settings_chat.cpp index 32cd54682a..f13d1d52e5 100644 --- a/Telegram/SourceFiles/settings/settings_chat.cpp +++ b/Telegram/SourceFiles/settings/settings_chat.cpp @@ -1009,7 +1009,7 @@ void SetupMessages( inner, tr::lng_settings_shortcuts(), st::settingsButton, - { &st::menuIconBotCommands } + { &st::menuIconShortcut } )->addClickHandler([=] { showOther(Shortcuts::Id()); }); diff --git a/Telegram/SourceFiles/settings/settings_shortcuts.cpp b/Telegram/SourceFiles/settings/settings_shortcuts.cpp index 754d110998..e61bffd311 100644 --- a/Telegram/SourceFiles/settings/settings_shortcuts.cpp +++ b/Telegram/SourceFiles/settings/settings_shortcuts.cpp @@ -14,9 +14,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #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" namespace Settings { @@ -135,6 +137,7 @@ struct Labeled { rpl::variable modified; rpl::variable recording; rpl::variable lastKey; + Fn showMenuFor; }; const auto state = content->lifetime().make_state(); const auto labeled = Entries(); @@ -144,17 +147,21 @@ struct Labeled { return Entry{ labeled.command, std::move(labeled.label) }; }) | ranges::to_vector; - for (const auto &[keys, command] : defaults) { - const auto i = ranges::find(entries, command, &Entry::command); - if (i != end(entries)) { - i->original.push_back(keys); + 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, command] : currents) { - const auto i = ranges::find(entries, command, &Entry::command); - if (i != end(entries)) { - i->now.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); + } } } @@ -173,6 +180,7 @@ struct Labeled { }; checkModified(); + const auto menu = std::make_shared>(); const auto fill = [=](Entry &entry) { auto index = 0; if (entry.original.empty()) { @@ -184,6 +192,7 @@ struct Labeled { 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