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 :
23 : #include "startupscenario.h"
24 :
25 : #include <QCoreApplication>
26 :
27 : #include "appshelltypes.h"
28 : #include "translation.h"
29 : #include "types/version.h"
30 :
31 : #include "project/projectcommands.h"
32 :
33 : #include "muse_framework_config.h"
34 :
35 : #include "log.h"
36 :
37 : using namespace mu::appshell;
38 : using namespace muse;
39 :
40 : static const muse::UriQuery FIRST_LAUNCH_SETUP_URI("musescore://firstLaunchSetup?floating=true");
41 : static const muse::UriQuery WELCOME_DIALOG_URI("musescore://welcomedialog");
42 :
43 0 : static StartupModeType modeTypeTromString(const std::string& str)
44 : {
45 0 : if ("start-empty" == str) {
46 0 : return StartupModeType::StartEmpty;
47 : }
48 :
49 0 : if ("continue-last" == str) {
50 0 : return StartupModeType::ContinueLastSession;
51 : }
52 :
53 0 : if ("start-with-new" == str) {
54 0 : return StartupModeType::StartWithNewScore;
55 : }
56 :
57 0 : if ("start-with-file" == str) {
58 0 : return StartupModeType::StartWithScore;
59 : }
60 :
61 0 : return StartupModeType::StartEmpty;
62 : }
63 :
64 0 : static const Uri& startupPageUri(StartupModeType modeType)
65 : {
66 0 : switch (modeType) {
67 0 : case StartupModeType::StartEmpty:
68 : case StartupModeType::StartWithNewScore:
69 : case StartupModeType::Recovery:
70 0 : return HOME_URI;
71 0 : case StartupModeType::StartWithScore:
72 : case StartupModeType::ContinueLastSession:
73 0 : return NOTATION_URI;
74 : }
75 :
76 0 : return HOME_URI;
77 : }
78 :
79 0 : void StartupScenario::setStartupType(const std::optional<std::string>& type)
80 : {
81 0 : m_startupTypeStr = type ? type.value() : "";
82 0 : }
83 :
84 0 : bool StartupScenario::isStartWithNewFileAsSecondaryInstance() const
85 : {
86 0 : if (m_startupScoreFile.isValid()) {
87 0 : return false;
88 : }
89 :
90 0 : if (!m_startupTypeStr.empty()) {
91 0 : return modeTypeTromString(m_startupTypeStr) == StartupModeType::StartWithNewScore;
92 : }
93 :
94 0 : return false;
95 : }
96 :
97 0 : const mu::project::ProjectFile& StartupScenario::startupScoreFile() const
98 : {
99 0 : return m_startupScoreFile;
100 : }
101 :
102 0 : void StartupScenario::setStartupScoreFile(const std::optional<project::ProjectFile>& file)
103 : {
104 0 : m_startupScoreFile = file ? file.value() : project::ProjectFile();
105 0 : }
106 :
107 0 : void StartupScenario::runOnSplashScreen()
108 : {
109 0 : TRACEFUNC;
110 :
111 0 : if (!multiwindowsProvider()->isFirstWindow()) {
112 0 : registerAudioPlugins();
113 0 : return;
114 : }
115 :
116 0 : if (appUpdateScenario() && appUpdateScenario()->needCheckForUpdate()) {
117 0 : appUpdateScenario()->checkForUpdate(/*manual*/ false);
118 : }
119 :
120 0 : if (museSoundsUpdateScenario() && museSoundsUpdateScenario()->needCheckForUpdate()) {
121 0 : museSoundsUpdateScenario()->checkForUpdate(/*manual*/ false);
122 : }
123 :
124 0 : registerAudioPlugins();
125 0 : }
126 :
127 0 : void StartupScenario::registerAudioPlugins()
128 : {
129 0 : if (!registerAudioPluginsScenario()) {
130 0 : return;
131 : }
132 :
133 : //! NOTE Registering plugins shows a window (dialog) before the main window is shown.
134 : //! After closing it, the application may in a state where there are no open windows,
135 : //! which leads to automatic exit from the application.
136 : //! (Thanks to the splashscreen, but this is not an obvious detail)
137 0 : qApp->setQuitLockEnabled(false);
138 0 : registerAudioPluginsScenario()->updatePluginsRegistry();
139 0 : qApp->setQuitLockEnabled(true);
140 : }
141 :
142 0 : void StartupScenario::runAfterSplashScreen()
143 : {
144 0 : TRACEFUNC;
145 :
146 0 : if (m_startupCompleted) {
147 0 : return;
148 : }
149 :
150 0 : m_startupCompleted = true;
151 :
152 0 : StartupModeType modeType = StartupModeType::StartEmpty;
153 0 : if (multiwindowsProvider()->isFirstWindow() && sessionsManager()->hasProjectsForRestore()) {
154 0 : modeType = StartupModeType::Recovery;
155 : } else {
156 0 : modeType = resolveStartupModeType();
157 : }
158 :
159 0 : const Uri& startupUri = startupPageUri(modeType);
160 0 : auto promise = interactive()->open(startupUri);
161 0 : promise.onResolve(this, [this, modeType](const Val&) {
162 0 : onStartupPageOpened(modeType);
163 0 : });
164 0 : }
165 :
166 0 : bool StartupScenario::startupCompleted() const
167 : {
168 0 : return m_startupCompleted;
169 : }
170 :
171 0 : StartupModeType StartupScenario::resolveStartupModeType() const
172 : {
173 0 : if (m_startupScoreFile.isValid()) {
174 0 : return StartupModeType::StartWithScore;
175 : }
176 :
177 0 : if (!m_startupTypeStr.empty()) {
178 0 : return modeTypeTromString(m_startupTypeStr);
179 : }
180 :
181 0 : return configuration()->startupModeType();
182 : }
183 :
184 0 : void StartupScenario::onStartupPageOpened(StartupModeType modeType)
185 : {
186 0 : TRACEFUNC;
187 :
188 0 : switch (modeType) {
189 0 : case StartupModeType::StartEmpty:
190 0 : break;
191 0 : case StartupModeType::StartWithNewScore:
192 0 : commandDispatcher()->dispatch(project::PROJECT_NEW_COMMAND);
193 0 : break;
194 0 : case StartupModeType::ContinueLastSession:
195 0 : commandDispatcher()->dispatch(project::PROJECT_CONTINUE_LAST_SESSION_COMMAND);
196 0 : break;
197 0 : case StartupModeType::Recovery:
198 0 : restoreLastSession();
199 0 : break;
200 0 : case StartupModeType::StartWithScore: {
201 0 : project::ProjectFile file = m_startupScoreFile.isValid()
202 0 : ? m_startupScoreFile
203 0 : : project::ProjectFile(configuration()->startupScorePath());
204 0 : openScore(file);
205 0 : } break;
206 : }
207 :
208 0 : m_activeUpdateCheckCount = 0;
209 :
210 0 : if (museSoundsUpdateScenario() && museSoundsUpdateScenario()->checkInProgress()) {
211 0 : m_activeUpdateCheckCount++;
212 0 : museSoundsUpdateScenario()->checkInProgressChanged().onNotify(this, [this, modeType]() {
213 0 : museSoundsUpdateScenario()->checkInProgressChanged().disconnect(this);
214 0 : m_activeUpdateCheckCount--;
215 0 : showStartupDialogsIfNeed(modeType);
216 0 : }, Asyncable::Mode::SetReplace);
217 : }
218 :
219 0 : showStartupDialogsIfNeed(modeType);
220 0 : }
221 :
222 0 : void StartupScenario::showStartupDialogsIfNeed(StartupModeType modeType)
223 : {
224 0 : TRACEFUNC;
225 :
226 0 : if (m_activeUpdateCheckCount != 0) {
227 0 : return;
228 : }
229 :
230 : //! NOTE: The welcome dialog should not show if the first launch setup has not been completed, or if we're going
231 : //! to show a MuseSounds update dialog (see ProjectActionsController::doFinishOpenProject). MuseSampler's update
232 : //! dialog should be shown after the welcome dialog.
233 0 : const auto showWelcomeDialogAndSamplerUpdateIfNeed = [this, modeType]() {
234 0 : if (!configuration()->hasCompletedFirstLaunchSetup()) {
235 0 : interactive()->open(FIRST_LAUNCH_SETUP_URI);
236 0 : return;
237 : }
238 :
239 0 : const Version welcomeDialogLastShownVersion(configuration()->welcomeDialogLastShownVersion());
240 0 : const Version currentMuseScoreVersion(configuration()->museScoreVersion());
241 0 : if (welcomeDialogLastShownVersion < currentMuseScoreVersion) {
242 0 : configuration()->setWelcomeDialogShowOnStartup(true); // override user preference
243 0 : configuration()->setWelcomeDialogLastShownIndex(-1); // reset
244 : }
245 :
246 0 : const bool shouldCheckForMuseSamplerUpdate = modeType == StartupModeType::StartEmpty
247 0 : || modeType == StartupModeType::StartWithNewScore;
248 :
249 0 : if (shouldShowWelcomeDialog(modeType)) {
250 0 : interactive()->open(WELCOME_DIALOG_URI).onResolve(this, [this, shouldCheckForMuseSamplerUpdate](const Val&) {
251 0 : configuration()->setWelcomeDialogLastShownVersion(configuration()->museScoreVersion());
252 :
253 0 : if (shouldCheckForMuseSamplerUpdate) {
254 0 : checkAndShowMuseSamplerStatusIfNeed();
255 : }
256 0 : });
257 0 : } else if (shouldCheckForMuseSamplerUpdate) {
258 0 : checkAndShowMuseSamplerStatusIfNeed();
259 : }
260 0 : };
261 :
262 0 : showWelcomeDialogAndSamplerUpdateIfNeed();
263 0 : }
264 :
265 0 : bool StartupScenario::shouldShowWelcomeDialog(StartupModeType modeType) const
266 : {
267 0 : if (!configuration()->welcomeDialogShowOnStartup()) {
268 0 : return false;
269 : }
270 :
271 0 : if (!multiwindowsProvider()->isFirstWindow()) {
272 0 : return false;
273 : }
274 :
275 0 : if (museSoundsUpdateScenario() && museSoundsUpdateScenario()->hasUpdate()) {
276 0 : return false;
277 : }
278 :
279 0 : const Uri& startupUri = startupPageUri(modeType);
280 0 : return interactive()->currentUri().val == startupUri;
281 : }
282 :
283 0 : void StartupScenario::checkAndShowMuseSamplerStatusIfNeed()
284 : {
285 0 : if (museSamplerCheckStatusScenario() && !museSamplerCheckStatusScenario()->alreadyChecked()) {
286 0 : museSamplerCheckStatusScenario()->checkAndShowStatusIfNeed();
287 : }
288 0 : }
289 :
290 0 : void StartupScenario::openScore(const project::ProjectFile& file)
291 : {
292 0 : commandDispatcher()->dispatch(project::PROJECT_OPEN_COMMAND,
293 0 : { { "url", muse::Val(file.url.toString()) }, { "display_name", muse::Val(file.displayNameOverride) } });
294 0 : }
295 :
296 0 : void StartupScenario::restoreLastSession()
297 : {
298 0 : auto promise = interactive()->question(muse::trc("appshell", "The previous session quit unexpectedly."),
299 0 : muse::trc("appshell", "Do you want to restore the session?"),
300 0 : { IInteractive::Button::No, IInteractive::Button::Yes });
301 :
302 0 : promise.onResolve(this, [this](const IInteractive::Result& res) {
303 0 : if (res.isButton(IInteractive::Button::Yes)) {
304 0 : sessionsManager()->restore();
305 : } else {
306 0 : removeProjectsUnsavedChanges(configuration()->sessionProjectsPaths());
307 0 : sessionsManager()->reset();
308 0 : checkAndShowMuseSamplerStatusIfNeed();
309 : }
310 0 : });
311 0 : }
312 :
313 0 : void StartupScenario::removeProjectsUnsavedChanges(const io::paths_t& projectsPaths)
314 : {
315 0 : for (const muse::io::path_t& path : projectsPaths) {
316 0 : projectAutoSaver()->removeProjectUnsavedChanges(path);
317 : }
318 0 : }
|