LCOV - code coverage report
Current view: top level - src/appshell/internal - startupscenario.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 160 0
Test Date: 2026-11-05 04:07:02

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

Generated by: LCOV version 2.0-1