LCOV - code coverage report
Current view: top level - muse/framework/shortcuts_v2/internal - commandshortcutsregister.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 73.7 % 357 263
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2026 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : #include "commandshortcutsregister.h"
      23              : 
      24              : #include "global/containers.h"
      25              : #include "global/stringutils.h"
      26              : #include "global/io/file.h"
      27              : #include "global/io/buffer.h"
      28              : #include "global/serialization/json.h"
      29              : #include "global/serialization/textstream.h"
      30              : 
      31              : #include "multiwindows/resourcelockguard.h"
      32              : #include "rcommand/commandtypes.h"
      33              : 
      34              : #include "log.h"
      35              : 
      36              : using namespace muse;
      37              : using namespace muse::shortcuts;
      38              : using namespace muse::async;
      39              : using namespace muse::rcommand;
      40              : 
      41              : static const std::string COMMAND_SHORTCUTS_TAG("CommandShortcuts");
      42              : static const std::string DEFAULT_SHORTCUTS_NAME("shortcuts");
      43              : 
      44              : namespace muse::shortcuts::command {
      45            0 : static const Shortcut& findShortcut(const ShortcutList& shortcuts, const Command& command)
      46              : {
      47            0 :     for (const Shortcut& shortcut : shortcuts) {
      48            0 :         if (shortcut.command == command) {
      49            0 :             return shortcut;
      50              :         }
      51              :     }
      52              : 
      53            0 :     static Shortcut null;
      54            0 :     return null;
      55              : }
      56              : }
      57              : 
      58           15 : void CommandShortcutsRegister::init()
      59              : {
      60           15 :     multiwindowsProvider()->resourceChanged().onReceive(this, [this](const std::string& resourceName) {
      61            0 :         if (resourceName == COMMAND_SHORTCUTS_TAG) {
      62            0 :             reload();
      63              :         }
      64            0 :     });
      65              : 
      66           15 :     configuration()->currentShortcutsPresetNameChanged().onReceive(this, [this](const std::string&) {
      67            4 :         reload();
      68            4 :     });
      69              : 
      70           15 :     reload();
      71           15 : }
      72              : 
      73           23 : void CommandShortcutsRegister::reload(bool onlyDef)
      74              : {
      75           24 :     TRACEFUNC;
      76              : 
      77           23 :     m_shortcuts.clear();
      78           23 :     m_defaultShortcuts.clear();
      79              : 
      80           23 :     io::path_t defPath = configuration()->commandShortcutsAppDataPath(DEFAULT_SHORTCUTS_NAME);
      81           23 :     io::path_t userPath = userShortcutsPath();
      82              : 
      83           23 :     bool ok = readFromFile(m_defaultShortcuts, defPath);
      84           23 :     if (ok) {
      85              :         //! NOTE Platform and preset shortcuts files are diffs from the base one
      86           23 :         std::string defaultName = configuration()->defaultShortcutsName();
      87           23 :         if (defaultName != DEFAULT_SHORTCUTS_NAME) {
      88            2 :             applyShortcutsDiff(defaultName, m_defaultShortcuts);
      89              :         }
      90              : 
      91           23 :         std::string presetName = configuration()->currentShortcutsPresetName();
      92           23 :         if (!presetName.empty()) {
      93            7 :             applyShortcutsDiff(presetName, m_defaultShortcuts);
      94              :         }
      95           23 :     }
      96              : 
      97              :     //! NOTE Add the remaining available commands to the shortcuts
      98           23 :     if (ok) {
      99           23 :         std::set<Command> shortcutCommands;
     100          115 :         for (const Shortcut& sc : m_defaultShortcuts) {
     101           92 :             shortcutCommands.insert(sc.command);
     102              :         }
     103           23 :         std::vector<CommandInfo> commandInfos = commandsRegister()->commandInfoList();
     104           23 :         for (const CommandInfo& commandInfo : commandInfos) {
     105            0 :             if (!commandInfo.availabilities.testFlag(rcommand::Availability::Shortcut)) {
     106            0 :                 continue;
     107              :             }
     108              : 
     109            0 :             if (shortcutCommands.find(commandInfo.command) == shortcutCommands.end()) {
     110            0 :                 Shortcut sc;
     111            0 :                 sc.command = commandInfo.command;
     112            0 :                 sc.scope = makeScope(commandInfo.command);
     113            0 :                 m_defaultShortcuts.push_back(std::move(sc));
     114            0 :             }
     115              :         }
     116           23 :     }
     117              : 
     118           23 :     if (ok) {
     119           23 :         if (!onlyDef) {
     120           23 :             if (!io::File::exists(userPath)) {
     121           18 :                 ok = false;
     122              :             } else {
     123              :                 //! NOTE The user shortcut file may change, so we need to lock it
     124            5 :                 mi::ReadResourceLockGuard guard(multiwindowsProvider(), COMMAND_SHORTCUTS_TAG);
     125            5 :                 ok = readFromFile(m_shortcuts, userPath);
     126            5 :             }
     127              :         } else {
     128            0 :             ok = false;
     129              :         }
     130              : 
     131           23 :         if (!ok) {
     132           18 :             m_shortcuts = m_defaultShortcuts;
     133              :         } else {
     134            5 :             mergeShortcuts(m_shortcuts, m_defaultShortcuts);
     135            5 :             mergeAdditionalShortcuts(m_shortcuts);
     136            5 :             removeNotAvailableCommands(m_shortcuts);
     137            5 :             makeUnique(m_shortcuts);
     138              :         }
     139              : 
     140           23 :         ok = true;
     141              :     }
     142              : 
     143              :     {
     144              :         //! NOTE Checking command availability.
     145              :         //! If a command is not available in the user file, it should have been filtered out earlier in the code.
     146              :         //! If a command is not available in the default shortcuts, the configuration file needs to be edited.
     147          115 :         for (const Shortcut& sc : m_defaultShortcuts) {
     148           92 :             const CommandInfo& info = commandsRegister()->commandInfo(sc.command);
     149           92 :             DO_ASSERT(info.availabilities.testFlag(rcommand::Availability::Shortcut));
     150              :         }
     151              : 
     152          115 :         for (const Shortcut& sc : m_shortcuts) {
     153           92 :             const CommandInfo& info = commandsRegister()->commandInfo(sc.command);
     154           92 :             DO_ASSERT(info.availabilities.testFlag(rcommand::Availability::Shortcut));
     155              :         }
     156              :     }
     157              : 
     158           23 :     if (ok) {
     159           23 :         m_shortcutsChanged.notify();
     160              :     }
     161           23 : }
     162              : 
     163            0 : std::string CommandShortcutsRegister::makeScope(const rcommand::Command& command) const
     164              : {
     165            0 :     auto segments = command.pathSegments();
     166            0 :     IF_ASSERT_FAILED(!segments.empty()) {
     167            0 :         return {};
     168              :     }
     169            0 :     return strings::toUpper(segments.at(0));
     170            0 : }
     171              : 
     172           32 : std::string CommandShortcutsRegister::activeShortcutsName() const
     173              : {
     174           32 :     std::string presetName = configuration()->currentShortcutsPresetName();
     175           64 :     return presetName.empty() ? configuration()->defaultShortcutsName() : presetName;
     176           32 : }
     177              : 
     178           32 : io::path_t CommandShortcutsRegister::userShortcutsPath() const
     179              : {
     180           32 :     return configuration()->commandShortcutsUserAppDataPath(activeShortcutsName());
     181              : }
     182              : 
     183            9 : void CommandShortcutsRegister::applyShortcutsDiff(const std::string& shortcutsName, ShortcutList& shortcuts) const
     184              : {
     185            9 :     ShortcutList diff;
     186            9 :     if (!readFromFile(diff, configuration()->commandShortcutsAppDataPath(shortcutsName))) {
     187            1 :         return;
     188              :     }
     189              : 
     190            8 :     mergeShortcuts(diff, shortcuts);
     191            8 :     shortcuts = diff;
     192            9 : }
     193              : 
     194              : //! NOTE Scope and autoRepeat always take default values on merge, so only sequences are compared
     195            7 : ShortcutList CommandShortcutsRegister::makeDiff(const ShortcutList& shortcuts, const ShortcutList& defaultShortcuts) const
     196              : {
     197            7 :     ShortcutList diff;
     198              : 
     199           35 :     for (const Shortcut& sc : shortcuts) {
     200           28 :         auto it = std::find_if(defaultShortcuts.begin(), defaultShortcuts.end(), [&sc](const Shortcut& defSc) {
     201           70 :             return defSc.command == sc.command;
     202              :         });
     203              : 
     204           28 :         if (it == defaultShortcuts.end() || it->sequences != sc.sequences) {
     205            6 :             diff.push_back(sc);
     206              :         }
     207              :     }
     208              : 
     209            7 :     return diff;
     210            0 : }
     211              : 
     212            3 : bool CommandShortcutsRegister::removeUserFile()
     213              : {
     214            3 :     io::path_t path = userShortcutsPath();
     215            3 :     if (path.empty()) {
     216            0 :         return false;
     217              :     }
     218              : 
     219            3 :     bool ok = false;
     220              :     {
     221            3 :         mi::WriteResourceLockGuard guard(multiwindowsProvider(), COMMAND_SHORTCUTS_TAG);
     222            3 :         ok = io::File::remove(path);
     223            3 :     }
     224              : 
     225            3 :     if (!ok) {
     226            0 :         LOGE() << "failed remove file: " << path;
     227              :     }
     228              : 
     229            3 :     return ok;
     230            3 : }
     231              : 
     232           13 : void CommandShortcutsRegister::mergeShortcuts(ShortcutList& shortcuts, const ShortcutList& defaultShortcuts) const
     233              : {
     234           14 :     TRACEFUNC;
     235              : 
     236           13 :     ShortcutList needadd;
     237           65 :     for (const Shortcut& defSc : defaultShortcuts) {
     238           52 :         Shortcut scForAdd = defSc;
     239           52 :         bool found = false;
     240              : 
     241          104 :         for (Shortcut& sc : shortcuts) {
     242              :             //! NOTE If a user shortcut is found, set scope & auto repeat (always use default values)
     243           52 :             if (sc.command == defSc.command) {
     244           13 :                 sc.scope = defSc.scope;
     245           13 :                 sc.autoRepeat = defSc.autoRepeat;
     246           13 :                 found = true;
     247           39 :             } else if (sc.scope == defSc.scope && defSc.command != rcommand::NONE_COMMAND) {
     248           62 :                 for (const std::string& seq : sc.sequences) {
     249              :                     //! NOTE If user shortcut has sequence from default shortcut, remove the sequence from default shortcut
     250           36 :                     muse::remove_if(scForAdd.sequences, [&seq](const std::string& cmp){
     251           42 :                         return cmp == seq;
     252              :                     });
     253              :                 }
     254              :             }
     255              :         }
     256              : 
     257              :         //! NOTE If no default shortcut is found in user shortcuts add def
     258           52 :         if (!found) {
     259           39 :             needadd.push_back(scForAdd);
     260              :         }
     261           52 :     }
     262              : 
     263           13 :     if (!needadd.empty()) {
     264           13 :         shortcuts.insert(shortcuts.end(), needadd.begin(), needadd.end());
     265              :     }
     266           13 : }
     267              : 
     268            5 : void CommandShortcutsRegister::mergeAdditionalShortcuts(ShortcutList& shortcuts)
     269              : {
     270            5 :     for (const auto& [context, additionalShortcuts] : m_additionalShortcutsMap) {
     271            0 :         mergeShortcuts(shortcuts, additionalShortcuts);
     272              :     }
     273            5 : }
     274              : 
     275            5 : void CommandShortcutsRegister::removeNotAvailableCommands(ShortcutList& shortcuts)
     276              : {
     277            5 :     ShortcutList availableShortcuts;
     278           25 :     for (const Shortcut& sc : shortcuts) {
     279           20 :         const CommandInfo& commandInfo = commandsRegister()->commandInfo(sc.command);
     280           20 :         if (commandInfo.availabilities.testFlag(rcommand::Availability::Shortcut)) {
     281           20 :             availableShortcuts.push_back(sc);
     282              :         }
     283              :     }
     284            5 :     shortcuts = availableShortcuts;
     285            5 : }
     286              : 
     287            5 : void CommandShortcutsRegister::makeUnique(ShortcutList& shortcuts)
     288              : {
     289            6 :     TRACEFUNC;
     290              : 
     291            5 :     const ShortcutList all = shortcuts;
     292              : 
     293            5 :     shortcuts.clear();
     294              : 
     295           25 :     for (const Shortcut& sc : all) {
     296           20 :         const Command& command = sc.command;
     297              : 
     298           20 :         auto it = std::find_if(shortcuts.begin(), shortcuts.end(), [command](const Shortcut& s) {
     299           30 :             return s.command == command;
     300              :         });
     301              : 
     302           20 :         if (it == shortcuts.end()) {
     303           20 :             shortcuts.push_back(sc);
     304           20 :             continue;
     305              :         }
     306              : 
     307            0 :         Shortcut& foundSc = *it;
     308              : 
     309            0 :         if (foundSc.scope != sc.scope) {
     310            0 :             LOGW() << "scope mismatch for command: " << sc.command << ", scope1: " << foundSc.scope << ", scope2: " << sc.scope;
     311              :         }
     312              : 
     313            0 :         foundSc.sequences.insert(foundSc.sequences.end(), sc.sequences.begin(), sc.sequences.end());
     314              :     }
     315            5 : }
     316              : 
     317            7 : ShortcutList CommandShortcutsRegister::filterAndUpdateAdditionalShortcuts(const ShortcutList& shortcuts)
     318              : {
     319            7 :     ShortcutList noAdditionalShortcuts = shortcuts;
     320              : 
     321            7 :     for (auto& [context, additionalShortcuts] : m_additionalShortcutsMap) {
     322            0 :         for (Shortcut& shortcut : additionalShortcuts) {
     323            0 :             auto it = std::find_if(shortcuts.begin(), shortcuts.end(),
     324            0 :                                    [&shortcut](const Shortcut& candidate) {
     325            0 :                 return candidate.command == shortcut.command;
     326              :             });
     327            0 :             if (it != shortcuts.end()) {
     328            0 :                 shortcut = *it;
     329            0 :                 muse::remove(noAdditionalShortcuts, shortcut);
     330              :             }
     331              :         }
     332              :     }
     333              : 
     334            7 :     return noAdditionalShortcuts;
     335            0 : }
     336              : 
     337           37 : bool CommandShortcutsRegister::readFromFile(ShortcutList& shortcuts, const io::path_t& path) const
     338              : {
     339           38 :     TRACEFUNC;
     340              : 
     341           37 :     ByteArray data;
     342           37 :     Ret ret = io::File::readFile(path, data);
     343           37 :     if (!ret) {
     344            1 :         LOGE() << "failed read file: " << path << ", err: " << ret.toString();
     345            1 :         return false;
     346              :     }
     347              : 
     348           36 :     JsonDocument doc = JsonDocument::fromJson(data);
     349           36 :     if (!doc.isArray()) {
     350            0 :         LOGE() << "failed parse json: " << path;
     351            0 :         return false;
     352              :     }
     353              : 
     354           36 :     JsonArray rootArr = doc.rootArray();
     355           36 :     const size_t scopesCount = rootArr.size();
     356           95 :     for (size_t i = 0; i < scopesCount; ++i) {
     357           59 :         JsonValue value = rootArr.at(i);
     358           59 :         if (!value.isObject()) {
     359            0 :             LOGE() << "failed parse json: " << path;
     360            0 :             continue;
     361              :         }
     362              : 
     363           59 :         JsonObject obj = value.toObject();
     364          177 :         std::string scope = obj.value("scope").toStdString();
     365              : 
     366          177 :         JsonArray shortcutsArr = obj.value("shortcuts").toArray();
     367          164 :         for (size_t j = 0; j < shortcutsArr.size(); ++j) {
     368          105 :             JsonObject shortcutObj = shortcutsArr.at(j).toObject();
     369          105 :             Shortcut shortcut;
     370          105 :             shortcut.scope = scope;
     371          315 :             shortcut.command = rcommand::Command(shortcutObj.value("command").toStdString());
     372          315 :             shortcut.autoRepeat = shortcutObj.value("autorepeat", JsonValue(true)).toBool();
     373          315 :             JsonArray sequencesArr = shortcutObj.value("sequences").toArray();
     374          238 :             for (size_t k = 0; k < sequencesArr.size(); ++k) {
     375          133 :                 shortcut.sequences.push_back(sequencesArr.at(k).toStdString());
     376              :             }
     377          105 :             shortcuts.push_back(std::move(shortcut));
     378          105 :         }
     379           59 :     }
     380              : 
     381           36 :     return true;
     382           37 : }
     383              : 
     384            6 : bool CommandShortcutsRegister::writeToFile(const ShortcutList& shortcuts, const io::path_t& path) const
     385              : {
     386            7 :     TRACEFUNC;
     387              : 
     388            6 :     if (path.empty()) {
     389            0 :         LOGE() << "failed write shortcuts: empty path";
     390            0 :         return false;
     391              :     }
     392              : 
     393              :     //! NOTE Json doesn't format very nicely and adds unnecessary escape characters, so we serialize manually.
     394            6 :     io::Buffer buf;
     395              :     {
     396            6 :         buf.open(io::IODevice::ReadWrite);
     397            6 :         TextStream s(&buf);
     398              : 
     399              :         // group shortcuts by scope
     400              :         struct Scope {
     401              :             std::string scope;
     402              :             ShortcutList shortcuts;
     403              :         };
     404              : 
     405            6 :         std::vector<Scope> scopes;
     406           12 :         for (const Shortcut& sc : shortcuts) {
     407            6 :             auto it = std::find_if(scopes.begin(), scopes.end(), [&sc](const Scope& scope) {
     408            0 :                 return scope.scope == sc.scope;
     409              :             });
     410            6 :             if (it == scopes.end()) {
     411           18 :                 scopes.push_back({ sc.scope, { sc } });
     412              :             } else {
     413            0 :                 it->shortcuts.push_back(sc);
     414              :             }
     415              :         }
     416              : 
     417              :         // serialize scopes
     418              : 
     419            5 :         auto escapeSeq = [](const std::string& seq) {
     420            5 :             std::string escaped = seq;
     421           20 :             muse::strings::replace(escaped, "\\", "\\\\");
     422           15 :             muse::strings::replace(escaped, "\"", "\\\"");
     423            5 :             return escaped;
     424            0 :         };
     425            6 :         s << "[\n";
     426           12 :         for (size_t si = 0; si < scopes.size(); ++si) {
     427            6 :             const Scope& scope = scopes.at(si);
     428            6 :             s << "  {\n";
     429            6 :             s << "    \"scope\": \"" << scope.scope << "\",\n";
     430            6 :             s << "    \"shortcuts\": [\n";
     431            6 :             size_t ci = 0;
     432           12 :             for (const Shortcut& sc : scope.shortcuts) {
     433            6 :                 s << "      {";
     434            6 :                 s << "\"command\": \"" << sc.command.toString() << "\", ";
     435            6 :                 s << " \"sequences\": [";
     436           11 :                 for (size_t i = 0; i < sc.sequences.size(); ++i) {
     437            5 :                     s << "\"" << escapeSeq(sc.sequences.at(i)) << "\"";
     438            5 :                     if (i < sc.sequences.size() - 1) {
     439            0 :                         s << ",";
     440              :                     }
     441              :                 }
     442            6 :                 s << "]";
     443            6 :                 if (!sc.autoRepeat) {
     444            0 :                     s << ", \"autorepeat\": false";
     445              :                 }
     446            6 :                 s << "}";
     447            6 :                 if (++ci < scope.shortcuts.size()) {
     448            0 :                     s << ",";
     449              :                 }
     450            6 :                 s << "\n";
     451              :             }
     452            6 :             s << "    ]\n";
     453            6 :             s << "  }";
     454            6 :             if (si < scopes.size() - 1) {
     455            0 :                 s << ",";
     456              :             }
     457            6 :             s << "\n";
     458              :         }
     459            6 :         s << "]";
     460            6 :         s.flush();
     461            6 :     }
     462              : 
     463            6 :     Ret ret;
     464              :     {
     465            6 :         mi::WriteResourceLockGuard guard(multiwindowsProvider(), COMMAND_SHORTCUTS_TAG);
     466            6 :         ret = io::File::writeFile(path, buf.data());
     467            6 :     }
     468              : 
     469            6 :     LOGD() << "write shortcuts to file: " << path;
     470              : 
     471            6 :     if (!ret) {
     472            0 :         LOGE() << ret.toString();
     473              :     }
     474              : 
     475            6 :     return ret;
     476           30 : }
     477              : 
     478           20 : const ShortcutList& CommandShortcutsRegister::shortcuts() const
     479              : {
     480           20 :     return m_shortcuts;
     481              : }
     482              : 
     483            7 : Ret CommandShortcutsRegister::updateShortcuts(const ShortcutList& shortcuts)
     484              : {
     485            8 :     TRACEFUNC;
     486              : 
     487           35 :     for (const Shortcut& sc : shortcuts) {
     488           28 :         auto it = std::find_if(m_shortcuts.begin(), m_shortcuts.end(), [&sc](const Shortcut& candidate) {
     489           70 :             return candidate.command == sc.command;
     490              :         });
     491           28 :         if (it != m_shortcuts.end()) {
     492           28 :             *it = sc;
     493              :         }
     494              :     }
     495              : 
     496            7 :     ShortcutList needToWrite = filterAndUpdateAdditionalShortcuts(m_shortcuts);
     497            7 :     ShortcutList diff = makeDiff(needToWrite, m_defaultShortcuts);
     498              : 
     499            7 :     bool ok = false;
     500            7 :     if (diff.empty()) {
     501            1 :         ok = removeUserFile();
     502              :     } else {
     503            6 :         ok = writeToFile(diff, userShortcutsPath());
     504              :     }
     505              : 
     506            7 :     if (ok) {
     507            7 :         m_shortcutsChanged.notify();
     508              :     }
     509              : 
     510           14 :     return ok;
     511            7 : }
     512              : 
     513            2 : void CommandShortcutsRegister::resetShortcuts()
     514              : {
     515              :     //! NOTE Even if the removal failed, reload to keep the state in sync with the file
     516            2 :     removeUserFile();
     517            2 :     reload();
     518            2 : }
     519              : 
     520            1 : Notification CommandShortcutsRegister::shortcutsChanged() const
     521              : {
     522            1 :     return m_shortcutsChanged;
     523              : }
     524              : 
     525            0 : Ret CommandShortcutsRegister::setAdditionalShortcuts(const std::string& context, const ShortcutList& shortcuts)
     526              : {
     527            0 :     m_additionalShortcutsMap[context] = shortcuts;
     528              : 
     529            0 :     mergeShortcuts(m_shortcuts, m_additionalShortcutsMap[context]);
     530            0 :     m_shortcutsChanged.notify();
     531              : 
     532            0 :     return muse::make_ok();
     533              : }
     534              : 
     535            0 : ShortcutList CommandShortcutsRegister::shortcutsForSequence(const std::string& sequence) const
     536              : {
     537            0 :     ShortcutList list;
     538            0 :     for (const Shortcut& sh : m_shortcuts) {
     539            0 :         auto it = std::find(sh.sequences.cbegin(), sh.sequences.cend(), sequence);
     540            0 :         if (it != sh.sequences.cend()) {
     541            0 :             list.push_back(sh);
     542              :         }
     543              :     }
     544            0 :     return list;
     545            0 : }
     546              : 
     547            0 : const Shortcut& CommandShortcutsRegister::shortcut(const Command& command) const
     548              : {
     549            0 :     return command::findShortcut(m_shortcuts, command);
     550              : }
     551              : 
     552            0 : const Shortcut& CommandShortcutsRegister::defaultShortcut(const Command& command) const
     553              : {
     554            0 :     return command::findShortcut(m_defaultShortcuts, command);
     555              : }
     556              : 
     557            0 : Ret CommandShortcutsRegister::importFromFile(const io::path_t& filePath)
     558              : {
     559            0 :     io::path_t userPath = userShortcutsPath();
     560            0 :     if (userPath.empty()) {
     561            0 :         LOGE() << "failed import file: invalid user shortcuts path";
     562            0 :         return make_ret(Ret::Code::UnknownError);
     563              :     }
     564              : 
     565              :     {
     566            0 :         mi::WriteResourceLockGuard guard(multiwindowsProvider(), COMMAND_SHORTCUTS_TAG);
     567            0 :         Ret ret = io::File::copy(filePath, userPath, true);
     568            0 :         if (!ret) {
     569            0 :             LOGE() << "failed import file: " << ret.toString();
     570            0 :             return ret;
     571              :         }
     572            0 :     }
     573              : 
     574            0 :     reload();
     575              : 
     576            0 :     return make_ret(Ret::Code::Ok);
     577            0 : }
     578              : 
     579            0 : Ret CommandShortcutsRegister::exportToFile(const io::path_t& filePath) const
     580              : {
     581            0 :     return writeToFile(m_shortcuts, filePath);
     582              : }
     583              : 
     584            0 : std::vector<std::string> CommandShortcutsRegister::availablePresets() const
     585              : {
     586            0 :     return configuration()->availableShortcutsPresets();
     587              : }
     588              : 
     589            1 : std::string CommandShortcutsRegister::currentPresetName() const
     590              : {
     591            1 :     return configuration()->currentShortcutsPresetName();
     592              : }
     593              : 
     594            1 : void CommandShortcutsRegister::setCurrentPresetName(const std::string& presetName)
     595              : {
     596            1 :     configuration()->setCurrentShortcutsPresetName(presetName);
     597            1 : }
     598              : 
     599            0 : async::Channel<std::string> CommandShortcutsRegister::currentPresetNameChanged() const
     600              : {
     601            0 :     return configuration()->currentShortcutsPresetNameChanged();
     602              : }
     603              : 
     604            3 : bool CommandShortcutsRegister::isPresetEdited(const std::string& presetName) const
     605              : {
     606            3 :     std::string name = presetName.empty() ? configuration()->defaultShortcutsName() : presetName;
     607            6 :     return io::File::exists(configuration()->commandShortcutsUserAppDataPath(name));
     608            3 : }
     609              : 
     610            5 : bool CommandShortcutsRegister::canDeletePreset(const std::string& presetName) const
     611              : {
     612            5 :     if (presetName.empty()) {
     613            1 :         return false;
     614              :     }
     615              : 
     616              :     //! NOTE Built-in presets cannot be deleted
     617            4 :     return !muse::contains(configuration()->availableShortcutsPresets(), presetName);
     618              : }
     619              : 
     620            2 : void CommandShortcutsRegister::deletePreset(const std::string& presetName)
     621              : {
     622            2 :     if (!canDeletePreset(presetName)) {
     623            1 :         return;
     624              :     }
     625              : 
     626            1 :     io::path_t path = configuration()->commandShortcutsUserAppDataPath(presetName);
     627            1 :     if (path.empty()) {
     628            0 :         return;
     629              :     }
     630              : 
     631            1 :     bool ok = false;
     632              :     {
     633            1 :         mi::WriteResourceLockGuard guard(multiwindowsProvider(), COMMAND_SHORTCUTS_TAG);
     634            1 :         ok = io::File::remove(path);
     635            1 :     }
     636              : 
     637            1 :     if (!ok) {
     638            0 :         LOGE() << "failed remove file: " << path;
     639            0 :         return;
     640              :     }
     641              : 
     642            1 :     if (currentPresetName() == presetName) {
     643            1 :         setCurrentPresetName(std::string());
     644              :     }
     645            1 : }
        

Generated by: LCOV version 2.0-1