From 5ebdf3ed39bf5c8b52c89accd0c217364a402acc Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 7 Feb 2025 14:46:51 +0400 Subject: [PATCH] Save custom shortcuts to disk. --- .../Resources/default_shortcuts-custom.json | 1 + Telegram/Resources/icons/menu/shortcut.png | Bin 0 -> 447 bytes Telegram/Resources/icons/menu/shortcut@2x.png | Bin 0 -> 671 bytes Telegram/Resources/icons/menu/shortcut@3x.png | Bin 0 -> 1004 bytes Telegram/Resources/langs/lang.strings | 5 +- Telegram/SourceFiles/core/shortcuts.cpp | 151 ++++++++++++------ Telegram/SourceFiles/core/shortcuts.h | 7 +- .../SourceFiles/settings/settings_chat.cpp | 2 +- .../settings/settings_shortcuts.cpp | 76 +++++++-- Telegram/SourceFiles/ui/menu_icons.style | 1 + 10 files changed, 172 insertions(+), 71 deletions(-) create mode 100644 Telegram/Resources/icons/menu/shortcut.png create mode 100644 Telegram/Resources/icons/menu/shortcut@2x.png create mode 100644 Telegram/Resources/icons/menu/shortcut@3x.png diff --git a/Telegram/Resources/default_shortcuts-custom.json b/Telegram/Resources/default_shortcuts-custom.json index 42d412b15..054ad70d0 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 0000000000000000000000000000000000000000..e5e3389e979c58518b2670d725e3065b1bbf91c4 GIT binary patch literal 447 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1SIoCSFHz9jKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlgfOw-fFF~maf z?G(dxE&(EKdp~lu-eOV_s^Y$|a2vn&h0UDa0h%u9jA9CcR$N=1l%M(Uktu(7=cAf& z@HySQf7Soq%RHapJ!#+T{pKGIGxj~cv2E=tE%EgJ&oc(^4f_4sOz!qpj*`)VDY_a*v`FGjoqfzopr0AB#0jsO4v literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/shortcut@2x.png b/Telegram/Resources/icons/menu/shortcut@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..f50a5dc710393189bcd7ffcdcd8500689090c961 GIT binary patch literal 671 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1SD@Ha)~Ll7Gxo;K_gns+gZZHXqbP^tX+OuJ zHCMA71zb)%%+ttl)bos;dG6}vr;Ln~PpV8l$#CQS_scI!?B*v=o&IzA{@Ckl!&dKm z|GdbOMV_y{YOmewvk@*DD_8jLzkfb$^BRLT;S~-ZOPg8B-)}f8(wxwC>aw1aI>*MQ zi&Y*O+ixF@kdBntqR`ILlJaL=?_-Ad4+|=;D}Q1+p~$E>^<=2x`OgfCx}UD}TmHFJ z?rquZ0^^n>@orWgp-G_$*+NBg?k_GcUMAJ+_R40~S+&3!iXzoEa)}cJ6dwgoo-ku_ z@7pr(enH-u!P(2Vtl%o!efMJpPmO`Zg*B3kjM|km%zNV2i@(cy#aI*+$hjkWncF|X zL%t{8_^b?Ec=~}_O4+h2D{csNRqSx7$Z+~LWv=&^&gD8K?;jUE_$H9o|Fr04MCs0$ zN7GJlrmME(gsl#>oB!M(tJZ9`@5+!zPnP_YJ=AaIQOJEQ=s1(wWX}&1F7Pf~<`m(( zFyO%4m>o%}N0SVX>8=jZQfQwrp(16^UD0oE^?t6e<^9F4-*qK^ebv;uD|c^Le-W<7n?*MO;g2U=e;D>&{(Nh>%_~Dt;_!6!b6Mw<&;$T#?-s=X literal 0 HcmV?d00001 diff --git a/Telegram/Resources/icons/menu/shortcut@3x.png b/Telegram/Resources/icons/menu/shortcut@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..5eb87725c6892bae40f9cca9535ba67d568824d6 GIT binary patch literal 1004 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1SD_us|Wxo#^NA%Cx&(BWL^R}E~ycoX}-P; zT0k}j17mw80}DtA5K93u0|WB{Mh0de%?J`(zyz1|Sip>6gA`6MbbAj}9_H!d7?Q#I zHfm#*k)y!1cvUgaRU%o+9+zBHT)E<%xLKoX878Ms_`x>miK@t&HJ=$?x`en0x?XUy zR&8?o%&*fU{r=AFyU|~-*X#D}ygBpdzL~pk`sQ7Kc)>oF5e+u}U&G|rStBne_wN0B z=Eeh8uTE{T-haBpN|vv^*KKjs+E3M-(Q)zd&p-c+TmQZIgM7ojJ$o1!6E98UTN#pN zBPVaOIKIJwAtNG4)ZoO^qWuDWRm>?BE26(gn9Z)Ou3nsc_4QY;rI+58Eo9HwTO?U! zA;NVuX=8zf4|{!8RaJfca??Yf9zT9uwDX9%i3HE1k2?xPZu2p4UGQMmU^I*lw70V} zh}nGe%g>r^hi(?IWhgYHG|Z^kkYUo*xNqOSy!`y9$tyqX*}a=t%7gQT;i7+uJ*q;S zGPSj}Z%eEysznyw&12?mS@v?_B<~-8{v7#~vsHikf4QCpwjideNgNwqP1g85?{CWw zM#)25FE}mpdiL+%@7WQnpSUm}LPsotIedcoJihi%iadNN9NG)oy$lE$;YR?ytelC+cGQj*VZTG`rB`4$lq#uwsWd~ z&eo`0#szGwCJww@PRydi`))`-`sMX`Imrt=W+?>9!Ml_AZNlY|ws-CA z?H3=M$I@V+cFgkb>F*InGfft)x_YBALA9;^-;NkPWneVsgsi$c!%0DMNjlfApJ!5J z+Vm%V$hUuyzIo=$b#DTzc#s`>5NkYqKW*J94&4^yS)FC04nyoqwZK zj3ikWg5t~KWv)s$&-voV{FChNu8q33#$(>I%Di=x@A!SRXmRa-_@PoRLGh*R_Y;D1 y%7msh;sc@?F!^d5XV5InC46&t;ucLK6Udl%)*- literal 0 HcmV?d00001 diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 0c6651782..37d2f97ca 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 63f8957e7..fe7809b84 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 eaad21249..70c8eb63a 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 32cd54682..f13d1d52e 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 754d11099..e61bffd31 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