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 "editstavesharing.h"
24 :
25 : #include "dom/factory.h"
26 : #include "dom/instrtemplate.h"
27 : #include "dom/part.h"
28 : #include "dom/score.h"
29 : #include "dom/sharedpart.h"
30 : #include "dom/staff.h"
31 :
32 : #include "transaction/transaction.h"
33 : #include "transaction/undoablecommand.h"
34 :
35 : using namespace mu::engraving;
36 :
37 : namespace {
38 : //-------------------------------------------------------------------
39 : // Undo commands
40 : //-------------------------------------------------------------------
41 :
42 : class ConnectSharedPart : public UndoableCommand
43 : {
44 38 : OBJECT_ALLOCATOR(engraving, ConnectSharedPart)
45 :
46 : SharedPart* sharedPart = nullptr;
47 : Part* originPart = nullptr;
48 :
49 : public:
50 20 : ConnectSharedPart(SharedPart* s, Part* o)
51 20 : : sharedPart(s), originPart(o) {}
52 :
53 8 : void undo() override
54 : {
55 8 : sharedPart->removeOriginPart(originPart);
56 8 : }
57 :
58 22 : void redo() override
59 : {
60 22 : sharedPart->addOriginPart(originPart);
61 22 : }
62 :
63 30 : UNDO_TYPE(CommandType::ConnectSharedPart)
64 0 : UNDO_NAME("Connect shared part")
65 : };
66 :
67 : class DisconnectSharedPart : public UndoableCommand
68 : {
69 6 : OBJECT_ALLOCATOR(engraving, DisconnectSharedPart)
70 :
71 : SharedPart* sharedPart = nullptr;
72 : Part* originPart = nullptr;
73 :
74 : public:
75 4 : DisconnectSharedPart(SharedPart* s, Part* o)
76 4 : : sharedPart(s), originPart(o) {}
77 :
78 2 : void undo() override
79 : {
80 2 : sharedPart->addOriginPart(originPart);
81 2 : }
82 :
83 4 : void redo() override
84 : {
85 4 : sharedPart->removeOriginPart(originPart);
86 4 : }
87 :
88 6 : UNDO_TYPE(CommandType::DisconnectSharedPart)
89 0 : UNDO_NAME("Disconnect shared part")
90 : };
91 : }
92 :
93 : //-------------------------------------------------------------------
94 : // EditStaveSharing
95 : //-------------------------------------------------------------------
96 :
97 13 : void EditStaveSharing::toggleStaveSharing(Transaction& tx, Score* score, bool on)
98 : {
99 13 : score->undoChangeStyleVal(Sid::enableStaveSharing, on);
100 :
101 13 : if (on && score->sharedParts().empty()) {
102 10 : cmdCreateSharedStaves(tx, score);
103 : }
104 :
105 13 : score->setBracketsAndBarlines();
106 :
107 13 : score->update();
108 13 : }
109 :
110 0 : void EditStaveSharing::cmdRemoveSharedStaves(Score* score)
111 : {
112 0 : std::vector<Part*> parts = score->parts(); // COPY because we're about to remove elements
113 :
114 0 : for (Part* part : parts) {
115 0 : if (part->isSharedPart()) {
116 0 : score->cmdRemovePart(part);
117 : }
118 : }
119 0 : }
120 :
121 10 : void EditStaveSharing::cmdCreateSharedStaves(Transaction& tx, Score* score)
122 : {
123 10 : StaveSharingGroups staveSharingGroups = computeGroups(score);
124 :
125 10 : createSharedParts(tx, staveSharingGroups, score);
126 10 : }
127 :
128 10 : StaveSharingGroups EditStaveSharing::computeGroups(Score* score)
129 : {
130 10 : const std::vector<Part*>& parts = score->parts();
131 :
132 10 : StaveSharingGroups staveSharingGroups;
133 :
134 30 : auto excludePart = [](Part* part) {
135 30 : if (part->nstaves() > 1 || part->isSharedPart()) {
136 0 : return true;
137 : }
138 :
139 30 : Staff* firstStaff = part->staves().front();
140 30 : const StaffType* staffType = firstStaff->staffType();
141 30 : if (staffType->group() != StaffGroup::STANDARD) {
142 0 : return true;
143 : }
144 :
145 30 : return false;
146 : };
147 :
148 20 : for (size_t partIdx = 0; partIdx < parts.size();) {
149 10 : Part* part = parts[partIdx];
150 10 : if (excludePart(part)) {
151 0 : ++partIdx;
152 0 : continue;
153 : }
154 :
155 10 : Instrument* instr = part->instrument();
156 10 : String instrGroup = instr->group();
157 10 : if (instrGroup != "woodwinds" && instrGroup != "brass") { // TODO: styles
158 0 : ++partIdx;
159 0 : continue;
160 : }
161 :
162 10 : StaveSharingGroup staveSharingGroup;
163 :
164 10 : size_t nextPartIdx = partIdx;
165 30 : while (nextPartIdx < parts.size()) {
166 20 : Part* nextPart = parts[nextPartIdx];
167 20 : if (excludePart(nextPart)) {
168 0 : break;
169 : }
170 20 : Instrument* nextInstr = nextPart->instrument();
171 20 : if (nextPart != part && nextInstr->id() != instr->id()) {
172 0 : break;
173 : }
174 :
175 20 : staveSharingGroup.push_back(nextPart);
176 20 : ++nextPartIdx;
177 : }
178 :
179 10 : if (staveSharingGroup.size() > 1) {
180 10 : staveSharingGroups.push_back(staveSharingGroup);
181 : }
182 :
183 10 : partIdx = nextPartIdx;
184 10 : }
185 :
186 10 : return staveSharingGroups;
187 0 : }
188 :
189 10 : void EditStaveSharing::createSharedParts(Transaction& tx, const StaveSharingGroups& groups, Score* score)
190 : {
191 20 : for (const StaveSharingGroup& group : groups) {
192 10 : SharedPart* sharedPart = nullptr;
193 30 : for (Part* part : group) {
194 20 : if (part->sharedPart() && !sharedPart) {
195 0 : sharedPart = part->sharedPart();
196 0 : break;
197 : }
198 : }
199 :
200 10 : Part* firstPart = group.front();
201 10 : const Instrument* instr = firstPart->instrument();
202 :
203 10 : if (!sharedPart) {
204 10 : size_t idx = muse::indexOf(score->parts(), firstPart);
205 10 : sharedPart = createSharedPart(score, idx, instr);
206 : }
207 :
208 30 : for (Part* originPart : group) {
209 20 : SharedPart* existingSharedPart = originPart->sharedPart();
210 20 : if (existingSharedPart && existingSharedPart != sharedPart) {
211 0 : disconnectSharedPart(tx, existingSharedPart, originPart);
212 0 : connectSharedPart(tx, sharedPart, originPart);
213 20 : } else if (!existingSharedPart) {
214 20 : connectSharedPart(tx, sharedPart, originPart);
215 : }
216 : }
217 : }
218 10 : }
219 :
220 10 : SharedPart* EditStaveSharing::createSharedPart(Score* score, size_t idx, const Instrument* instr)
221 : {
222 10 : SharedPart* sharedPart = new SharedPart(score);
223 10 : score->undoInsertPart(sharedPart, idx);
224 :
225 10 : Instrument* instrument = new Instrument(*instr);
226 10 : InstrumentLabel& label = instrument->instrumentLabel();
227 10 : label.setAllowGroupName(false);
228 10 : label.setNumber(0);
229 10 : label.setShowNumberLong(false);
230 10 : label.setShowNumberShort(false);
231 :
232 10 : sharedPart->setInstrument(instrument);
233 :
234 10 : return sharedPart;
235 : }
236 :
237 20 : void EditStaveSharing::addStaffToSharedPart(SharedPart* sharedPart, const Part* originPart, const KeyList& keyList,
238 : const StaffType* staffType)
239 : {
240 20 : Staff* staff = Factory::createStaff(sharedPart);
241 20 : staff->setStaffType(Fraction(0, 1), *staffType);
242 20 : staff->setPart(sharedPart);
243 :
244 20 : Score* score = sharedPart->score();
245 20 : staff->setScore(score);
246 :
247 20 : staff_idx_t relStaffIdx = sharedPart->nstaves();
248 20 : staff->setDefaultClefType(originPart->instrument()->clefType(0));
249 :
250 20 : score->undoInsertStaff(staff, relStaffIdx);
251 :
252 20 : staff_idx_t absStaffIdx = track2staff(sharedPart->trackRange().startTrack) + relStaffIdx;
253 20 : score->adjustKeySigs(absStaffIdx, absStaffIdx + 1, keyList);
254 20 : }
255 :
256 20 : void EditStaveSharing::connectSharedPart(Transaction& tx, SharedPart* sharedPart, Part* originPart)
257 : {
258 20 : Score* score = originPart->score();
259 20 : tx.push(new ConnectSharedPart(sharedPart, originPart));
260 20 : addStaffToSharedPart(sharedPart, originPart, score->keyList(), originPart->staff(0)->staffType());
261 20 : }
262 :
263 2 : void EditStaveSharing::disconnectSharedPart(Transaction& tx, SharedPart* sharedPart, Part* originPart)
264 : {
265 2 : Score* score = originPart->score();
266 2 : tx.push(new DisconnectSharedPart(sharedPart, originPart));
267 2 : score->undoRemoveStaff(sharedPart->staves().back());
268 2 : }
269 :
270 8 : void EditStaveSharing::handleRemovePart(Transaction& tx, Part* part)
271 : {
272 8 : Score* score = part->score();
273 :
274 8 : if (SharedPart* sharedPart = part->sharedPart()) {
275 2 : disconnectSharedPart(tx, sharedPart, part);
276 2 : if (sharedPart->originParts().empty()) {
277 1 : score->cmdRemovePart(sharedPart);
278 : }
279 6 : } else if (part->isSharedPart()) {
280 2 : sharedPart = toSharedPart(part);
281 2 : std::vector<Part*> originParts = sharedPart->originParts();
282 4 : for (Part* originPart : originParts) {
283 2 : tx.push(new DisconnectSharedPart(sharedPart, originPart));
284 : }
285 2 : }
286 8 : }
|