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 "appshellconfiguration.h"
23 :
24 : #include <QJsonArray>
25 : #include <QJsonDocument>
26 :
27 : #include "settings.h"
28 :
29 : #include "multiwindows/resourcelockguard.h"
30 :
31 : #include "log.h"
32 :
33 : using namespace muse;
34 : using namespace mu;
35 : using namespace mu::appshell;
36 : using namespace mu::notation;
37 :
38 : static const std::string module_name("appshell");
39 :
40 : static const Settings::Key HAS_COMPLETED_FIRST_LAUNCH_SETUP(module_name, "application/hasCompletedFirstLaunchSetup");
41 :
42 : static const Settings::Key WELCOME_DIALOG_SHOW_ON_STARTUP_KEY(module_name, "application/welcomeDialogShowOnStartup");
43 : static const Settings::Key WELCOME_DIALOG_LAST_SHOWN_VERSION_KEY(module_name, "application/welcomeDialogLastShownVersion");
44 : static const Settings::Key WELCOME_DIALOG_LAST_SHOWN_INDEX(module_name, "application/welcomeDialogLastShownIndex");
45 :
46 : static const Settings::Key STARTUP_MODE_TYPE(module_name, "application/startup/modeStart");
47 : static const Settings::Key STARTUP_SCORE_PATH(module_name, "application/startup/startScore");
48 :
49 : static const std::string MUSESCORE_ONLINE_HANDBOOK_URL("https://handbook.musescore.org");
50 :
51 : static const std::string MUSESCORE_ASK_FOR_HELP_URL_PATH("/redirect/post/question");
52 : static const std::string MUSESCORE_ACCESSIBILITY_STATEMENT_URL_PATH("/about/musescore-studio-accessibility-statement");
53 : static const std::string MUSESCORE_FORUM_URL_PATH("/forum");
54 : static const std::string MUSESCORE_CONTRIBUTE_URL_PATH("/contribute");
55 : static const std::string MUSEHUB_FREE_MUSE_SOUNDS_URL("https://www.musehub.com/free-musesounds"
56 : "?utm_source=mss-app-dialog-ms-free"
57 : "&utm_medium=mss-app-dialog-ms-free"
58 : "&utm_campaign=mss-app-dialog-ms-free"
59 : "&utm_id=mss-app-dialog");
60 : static const std::string MUSICXML_URL("https://w3.org");
61 : static const std::string MUSICXML_LICENSE_URL(MUSICXML_URL + "/community/about/process/final/");
62 : static const std::string MUSICXML_LICENSE_DEED_URL(MUSICXML_URL + "/community/about/process/fsa-deed/");
63 :
64 : static const std::string UTM_MEDIUM_MENU("menu");
65 :
66 : static const Settings::Key SPLASH_SCREEN_VISIBLE_KEY(module_name, "ui/application/startup/showSplashScreen");
67 :
68 : static const muse::io::path_t SESSION_FILE("/session.json");
69 : static const std::string SESSION_RESOURCE_NAME("SESSION");
70 :
71 0 : void AppShellConfiguration::init()
72 : {
73 0 : settings()->setDefaultValue(HAS_COMPLETED_FIRST_LAUNCH_SETUP, Val(false));
74 :
75 0 : settings()->setDefaultValue(WELCOME_DIALOG_SHOW_ON_STARTUP_KEY, Val(true));
76 0 : settings()->valueChanged(WELCOME_DIALOG_SHOW_ON_STARTUP_KEY).onReceive(this, [this](const Val&) {
77 0 : m_welcomeDialogShowOnStartupChanged.notify();
78 0 : });
79 :
80 0 : settings()->setDefaultValue(WELCOME_DIALOG_LAST_SHOWN_VERSION_KEY, Val("0.0.0"));
81 0 : settings()->setDefaultValue(WELCOME_DIALOG_LAST_SHOWN_INDEX, Val(-1));
82 :
83 0 : settings()->setDefaultValue(STARTUP_MODE_TYPE, Val(StartupModeType::StartEmpty));
84 0 : settings()->valueChanged(STARTUP_MODE_TYPE).onReceive(this, [this](const Val&) {
85 0 : m_startupModeTypeChanged.notify();
86 0 : });
87 :
88 0 : settings()->setDefaultValue(STARTUP_SCORE_PATH, Val(projectConfiguration()->myFirstProjectPath().toStdString()));
89 0 : settings()->valueChanged(STARTUP_SCORE_PATH).onReceive(this, [this](const Val&) {
90 0 : m_startupScorePathChanged.notify();
91 0 : });
92 :
93 0 : fileSystem()->makePath(sessionDataPath());
94 0 : }
95 :
96 0 : bool AppShellConfiguration::hasCompletedFirstLaunchSetup() const
97 : {
98 : #ifdef Q_OS_WASM
99 : return true;
100 : #else
101 0 : return settings()->value(HAS_COMPLETED_FIRST_LAUNCH_SETUP).toBool();
102 : #endif
103 : }
104 :
105 0 : void AppShellConfiguration::setHasCompletedFirstLaunchSetup(bool has)
106 : {
107 0 : settings()->setSharedValue(HAS_COMPLETED_FIRST_LAUNCH_SETUP, Val(has));
108 0 : }
109 :
110 0 : bool AppShellConfiguration::welcomeDialogShowOnStartup() const
111 : {
112 0 : return settings()->value(WELCOME_DIALOG_SHOW_ON_STARTUP_KEY).toBool();
113 : }
114 :
115 0 : void AppShellConfiguration::setWelcomeDialogShowOnStartup(bool show)
116 : {
117 0 : settings()->setSharedValue(WELCOME_DIALOG_SHOW_ON_STARTUP_KEY, Val(show));
118 0 : }
119 :
120 0 : async::Notification AppShellConfiguration::welcomeDialogShowOnStartupChanged() const
121 : {
122 0 : return m_welcomeDialogShowOnStartupChanged;
123 : }
124 :
125 0 : std::string AppShellConfiguration::welcomeDialogLastShownVersion() const
126 : {
127 0 : return settings()->value(WELCOME_DIALOG_LAST_SHOWN_VERSION_KEY).toString();
128 : }
129 :
130 0 : void AppShellConfiguration::setWelcomeDialogLastShownVersion(const std::string& version)
131 : {
132 0 : settings()->setSharedValue(WELCOME_DIALOG_LAST_SHOWN_VERSION_KEY, Val(version));
133 0 : }
134 :
135 0 : int AppShellConfiguration::welcomeDialogLastShownIndex() const
136 : {
137 0 : return settings()->value(WELCOME_DIALOG_LAST_SHOWN_INDEX).toInt();
138 : }
139 :
140 0 : void AppShellConfiguration::setWelcomeDialogLastShownIndex(int index)
141 : {
142 0 : settings()->setSharedValue(WELCOME_DIALOG_LAST_SHOWN_INDEX, Val(index));
143 0 : }
144 :
145 0 : StartupModeType AppShellConfiguration::startupModeType() const
146 : {
147 0 : return settings()->value(STARTUP_MODE_TYPE).toEnum<StartupModeType>();
148 : }
149 :
150 0 : void AppShellConfiguration::setStartupModeType(StartupModeType type)
151 : {
152 0 : settings()->setSharedValue(STARTUP_MODE_TYPE, Val(type));
153 0 : }
154 :
155 0 : async::Notification AppShellConfiguration::startupModeTypeChanged() const
156 : {
157 0 : return m_startupModeTypeChanged;
158 : }
159 :
160 0 : muse::io::path_t AppShellConfiguration::startupScorePath() const
161 : {
162 0 : return settings()->value(STARTUP_SCORE_PATH).toString();
163 : }
164 :
165 0 : void AppShellConfiguration::setStartupScorePath(const muse::io::path_t& scorePath)
166 : {
167 0 : settings()->setSharedValue(STARTUP_SCORE_PATH, Val(scorePath.toStdString()));
168 0 : }
169 :
170 0 : async::Notification AppShellConfiguration::startupScorePathChanged() const
171 : {
172 0 : return m_startupScorePathChanged;
173 : }
174 :
175 0 : std::string AppShellConfiguration::handbookUrl() const
176 : {
177 0 : std::string utm = utmParameters(UTM_MEDIUM_MENU);
178 0 : std::string languageCode = currentLanguageCode();
179 :
180 : QStringList params = {
181 : "tag=handbook",
182 0 : "locale=" + QString::fromStdString(languageCode),
183 : QString::fromStdString(utm)
184 0 : };
185 :
186 0 : return MUSESCORE_ONLINE_HANDBOOK_URL + "?" + params.join("&").toStdString();
187 0 : }
188 :
189 0 : std::string AppShellConfiguration::askForHelpUrl() const
190 : {
191 0 : std::string languageCode = currentLanguageCode();
192 :
193 : QStringList params = {
194 0 : "locale=" + QString::fromStdString(languageCode)
195 0 : };
196 :
197 0 : return museScoreUrl() + MUSESCORE_ASK_FOR_HELP_URL_PATH + "?" + params.join("&").toStdString();
198 0 : }
199 :
200 0 : std::string AppShellConfiguration::accessibilityStatementUrl() const
201 : {
202 0 : return museScoreUrl() + MUSESCORE_ACCESSIBILITY_STATEMENT_URL_PATH;
203 : }
204 :
205 0 : std::string AppShellConfiguration::museScoreUrl() const
206 : {
207 0 : return updateConfiguration()->appWebSiteUrl();
208 : }
209 :
210 0 : std::string AppShellConfiguration::museScoreForumUrl() const
211 : {
212 0 : return museScoreUrl() + MUSESCORE_FORUM_URL_PATH;
213 : }
214 :
215 0 : std::string AppShellConfiguration::museScoreContributionUrl() const
216 : {
217 0 : return museScoreUrl() + MUSESCORE_CONTRIBUTE_URL_PATH;
218 : }
219 :
220 0 : std::string AppShellConfiguration::museHubFreeMuseSoundsUrl() const
221 : {
222 0 : return MUSEHUB_FREE_MUSE_SOUNDS_URL;
223 : }
224 :
225 0 : std::string AppShellConfiguration::musicXMLLicenseUrl() const
226 : {
227 0 : return MUSICXML_LICENSE_URL;
228 : }
229 :
230 0 : std::string AppShellConfiguration::musicXMLLicenseDeedUrl() const
231 : {
232 0 : return MUSICXML_LICENSE_DEED_URL;
233 : }
234 :
235 0 : std::string AppShellConfiguration::museScoreVersion() const
236 : {
237 0 : return String(application()->version().toString() + u"." + application()->build()).toStdString();
238 : }
239 :
240 0 : std::string AppShellConfiguration::museScoreRevision() const
241 : {
242 0 : return application()->revision().toStdString();
243 : }
244 :
245 0 : bool AppShellConfiguration::needShowSplashScreen() const
246 : {
247 0 : return settings()->value(SPLASH_SCREEN_VISIBLE_KEY).toBool();
248 : }
249 :
250 0 : void AppShellConfiguration::setNeedShowSplashScreen(bool show)
251 : {
252 0 : settings()->setSharedValue(SPLASH_SCREEN_VISIBLE_KEY, Val(show));
253 0 : }
254 :
255 0 : const QString& AppShellConfiguration::preferencesDialogLastOpenedPageId() const
256 : {
257 0 : return m_preferencesDialogCurrentPageId;
258 : }
259 :
260 0 : void AppShellConfiguration::setPreferencesDialogLastOpenedPageId(const QString& lastOpenedPageId)
261 : {
262 0 : m_preferencesDialogCurrentPageId = lastOpenedPageId;
263 0 : }
264 :
265 0 : void AppShellConfiguration::startEditSettings()
266 : {
267 0 : settings()->beginTransaction();
268 0 : }
269 :
270 0 : void AppShellConfiguration::applySettings()
271 : {
272 0 : settings()->commitTransaction();
273 0 : }
274 :
275 0 : void AppShellConfiguration::rollbackSettings()
276 : {
277 0 : settings()->rollbackTransaction();
278 0 : }
279 :
280 0 : void AppShellConfiguration::revertToFactorySettings(bool keepDefaultSettings, bool notifyAboutChanges, bool notifyOtherInstances) const
281 : {
282 0 : settings()->reset(keepDefaultSettings, notifyAboutChanges, notifyOtherInstances);
283 0 : }
284 :
285 0 : muse::io::paths_t AppShellConfiguration::sessionProjectsPaths() const
286 : {
287 0 : RetVal<ByteArray> retVal = readSessionState();
288 0 : if (!retVal.ret) {
289 0 : LOGE() << retVal.ret.toString();
290 0 : return {};
291 : }
292 :
293 0 : return parseSessionProjectsPaths(retVal.val.toQByteArrayNoCopy());
294 0 : }
295 :
296 0 : muse::Ret AppShellConfiguration::setSessionProjectsPaths(const muse::io::paths_t& paths)
297 : {
298 0 : QJsonArray jsonArray;
299 0 : for (const muse::io::path_t& path : paths) {
300 0 : jsonArray << QJsonValue(path.toQString());
301 : }
302 :
303 0 : QByteArray data = QJsonDocument(jsonArray).toJson();
304 0 : return writeSessionState(data);
305 0 : }
306 :
307 0 : std::string AppShellConfiguration::utmParameters(const std::string& utmMedium) const
308 : {
309 0 : return "utm_source=desktop&utm_medium=" + utmMedium
310 0 : + "&utm_content=" + application()->revision().toStdString()
311 0 : + "&utm_campaign=MuseScore" + application()->version().toStdString();
312 : }
313 :
314 0 : std::string AppShellConfiguration::currentLanguageCode() const
315 : {
316 0 : QString languageCode = languagesConfiguration()->currentLanguageCode().val;
317 0 : QLocale locale(languageCode);
318 :
319 0 : return locale.bcp47Name().toStdString();
320 0 : }
321 :
322 0 : muse::io::path_t AppShellConfiguration::sessionDataPath() const
323 : {
324 0 : return globalConfiguration()->userAppDataPath() + "/session";
325 : }
326 :
327 0 : muse::io::path_t AppShellConfiguration::sessionFilePath() const
328 : {
329 0 : return sessionDataPath() + SESSION_FILE;
330 : }
331 :
332 0 : RetVal<muse::ByteArray> AppShellConfiguration::readSessionState() const
333 : {
334 0 : muse::mi::ReadResourceLockGuard lock_guard(multiwindowsProvider.get(), SESSION_RESOURCE_NAME);
335 0 : return fileSystem()->readFile(sessionFilePath());
336 0 : }
337 :
338 0 : muse::Ret AppShellConfiguration::writeSessionState(const QByteArray& data)
339 : {
340 0 : muse::mi::WriteResourceLockGuard lock_guard(multiwindowsProvider.get(), SESSION_RESOURCE_NAME);
341 0 : return fileSystem()->writeFile(sessionFilePath(), ByteArray::fromQByteArrayNoCopy(data));
342 0 : }
343 :
344 0 : muse::io::paths_t AppShellConfiguration::parseSessionProjectsPaths(const QByteArray& json) const
345 : {
346 0 : QJsonParseError err;
347 0 : QJsonDocument jsodDoc = QJsonDocument::fromJson(json, &err);
348 0 : if (err.error != QJsonParseError::NoError || !jsodDoc.isArray()) {
349 0 : LOGE() << "failed parse, err: " << err.errorString();
350 0 : return {};
351 : }
352 :
353 0 : io::paths_t result;
354 0 : const QVariantList pathsList = jsodDoc.array().toVariantList();
355 0 : for (const QVariant& pathVal : pathsList) {
356 0 : muse::io::path_t path = pathVal.toString().toStdString();
357 0 : if (!path.empty()) {
358 0 : result.push_back(path);
359 : }
360 0 : }
361 :
362 0 : return result;
363 0 : }
|