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 "projectcommandsstate.h"
24 :
25 : #include "global/containers.h"
26 :
27 : #include "../projectcommands.h"
28 : #include "rcommand/commandtypes.h"
29 :
30 : using namespace muse;
31 : using namespace muse::rcommand;
32 : using namespace mu::project;
33 :
34 : enum class PCondition {
35 : Any,
36 : HasProject,
37 : NeedSave,
38 : NotBusy,
39 : HasSelection,
40 : };
41 :
42 : static std::map<Command, PCondition> PROJECT_COMMAND_CONDITIONS = {
43 : { PROJECT_NEW_COMMAND, PCondition::Any },
44 : { PROJECT_OPEN_COMMAND, PCondition::Any },
45 : { PROJECT_CLOSE_COMMAND, PCondition::NotBusy },
46 : { PROJECT_SAVE_COMMAND, PCondition::NeedSave },
47 : { PROJECT_SAVE_AS_COMMAND, PCondition::NotBusy },
48 : { PROJECT_SAVE_A_COPY_COMMAND, PCondition::NotBusy },
49 : { PROJECT_SAVE_SELECTION_COMMAND, PCondition::HasSelection },
50 : { PROJECT_SAVE_TO_CLOUD_COMMAND, PCondition::NotBusy },
51 : { PROJECT_SAVE_AT_COMMAND, PCondition::NotBusy },
52 : { PROJECT_PUBLISH_COMMAND, PCondition::NotBusy },
53 : { PROJECT_SHARE_AUDIO_COMMAND, PCondition::NotBusy },
54 : { PROJECT_EXPORT_COMMAND, PCondition::HasProject },
55 : { PROJECT_CONVERT_TO_SCORE_COMMAND, PCondition::Any },
56 : { PROJECT_PRINT_COMMAND, PCondition::HasProject },
57 : { PROJECT_CLEAR_RECENT_COMMAND, PCondition::Any },
58 : { PROJECT_CONTINUE_LAST_SESSION_COMMAND, PCondition::Any },
59 : { PROJECT_PROPERTIES_COMMAND, PCondition::HasProject },
60 : };
61 :
62 : using BusyStatus = BusyStatus;
63 : static std::map<Command, BusyStatus> PROJECT_COMMAND_BUSY_STATUSES = {
64 : { PROJECT_CLOSE_COMMAND, BusyStatus::Closing },
65 : { PROJECT_SAVE_COMMAND, BusyStatus::Saving },
66 : { PROJECT_SAVE_AS_COMMAND, BusyStatus::Saving },
67 : { PROJECT_SAVE_A_COPY_COMMAND, BusyStatus::Saving },
68 : { PROJECT_SAVE_SELECTION_COMMAND, BusyStatus::Saving },
69 : { PROJECT_SAVE_TO_CLOUD_COMMAND, BusyStatus::Uploading },
70 : { PROJECT_SAVE_AT_COMMAND, BusyStatus::Uploading },
71 : { PROJECT_PUBLISH_COMMAND, BusyStatus::Publishing },
72 : { PROJECT_SHARE_AUDIO_COMMAND, BusyStatus::AudioSharing },
73 : };
74 :
75 0 : static inline std::vector<Command> commands(const PCondition& condition)
76 : {
77 0 : std::vector<Command> result;
78 0 : for (const auto& p : PROJECT_COMMAND_CONDITIONS) {
79 0 : if (p.second == condition) {
80 0 : result.push_back(p.first);
81 : }
82 : }
83 0 : return result;
84 0 : }
85 :
86 0 : std::string ProjectCommandsState::moduleName() const
87 : {
88 0 : return "project";
89 : }
90 :
91 0 : void ProjectCommandsState::init()
92 : {
93 0 : m_moduleRegister = commandsRegister()->moduleRegister(moduleName());
94 0 : IF_ASSERT_FAILED(m_moduleRegister) {
95 0 : return;
96 : }
97 :
98 0 : controller()->hasProjectChanged().onNotify(this, [this]() {
99 0 : updateCommandStates();
100 0 : });
101 :
102 0 : controller()->needSaveChanged().onNotify(this, [this]() {
103 0 : updateCommandStates(commands(PCondition::NeedSave));
104 0 : });
105 :
106 0 : controller()->busyChanged().onNotify(this, [this]() {
107 0 : updateCommandStates(commands(PCondition::NotBusy));
108 0 : });
109 :
110 0 : controller()->hasSelectionChanged().onNotify(this, [this]() {
111 0 : updateCommandStates(commands(PCondition::HasSelection));
112 0 : });
113 :
114 0 : updateCommandStates();
115 : }
116 :
117 0 : void ProjectCommandsState::deinit()
118 : {
119 0 : controller()->hasProjectChanged().disconnect(this);
120 0 : controller()->needSaveChanged().disconnect(this);
121 0 : controller()->busyChanged().disconnect(this);
122 0 : controller()->hasSelectionChanged().disconnect(this);
123 0 : }
124 :
125 0 : void ProjectCommandsState::updateCommandStates(const std::vector<Command>& commands)
126 : {
127 0 : IF_ASSERT_FAILED(m_moduleRegister) {
128 0 : return;
129 : }
130 :
131 0 : const auto& commandList = commands.empty() ? m_moduleRegister->commandList() : commands;
132 :
133 0 : for (const auto& command : commandList) {
134 0 : CommandState newState = commandState(command);
135 0 : if (m_commandStates[command] != newState) {
136 0 : m_commandStates[command] = newState;
137 0 : m_commandStateChanged.send(command, newState);
138 : }
139 : }
140 : }
141 :
142 0 : CommandState ProjectCommandsState::commandState(const Command& command) const
143 : {
144 0 : PCondition condition = muse::value(PROJECT_COMMAND_CONDITIONS, command, PCondition::Any);
145 0 : switch (condition) {
146 0 : case PCondition::Any: return CommandState(true, false);
147 0 : case PCondition::HasProject: return CommandState(controller()->hasProject(), false);
148 0 : case PCondition::NeedSave: return CommandState(controller()->hasProject() && controller()->needSave(), false);
149 0 : case PCondition::NotBusy: {
150 0 : return CommandState(controller()->hasProject() && !controller()->isBusy(PROJECT_COMMAND_BUSY_STATUSES.at(command)), false);
151 : }
152 0 : case PCondition::HasSelection: return CommandState(controller()->hasProject() && controller()->hasSelection(), false);
153 : }
154 0 : return CommandState(true, false);
155 : }
156 :
157 0 : async::Channel<Command, CommandState> ProjectCommandsState::commandStateChanged() const
158 : {
159 0 : return m_commandStateChanged;
160 : }
|