LCOV - code coverage report
Current view: top level - src/engraving/editing - editstavesharing.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 85.3 % 143 122
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) 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 : }
        

Generated by: LCOV version 2.0-1