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) 2026 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 "closeprojectscenario.h"
24 :
25 : #include "translation.h"
26 : #include "types/projecturis.h"
27 :
28 : #include "playback/playbackcommands.h"
29 :
30 : #include "log.h"
31 :
32 : using namespace muse;
33 : using namespace mu::project;
34 : using namespace mu::playback;
35 : using muse::async::Promise;
36 :
37 2 : Promise<Ret> CloseProjectScenario::resolvedPromise(const Ret& ret)
38 : {
39 4 : return async::make_promise<Ret>([ret](auto resolve) {
40 2 : return resolve(ret);
41 4 : });
42 : }
43 :
44 14 : Promise<Ret> CloseProjectScenario::closeOpenedProject(bool goToHome)
45 : {
46 28 : return runIfNotBusy(BusyStatus::Closing, [this, goToHome]() -> Promise<Ret> {
47 13 : INotationProjectPtr project = currentNotationProject();
48 13 : if (!project) {
49 1 : return resolvedPromise(make_ok());
50 : }
51 :
52 12 : if (playbackState()->isPlaying()) {
53 1 : commandDispatcher()->dispatch(STOP_COMMAND);
54 : }
55 :
56 12 : if (!project->isNeedSave()) {
57 6 : return doCloseProject(goToHome);
58 : }
59 :
60 12 : return askAboutSavingScore(project)
61 6 : .then<Ret>(this, [this, goToHome](const IInteractive::Result& res, auto resolve) {
62 6 : IInteractive::Button btn = res.standardButton();
63 :
64 6 : if (btn == IInteractive::Button::Cancel) {
65 1 : return resolve(make_ret(Ret::Code::Cancel));
66 : }
67 :
68 5 : if (btn != IInteractive::Button::Save) {
69 3 : doCloseProject(goToHome).onResolve(this, [resolve](const Ret& ret) {
70 3 : (void)resolve(ret);
71 : });
72 :
73 3 : return Promise<Ret>::dummy_result();
74 : }
75 :
76 : //! NOTE The score is only let go of once its changes are safely written
77 2 : saveProjectScenario()->saveProject().onResolve(this, [this, goToHome, resolve](const Ret& ret) {
78 2 : if (!ret) {
79 1 : (void)resolve(ret);
80 1 : return;
81 : }
82 :
83 1 : doCloseProject(goToHome).onResolve(this, [resolve](const Ret& closeRet) {
84 1 : (void)resolve(closeRet);
85 : });
86 : });
87 :
88 2 : return Promise<Ret>::dummy_result();
89 6 : });
90 41 : });
91 : }
92 :
93 16 : bool CloseProjectScenario::isBusy(BusyStatus status) const
94 : {
95 16 : return m_busyStatuses.contains(status);
96 : }
97 :
98 0 : muse::async::Notification CloseProjectScenario::busyChanged() const
99 : {
100 0 : return m_busyChanged;
101 : }
102 :
103 13 : INotationProjectPtr CloseProjectScenario::currentNotationProject() const
104 : {
105 13 : return projectSession()->project();
106 : }
107 :
108 25 : void CloseProjectScenario::setBusy(BusyStatus status, bool isBusy)
109 : {
110 25 : bool wasBusy = m_busyStatuses.contains(status);
111 25 : if (wasBusy == isBusy) {
112 0 : return;
113 : }
114 :
115 25 : if (isBusy) {
116 13 : m_busyStatuses.insert(status);
117 : } else {
118 12 : m_busyStatuses.erase(status);
119 : }
120 :
121 25 : m_busyChanged.notify();
122 : }
123 :
124 14 : Promise<Ret> CloseProjectScenario::runIfNotBusy(BusyStatus status, const std::function<Promise<Ret>()>& flow)
125 : {
126 14 : if (isBusy(status)) {
127 1 : return resolvedPromise(make_ret(Ret::Code::Busy));
128 : }
129 :
130 13 : setBusy(status, true);
131 :
132 26 : return flow().then<Ret>(this, [this, status](const Ret& ret, auto resolve) {
133 12 : setBusy(status, false);
134 12 : return resolve(ret);
135 13 : });
136 : }
137 :
138 6 : Promise<IInteractive::Result> CloseProjectScenario::askAboutSavingScore(const INotationProjectPtr& project)
139 : {
140 6 : std::string title = muse::qtrc("project", "Do you want to save changes to the score “%1” before closing?")
141 6 : .arg(project->displayName()).toStdString();
142 :
143 6 : std::string body = muse::trc("project", "Your changes will be lost if you don’t save them.");
144 :
145 30 : return interactive()->warning(title, body, {
146 : IInteractive::Button::DontSave,
147 : IInteractive::Button::Cancel,
148 : IInteractive::Button::Save
149 18 : }, IInteractive::Button::Save);
150 6 : }
151 :
152 10 : Promise<Ret> CloseProjectScenario::doCloseProject(bool goToHome)
153 : {
154 20 : return interactive()->closeAllDialogs().then<Ret>(this, [this, goToHome](const Ret&, auto resolve) {
155 9 : projectSession()->setProject(nullptr);
156 :
157 9 : if (goToHome) {
158 8 : openHomePageIfNeed();
159 : }
160 :
161 9 : return resolve(make_ok());
162 20 : });
163 : }
164 :
165 8 : void CloseProjectScenario::openHomePageIfNeed()
166 : {
167 8 : if (interactive()->isOpened(HOME_PAGE_URI).val) {
168 1 : return;
169 : }
170 :
171 7 : interactive()->open(HOME_PAGE_URI);
172 : }
|