LCOV - code coverage report
Current view: top level - src/project/internal - projectactionscontroller.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 182 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 "projectactionscontroller.h"
      24              : 
      25              : #include <QBuffer>
      26              : #include <QFileInfo>
      27              : #include <QTemporaryFile>
      28              : #include <QUrl>
      29              : #include <QUrlQuery>
      30              : #include <QTimer>
      31              : #include <qobject.h>
      32              : 
      33              : #include "async/async.h"
      34              : #include "defer.h"
      35              : #include "types/ret.h"
      36              : #include "rcommand/commandtypes.h"
      37              : 
      38              : #include "notation/imasternotation.h"
      39              : #include "notation/inotationinteraction.h"
      40              : #include "notation/inotationselection.h"
      41              : 
      42              : #include "inotationproject.h"
      43              : #include "types/projecturis.h"
      44              : 
      45              : #include "../projectcommands.h"
      46              : #include "appshell/appshellcommands.h"
      47              : 
      48              : #include "log.h"
      49              : 
      50              : using namespace mu;
      51              : using namespace mu::project;
      52              : using namespace mu::notation;
      53              : using namespace muse;
      54              : 
      55            0 : void ProjectActionsController::init()
      56              : {
      57            0 :     auto d = commandDispatcher();
      58              : 
      59            0 :     d->onRequest(this, PROJECT_NEW_COMMAND, [this]() { return newProject(); });
      60            0 :     d->onRequest(this, PROJECT_OPEN_COMMAND, [this](const rcommand::Params& params) { return openProject(params); });
      61            0 :     d->onRequest(this, PROJECT_CLOSE_COMMAND, [this]() { return closeProject(); });
      62              : 
      63            0 :     d->onRequest(this, PROJECT_SAVE_COMMAND, [this]() { return runAsync(saveProject(SaveMode::Save)); });
      64            0 :     d->onRequest(this, PROJECT_SAVE_AS_COMMAND, [this]() { return runAsync(saveProject(SaveMode::SaveAs)); });
      65            0 :     d->onRequest(this, PROJECT_SAVE_A_COPY_COMMAND, [this]() { return runAsync(saveProject(SaveMode::SaveCopy)); });
      66            0 :     d->onRequest(this, PROJECT_SAVE_SELECTION_COMMAND, [this]() {
      67            0 :         return runAsync(saveProject(SaveMode::SaveSelection, SaveLocationType::Local));
      68              :     });
      69            0 :     d->onRequest(this, PROJECT_SAVE_TO_CLOUD_COMMAND, [this]() { return runAsync(saveProject(SaveMode::Save, SaveLocationType::Cloud)); });
      70            0 :     d->onRequest(this, PROJECT_SAVE_AT_COMMAND, [this](const rcommand::Params& params) { return runAsync(saveProjectAt(params)); });
      71              : 
      72            0 :     d->onRequest(this, PROJECT_PUBLISH_COMMAND, [this]() { return runAsync(publish()); });
      73            0 :     d->onRequest(this, PROJECT_SHARE_AUDIO_COMMAND, [this]() { return runAsync(sharedAudio()); });
      74              : 
      75            0 :     d->onRequest(this, PROJECT_EXPORT_COMMAND, [this]() { return exportScore(); });
      76            0 :     d->onRequest(this, PROJECT_CONVERT_TO_SCORE_COMMAND, [this]() { return convertFileToScore(); });
      77              : 
      78            0 :     d->onRequest(this, PROJECT_PRINT_COMMAND, [this]() { return printScore(); });
      79            0 :     d->onRequest(this, PROJECT_CLEAR_RECENT_COMMAND, [this]() { return clearRecentScores(); });
      80            0 :     d->onRequest(this, PROJECT_CONTINUE_LAST_SESSION_COMMAND, [this]() { return continueLastSession(); });
      81            0 :     d->onRequest(this, PROJECT_PROPERTIES_COMMAND, [this]() { return openProjectProperties(); });
      82              : 
      83            0 :     saveProjectScenario()->busyChanged().onNotify(this, [this]() {
      84            0 :         m_busyChanged.notify();
      85            0 :     });
      86              : 
      87            0 :     openProjectScenario()->busyChanged().onNotify(this, [this]() {
      88            0 :         m_busyChanged.notify();
      89            0 :     });
      90              : 
      91            0 :     closeProjectScenario()->busyChanged().onNotify(this, [this]() {
      92            0 :         m_busyChanged.notify();
      93            0 :     });
      94              : 
      95              :     // listen changes
      96            0 :     projectSession()->projectChanged().onNotify(this, [this]() {
      97            0 :         auto project = projectSession()->project();
      98            0 :         if (project) {
      99            0 :             project->needSaveChanged().onNotify(this, [this]() {
     100            0 :                 m_needSaveChanged.notify();
     101            0 :             });
     102              :         }
     103              : 
     104            0 :         auto notation = projectSession()->currentNotation();
     105            0 :         if (notation) {
     106            0 :             notation->interaction()->selectionChanged().onNotify(this, [this]() {
     107            0 :                 m_hasSelectionChanged.notify();
     108            0 :             });
     109              :         }
     110            0 :     });
     111            0 : }
     112              : 
     113            0 : bool ProjectActionsController::hasProject() const
     114              : {
     115            0 :     return currentNotationProject() != nullptr;
     116              : }
     117              : 
     118            0 : muse::async::Notification ProjectActionsController::hasProjectChanged() const
     119              : {
     120            0 :     return projectSession()->projectChanged();
     121              : }
     122              : 
     123            0 : bool ProjectActionsController::needSave() const
     124              : {
     125            0 :     return currentNotationProject() ? currentNotationProject()->isNeedSave() : false;
     126              : }
     127              : 
     128            0 : muse::async::Notification ProjectActionsController::needSaveChanged() const
     129              : {
     130            0 :     return m_needSaveChanged;
     131              : }
     132              : 
     133            0 : bool ProjectActionsController::isBusy(BusyStatus status) const
     134              : {
     135            0 :     return m_busyStatuses.contains(status)
     136            0 :            || saveProjectScenario()->isBusy(status)
     137            0 :            || openProjectScenario()->isBusy(status)
     138            0 :            || closeProjectScenario()->isBusy(status);
     139              : }
     140              : 
     141            0 : void ProjectActionsController::setBusy(BusyStatus status, bool isBusy)
     142              : {
     143            0 :     bool wasBusy = m_busyStatuses.contains(status);
     144            0 :     if (wasBusy == isBusy) {
     145            0 :         return;
     146              :     }
     147              : 
     148            0 :     if (isBusy) {
     149            0 :         m_busyStatuses.insert(status);
     150              :     } else {
     151            0 :         m_busyStatuses.erase(status);
     152              :     }
     153              : 
     154            0 :     m_busyChanged.notify();
     155              : }
     156              : 
     157            0 : muse::async::Notification ProjectActionsController::busyChanged() const
     158              : {
     159            0 :     return m_busyChanged;
     160              : }
     161              : 
     162            0 : bool ProjectActionsController::hasSelection() const
     163              : {
     164            0 :     return currentNotationSelection() ? !currentNotationSelection()->isNone() : false;
     165              : }
     166              : 
     167            0 : muse::async::Notification ProjectActionsController::hasSelectionChanged() const
     168              : {
     169            0 :     return m_hasSelectionChanged;
     170              : }
     171              : 
     172            0 : INotationProjectPtr ProjectActionsController::currentNotationProject() const
     173              : {
     174            0 :     return projectSession()->project();
     175              : }
     176              : 
     177            0 : IMasterNotationPtr ProjectActionsController::currentMasterNotation() const
     178              : {
     179            0 :     return currentNotationProject() ? currentNotationProject()->masterNotation() : nullptr;
     180              : }
     181              : 
     182            0 : INotationPtr ProjectActionsController::currentNotation() const
     183              : {
     184            0 :     return currentMasterNotation() ? currentMasterNotation()->notation() : nullptr;
     185              : }
     186              : 
     187            0 : INotationInteractionPtr ProjectActionsController::currentInteraction() const
     188              : {
     189            0 :     return currentNotation() ? currentNotation()->interaction() : nullptr;
     190              : }
     191              : 
     192            0 : INotationSelectionPtr ProjectActionsController::currentNotationSelection() const
     193              : {
     194            0 :     return currentNotation() ? currentInteraction()->selection() : nullptr;
     195              : }
     196              : 
     197            0 : muse::Ret ProjectActionsController::openPageIfNeed(muse::Uri pageUri)
     198              : {
     199            0 :     if (!interactive()->isOpened(pageUri).val) {
     200            0 :         interactive()->open(pageUri);
     201              :     }
     202            0 :     return make_ret(Ret::Code::Ok);
     203              : }
     204              : 
     205            0 : muse::Ret ProjectActionsController::openProject(const muse::io::path_t& path, const QString& displayNameOverride)
     206              : {
     207            0 :     return runAsync(openProjectScenario()->openProject(path, displayNameOverride));
     208              : }
     209              : 
     210            0 : muse::Ret ProjectActionsController::openProject(const muse::rcommand::Params& params)
     211              : {
     212            0 :     return runAsync(openProjectScenario()->openProject(params));
     213              : }
     214              : 
     215            0 : bool ProjectActionsController::isProjectOpened(const muse::io::path_t& scorePath) const
     216              : {
     217            0 :     auto project = projectSession()->project();
     218            0 :     if (!project) {
     219            0 :         return false;
     220              :     }
     221              : 
     222            0 :     LOGD() << "project->path: " << project->path() << ", check path: " << scorePath;
     223            0 :     if (project->path() == scorePath) {
     224            0 :         return true;
     225              :     }
     226              : 
     227            0 :     return false;
     228            0 : }
     229              : 
     230            0 : bool ProjectActionsController::isAnyProjectOpened() const
     231              : {
     232            0 :     auto project = projectSession()->project();
     233            0 :     if (project) {
     234            0 :         return true;
     235              :     }
     236            0 :     return false;
     237            0 : }
     238              : 
     239            0 : muse::Ret ProjectActionsController::newProject()
     240              : {
     241              :     //! NOTE This method is synchronous,
     242              :     //! but inside `multiwindowsProvider` there can be an event loop
     243              :     //! to wait for the responses from other instances, accordingly,
     244              :     //! the events (like user click) can be executed and this method can be called several times,
     245              :     //! before the end of the current call.
     246              :     //! So we ignore all subsequent calls until the current one completes.
     247            0 :     if (isBusy(BusyStatus::Opening)) {
     248            0 :         return make_ret(Ret::Code::Busy);
     249              :     }
     250            0 :     setBusy(BusyStatus::Opening, true);
     251              : 
     252            0 :     DEFER {
     253            0 :         setBusy(BusyStatus::Opening, false);
     254            0 :     };
     255              : 
     256            0 :     if (projectSession()->project()) {
     257            0 :         if (multiwindowsProvider()->isHasWindowWithoutProject()) {
     258            0 :             multiwindowsProvider()->activateWindowWithoutProject({ QString::fromStdString(PROJECT_NEW_COMMAND.toString()) });
     259            0 :             return make_ok();
     260              :         }
     261            0 :         QStringList args;
     262            0 :         args << "--session-type" << "start-with-new";
     263            0 :         multiwindowsProvider()->openNewWindow(args);
     264            0 :         return make_ok();
     265            0 :     }
     266              : 
     267            0 :     auto promise = interactive()->open(NEW_SCORE_URI);
     268            0 :     promise.onResolve(this, [this](const Val&) {
     269            0 :         Ret ret = openProjectScenario()->finishOpening();
     270              : 
     271            0 :         if (!ret) {
     272            0 :             LOGE() << ret.toString();
     273              :         }
     274            0 :     });
     275              : 
     276            0 :     return make_ok();
     277            0 : }
     278              : 
     279            0 : muse::Ret ProjectActionsController::closeProject()
     280              : {
     281            0 :     bool anyInstanceWithoutProject = multiwindowsProvider()->isHasWindowWithoutProject();
     282              : 
     283            0 :     return runAsync(closeProjectScenario()->closeOpenedProject(true)
     284            0 :                     .then<Ret>(this, [this, anyInstanceWithoutProject](const Ret& ret, auto resolve) {
     285            0 :         if (ret && anyInstanceWithoutProject) {
     286              :             //! NOTE: we need to call `quit` in the next event loop due to controlling the lifecycle of this method
     287            0 :             async::Async::call(this, [this]() {
     288            0 :                 commandDispatcher()->dispatch(appshell::APP_QUIT_COMMAND, { { "all_instances", muse::Val(false) } });
     289              :             });
     290            0 :             multiwindowsProvider()->activateWindowWithoutProject();
     291              :         }
     292              : 
     293            0 :         return resolve(ret);
     294            0 :     }));
     295              : }
     296              : 
     297              : //! Commands report that the flow has started; its outcome is shown to the user by the scenario itself
     298            0 : muse::Ret ProjectActionsController::runAsync(async::Promise<Ret> flow)
     299              : {
     300            0 :     flow.onResolve(this, [](const Ret& ret) {
     301            0 :         if (!ret) {
     302            0 :             LOGD() << ret.toString();
     303              :         }
     304            0 :     });
     305              : 
     306            0 :     return make_ok();
     307              : }
     308              : 
     309            0 : async::Promise<Ret> ProjectActionsController::saveProject(SaveMode saveMode, SaveLocationType saveLocationType, bool force)
     310              : {
     311            0 :     return saveProjectScenario()->saveProject(saveMode, saveLocationType, force);
     312              : }
     313              : 
     314            0 : async::Promise<Ret> ProjectActionsController::publish()
     315              : {
     316            0 :     return saveProjectScenario()->publish();
     317              : }
     318              : 
     319            0 : async::Promise<Ret> ProjectActionsController::sharedAudio()
     320              : {
     321            0 :     return saveProjectScenario()->shareAudio();
     322              : }
     323              : 
     324            0 : async::Promise<Ret> ProjectActionsController::saveProjectAt(const muse::rcommand::Params& params)
     325              : {
     326            0 :     return saveProjectScenario()->saveProjectAt(params);
     327              : }
     328              : 
     329            0 : muse::Ret ProjectActionsController::convertFileToScore()
     330              : {
     331            0 :     convertFileToScoreScenario()->convertFiles();
     332            0 :     return make_ok();
     333              : }
     334              : 
     335            0 : muse::Ret ProjectActionsController::clearRecentScores()
     336              : {
     337            0 :     recentFilesController()->clearRecentFiles();
     338            0 :     return make_ok();
     339              : }
     340              : 
     341            0 : muse::Ret ProjectActionsController::continueLastSession()
     342              : {
     343            0 :     const RecentFilesList& recentScorePaths = recentFilesController()->recentFilesList();
     344              : 
     345            0 :     if (recentScorePaths.empty()) {
     346            0 :         Ret ret = openPageIfNeed(HOME_PAGE_URI);
     347            0 :         if (!ret) {
     348            0 :             LOGE() << ret.toString();
     349              :         }
     350            0 :         return ret;
     351            0 :     }
     352              : 
     353            0 :     muse::io::path_t lastScorePath = recentScorePaths.front().path;
     354            0 :     return openProject(lastScorePath);
     355            0 : }
     356              : 
     357            0 : muse::Ret ProjectActionsController::exportScore()
     358              : {
     359            0 :     static const Uri EXPORT_URI("musescore://project/export");
     360            0 :     if (!interactive()->isOpened(EXPORT_URI).val) {
     361            0 :         interactive()->open(EXPORT_URI);
     362              :     }
     363            0 :     return make_ok();
     364              : }
     365              : 
     366            0 : muse::Ret ProjectActionsController::printScore()
     367              : {
     368            0 :     INotationPtr notation = projectSession()->currentNotation();
     369            0 :     if (!notation) {
     370            0 :         return make_ret(Ret::Code::InternalError);
     371              :     }
     372              : 
     373            0 :     printProvider()->printNotation(notation);
     374            0 :     return make_ok();
     375            0 : }
     376              : 
     377            0 : muse::Ret ProjectActionsController::openProjectProperties()
     378              : {
     379            0 :     interactive()->open(PROJECT_PROPERTIES_URI);
     380            0 :     return make_ok();
     381              : }
        

Generated by: LCOV version 2.0-1