| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876 |
- /*
- 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 "core/shortcuts.h"
- #include "mainwindow.h"
- #include "mainwidget.h"
- #include "window/window_controller.h"
- #include "core/application.h"
- #include "media/player/media_player_instance.h"
- #include "base/platform/base_platform_info.h"
- #include "platform/platform_specific.h"
- #include "base/parse_helper.h"
- #include <QAction>
- #include <QShortcut>
- #include <QtCore/QJsonDocument>
- #include <QtCore/QJsonObject>
- #include <QtCore/QJsonArray>
- namespace Shortcuts {
- namespace {
- constexpr auto kCountLimit = 256; // How many shortcuts can be in json file.
- rpl::event_stream<not_null<Request*>> RequestsStream;
- bool Paused/* = false*/;
- const auto AutoRepeatCommands = base::flat_set<Command>{
- Command::MediaPrevious,
- Command::MediaNext,
- Command::ChatPrevious,
- Command::ChatNext,
- Command::ChatFirst,
- Command::ChatLast,
- };
- const auto MediaCommands = base::flat_set<Command>{
- Command::MediaPlay,
- Command::MediaPause,
- Command::MediaPlayPause,
- Command::MediaStop,
- Command::MediaPrevious,
- Command::MediaNext,
- };
- const auto SupportCommands = base::flat_set<Command>{
- Command::SupportReloadTemplates,
- Command::SupportToggleMuted,
- Command::SupportScrollToCurrent,
- Command::SupportHistoryBack,
- Command::SupportHistoryForward,
- };
- const auto CommandByName = base::flat_map<QString, Command>{
- { u"close_telegram"_q , Command::Close },
- { u"lock_telegram"_q , Command::Lock },
- { u"minimize_telegram"_q , Command::Minimize },
- { u"quit_telegram"_q , Command::Quit },
- { u"media_play"_q , Command::MediaPlay },
- { u"media_pause"_q , Command::MediaPause },
- { u"media_playpause"_q , Command::MediaPlayPause },
- { u"media_stop"_q , Command::MediaStop },
- { u"media_previous"_q , Command::MediaPrevious },
- { u"media_next"_q , Command::MediaNext },
- { u"search"_q , Command::Search },
- { u"previous_chat"_q , Command::ChatPrevious },
- { u"next_chat"_q , Command::ChatNext },
- { u"first_chat"_q , Command::ChatFirst },
- { u"last_chat"_q , Command::ChatLast },
- { u"self_chat"_q , Command::ChatSelf },
- { u"pinned_chat1"_q , Command::ChatPinned1 },
- { u"pinned_chat2"_q , Command::ChatPinned2 },
- { u"pinned_chat3"_q , Command::ChatPinned3 },
- { u"pinned_chat4"_q , Command::ChatPinned4 },
- { u"pinned_chat5"_q , Command::ChatPinned5 },
- { u"pinned_chat6"_q , Command::ChatPinned6 },
- { u"pinned_chat7"_q , Command::ChatPinned7 },
- { u"pinned_chat8"_q , Command::ChatPinned8 },
- { u"previous_folder"_q , Command::FolderPrevious },
- { u"next_folder"_q , Command::FolderNext },
- { u"all_chats"_q , Command::ShowAllChats },
- { u"account1"_q , Command::ShowAccount1 },
- { u"account2"_q , Command::ShowAccount2 },
- { u"account3"_q , Command::ShowAccount3 },
- { u"account4"_q , Command::ShowAccount4 },
- { u"account5"_q , Command::ShowAccount5 },
- { u"account6"_q , Command::ShowAccount6 },
- { u"folder1"_q , Command::ShowFolder1 },
- { u"folder2"_q , Command::ShowFolder2 },
- { u"folder3"_q , Command::ShowFolder3 },
- { u"folder4"_q , Command::ShowFolder4 },
- { u"folder5"_q , Command::ShowFolder5 },
- { u"folder6"_q , Command::ShowFolder6 },
- { u"last_folder"_q , Command::ShowFolderLast },
- { u"show_archive"_q , Command::ShowArchive },
- { u"show_contacts"_q , Command::ShowContacts },
- { u"read_chat"_q , Command::ReadChat },
- { u"show_chat_menu"_q , Command::ShowChatMenu },
- { u"show_chat_preview"_q , Command::ShowChatPreview },
- // Shortcuts that have no default values.
- { u"message"_q , Command::JustSendMessage },
- { u"message_silently"_q , Command::SendSilentMessage },
- { u"message_scheduled"_q , Command::ScheduleMessage },
- { u"media_viewer_video_fullscreen"_q , Command::MediaViewerFullscreen },
- { u"show_scheduled"_q , Command::ShowScheduled },
- { u"archive_chat"_q , Command::ArchiveChat },
- //
- };
- const base::flat_map<Command, QString> &CommandNames() {
- static const auto result = [&] {
- auto result = base::flat_map<Command, QString>();
- for (const auto &[name, command] : CommandByName) {
- result.emplace(command, name);
- }
- return result;
- }();
- return result;
- };
- [[maybe_unused]] constexpr auto kNoValue = {
- Command::JustSendMessage,
- Command::SendSilentMessage,
- Command::ScheduleMessage,
- Command::MediaViewerFullscreen,
- Command::ShowScheduled,
- Command::ArchiveChat,
- };
- class Manager {
- public:
- void fill();
- void clear();
- [[nodiscard]] std::vector<Command> lookup(
- not_null<QObject*> object) const;
- void toggleMedia(bool toggled);
- void toggleSupport(bool toggled);
- void listen(not_null<QWidget*> widget);
- [[nodiscard]] const QStringList &errors() const;
- [[nodiscard]] auto keysDefaults() const
- -> base::flat_map<QKeySequence, base::flat_set<Command>>;
- [[nodiscard]] auto keysCurrents() const
- -> base::flat_map<QKeySequence, base::flat_set<Command>>;
- void change(
- QKeySequence was,
- QKeySequence now,
- Command command,
- std::optional<Command> restore);
- void resetToDefaults();
- private:
- void fillDefaults();
- void writeDefaultFile();
- void writeCustomFile();
- bool readCustomFile();
- void set(const QString &keys, Command command, bool replace = false);
- void set(const QKeySequence &result, Command command, bool replace);
- void remove(const QString &keys);
- void remove(const QKeySequence &keys);
- void remove(const QKeySequence &keys, Command command);
- void unregister(base::unique_qptr<QAction> shortcut);
- void pruneListened();
- QStringList _errors;
- base::flat_map<QKeySequence, base::unique_qptr<QAction>> _shortcuts;
- base::flat_multi_map<not_null<QObject*>, Command> _commandByObject;
- std::vector<QPointer<QWidget>> _listened;
- base::flat_map<QKeySequence, base::flat_set<Command>> _defaults;
- base::flat_set<QAction*> _mediaShortcuts;
- base::flat_set<QAction*> _supportShortcuts;
- };
- QString DefaultFilePath() {
- return cWorkingDir() + u"tdata/shortcuts-default.json"_q;
- }
- QString CustomFilePath() {
- return cWorkingDir() + u"tdata/shortcuts-custom.json"_q;
- }
- bool DefaultFileIsValid() {
- QFile file(DefaultFilePath());
- if (!file.open(QIODevice::ReadOnly)) {
- return false;
- }
- auto error = QJsonParseError{ 0, QJsonParseError::NoError };
- const auto document = QJsonDocument::fromJson(
- base::parse::stripComments(file.readAll()),
- &error);
- file.close();
- if (error.error != QJsonParseError::NoError || !document.isArray()) {
- return false;
- }
- const auto shortcuts = document.array();
- if (shortcuts.isEmpty() || !(*shortcuts.constBegin()).isObject()) {
- return false;
- }
- const auto versionObject = (*shortcuts.constBegin()).toObject();
- const auto version = versionObject.constFind(u"version"_q);
- if (version == versionObject.constEnd()
- || !(*version).isString()
- || (*version).toString() != QString::number(AppVersion)) {
- return false;
- }
- return true;
- }
- void WriteDefaultCustomFile() {
- const auto path = CustomFilePath();
- auto input = QFile(":/misc/default_shortcuts-custom.json");
- auto output = QFile(path);
- if (input.open(QIODevice::ReadOnly)
- && output.open(QIODevice::WriteOnly)) {
- #ifdef Q_OS_MAC
- auto text = qs(input.readAll());
- const auto note = R"(
- // Note:
- // On Apple platforms, reference to "ctrl" corresponds to the Command keys )"
- + QByteArray()
- + R"(on the Macintosh keyboard.
- // On Apple platforms, reference to "meta" corresponds to the Control keys.
- [
- )";
- text.replace(u"\n\n["_q, QString(note));
- output.write(text.toUtf8());
- #else
- output.write(input.readAll());
- #endif // !Q_OS_MAC
- }
- }
- void Manager::fill() {
- fillDefaults();
- if (!DefaultFileIsValid()) {
- writeDefaultFile();
- }
- if (!readCustomFile()) {
- WriteDefaultCustomFile();
- }
- }
- void Manager::clear() {
- _errors.clear();
- _shortcuts.clear();
- _commandByObject.clear();
- _mediaShortcuts.clear();
- _supportShortcuts.clear();
- }
- const QStringList &Manager::errors() const {
- return _errors;
- }
- auto Manager::keysDefaults() const
- -> base::flat_map<QKeySequence, base::flat_set<Command>> {
- return _defaults;
- }
- auto Manager::keysCurrents() const
- -> base::flat_map<QKeySequence, base::flat_set<Command>> {
- auto result = base::flat_map<QKeySequence, base::flat_set<Command>>();
- for (const auto &[keys, command] : _shortcuts) {
- auto i = _commandByObject.findFirst(command);
- const auto end = _commandByObject.end();
- for (; i != end && (i->first == command); ++i) {
- result[keys].emplace(i->second);
- }
- }
- return result;
- }
- void Manager::change(
- QKeySequence was,
- QKeySequence now,
- Command command,
- std::optional<Command> restore) {
- if (!was.isEmpty()) {
- remove(was, command);
- }
- if (!now.isEmpty()) {
- set(now, command, true);
- }
- if (restore) {
- 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<Command> Manager::lookup(not_null<QObject*> object) const {
- auto result = std::vector<Command>();
- auto i = _commandByObject.findFirst(object);
- const auto end = _commandByObject.end();
- for (; i != end && (i->first == object); ++i) {
- result.push_back(i->second);
- }
- return result;
- }
- void Manager::toggleMedia(bool toggled) {
- for (const auto shortcut : _mediaShortcuts) {
- shortcut->setEnabled(toggled);
- }
- }
- void Manager::toggleSupport(bool toggled) {
- for (const auto shortcut : _supportShortcuts) {
- shortcut->setEnabled(toggled);
- }
- }
- void Manager::listen(not_null<QWidget*> widget) {
- pruneListened();
- _listened.push_back(widget.get());
- for (const auto &[keys, shortcut] : _shortcuts) {
- widget->addAction(shortcut.get());
- }
- }
- void Manager::pruneListened() {
- for (auto i = begin(_listened); i != end(_listened);) {
- if (i->data()) {
- ++i;
- } else {
- i = _listened.erase(i);
- }
- }
- }
- bool Manager::readCustomFile() {
- // read custom shortcuts from file if it exists or write an empty custom shortcuts file
- QFile file(CustomFilePath());
- if (!file.exists()) {
- return false;
- }
- const auto guard = gsl::finally([&] {
- if (!_errors.isEmpty()) {
- _errors.push_front((u"While reading file '%1'..."_q
- ).arg(file.fileName()));
- }
- });
- if (!file.open(QIODevice::ReadOnly)) {
- _errors.push_back(u"Could not read the file!"_q);
- return true;
- }
- auto error = QJsonParseError{ 0, QJsonParseError::NoError };
- const auto document = QJsonDocument::fromJson(
- base::parse::stripComments(file.readAll()),
- &error);
- file.close();
- if (error.error != QJsonParseError::NoError) {
- _errors.push_back((u"Failed to parse! Error: %2"_q
- ).arg(error.errorString()));
- return true;
- } else if (!document.isArray()) {
- _errors.push_back(u"Failed to parse! Error: array expected"_q);
- return true;
- }
- const auto shortcuts = document.array();
- auto limit = kCountLimit;
- for (auto i = shortcuts.constBegin(), e = shortcuts.constEnd(); i != e; ++i) {
- if (!(*i).isObject()) {
- _errors.push_back(u"Bad entry! Error: object expected"_q);
- continue;
- }
- const auto entry = (*i).toObject();
- const auto keys = entry.constFind(u"keys"_q);
- const auto command = entry.constFind(u"command"_q);
- const auto removed = entry.constFind(u"removed"_q);
- if (keys == entry.constEnd()
- || command == entry.constEnd()
- || !(*keys).isString()
- || (!(*command).isString() && !(*command).isNull())) {
- _errors.push_back(qsl("Bad entry! "
- "{\"keys\": \"...\", \"command\": [ \"...\" | null ]} "
- "expected."));
- } else if ((*command).isNull()) {
- remove((*keys).toString());
- } else {
- const auto name = (*command).toString();
- const auto i = CommandByName.find(name);
- if (i != end(CommandByName)) {
- if (removed != entry.constEnd() && removed->toBool()) {
- remove((*keys).toString(), i->second);
- } else {
- set((*keys).toString(), i->second, true);
- }
- } else {
- LOG(("Shortcut Warning: "
- "could not find shortcut command handler '%1'"
- ).arg(name));
- }
- }
- if (!--limit) {
- _errors.push_back(u"Too many entries! Limit is %1"_q.arg(
- kCountLimit));
- break;
- }
- }
- return true;
- }
- void Manager::fillDefaults() {
- const auto ctrl = Platform::IsMac() ? u"meta"_q : u"ctrl"_q;
- set(u"ctrl+w"_q, Command::Close);
- set(u"ctrl+f4"_q, Command::Close);
- set(u"ctrl+l"_q, Command::Lock);
- set(u"ctrl+m"_q, Command::Minimize);
- set(u"ctrl+q"_q, Command::Quit);
- set(u"media play"_q, Command::MediaPlay);
- set(u"media pause"_q, Command::MediaPause);
- set(u"toggle media play/pause"_q, Command::MediaPlayPause);
- set(u"media stop"_q, Command::MediaStop);
- set(u"media previous"_q, Command::MediaPrevious);
- set(u"media next"_q, Command::MediaNext);
- set(u"ctrl+f"_q, Command::Search);
- set(u"search"_q, Command::Search);
- set(u"find"_q, Command::Search);
- set(u"ctrl+pgdown"_q, Command::ChatNext);
- set(u"alt+down"_q, Command::ChatNext);
- set(u"ctrl+pgup"_q, Command::ChatPrevious);
- set(u"alt+up"_q, Command::ChatPrevious);
- set(u"%1+tab"_q.arg(ctrl), Command::ChatNext);
- set(u"%1+shift+tab"_q.arg(ctrl), Command::ChatPrevious);
- set(u"%1+backtab"_q.arg(ctrl), Command::ChatPrevious);
- set(u"ctrl+alt+home"_q, Command::ChatFirst);
- set(u"ctrl+alt+end"_q, Command::ChatLast);
- set(u"f5"_q, Command::SupportReloadTemplates);
- set(u"ctrl+delete"_q, Command::SupportToggleMuted);
- set(u"ctrl+insert"_q, Command::SupportScrollToCurrent);
- set(u"ctrl+shift+x"_q, Command::SupportHistoryBack);
- set(u"ctrl+shift+c"_q, Command::SupportHistoryForward);
- set(u"ctrl+1"_q, Command::ChatPinned1);
- set(u"ctrl+2"_q, Command::ChatPinned2);
- set(u"ctrl+3"_q, Command::ChatPinned3);
- set(u"ctrl+4"_q, Command::ChatPinned4);
- set(u"ctrl+5"_q, Command::ChatPinned5);
- set(u"ctrl+6"_q, Command::ChatPinned6);
- set(u"ctrl+7"_q, Command::ChatPinned7);
- set(u"ctrl+8"_q, Command::ChatPinned8);
- auto &&folders = ranges::views::zip(
- kShowFolder,
- ranges::views::ints(1, ranges::unreachable));
- for (const auto &[command, index] : folders) {
- set(u"%1+%2"_q.arg(ctrl).arg(index), command);
- }
- set(u"%1+shift+down"_q.arg(ctrl), Command::FolderNext);
- set(u"%1+shift+up"_q.arg(ctrl), Command::FolderPrevious);
- set(u"ctrl+0"_q, Command::ChatSelf);
- set(u"ctrl+9"_q, Command::ShowArchive);
- set(u"ctrl+j"_q, Command::ShowContacts);
- set(u"ctrl+r"_q, Command::ReadChat);
- set(u"ctrl+\\"_q, Command::ShowChatMenu);
- set(u"ctrl+]"_q, Command::ShowChatPreview);
- _defaults = keysCurrents();
- }
- void Manager::writeDefaultFile() {
- auto file = QFile(DefaultFilePath());
- if (!file.open(QIODevice::WriteOnly)) {
- return;
- }
- const char *defaultHeader = R"HEADER(
- // This is a list of default shortcuts for Telegram Desktop
- // Please don't modify it, its content is not used in any way
- // You can place your own shortcuts in the 'shortcuts-custom.json' file
- )HEADER";
- file.write(defaultHeader);
- auto shortcuts = QJsonArray();
- auto version = QJsonObject();
- version.insert(u"version"_q, QString::number(AppVersion));
- shortcuts.push_back(version);
- 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 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);
- }
- }
- }
- // Commands without a default value.
- for (const auto c : ranges::views::concat(kShowAccount, kNoValue)) {
- for (const auto &[name, command] : CommandByName) {
- if (c == command) {
- auto entry = QJsonObject();
- entry.insert(u"keys"_q, QJsonValue());
- entry.insert(u"command"_q, name);
- shortcuts.append(entry);
- }
- }
- }
- 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);
- }
- }
- }
- }
- const auto has = [&](not_null<QObject*> shortcut, Command command) {
- for (auto i = _commandByObject.findFirst(shortcut)
- ; i != end(_commandByObject) && i->first == shortcut
- ; ++i) {
- if (i->second == command) {
- return true;
- }
- }
- return false;
- };
- for (const auto &[sequence, commands] : _defaults) {
- const auto i = _shortcuts.find(sequence);
- if (i == end(_shortcuts)) {
- QJsonObject entry;
- entry.insert(u"keys"_q, sequence.toString().toLower());
- entry.insert(u"command"_q, QJsonValue());
- shortcuts.append(entry);
- continue;
- }
- for (const auto command : commands) {
- if (!has(i->second.get(), command)) {
- const auto j = CommandNames().find(command);
- if (j != CommandNames().end()) {
- QJsonObject entry;
- entry.insert(u"keys"_q, sequence.toString().toLower());
- entry.insert(u"command"_q, j->second);
- entry.insert(u"removed"_q, true);
- 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);
- file.write(document.toJson(QJsonDocument::Indented));
- }
- void Manager::set(const QString &keys, Command command, bool replace) {
- if (keys.isEmpty()) {
- return;
- }
- const auto result = QKeySequence(keys, QKeySequence::PortableText);
- if (result.isEmpty()) {
- _errors.push_back(u"Could not derive key sequence '%1'!"_q.arg(keys));
- return;
- }
- set(result, command, replace);
- }
- void Manager::set(
- const QKeySequence &keys,
- Command command,
- bool replace) {
- auto shortcut = base::make_unique_q<QAction>();
- shortcut->setShortcut(keys);
- shortcut->setShortcutContext(Qt::ApplicationShortcut);
- if (!AutoRepeatCommands.contains(command)) {
- shortcut->setAutoRepeat(false);
- }
- const auto isMediaShortcut = MediaCommands.contains(command);
- const auto isSupportShortcut = SupportCommands.contains(command);
- if (isMediaShortcut || isSupportShortcut) {
- shortcut->setEnabled(false);
- }
- auto object = shortcut.get();
- auto i = _shortcuts.find(keys);
- if (i == end(_shortcuts)) {
- i = _shortcuts.emplace(keys, std::move(shortcut)).first;
- } else if (replace) {
- unregister(std::exchange(i->second, std::move(shortcut)));
- } else {
- object = i->second.get();
- }
- _commandByObject.emplace(object, command);
- if (!shortcut) { // Added the new one.
- if (isMediaShortcut) {
- _mediaShortcuts.emplace(i->second.get());
- }
- if (isSupportShortcut) {
- _supportShortcuts.emplace(i->second.get());
- }
- pruneListened();
- for (const auto &widget : _listened) {
- widget->addAction(i->second.get());
- }
- }
- }
- void Manager::remove(const QString &keys) {
- if (keys.isEmpty()) {
- return;
- }
- const auto result = QKeySequence(keys, QKeySequence::PortableText);
- if (result.isEmpty()) {
- _errors.push_back(u"Could not derive key sequence '%1'!"_q.arg(keys));
- return;
- }
- remove(result);
- }
- void Manager::remove(const QKeySequence &keys) {
- const auto i = _shortcuts.find(keys);
- if (i != end(_shortcuts)) {
- unregister(std::move(i->second));
- _shortcuts.erase(i);
- }
- }
- void Manager::remove(const QKeySequence &keys, Command command) {
- const auto i = _shortcuts.find(keys);
- if (i != end(_shortcuts)) {
- _commandByObject.remove(i->second.get(), command);
- if (!_commandByObject.contains(i->second.get())) {
- unregister(std::move(i->second));
- _shortcuts.erase(i);
- }
- }
- }
- void Manager::unregister(base::unique_qptr<QAction> shortcut) {
- if (shortcut) {
- _commandByObject.removeAll(shortcut.get());
- _mediaShortcuts.erase(shortcut.get());
- _supportShortcuts.erase(shortcut.get());
- }
- }
- Manager Data;
- } // namespace
- Request::Request(std::vector<Command> commands)
- : _commands(std::move(commands)) {
- }
- bool Request::check(Command command, int priority) {
- if (ranges::contains(_commands, command)
- && priority > _handlerPriority) {
- _handlerPriority = priority;
- return true;
- }
- return false;
- }
- bool Request::handle(FnMut<bool()> handler) {
- _handler = std::move(handler);
- return true;
- }
- FnMut<bool()> RequestHandler(std::vector<Command> commands) {
- auto request = Request(std::move(commands));
- RequestsStream.fire(&request);
- return std::move(request._handler);
- }
- FnMut<bool()> RequestHandler(Command command) {
- return RequestHandler(std::vector<Command>{ command });
- }
- bool Launch(Command command) {
- if (auto handler = RequestHandler(command)) {
- return handler();
- }
- return false;
- }
- bool Launch(std::vector<Command> commands) {
- if (Paused) {
- return false;
- } else if (auto handler = RequestHandler(std::move(commands))) {
- return handler();
- }
- return false;
- }
- rpl::producer<not_null<Request*>> Requests() {
- return RequestsStream.events();
- }
- void Start() {
- Data.fill();
- }
- const QStringList &Errors() {
- return Data.errors();
- }
- bool HandleEvent(
- not_null<QObject*> object,
- not_null<QShortcutEvent*> event) {
- return Launch(Data.lookup(object));
- }
- void ToggleMediaShortcuts(bool toggled) {
- Data.toggleMedia(toggled);
- }
- void ToggleSupportShortcuts(bool toggled) {
- Data.toggleSupport(toggled);
- }
- void Pause() {
- Paused = true;
- }
- void Unpause() {
- Paused = false;
- }
- auto KeysDefaults()
- -> base::flat_map<QKeySequence, base::flat_set<Command>> {
- return Data.keysDefaults();
- }
- auto KeysCurrents()
- -> base::flat_map<QKeySequence, base::flat_set<Command>> {
- return Data.keysCurrents();
- }
- void Change(
- QKeySequence was,
- QKeySequence now,
- Command command,
- std::optional<Command> restore) {
- Data.change(was, now, command, restore);
- }
- void ResetToDefaults() {
- Data.resetToDefaults();
- }
- bool AllowWithoutModifiers(int key) {
- const auto service = {
- Qt::Key_Escape,
- Qt::Key_Tab,
- Qt::Key_Backtab,
- Qt::Key_Backspace,
- Qt::Key_Return,
- Qt::Key_Enter,
- Qt::Key_Insert,
- Qt::Key_Delete,
- Qt::Key_Pause,
- Qt::Key_Print,
- Qt::Key_SysReq,
- Qt::Key_Clear,
- Qt::Key_Home,
- Qt::Key_End,
- Qt::Key_Left,
- Qt::Key_Up,
- Qt::Key_Right,
- Qt::Key_Down,
- Qt::Key_PageUp,
- Qt::Key_PageDown,
- Qt::Key_Shift,
- Qt::Key_Control,
- Qt::Key_Meta,
- Qt::Key_Alt,
- Qt::Key_CapsLock,
- Qt::Key_NumLock,
- Qt::Key_ScrollLock,
- };
- return (key >= 0x80) && !ranges::contains(service, key);
- }
- void Finish() {
- Data.clear();
- }
- void Listen(not_null<QWidget*> widget) {
- Data.listen(widget);
- }
- } // namespace Shortcuts
|