Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-Studio-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2021 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 "projectconfiguration.h"
23 :
24 : #include <QJsonDocument>
25 : #include <QJsonObject>
26 : #include <QJsonValue>
27 : #include <QJsonArray>
28 :
29 : #include "settings.h"
30 :
31 : #include "engraving/infrastructure/mscio.h"
32 :
33 : #include "internal/notationproject.h"
34 : #include "types/projectmeta.h"
35 :
36 : #include "log.h"
37 :
38 : using namespace mu;
39 : using namespace mu::project;
40 : using namespace mu::notation;
41 : using namespace muse;
42 :
43 : static const std::string module_name("project");
44 :
45 : static const Settings::Key COMPAT_RECENT_FILES_DATA(module_name, "project/recentList");
46 : static const Settings::Key USER_TEMPLATES_PATH(module_name, "application/paths/myTemplates");
47 : static const Settings::Key LAST_OPENED_PROJECTS_PATH(module_name, "application/paths/lastOpenedProjectsPath");
48 : static const Settings::Key LAST_SAVED_PROJECTS_PATH(module_name, "application/paths/lastSavedProjectsPath");
49 : static const Settings::Key USER_PROJECTS_PATH(module_name, "application/paths/myScores");
50 : static const Settings::Key LAST_OPENED_CONVERT_FILE_PATH(module_name, "application/paths/lastOpenedConvertFilePath");
51 : static const Settings::Key SHOULD_ASK_SAVE_LOCATION_TYPE(module_name, "project/shouldAskSaveLocationType");
52 : static const Settings::Key LAST_USED_SAVE_LOCATION_TYPE(module_name, "project/lastUsedSaveLocationType");
53 : static const Settings::Key SHOULD_WARN_BEFORE_PUBLISH(module_name, "project/shouldWarnBeforePublish");
54 : static const Settings::Key SHOULD_WARN_BEFORE_SAVING_PUBLICLY_TO_CLOUD(module_name, "project/shouldWarnBeforeSavingPubliclyToCloud");
55 : static const Settings::Key HOME_SCORES_PAGE_TAB_INDEX(module_name, "project/homeScoresPageTabIndex");
56 : static const Settings::Key HOME_SCORES_PAGE_VIEW_TYPE(module_name, "project/homeScoresPageViewType");
57 : static const Settings::Key PREFERRED_SCORE_CREATION_MODE_KEY(module_name, "project/preferredScoreCreationMode");
58 : static const Settings::Key MIGRATION_OPTIONS(module_name, "project/migration");
59 : static const Settings::Key AUTOSAVE_ENABLED_KEY(module_name, "project/autoSaveEnabled");
60 : static const Settings::Key AUTOSAVE_INTERVAL_KEY(module_name, "project/autoSaveInterval");
61 : static const Settings::Key ALSO_SHARE_AUDIO_COM_AFTER_PUBLISH(module_name, "project/alsoShareAudioCom");
62 : static const Settings::Key SHOW_ALSO_SHARE_AUDIO_COM_DIALOG(module_name, "project/showAlsoShareAudioComDialog");
63 : static const Settings::Key HAS_ASKED_ALSO_SHARE_AUDIO_COM(module_name, "project/hasAskedAlsoShareAudioCom");
64 : static const Settings::Key SHOULD_DESTINATION_FOLDER_BE_OPENED_ON_EXPORT(module_name, "project/shouldDestinationFolderBeOpenedOnExport");
65 : static const Settings::Key OPEN_DETAILED_PROJECT_UPLOADED_DIALOG(module_name, "project/openDetailedProjectUploadedDialog");
66 : static const Settings::Key HAS_ASKED_AUDIO_GENERATION_SETTINGS(module_name, "project/hasAskedAudioGenerationSettings");
67 : static const Settings::Key GENERATE_AUDIO_TIME_PERIOD_TYPE_KEY(module_name, "project/generateAudioTimePeriodType");
68 : static const Settings::Key NUMBER_OF_SAVES_TO_GENERATE_AUDIO_KEY(module_name, "project/numberOfSavesToGenerateAudio");
69 : static const Settings::Key SHOW_CONVERT_FILE_PROCESSING_DIALOG(module_name, "project/showConvertFileProcessingDialog");
70 : static const Settings::Key SHOW_CLOUD_IS_NOT_AVAILABLE_WARNING(module_name, "project/showCloudIsNotAvailableWarning");
71 : static const Settings::Key DISABLE_VERSION_CHECKING(module_name, "project/disableVersionChecking");
72 : static const Settings::Key CREATE_BACKUP_BEFORE_SAVING(module_name, "project/createBackupBeforeSaving");
73 :
74 : static const std::string DEFAULT_FILE_SUFFIX(".mscz");
75 :
76 0 : ProjectConfiguration::ProjectConfiguration(const muse::modularity::ContextPtr& iocCtx)
77 0 : : muse::Contextable(iocCtx)
78 : {
79 0 : }
80 :
81 0 : void ProjectConfiguration::init()
82 : {
83 0 : settings()->setDefaultValue(USER_TEMPLATES_PATH, Val(globalConfiguration()->userDataPath() + "/Templates"));
84 0 : settings()->valueChanged(USER_TEMPLATES_PATH).onReceive(nullptr, [this](const Val& val) {
85 0 : m_userTemplatesPathChanged.send(val.toPath());
86 0 : });
87 :
88 0 : settings()->setDefaultValue(USER_PROJECTS_PATH, Val(globalConfiguration()->userDataPath() + "/Scores"));
89 :
90 0 : settings()->valueChanged(USER_PROJECTS_PATH).onReceive(nullptr, [this](const Val& val) {
91 0 : m_userScoresPathChanged.send(val.toPath());
92 0 : });
93 :
94 0 : Val preferredScoreCreationMode = Val(PreferredScoreCreationMode::FromInstruments);
95 0 : settings()->setDefaultValue(PREFERRED_SCORE_CREATION_MODE_KEY, preferredScoreCreationMode);
96 :
97 0 : settings()->setDefaultValue(HOME_SCORES_PAGE_TAB_INDEX, Val(0));
98 :
99 0 : settings()->setDefaultValue(HOME_SCORES_PAGE_VIEW_TYPE, Val(HomeScoresPageViewType::Grid));
100 :
101 0 : settings()->setDefaultValue(SHOULD_ASK_SAVE_LOCATION_TYPE, Val(true));
102 0 : settings()->setDefaultValue(LAST_USED_SAVE_LOCATION_TYPE, Val(SaveLocationType::Undefined));
103 :
104 0 : settings()->setDefaultValue(SHOULD_WARN_BEFORE_PUBLISH, Val(true));
105 0 : settings()->setDefaultValue(SHOULD_WARN_BEFORE_SAVING_PUBLICLY_TO_CLOUD, Val(true));
106 :
107 0 : settings()->setDefaultValue(AUTOSAVE_ENABLED_KEY, Val(true));
108 0 : settings()->valueChanged(AUTOSAVE_ENABLED_KEY).onReceive(nullptr, [this](const Val& val) {
109 0 : m_autoSaveEnabledChanged.send(val.toBool());
110 0 : });
111 :
112 0 : settings()->setDefaultValue(AUTOSAVE_INTERVAL_KEY, Val(5));
113 0 : settings()->valueChanged(AUTOSAVE_INTERVAL_KEY).onReceive(nullptr, [this](const Val& val) {
114 0 : m_autoSaveIntervalChanged.send(val.toInt());
115 0 : });
116 :
117 0 : settings()->setDefaultValue(ALSO_SHARE_AUDIO_COM_AFTER_PUBLISH, Val(true));
118 0 : settings()->valueChanged(ALSO_SHARE_AUDIO_COM_AFTER_PUBLISH).onReceive(nullptr, [this](const Val& val) {
119 0 : m_alsoShareAudioComChanged.send(val.toBool());
120 0 : });
121 :
122 0 : settings()->setDefaultValue(SHOW_ALSO_SHARE_AUDIO_COM_DIALOG, Val(true));
123 0 : settings()->setDefaultValue(HAS_ASKED_ALSO_SHARE_AUDIO_COM, Val(false));
124 :
125 0 : settings()->setDefaultValue(SHOULD_DESTINATION_FOLDER_BE_OPENED_ON_EXPORT, Val(false));
126 0 : settings()->setDefaultValue(OPEN_DETAILED_PROJECT_UPLOADED_DIALOG, Val(true));
127 0 : settings()->setDefaultValue(HAS_ASKED_AUDIO_GENERATION_SETTINGS, Val(false));
128 0 : settings()->setDefaultValue(GENERATE_AUDIO_TIME_PERIOD_TYPE_KEY, Val(static_cast<int>(GenerateAudioTimePeriodType::Never)));
129 0 : settings()->valueChanged(GENERATE_AUDIO_TIME_PERIOD_TYPE_KEY).onReceive(nullptr, [this](const Val& val) {
130 0 : m_generateAudioTimePeriodTypeChanged.send(val.toInt());
131 0 : });
132 0 : settings()->setDefaultValue(NUMBER_OF_SAVES_TO_GENERATE_AUDIO_KEY, Val(10));
133 0 : settings()->valueChanged(NUMBER_OF_SAVES_TO_GENERATE_AUDIO_KEY).onReceive(nullptr, [this](const Val& val) {
134 0 : m_numberOfSavesToGenerateAudioChanged.send(val.toInt());
135 0 : });
136 0 : settings()->setDefaultValue(SHOW_CONVERT_FILE_PROCESSING_DIALOG, Val(true));
137 :
138 0 : settings()->setDefaultValue(SHOW_CLOUD_IS_NOT_AVAILABLE_WARNING, Val(true));
139 :
140 0 : settings()->setDefaultValue(DISABLE_VERSION_CHECKING, Val(false));
141 :
142 0 : settings()->setDefaultValue(CREATE_BACKUP_BEFORE_SAVING, Val(true));
143 0 : settings()->setDescription(CREATE_BACKUP_BEFORE_SAVING, muse::trc("project",
144 : "Create backup of file on disk before saving new changes"));
145 0 : settings()->setCanBeManuallyEdited(CREATE_BACKUP_BEFORE_SAVING, true);
146 :
147 0 : if (!userTemplatesPath().empty()) {
148 0 : fileSystem()->makePath(userTemplatesPath());
149 : }
150 :
151 0 : if (!userProjectsPath().empty()) {
152 0 : fileSystem()->makePath(userProjectsPath());
153 : }
154 :
155 0 : fileSystem()->makePath(cloudProjectsPath());
156 0 : }
157 :
158 0 : muse::io::path_t ProjectConfiguration::recentFilesJsonPath() const
159 : {
160 0 : return globalConfiguration()->userAppDataPath().appendingComponent("recent_files.json");
161 : }
162 :
163 0 : ByteArray ProjectConfiguration::compatRecentFilesData() const
164 : {
165 0 : std::string data = settings()->value(COMPAT_RECENT_FILES_DATA).toString();
166 :
167 0 : return ByteArray(data.data(), data.size());
168 0 : }
169 :
170 0 : muse::io::path_t ProjectConfiguration::myFirstProjectPath() const
171 : {
172 0 : return appTemplatesPath() + "/My_First_Score.mscx";
173 : }
174 :
175 0 : muse::io::path_t ProjectConfiguration::appTemplatesPath() const
176 : {
177 0 : return globalConfiguration()->appDataPath() + "/templates";
178 : }
179 :
180 0 : io::paths_t ProjectConfiguration::availableTemplateDirs() const
181 : {
182 0 : io::paths_t dirs;
183 :
184 0 : muse::io::path_t defaultTemplatesPath = this->appTemplatesPath();
185 0 : dirs.push_back(defaultTemplatesPath);
186 :
187 0 : muse::io::path_t userTemplatesPath = this->userTemplatesPath();
188 0 : if (!userTemplatesPath.empty() && userTemplatesPath != defaultTemplatesPath) {
189 0 : dirs.push_back(userTemplatesPath);
190 : }
191 :
192 0 : return dirs;
193 0 : }
194 :
195 0 : muse::io::path_t ProjectConfiguration::templateCategoriesJsonPath(const muse::io::path_t& templatesDir) const
196 : {
197 0 : return templatesDir + "/categories.json";
198 : }
199 :
200 0 : muse::io::path_t ProjectConfiguration::userTemplatesPath() const
201 : {
202 0 : return settings()->value(USER_TEMPLATES_PATH).toPath();
203 : }
204 :
205 0 : void ProjectConfiguration::setUserTemplatesPath(const muse::io::path_t& path)
206 : {
207 0 : settings()->setSharedValue(USER_TEMPLATES_PATH, Val(path));
208 0 : }
209 :
210 0 : muse::async::Channel<muse::io::path_t> ProjectConfiguration::userTemplatesPathChanged() const
211 : {
212 0 : return m_userTemplatesPathChanged;
213 : }
214 :
215 0 : muse::io::path_t ProjectConfiguration::lastOpenedProjectsPath() const
216 : {
217 0 : return settings()->value(LAST_OPENED_PROJECTS_PATH).toPath();
218 : }
219 :
220 0 : void ProjectConfiguration::setLastOpenedProjectsPath(const muse::io::path_t& path)
221 : {
222 0 : settings()->setSharedValue(LAST_OPENED_PROJECTS_PATH, Val(path));
223 0 : }
224 :
225 0 : muse::io::path_t ProjectConfiguration::lastSavedProjectsPath() const
226 : {
227 0 : return settings()->value(LAST_SAVED_PROJECTS_PATH).toPath();
228 : }
229 :
230 0 : void ProjectConfiguration::setLastSavedProjectsPath(const muse::io::path_t& path)
231 : {
232 0 : settings()->setSharedValue(LAST_SAVED_PROJECTS_PATH, Val(path));
233 0 : }
234 :
235 0 : muse::io::path_t ProjectConfiguration::userProjectsPath() const
236 : {
237 0 : return settings()->value(USER_PROJECTS_PATH).toPath();
238 : }
239 :
240 0 : void ProjectConfiguration::setUserProjectsPath(const muse::io::path_t& path)
241 : {
242 0 : settings()->setSharedValue(USER_PROJECTS_PATH, Val(path));
243 0 : }
244 :
245 0 : muse::async::Channel<muse::io::path_t> ProjectConfiguration::userProjectsPathChanged() const
246 : {
247 0 : return m_userScoresPathChanged;
248 : }
249 :
250 0 : muse::io::path_t ProjectConfiguration::defaultUserProjectsPath() const
251 : {
252 0 : return settings()->defaultValue(USER_PROJECTS_PATH).toPath();
253 : }
254 :
255 0 : void ProjectConfiguration::setLastOpenedConvertFilePath(const muse::io::path_t& path)
256 : {
257 0 : settings()->setSharedValue(LAST_OPENED_CONVERT_FILE_PATH, Val(path));
258 0 : }
259 :
260 0 : muse::io::path_t ProjectConfiguration::defaultConvertFilePath() const
261 : {
262 0 : muse::io::path_t dir = settings()->value(LAST_OPENED_CONVERT_FILE_PATH).toPath();
263 0 : if (dir.empty()) {
264 0 : dir = globalConfiguration()->documentsPath();
265 : }
266 :
267 0 : return dir;
268 0 : }
269 :
270 0 : bool ProjectConfiguration::shouldAskSaveLocationType() const
271 : {
272 0 : return settings()->value(SHOULD_ASK_SAVE_LOCATION_TYPE).toBool();
273 : }
274 :
275 0 : void ProjectConfiguration::setShouldAskSaveLocationType(bool shouldAsk)
276 : {
277 0 : settings()->setSharedValue(SHOULD_ASK_SAVE_LOCATION_TYPE, Val(shouldAsk));
278 0 : }
279 :
280 0 : muse::io::path_t ProjectConfiguration::legacyCloudProjectsPath() const
281 : {
282 0 : return globalConfiguration()->userDataPath() + "/Cloud Scores";
283 : }
284 :
285 0 : muse::io::path_t ProjectConfiguration::cloudProjectsPath() const
286 : {
287 0 : return globalConfiguration()->userAppDataPath() + "/cloud_scores";
288 : }
289 :
290 0 : bool ProjectConfiguration::isCloudProject(const muse::io::path_t& projectPath) const
291 : {
292 0 : muse::io::path_t dirpath = io::dirpath(projectPath);
293 0 : return dirpath == legacyCloudProjectsPath() || dirpath == cloudProjectsPath();
294 0 : }
295 :
296 0 : bool ProjectConfiguration::isLegacyCloudProject(const muse::io::path_t& projectPath) const
297 : {
298 0 : return io::dirpath(projectPath) == legacyCloudProjectsPath();
299 : }
300 :
301 0 : muse::io::path_t ProjectConfiguration::cloudProjectPath(int scoreId) const
302 : {
303 0 : return cloudProjectsPath().appendingComponent(QString::number(scoreId)).appendingSuffix(DEFAULT_FILE_SUFFIX);
304 : }
305 :
306 0 : int ProjectConfiguration::cloudScoreIdFromPath(const muse::io::path_t& projectPath) const
307 : {
308 0 : return io::filename(projectPath, false).toQString().toInt();
309 : }
310 :
311 0 : muse::io::path_t ProjectConfiguration::cloudProjectSavingPath(int scoreId) const
312 : {
313 0 : if (scoreId != 0) {
314 0 : return cloudProjectPath(scoreId);
315 : }
316 :
317 0 : muse::io::path_t path;
318 0 : int counter = 0;
319 :
320 : do {
321 0 : path = cloudProjectsPath()
322 0 : .appendingComponent(QStringLiteral("not_uploaded_") + QString::number(counter++))
323 0 : .appendingSuffix(DEFAULT_FILE_SUFFIX);
324 0 : } while (fileSystem()->exists(path));
325 :
326 0 : return path;
327 0 : }
328 :
329 0 : muse::io::path_t ProjectConfiguration::defaultSavingFilePath(INotationProjectPtr project, const std::string& filenameAddition,
330 : const std::string& suffix) const
331 : {
332 0 : muse::io::path_t folderPath;
333 0 : muse::io::path_t filename;
334 0 : std::string theSuffix = suffix;
335 0 : std::string theFilenameAddition = filenameAddition;
336 :
337 0 : muse::io::path_t projectPath = project->path();
338 0 : bool isLocalProject = !project->isCloudProject();
339 :
340 0 : if (isLocalProject) {
341 0 : if (project->isNewlyCreated()) {
342 0 : if (io::isAbsolute(projectPath)) {
343 0 : filename = io::filename(projectPath, false);
344 0 : folderPath = io::dirpath(projectPath);
345 : } else {
346 0 : filename = io::filename(projectPath, true);
347 : }
348 : } else {
349 0 : projectPath = engraving::containerPath(projectPath);
350 0 : folderPath = io::dirpath(projectPath);
351 0 : filename = io::filename(projectPath, false);
352 :
353 0 : if (theSuffix.empty()) {
354 0 : theSuffix = io::suffix(projectPath);
355 : }
356 : }
357 : }
358 :
359 0 : if (folderPath.empty()) {
360 0 : folderPath = userProjectsPath();
361 : }
362 :
363 0 : if (folderPath.empty()) {
364 0 : folderPath = lastSavedProjectsPath();
365 : }
366 :
367 0 : if (folderPath.empty()) {
368 0 : folderPath = defaultUserProjectsPath();
369 : }
370 :
371 0 : if (filename.empty()) {
372 0 : filename = project->metaInfo().title;
373 : }
374 :
375 0 : if (filename.empty()) {
376 0 : filename = muse::qtrc("project", "Untitled");
377 : }
378 :
379 0 : if (theSuffix.empty()) {
380 0 : theSuffix = DEFAULT_FILE_SUFFIX;
381 : }
382 :
383 0 : if (project->isNewlyCreated() && filename.toQString() == NotationProject::scoreDefaultTitle()) {
384 0 : theFilenameAddition += uniqueFileNameAddition(filename + theFilenameAddition, folderPath, theSuffix);
385 : }
386 :
387 : return folderPath
388 0 : .appendingComponent(filename + theFilenameAddition)
389 0 : .appendingSuffix(theSuffix);
390 0 : }
391 :
392 0 : std::string ProjectConfiguration::uniqueFileNameAddition(const io::path_t& filename, const io::path_t& folderPath,
393 : const std::string& suffix) const
394 : {
395 : // Return a filename addition which would make filename unique if it wasn't already
396 0 : const std::string theSuffix = suffix.empty() ? DEFAULT_FILE_SUFFIX : suffix;
397 0 : std::string addition;
398 0 : int id = 1;
399 0 : while (fileSystem()->exists(folderPath
400 0 : .appendingComponent(filename + addition)
401 0 : .appendingSuffix(theSuffix))) {
402 0 : addition = " (" + std::to_string(id) + ")";
403 0 : id++;
404 : }
405 0 : return addition;
406 0 : }
407 :
408 0 : SaveLocationType ProjectConfiguration::lastUsedSaveLocationType() const
409 : {
410 0 : return settings()->value(LAST_USED_SAVE_LOCATION_TYPE).toEnum<SaveLocationType>();
411 : }
412 :
413 0 : void ProjectConfiguration::setLastUsedSaveLocationType(SaveLocationType type)
414 : {
415 0 : settings()->setSharedValue(LAST_USED_SAVE_LOCATION_TYPE, Val(type));
416 0 : }
417 :
418 0 : bool ProjectConfiguration::shouldWarnBeforePublish() const
419 : {
420 0 : return settings()->value(SHOULD_WARN_BEFORE_PUBLISH).toBool();
421 : }
422 :
423 0 : void ProjectConfiguration::setShouldWarnBeforePublish(bool shouldWarn)
424 : {
425 0 : return settings()->setSharedValue(SHOULD_WARN_BEFORE_PUBLISH, Val(shouldWarn));
426 : }
427 :
428 0 : bool ProjectConfiguration::shouldWarnBeforeSavingPubliclyToCloud() const
429 : {
430 0 : return settings()->value(SHOULD_WARN_BEFORE_SAVING_PUBLICLY_TO_CLOUD).toBool();
431 : }
432 :
433 0 : void ProjectConfiguration::setShouldWarnBeforeSavingPubliclyToCloud(bool shouldWarn)
434 : {
435 0 : settings()->setSharedValue(SHOULD_WARN_BEFORE_SAVING_PUBLICLY_TO_CLOUD, Val(shouldWarn));
436 0 : }
437 :
438 0 : int ProjectConfiguration::homeScoresPageTabIndex() const
439 : {
440 0 : return settings()->value(HOME_SCORES_PAGE_TAB_INDEX).toInt();
441 : }
442 :
443 0 : void ProjectConfiguration::setHomeScoresPageTabIndex(int index)
444 : {
445 : // Intentionally not directly synced between instances
446 : // (it would be weird if you switch the tab in one instance, and the others suddenly switch too)
447 0 : settings()->setLocalValue(HOME_SCORES_PAGE_TAB_INDEX, Val(index));
448 0 : }
449 :
450 0 : IProjectConfiguration::HomeScoresPageViewType ProjectConfiguration::homeScoresPageViewType() const
451 : {
452 0 : return settings()->value(HOME_SCORES_PAGE_VIEW_TYPE).toEnum<HomeScoresPageViewType>();
453 : }
454 :
455 0 : void ProjectConfiguration::setHomeScoresPageViewType(HomeScoresPageViewType type)
456 : {
457 : // Intentionally not directly synced between instances
458 : // (it would be weird if you switch the view in one instance, and the others suddenly switch too)
459 0 : settings()->setLocalValue(HOME_SCORES_PAGE_VIEW_TYPE, Val(type));
460 0 : }
461 :
462 0 : QColor ProjectConfiguration::templatePreviewBackgroundColor() const
463 : {
464 0 : return notationConfiguration()->backgroundColor();
465 : }
466 :
467 0 : muse::async::Notification ProjectConfiguration::templatePreviewBackgroundChanged() const
468 : {
469 0 : return notationConfiguration()->backgroundChanged();
470 : }
471 :
472 0 : ProjectConfiguration::PreferredScoreCreationMode ProjectConfiguration::preferredScoreCreationMode() const
473 : {
474 0 : return settings()->value(PREFERRED_SCORE_CREATION_MODE_KEY).toEnum<PreferredScoreCreationMode>();
475 : }
476 :
477 0 : void ProjectConfiguration::setPreferredScoreCreationMode(PreferredScoreCreationMode mode)
478 : {
479 0 : settings()->setSharedValue(PREFERRED_SCORE_CREATION_MODE_KEY, Val(mode));
480 0 : }
481 :
482 0 : static MigrationType migrationTypeFromString(const QString& str)
483 : {
484 0 : if (str == "Pre_3_6") {
485 0 : return MigrationType::Pre_3_6;
486 : }
487 0 : if (str == "Ver_3_6") {
488 0 : return MigrationType::Ver_3_6;
489 : }
490 0 : LOGE() << "Unknown migration type string: " << str;
491 0 : return MigrationType::Unknown;
492 : }
493 :
494 0 : MigrationOptions ProjectConfiguration::migrationOptions(MigrationType type) const
495 : {
496 0 : auto it = m_migrationOptions.find(type);
497 0 : if (it != m_migrationOptions.end()) {
498 0 : return it->second;
499 : }
500 :
501 0 : QString json = settings()->value(MIGRATION_OPTIONS).toQString();
502 0 : QJsonArray array = QJsonDocument::fromJson(json.toUtf8()).array();
503 :
504 0 : MigrationOptions result;
505 :
506 0 : for (const auto val : array) {
507 0 : QJsonObject obj = val.toObject();
508 :
509 0 : auto migrationType = migrationTypeFromString(obj["migrationType"].toString());
510 0 : if (migrationType == MigrationType::Unknown) {
511 0 : continue;
512 : }
513 :
514 0 : QJsonObject optionsObj = obj["options"].toObject();
515 :
516 0 : MigrationOptions opt;
517 0 : opt.appVersion = optionsObj["appVersion"].toInt(0);
518 0 : opt.isApplyMigration = optionsObj["isApplyMigration"].toBool();
519 0 : opt.isAskAgain = optionsObj["isAskAgain"].toBool();
520 0 : opt.isApplyLeland = optionsObj["isApplyLeland"].toBool();
521 0 : opt.isApplyEdwin = optionsObj["isApplyEdwin"].toBool();
522 0 : opt.isRemapPercussion = optionsObj["isRemapPercussion"].toBool();
523 :
524 0 : m_migrationOptions[migrationType] = opt;
525 :
526 0 : if (migrationType == type) {
527 0 : result = opt;
528 : }
529 0 : }
530 :
531 0 : return result;
532 0 : }
533 :
534 0 : static QString migrationTypeToString(MigrationType type)
535 : {
536 0 : switch (type) {
537 0 : case MigrationType::Pre_3_6:
538 0 : return QStringLiteral("Pre_3_6");
539 0 : case MigrationType::Ver_3_6:
540 0 : return QStringLiteral("Ver_3_6");
541 0 : case MigrationType::Unknown:
542 0 : break;
543 : }
544 0 : return QString();
545 : }
546 :
547 0 : void ProjectConfiguration::setMigrationOptions(MigrationType type, const MigrationOptions& opt, bool persistent)
548 : {
549 0 : m_migrationOptions[type] = opt;
550 :
551 0 : if (!persistent) {
552 0 : return;
553 : }
554 :
555 0 : QJsonArray array;
556 :
557 0 : for (auto it = m_migrationOptions.cbegin(); it != m_migrationOptions.cend(); ++it) {
558 0 : const MigrationOptions& o = it->second;
559 :
560 0 : QJsonObject options;
561 0 : options["appVersion"] = o.appVersion;
562 0 : options["isApplyMigration"] = o.isApplyMigration;
563 0 : options["isAskAgain"] = o.isAskAgain;
564 0 : options["isApplyLeland"] = o.isApplyLeland;
565 0 : options["isApplyEdwin"] = o.isApplyEdwin;
566 0 : options["isRemapPercussion"] = o.isRemapPercussion;
567 :
568 0 : QJsonObject obj;
569 0 : obj["migrationType"] = migrationTypeToString(it->first);
570 0 : obj["options"] = options;
571 :
572 0 : array << obj;
573 0 : }
574 :
575 0 : QString json = QJsonDocument(array).toJson(QJsonDocument::Compact);
576 0 : settings()->setSharedValue(MIGRATION_OPTIONS, Val(json));
577 0 : }
578 :
579 0 : bool ProjectConfiguration::isAutoSaveEnabled() const
580 : {
581 0 : return settings()->value(AUTOSAVE_ENABLED_KEY).toBool();
582 : }
583 :
584 0 : void ProjectConfiguration::setAutoSaveEnabled(bool enabled)
585 : {
586 0 : settings()->setSharedValue(AUTOSAVE_ENABLED_KEY, Val(enabled));
587 0 : }
588 :
589 0 : muse::async::Channel<bool> ProjectConfiguration::autoSaveEnabledChanged() const
590 : {
591 0 : return m_autoSaveEnabledChanged;
592 : }
593 :
594 0 : int ProjectConfiguration::autoSaveIntervalMinutes() const
595 : {
596 0 : return settings()->value(AUTOSAVE_INTERVAL_KEY).toInt();
597 : }
598 :
599 0 : void ProjectConfiguration::setAutoSaveInterval(int minutes)
600 : {
601 0 : settings()->setSharedValue(AUTOSAVE_INTERVAL_KEY, Val(minutes));
602 0 : }
603 :
604 0 : muse::async::Channel<int> ProjectConfiguration::autoSaveIntervalChanged() const
605 : {
606 0 : return m_autoSaveIntervalChanged;
607 : }
608 :
609 0 : bool ProjectConfiguration::alsoShareAudioCom() const
610 : {
611 0 : return settings()->value(ALSO_SHARE_AUDIO_COM_AFTER_PUBLISH).toBool();
612 : }
613 :
614 0 : void ProjectConfiguration::setAlsoShareAudioCom(bool share)
615 : {
616 0 : settings()->setSharedValue(ALSO_SHARE_AUDIO_COM_AFTER_PUBLISH, Val(share));
617 0 : }
618 :
619 0 : muse::async::Channel<bool> ProjectConfiguration::alsoShareAudioComChanged() const
620 : {
621 0 : return m_alsoShareAudioComChanged;
622 : }
623 :
624 0 : bool ProjectConfiguration::showAlsoShareAudioComDialog() const
625 : {
626 0 : return settings()->value(SHOW_ALSO_SHARE_AUDIO_COM_DIALOG).toBool();
627 : }
628 :
629 0 : void ProjectConfiguration::setShowAlsoShareAudioComDialog(bool show)
630 : {
631 0 : settings()->setSharedValue(SHOW_ALSO_SHARE_AUDIO_COM_DIALOG, Val(show));
632 0 : }
633 :
634 0 : bool ProjectConfiguration::hasAskedAlsoShareAudioCom() const
635 : {
636 0 : return settings()->value(HAS_ASKED_ALSO_SHARE_AUDIO_COM).toBool();
637 : }
638 :
639 0 : void ProjectConfiguration::setHasAskedAlsoShareAudioCom(bool has)
640 : {
641 0 : settings()->setSharedValue(HAS_ASKED_ALSO_SHARE_AUDIO_COM, Val(has));
642 0 : }
643 :
644 0 : muse::io::path_t ProjectConfiguration::newProjectTemporaryPath() const
645 : {
646 0 : return globalConfiguration()->userAppDataPath() + "/new_project" + DEFAULT_FILE_SUFFIX;
647 : }
648 :
649 0 : bool ProjectConfiguration::shouldDestinationFolderBeOpenedOnExport() const
650 : {
651 0 : return settings()->value(SHOULD_DESTINATION_FOLDER_BE_OPENED_ON_EXPORT).toBool();
652 : }
653 :
654 0 : void ProjectConfiguration::setShouldDestinationFolderBeOpenedOnExport(bool shouldDestinationFolderBeOpenedOnExport)
655 : {
656 0 : settings()->setSharedValue(SHOULD_DESTINATION_FOLDER_BE_OPENED_ON_EXPORT, Val(shouldDestinationFolderBeOpenedOnExport));
657 0 : }
658 :
659 0 : QUrl ProjectConfiguration::supportForumUrl() const
660 : {
661 0 : if (languagesService()->currentLanguage().code.startsWith("en")) {
662 : // The English support forum
663 0 : return QUrl("https://musescore.org/forum/6");
664 : }
665 :
666 : // The general forum page, where the support forum is linked at the top
667 : // (except in English; there you have the Announcements forum)
668 0 : return QUrl("https://musescore.org/forum");
669 : }
670 :
671 0 : QUrl ProjectConfiguration::dotComBugReportUrl() const
672 : {
673 : // The general .com bug report page
674 0 : return QUrl("https://musescore.com/groups/bug-reports");
675 : }
676 :
677 0 : QUrl ProjectConfiguration::scoreUploadingGuidelinesUrl() const
678 : {
679 0 : return QUrl("https://musescore.com/score-uploading-guidelines");
680 : }
681 :
682 0 : QUrl ProjectConfiguration::termsOfUseUrl() const
683 : {
684 : return QUrl("https://musescore.com/redirect/af?mu_target=https%3A%2F%2Fmusescore.com%2Flegal%2Fterms"
685 : "%3Futm_medium%3DReferral%26utm_source%3Dmss%26utm_campaign%3DMU_WW_REF_MSS_DESKTOP_ALL_261026_crosslink"
686 : "%26utm_content%3Dconvert_to_score"
687 0 : "&utm_medium=Referral&utm_source=mss&utm_campaign=MU_WW_REF_MSS_DESKTOP_ALL_261026_crosslink&utm_content=convert_to_score");
688 : }
689 :
690 0 : bool ProjectConfiguration::openDetailedProjectUploadedDialog() const
691 : {
692 0 : return settings()->value(OPEN_DETAILED_PROJECT_UPLOADED_DIALOG).toBool();
693 : }
694 :
695 0 : void ProjectConfiguration::setOpenDetailedProjectUploadedDialog(bool show)
696 : {
697 0 : settings()->setSharedValue(OPEN_DETAILED_PROJECT_UPLOADED_DIALOG, Val(show));
698 0 : }
699 :
700 0 : bool ProjectConfiguration::hasAskedAudioGenerationSettings() const
701 : {
702 0 : return settings()->value(HAS_ASKED_AUDIO_GENERATION_SETTINGS).toBool();
703 : }
704 :
705 0 : void ProjectConfiguration::setHasAskedAudioGenerationSettings(bool has)
706 : {
707 0 : settings()->setSharedValue(HAS_ASKED_AUDIO_GENERATION_SETTINGS, Val(has));
708 0 : }
709 :
710 0 : GenerateAudioTimePeriodType ProjectConfiguration::generateAudioTimePeriodType() const
711 : {
712 0 : return static_cast<GenerateAudioTimePeriodType>(settings()->value(GENERATE_AUDIO_TIME_PERIOD_TYPE_KEY).toInt());
713 : }
714 :
715 0 : void ProjectConfiguration::setGenerateAudioTimePeriodType(GenerateAudioTimePeriodType type)
716 : {
717 0 : settings()->setSharedValue(GENERATE_AUDIO_TIME_PERIOD_TYPE_KEY, Val(static_cast<int>(type)));
718 0 : }
719 :
720 0 : muse::async::Channel<int> ProjectConfiguration::generateAudioTimePeriodTypeChanged() const
721 : {
722 0 : return m_generateAudioTimePeriodTypeChanged;
723 : }
724 :
725 0 : int ProjectConfiguration::numberOfSavesToGenerateAudio() const
726 : {
727 0 : return settings()->value(NUMBER_OF_SAVES_TO_GENERATE_AUDIO_KEY).toInt();
728 : }
729 :
730 0 : void ProjectConfiguration::setNumberOfSavesToGenerateAudio(int number)
731 : {
732 0 : settings()->setSharedValue(NUMBER_OF_SAVES_TO_GENERATE_AUDIO_KEY, Val(number));
733 0 : }
734 :
735 0 : muse::async::Channel<int> ProjectConfiguration::numberOfSavesToGenerateAudioChanged() const
736 : {
737 0 : return m_numberOfSavesToGenerateAudioChanged;
738 : }
739 :
740 0 : muse::io::path_t ProjectConfiguration::temporaryMp3FilePathTemplate() const
741 : {
742 0 : return globalConfiguration()->userAppDataPath() + "/audioFile_XXXXXX.mp3";
743 : }
744 :
745 0 : muse::io::path_t ProjectConfiguration::projectBackupPath(const muse::io::path_t& projectPath) const
746 : {
747 0 : muse::io::path_t projectDir = io::absoluteDirpath(projectPath);
748 0 : muse::io::path_t projectName = io::filename(projectPath);
749 :
750 0 : return projectDir + "/.mscbackup/." + projectName + "~";
751 0 : }
752 :
753 0 : bool ProjectConfiguration::showCloudIsNotAvailableWarning() const
754 : {
755 0 : return settings()->value(SHOW_CLOUD_IS_NOT_AVAILABLE_WARNING).toBool();
756 : }
757 :
758 0 : void ProjectConfiguration::setShowCloudIsNotAvailableWarning(bool show)
759 : {
760 0 : settings()->setSharedValue(SHOW_CLOUD_IS_NOT_AVAILABLE_WARNING, Val(show));
761 0 : }
762 :
763 0 : bool ProjectConfiguration::createBackupBeforeSaving() const
764 : {
765 0 : return settings()->value(CREATE_BACKUP_BEFORE_SAVING).toBool();
766 : }
767 :
768 0 : void ProjectConfiguration::setCreateBackupBeforeSaving(bool create)
769 : {
770 0 : settings()->setSharedValue(CREATE_BACKUP_BEFORE_SAVING, Val(create));
771 0 : }
772 :
773 0 : bool ProjectConfiguration::disableVersionChecking() const
774 : {
775 0 : return settings()->value(DISABLE_VERSION_CHECKING).toBool();
776 : }
777 :
778 0 : void ProjectConfiguration::setDisableVersionChecking(bool disable)
779 : {
780 0 : settings()->setSharedValue(DISABLE_VERSION_CHECKING, Val(disable));
781 0 : }
782 :
783 0 : bool ProjectConfiguration::showConvertFileProcessingDialog() const
784 : {
785 0 : return settings()->value(SHOW_CONVERT_FILE_PROCESSING_DIALOG).toBool();
786 : }
787 :
788 0 : void ProjectConfiguration::setShowConvertFileProcessingDialog(bool show)
789 : {
790 0 : settings()->setSharedValue(SHOW_CONVERT_FILE_PROCESSING_DIALOG, Val(show));
791 0 : }
792 :
793 0 : muse::io::path_t ProjectConfiguration::watchedConvertsJsonPath() const
794 : {
795 0 : return globalConfiguration()->userAppDataPath().appendingComponent("watched_converts.json");
796 : }
|