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 : }
|