LCOV - code coverage report
Current view: top level - src/notation/internal - notationparts.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 772 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              : #include "notationparts.h"
      23              : 
      24              : #include "translation.h"
      25              : 
      26              : #include "engraving/dom/barline.h"
      27              : #include "engraving/dom/excerpt.h"
      28              : #include "engraving/dom/factory.h"
      29              : #include "engraving/dom/instrument.h"
      30              : #include "engraving/dom/page.h"
      31              : #include "engraving/editing/addremoveelement.h"
      32              : #include "engraving/editing/editexcerpt.h"
      33              : #include "engraving/editing/editpagelocks.h"
      34              : #include "engraving/editing/editpart.h"
      35              : #include "engraving/editing/editstaff.h"
      36              : #include "engraving/editing/editstavesharing.h"
      37              : #include "engraving/editing/editsystemlocks.h"
      38              : #include "engraving/editing/transaction/transaction.h"
      39              : #include "engraving/editing/transpose.h"
      40              : 
      41              : #include "igetscore.h"
      42              : #include "inotationnoteinput.h" // IWYU pragma: keep
      43              : 
      44              : #include "log.h"
      45              : 
      46              : using namespace muse;
      47              : using namespace muse::async;
      48              : using namespace mu::notation;
      49              : using namespace mu::engraving;
      50              : 
      51              : static const mu::engraving::Fraction DEFAULT_TICK = mu::engraving::Fraction(0, 1);
      52              : 
      53            0 : NotationParts::NotationParts(IGetScore* getScore, INotationInteractionPtr interaction, INotationUndoStackPtr undoStack,
      54            0 :                              INotationStylePtr style)
      55            0 :     : m_getScore(getScore), m_undoStack(undoStack), m_interaction(interaction), m_style(style)
      56              : {
      57            0 :     m_getScore->scoreInited().onNotify(this, [this]() {
      58            0 :         listenUndoStackChanges();
      59            0 :         listenStyleChanges();
      60            0 :     });
      61            0 : }
      62              : 
      63            0 : NotifyList<const Part*> NotationParts::partList() const
      64              : {
      65            0 :     NotifyList<const Part*> result;
      66            0 :     result.setNotify(m_partChangedNotifier.notify());
      67              : 
      68            0 :     if (Score* _score = score()) {
      69            0 :         for (const Part* part: _score->parts()) {
      70            0 :             result.push_back(part);
      71              :         }
      72              :     }
      73              : 
      74            0 :     return result;
      75            0 : }
      76              : 
      77            0 : NotifyList<const Staff*> NotationParts::staffList(const ID& partId) const
      78              : {
      79            0 :     NotifyList<const Staff*> result;
      80            0 :     ChangedNotifier<const Staff*>& notifier = m_staffChangedNotifierMap[partId];
      81            0 :     result.setNotify(notifier.notify());
      82              : 
      83            0 :     const Part* part = this->part(partId);
      84            0 :     if (!part) {
      85            0 :         return result;
      86              :     }
      87              : 
      88            0 :     for (const Staff* staff: part->staves()) {
      89            0 :         result.push_back(staff);
      90              :     }
      91              : 
      92            0 :     return result;
      93            0 : }
      94              : 
      95            0 : bool NotationParts::hasParts() const
      96              : {
      97            0 :     const Score* score = this->score();
      98            0 :     if (!score) {
      99            0 :         return false;
     100              :     }
     101              : 
     102            0 :     return !score->parts().empty();
     103              : }
     104              : 
     105            0 : const Part* NotationParts::part(const ID& partId) const
     106              : {
     107            0 :     return partModifiable(partId);
     108              : }
     109              : 
     110            0 : bool NotationParts::partExists(const ID& partId) const
     111              : {
     112            0 :     return part(partId) != nullptr;
     113              : }
     114              : 
     115            0 : const Staff* NotationParts::staff(const ID& staffId) const
     116              : {
     117            0 :     return staffModifiable(staffId);
     118              : }
     119              : 
     120            0 : bool NotationParts::staffExists(const ID& staffId) const
     121              : {
     122            0 :     return staff(staffId) != nullptr;
     123              : }
     124              : 
     125            0 : StaffConfig NotationParts::staffConfig(const ID& staffId, Fraction tick) const
     126              : {
     127            0 :     StaffConfig config;
     128            0 :     Staff* staff = staffModifiable(staffId);
     129            0 :     if (!staff) {
     130            0 :         return config;
     131              :     }
     132              : 
     133            0 :     mu::engraving::StaffType* staffType = staff->staffType(tick);
     134            0 :     if (!staffType) {
     135            0 :         return config;
     136              :     }
     137              : 
     138            0 :     config.visible = staff->visible();
     139            0 :     config.userDistance = staff->userDist();
     140            0 :     config.cutaway = staff->cutaway();
     141            0 :     config.hideSystemBarline = staff->hideSystemBarLine();
     142            0 :     config.mergeMatchingRests = staff->mergeMatchingRests();
     143            0 :     config.clefTypeList = staff->defaultClefType();
     144            0 :     config.reflectTranspositionInLinkedTab = staff->reflectTranspositionInLinkedTab();
     145              : 
     146            0 :     config.staffType = *staffType;
     147              : 
     148            0 :     return config;
     149            0 : }
     150              : 
     151            0 : ScoreOrder NotationParts::scoreOrder() const
     152              : {
     153            0 :     return score()->scoreOrder();
     154              : }
     155              : 
     156            0 : Part* NotationParts::partModifiable(const ID& partId) const
     157              : {
     158            0 :     return score()->partById(partId.toUint64());
     159              : }
     160              : 
     161            0 : Staff* NotationParts::staffModifiable(const ID& staffId) const
     162              : {
     163            0 :     return score()->staffById(staffId.toUint64());
     164              : }
     165              : 
     166            0 : std::vector<Staff*> NotationParts::staves(const IDList& stavesIds) const
     167              : {
     168            0 :     std::vector<Staff*> staves;
     169              : 
     170            0 :     if (stavesIds.empty()) {
     171            0 :         return staves;
     172              :     }
     173              : 
     174            0 :     for (Staff* staff : score()->staves()) {
     175            0 :         if (muse::contains(stavesIds, staff->id())) {
     176            0 :             staves.push_back(staff);
     177              :         }
     178              :     }
     179              : 
     180            0 :     return staves;
     181            0 : }
     182              : 
     183            0 : std::vector<Part*> NotationParts::parts(const IDList& partsIds) const
     184              : {
     185            0 :     std::vector<Part*> parts;
     186              : 
     187            0 :     for (Part* part : score()->parts()) {
     188            0 :         if (std::find(partsIds.cbegin(), partsIds.cend(), part->id()) != partsIds.cend()) {
     189            0 :             parts.push_back(part);
     190              :         }
     191              :     }
     192              : 
     193            0 :     return parts;
     194            0 : }
     195              : 
     196            0 : void NotationParts::setParts(const PartInstrumentList& parts, const ScoreOrder& order)
     197              : {
     198            0 :     TRACEFUNC;
     199              : 
     200            0 :     endInteractionWithScore();
     201            0 :     startEdit(TranslatableString("undoableAction", "Add/remove instruments"));
     202              : 
     203            0 :     doSetScoreOrder(order);
     204            0 :     removeMissingParts(parts);
     205            0 :     insertNewParts(parts);
     206            0 :     updateSoloist(parts);
     207            0 :     sortParts(parts);
     208            0 :     setBracketsAndBarlines();
     209            0 :     updatePartsAndSystemObjectStaves();
     210              : 
     211            0 :     apply();
     212            0 : }
     213              : 
     214            0 : void NotationParts::setScoreOrder(const ScoreOrder& order)
     215              : {
     216            0 :     if (score()->scoreOrder() == order) {
     217            0 :         return;
     218              :     }
     219              : 
     220            0 :     startEdit(TranslatableString("undoableAction", "Set score order"));
     221              : 
     222            0 :     doSetScoreOrder(order);
     223            0 :     setBracketsAndBarlines();
     224              : 
     225            0 :     apply();
     226              : }
     227              : 
     228            0 : void NotationParts::setPartVisible(const ID& partId, bool visible)
     229              : {
     230            0 :     TRACEFUNC;
     231              : 
     232            0 :     Part* part = partModifiable(partId);
     233            0 :     if (!part) {
     234            0 :         return;
     235              :     }
     236              : 
     237            0 :     if (part->show() == visible) {
     238            0 :         return;
     239              :     }
     240              : 
     241              :     const TranslatableString actionName = visible
     242            0 :                                           ? TranslatableString("undoableAction", "Show instrument")
     243            0 :                                           : TranslatableString("undoableAction", "Hide instrument");
     244              : 
     245            0 :     startEdit(actionName);
     246              : 
     247            0 :     mu::engraving::EditPart::setPartVisible(score(), part, visible);
     248              : 
     249            0 :     if (visible) {
     250            0 :         engraving::Transaction& tx = score()->transactionManager()->currentOrDummyTransaction();
     251            0 :         EditPageLocks::removePageLocksContainingMMRests(tx, score());
     252            0 :         EditSystemLocks::removeSystemLocksContainingMMRests(tx, score());
     253              :     }
     254              : 
     255            0 :     apply();
     256              : 
     257            0 :     notifyAboutPartChanged(part);
     258            0 : }
     259              : 
     260            0 : void NotationParts::setPartSharpFlat(const ID& partId, const SharpFlat& sharpFlat)
     261              : {
     262            0 :     TRACEFUNC;
     263              : 
     264            0 :     Part* part = partModifiable(partId);
     265            0 :     if (!part) {
     266            0 :         return;
     267              :     }
     268              : 
     269            0 :     int shartFlatInt = static_cast<int>(sharpFlat);
     270            0 :     if (part->getProperty(mu::engraving::Pid::PREFER_SHARP_FLAT) == shartFlatInt) {
     271            0 :         return;
     272              :     }
     273              : 
     274            0 :     auto calcActionName = [](const SharpFlat& sharpFlat) -> TranslatableString {
     275            0 :         switch (sharpFlat) {
     276            0 :         case SharpFlat::NONE: return TranslatableString("undoableAction", "Set sharps/flats no preference");
     277            0 :         case SharpFlat::FLATS: return TranslatableString("undoableAction", "Set prefer flats");
     278            0 :         case SharpFlat::SHARPS: return TranslatableString("undoableAction", "Set prefer sharps");
     279            0 :         case SharpFlat::AUTO: return TranslatableString("undoableAction", "Set sharps/flats automatic");
     280              :         }
     281            0 :         return TranslatableString("undoableAction", "Set sharps/flats preference");
     282              :     };
     283              : 
     284            0 :     startEdit(calcActionName(sharpFlat));
     285              : 
     286            0 :     mu::engraving::EditPart::setPartSharpFlat(score(), part, sharpFlat);
     287              : 
     288            0 :     apply();
     289              : 
     290            0 :     notifyAboutPartChanged(part);
     291            0 : }
     292              : 
     293            0 : void NotationParts::listenUndoStackChanges()
     294              : {
     295            0 :     if (!score()) {
     296            0 :         return;
     297              :     }
     298              : 
     299            0 :     updatePartsAndSystemObjectStaves();
     300              : 
     301            0 :     m_undoStack->changesChannel().onReceive(this, [this](const ScoreChanges& changes) {
     302            0 :         if (changes.isTextEditing || changes.changedTypes.empty() || m_ignoreUndoStackChanges) {
     303            0 :             return;
     304              :         }
     305              : 
     306              :         static const ElementTypeSet TYPES_TO_CHECK {
     307              :             ElementType::SCORE,
     308              :             ElementType::STAFF,
     309              :             ElementType::PART,
     310              :             ElementType::SHARED_PART,
     311            0 :         };
     312              : 
     313            0 :         for (ElementType type : TYPES_TO_CHECK) {
     314            0 :             if (muse::contains(changes.changedTypes, type)) {
     315            0 :                 updatePartsAndSystemObjectStaves(changes);
     316            0 :                 return;
     317              :             }
     318              :         }
     319              :     });
     320            0 : }
     321              : 
     322            0 : void NotationParts::updatePartsAndSystemObjectStaves(const mu::engraving::ScoreChanges& changes)
     323              : {
     324            0 :     const bool partsChanged = m_parts != score()->parts();
     325            0 :     m_parts = score()->parts();
     326              : 
     327            0 :     std::vector<Staff*> newSystemObjectStaves = score()->systemObjectStavesWithTopStaff();
     328            0 :     const bool systemObjectStavesChanged = m_systemObjectStaves != newSystemObjectStaves;
     329            0 :     m_systemObjectStaves = std::move(newSystemObjectStaves);
     330              : 
     331            0 :     if (partsChanged) {
     332            0 :         m_partChangedNotifier.changed();
     333              :     }
     334              : 
     335            0 :     if (systemObjectStavesChanged) {
     336            0 :         m_systemObjectStavesChanged.notify();
     337              :     }
     338              : 
     339            0 :     if (muse::contains(changes.changedTypes, ElementType::SHARED_PART)) {
     340            0 :         m_sharedPartsChanged.notify();
     341              :     }
     342              : 
     343            0 :     std::vector<Staff*> removedStaves;
     344            0 :     std::vector<Staff*> addedStaves;
     345              : 
     346            0 :     bool stavesSorted = false;
     347              : 
     348            0 :     for (auto& pair : changes.changedObjects) {
     349            0 :         if (muse::contains(pair.second, CommandType::SortStaves)) {
     350            0 :             stavesSorted = true;
     351            0 :             break;
     352              :         }
     353              : 
     354            0 :         if (!pair.first->isStaff()) {
     355            0 :             continue;
     356              :         }
     357              : 
     358            0 :         Staff* staff = toStaff(pair.first);
     359              : 
     360            0 :         if (muse::contains(pair.second, CommandType::RemoveStaff)) {
     361            0 :             removedStaves.push_back(staff);
     362            0 :         } else if (muse::contains(pair.second, CommandType::InsertStaff)) {
     363            0 :             addedStaves.push_back(staff);
     364              :         }
     365              :     }
     366              : 
     367            0 :     if (stavesSorted && !partsChanged) {
     368            0 :         m_partChangedNotifier.changed();
     369            0 :         return;
     370              :     }
     371              : 
     372            0 :     for (Staff* staff : removedStaves) {
     373            0 :         notifyAboutStaffRemoved(staff);
     374              :     }
     375              : 
     376            0 :     for (Staff* staff: addedStaves) {
     377            0 :         notifyAboutStaffAdded(staff);
     378              :     }
     379            0 : }
     380              : 
     381            0 : void NotationParts::listenStyleChanges()
     382              : {
     383            0 :     if (!score()) {
     384            0 :         return;
     385              :     }
     386              : 
     387            0 :     m_style->styleChanged().onNotify(this, [this]{ m_sharedPartsChanged.notify(); });
     388              : }
     389              : 
     390            0 : void NotationParts::doSetScoreOrder(const ScoreOrder& order)
     391              : {
     392            0 :     EditPart::setScoreOrder(score(), order);
     393              : 
     394            0 :     m_scoreOrderChanged.notify();
     395            0 : }
     396              : 
     397            0 : void NotationParts::setInstrumentName(const InstrumentKey& instrumentKey, const QString& name)
     398              : {
     399            0 :     TRACEFUNC;
     400              : 
     401            0 :     Part* part = partModifiable(instrumentKey.partId);
     402            0 :     if (!part) {
     403            0 :         return;
     404              :     }
     405              : 
     406            0 :     const mu::engraving::Instrument* instrument = part->instrument(instrumentKey.tick);
     407            0 :     if (!instrument) {
     408            0 :         return;
     409              :     }
     410              : 
     411            0 :     String newName = String::fromQString(name);
     412            0 :     if (instrument->longName() == newName) {
     413            0 :         return;
     414              :     }
     415              : 
     416            0 :     startEdit(TranslatableString("undoableAction", "Set instrument name"));
     417              : 
     418            0 :     mu::engraving::EditPart::setInstrumentName(score(), part, instrumentKey.tick, name);
     419              : 
     420            0 :     apply();
     421              : 
     422            0 :     notifyAboutPartChanged(part);
     423            0 : }
     424              : 
     425            0 : void NotationParts::setInstrumentAbbreviature(const InstrumentKey& instrumentKey, const QString& abbreviature)
     426              : {
     427            0 :     TRACEFUNC;
     428              : 
     429            0 :     Part* part = partModifiable(instrumentKey.partId);
     430            0 :     if (!part) {
     431            0 :         return;
     432              :     }
     433              : 
     434            0 :     const mu::engraving::Instrument* instrument = part->instrument(instrumentKey.tick);
     435            0 :     if (!instrument) {
     436            0 :         return;
     437              :     }
     438              : 
     439            0 :     if (instrument->abbreviatureAsPlainText() == abbreviature) {
     440            0 :         return;
     441              :     }
     442              : 
     443            0 :     startEdit(TranslatableString("undoableAction", "Set abbreviated instrument name"));
     444              : 
     445            0 :     mu::engraving::EditPart::setInstrumentAbbreviature(score(), part, instrumentKey.tick, abbreviature);
     446              : 
     447            0 :     apply();
     448              : 
     449            0 :     notifyAboutPartChanged(part);
     450            0 : }
     451              : 
     452            0 : void NotationParts::setInstrumentGroupNameOptions(const std::vector<InstrumentKey>& instruments, bool useCustom, const QString& name,
     453              :                                                   const QString& shortName)
     454              : {
     455            0 :     TRACEFUNC;
     456              : 
     457            0 :     startEdit(TranslatableString("undoableAction", "Set instrument custom group name"));
     458              : 
     459            0 :     std::vector<Part*> changedParts;
     460              : 
     461            0 :     for (const InstrumentKey& key : instruments) {
     462            0 :         Part* part = partModifiable(key.partId);
     463            0 :         if (!part) {
     464            0 :             continue;
     465              :         }
     466              : 
     467            0 :         const mu::engraving::Instrument* instrument = part->instrument(key.tick);
     468            0 :         if (!instrument) {
     469            0 :             continue;
     470              :         }
     471              : 
     472            0 :         const InstrumentLabel& instrLabel = instrument->instrumentLabel();
     473            0 :         if (instrLabel.useCustomGroupName() == useCustom && instrLabel.customNameLongGroup() == name
     474            0 :             && instrLabel.customNameShortGroup() == shortName) {
     475            0 :             continue;
     476              :         }
     477              : 
     478            0 :         mu::engraving::EditPart::setInstrumentGroupNameOptions(score(), part, key.tick, useCustom, name, shortName);
     479              : 
     480            0 :         changedParts.push_back(part);
     481              :     }
     482              : 
     483            0 :     apply();
     484              : 
     485            0 :     for (Part* p : changedParts) {
     486            0 :         notifyAboutPartChanged(p);
     487              :     }
     488            0 : }
     489              : 
     490            0 : void NotationParts::setInstrumentNumber(const InstrumentKey& instrumentKey, int v)
     491              : {
     492            0 :     TRACEFUNC;
     493              : 
     494            0 :     Part* part = partModifiable(instrumentKey.partId);
     495            0 :     if (!part) {
     496            0 :         return;
     497              :     }
     498              : 
     499            0 :     const mu::engraving::Instrument* instrument = part->instrument(instrumentKey.tick);
     500            0 :     if (!instrument) {
     501            0 :         return;
     502              :     }
     503              : 
     504            0 :     if (instrument->number() == v) {
     505            0 :         return;
     506              :     }
     507              : 
     508            0 :     startEdit(TranslatableString("undoableAction", "Set instrument number"));
     509              : 
     510            0 :     score()->undo(new mu::engraving::ChangeInstrumentNumber(instrumentKey.tick, part, v));
     511              : 
     512            0 :     apply();
     513              : 
     514            0 :     notifyAboutPartChanged(part);
     515            0 : }
     516              : 
     517            0 : bool NotationParts::setVoiceVisible(const ID& staffId, int voiceIndex, bool visible)
     518              : {
     519            0 :     TRACEFUNC;
     520              : 
     521            0 :     Staff* staff = staffModifiable(staffId);
     522            0 :     if (!staff) {
     523            0 :         return false;
     524              :     }
     525              : 
     526              :     const TranslatableString actionName = visible
     527            0 :                                           ? TranslatableString("undoableAction", "Show voice %1").arg(voiceIndex + 1)
     528            0 :                                           : TranslatableString("undoableAction", "Hide voice %1").arg(voiceIndex + 1);
     529              : 
     530            0 :     startEdit(actionName);
     531              : 
     532            0 :     bool result = EditPart::setVoiceVisible(score(), staff, voiceIndex, visible);
     533            0 :     if (!result) {
     534            0 :         rollback();
     535            0 :         return false;
     536              :     }
     537              : 
     538              :     //! HACK: Excerpt::setVoiceVisible recreates the staff,
     539              :     //! so later in listenUndoStackChanges() we will call notifyAboutStaffRemoved() and notifyAboutStaffAdded(),
     540              :     //! which will result in the wrong UI state in the Layout panel.
     541              :     //! We should not recreate the staff here, only update it
     542            0 :     m_ignoreUndoStackChanges = true;
     543            0 :     apply();
     544            0 :     m_ignoreUndoStackChanges = false;
     545              : 
     546            0 :     Staff* newStaff = staffModifiable(staffId);
     547            0 :     notifyAboutStaffChanged(newStaff);
     548              : 
     549            0 :     return true;
     550            0 : }
     551              : 
     552            0 : void NotationParts::setStaffVisible(const ID& staffId, bool visible)
     553              : {
     554            0 :     TRACEFUNC;
     555              : 
     556            0 :     Staff* staff = staffModifiable(staffId);
     557            0 :     if (!staff) {
     558            0 :         return;
     559              :     }
     560              : 
     561            0 :     StaffConfig config = staffConfig(staffId);
     562            0 :     if (config.visible == visible) {
     563            0 :         return;
     564              :     }
     565              : 
     566              :     const TranslatableString actionName = visible
     567            0 :                                           ? TranslatableString("undoableAction", "Show staff")
     568            0 :                                           : TranslatableString("undoableAction", "Hide staff");
     569              : 
     570            0 :     startEdit(actionName);
     571              : 
     572            0 :     config.visible = visible;
     573            0 :     doSetStaffConfig(staff, config);
     574              : 
     575            0 :     if (visible) {
     576            0 :         engraving::Transaction& tx = score()->transactionManager()->currentOrDummyTransaction();
     577            0 :         EditPageLocks::removePageLocksContainingMMRests(tx, score());
     578            0 :         EditSystemLocks::removeSystemLocksContainingMMRests(tx, score());
     579              :     }
     580              : 
     581            0 :     apply();
     582              : 
     583            0 :     notifyAboutStaffChanged(staff);
     584            0 : }
     585              : 
     586            0 : void NotationParts::setStaffType(const ID& staffId, StaffTypeId type)
     587              : {
     588            0 :     TRACEFUNC;
     589              : 
     590            0 :     Staff* staff = staffModifiable(staffId);
     591            0 :     const mu::engraving::StaffType* staffType = mu::engraving::StaffType::preset(type);
     592              : 
     593            0 :     if (!staff || !staffType) {
     594            0 :         return;
     595              :     }
     596              : 
     597            0 :     if (staff->staffType(DEFAULT_TICK) == staffType) {
     598            0 :         return;
     599              :     }
     600              : 
     601            0 :     startEdit(TranslatableString("undoableAction", "Set staff type"));
     602              : 
     603            0 :     mu::engraving::EditPart::setStaffType(score(), staff, type);
     604              : 
     605            0 :     apply();
     606              : 
     607            0 :     notifyAboutStaffChanged(staff);
     608            0 : }
     609              : 
     610            0 : void NotationParts::setStaffConfig(const ID& staffId, const StaffConfig& config, Fraction tick)
     611              : {
     612            0 :     TRACEFUNC;
     613              : 
     614            0 :     Staff* staff = staffModifiable(staffId);
     615            0 :     if (!staff) {
     616            0 :         return;
     617              :     }
     618              : 
     619            0 :     if (staffConfig(staffId) == config) {
     620            0 :         return;
     621              :     }
     622              : 
     623            0 :     startEdit(TranslatableString("undoableAction", "Edit staff properties"));
     624              : 
     625            0 :     doSetStaffConfig(staff, config, tick);
     626              : 
     627            0 :     apply();
     628              : 
     629            0 :     notifyAboutStaffChanged(staff);
     630            0 : }
     631              : 
     632            0 : void NotationParts::setSharedPartEnabled(const muse::ID& partId, bool enable)
     633              : {
     634            0 :     TRACEFUNC;
     635              : 
     636            0 :     Part* part = partModifiable(partId);
     637            0 :     if (!part) {
     638            0 :         return;
     639              :     }
     640              : 
     641            0 :     if (part->getProperty(Pid::SHARED_PART_ENABLED).toBool() == enable) {
     642            0 :         return;
     643              :     }
     644              : 
     645              :     const TranslatableString actionName = enable
     646            0 :                                           ? TranslatableString("undoableAction", "Enable shared staff")
     647            0 :                                           : TranslatableString("undoableAction", "Disable shared staff");
     648              : 
     649            0 :     startEdit(actionName);
     650              : 
     651            0 :     part->undoChangeProperty(Pid::SHARED_PART_ENABLED, enable);
     652              : 
     653            0 :     apply();
     654              : 
     655            0 :     notifyAboutPartChanged(part);
     656            0 : }
     657              : 
     658            0 : bool NotationParts::appendStaff(Staff* staff, const ID& destinationPartId)
     659              : {
     660            0 :     TRACEFUNC;
     661              : 
     662            0 :     IF_ASSERT_FAILED(staff) {
     663            0 :         return false;
     664              :     }
     665              : 
     666            0 :     Part* destinationPart = partModifiable(destinationPartId);
     667            0 :     if (!destinationPart) {
     668            0 :         return false;
     669              :     }
     670              : 
     671            0 :     startEdit(TranslatableString("undoableAction", "Add staff"));
     672            0 :     doAppendStaff(staff, destinationPart);
     673            0 :     apply();
     674              : 
     675            0 :     return true;
     676            0 : }
     677              : 
     678            0 : bool NotationParts::appendStaffLinkedToMaster(Staff* staff, Staff* masterSourceStaff, const muse::ID& destinationPartId)
     679              : {
     680            0 :     TRACEFUNC;
     681              : 
     682            0 :     IF_ASSERT_FAILED(staff && masterSourceStaff) {
     683            0 :         return false;
     684              :     }
     685              : 
     686            0 :     Part* destinationPart = partModifiable(destinationPartId);
     687            0 :     if (!destinationPart) {
     688            0 :         return false;
     689              :     }
     690              : 
     691            0 :     startEdit(TranslatableString("undoableAction", "Add staff"));
     692              : 
     693            0 :     doAppendStaff(staff, destinationPart, /*createRests*/ false);
     694            0 :     score()->undo(new mu::engraving::Link(staff, masterSourceStaff));
     695              : 
     696            0 :     mu::engraving::Excerpt::cloneStaff2(masterSourceStaff, staff, Fraction(0, 1), score()->endTick());
     697              : 
     698            0 :     return true;
     699            0 : }
     700              : 
     701            0 : bool NotationParts::appendLinkedStaff(Staff* staff, const muse::ID& sourceStaffId, const muse::ID& destinationPartId)
     702              : {
     703            0 :     TRACEFUNC;
     704              : 
     705            0 :     IF_ASSERT_FAILED(staff) {
     706            0 :         return false;
     707              :     }
     708              : 
     709            0 :     Staff* sourceStaff = staffModifiable(sourceStaffId);
     710            0 :     Part* destinationPart = partModifiable(destinationPartId);
     711            0 :     if (!sourceStaff || !destinationPart) {
     712            0 :         return false;
     713              :     }
     714              : 
     715            0 :     startEdit(TranslatableString("undoableAction", "Add linked staff"));
     716              : 
     717            0 :     doAppendStaff(staff, destinationPart, false);
     718              : 
     719              :     ///! NOTE: need to unlink before linking
     720            0 :     staff->setLinks(nullptr);
     721            0 :     mu::engraving::Excerpt::cloneStaff(sourceStaff, staff);
     722              : 
     723            0 :     apply();
     724              : 
     725            0 :     return true;
     726            0 : }
     727              : 
     728            0 : void NotationParts::insertPart(Part* part, size_t index)
     729              : {
     730            0 :     TRACEFUNC;
     731              : 
     732            0 :     if (!part) {
     733            0 :         return;
     734              :     }
     735              : 
     736            0 :     startEdit(TranslatableString("undoableAction", "Add instrument"));
     737              : 
     738            0 :     engraving::Transaction& tx = score()->transactionManager()->currentOrDummyTransaction();
     739            0 :     EditPageLocks::removePageLocksContainingMMRests(tx, score());
     740            0 :     EditSystemLocks::removeSystemLocksContainingMMRests(tx, score());
     741              : 
     742            0 :     doInsertPart(part, index);
     743              : 
     744            0 :     apply();
     745              : 
     746            0 :     notifyAboutPartAdded(part);
     747            0 : }
     748              : 
     749            0 : void NotationParts::replacePart(const ID& partId, Part* newPart)
     750              : {
     751            0 :     TRACEFUNC;
     752              : 
     753            0 :     Part* part = partModifiable(partId);
     754            0 :     if (!part || !newPart) {
     755            0 :         return;
     756              :     }
     757              : 
     758            0 :     startEdit(TranslatableString("undoableAction", "Reset instrument formatting"));
     759              : 
     760            0 :     size_t partIndex = muse::indexOf(score()->parts(), part);
     761            0 :     score()->cmdRemovePart(part);
     762            0 :     doInsertPart(newPart, partIndex);
     763              : 
     764            0 :     apply();
     765              : 
     766            0 :     notifyAboutPartReplaced(part, newPart);
     767            0 : }
     768              : 
     769            0 : void NotationParts::replaceInstrument(const InstrumentKey& instrumentKey, const Instrument& newInstrument, const StaffType* newStaffType)
     770              : {
     771            0 :     TRACEFUNC;
     772              : 
     773            0 :     Part* part = partModifiable(instrumentKey.partId);
     774            0 :     if (!part) {
     775            0 :         return;
     776              :     }
     777              : 
     778            0 :     startEdit(TranslatableString("undoableAction", "Replace instrument"));
     779              : 
     780            0 :     if (isMainInstrumentForPart(instrumentKey, part)) {
     781            0 :         mu::engraving::EditPart::replacePartInstrument(score(), part, newInstrument, newStaffType);
     782              :     } else {
     783            0 :         if (!mu::engraving::EditPart::replaceInstrumentAtTick(score(), part, instrumentKey.tick, newInstrument)) {
     784            0 :             rollback();
     785            0 :             return;
     786              :         }
     787              :     }
     788              : 
     789            0 :     apply();
     790              : 
     791            0 :     notifyAboutPartChanged(part);
     792            0 : }
     793              : 
     794            0 : void NotationParts::replaceDrumset(const InstrumentKey& instrumentKey, const Drumset& newDrumset, bool undoable)
     795              : {
     796            0 :     Part* part = partModifiable(instrumentKey.partId);
     797            0 :     if (!part) {
     798            0 :         return;
     799              :     }
     800              : 
     801            0 :     if (undoable) {
     802            0 :         startEdit(TranslatableString("undoableAction", "Edit drumset"));
     803              : 
     804            0 :         for (auto pair : part->instruments()) {
     805            0 :             Instrument* instrument = pair.second;
     806            0 :             if (instrument && instrument->drumset() && instrument->id() == instrumentKey.instrumentId) {
     807            0 :                 EditPart::replaceDrumset(score(), part, Fraction::fromTicks(pair.first), newDrumset);
     808              :             }
     809              :         }
     810              : 
     811            0 :         apply();
     812              :     } else {
     813            0 :         for (auto pair : part->instruments()) {
     814            0 :             Instrument* instrument = pair.second;
     815            0 :             if (instrument && instrument->drumset() && instrument->id() == instrumentKey.instrumentId) {
     816            0 :                 instrument->setDrumset(&newDrumset);
     817              :             }
     818              :         }
     819              :     }
     820              : 
     821            0 :     notifyAboutPartChanged(part);
     822            0 :     m_interaction->noteInput()->stateChanged().notify();
     823              : }
     824              : 
     825            0 : const std::vector<Staff*>& NotationParts::systemObjectStaves() const
     826              : {
     827            0 :     return m_systemObjectStaves;
     828              : }
     829              : 
     830            0 : muse::async::Notification NotationParts::systemObjectStavesChanged() const
     831              : {
     832            0 :     return m_systemObjectStavesChanged;
     833              : }
     834              : 
     835            0 : void NotationParts::addSystemObjects(const muse::IDList& stavesIds)
     836              : {
     837            0 :     std::vector<Staff*> staves = this->staves(stavesIds);
     838            0 :     if (staves.empty()) {
     839            0 :         return;
     840              :     }
     841              : 
     842            0 :     startEdit(TranslatableString("undoableAction", "Add system markings"));
     843              : 
     844            0 :     EditPart::addSystemObjects(score(), staves);
     845              : 
     846            0 :     apply();
     847            0 : }
     848              : 
     849            0 : void NotationParts::removeSystemObjects(const IDList& stavesIds)
     850              : {
     851            0 :     std::vector<Staff*> staves = this->staves(stavesIds);
     852            0 :     if (staves.empty()) {
     853            0 :         return;
     854              :     }
     855              : 
     856            0 :     startEdit(TranslatableString("undoableAction", "Remove system markings"));
     857              : 
     858            0 :     EditPart::removeSystemObjects(score(), staves);
     859              : 
     860            0 :     apply();
     861            0 : }
     862              : 
     863            0 : void NotationParts::moveSystemObjects(const ID& sourceStaffId, const ID& destinationStaffId)
     864              : {
     865            0 :     Staff* srcStaff = staffModifiable(sourceStaffId);
     866            0 :     if (!srcStaff || !srcStaff->isSystemObjectStaff()) {
     867            0 :         return;
     868              :     }
     869              : 
     870            0 :     Staff* dstStaff = staffModifiable(destinationStaffId);
     871            0 :     if (!dstStaff) {
     872            0 :         return;
     873              :     }
     874              : 
     875            0 :     startEdit(TranslatableString("undoableAction", "Move system markings"));
     876              : 
     877            0 :     EditPart::moveSystemObjects(score(), srcStaff, dstStaff);
     878              : 
     879            0 :     apply();
     880              : }
     881              : 
     882            0 : void NotationParts::moveSystemObjectLayerBelowBottomStaff()
     883              : {
     884            0 :     startEdit(TranslatableString("undoableAction", "Add system object layer below the bottom staff"));
     885              : 
     886            0 :     score()->undoChangeStyleVal(Sid::systemObjectsBelowBottomStaff, true);
     887              : 
     888            0 :     apply();
     889            0 : }
     890              : 
     891            0 : void NotationParts::moveSystemObjectLayerAboveBottomStaff()
     892              : {
     893            0 :     startEdit(TranslatableString("undoableAction", "Remove system object layer below the bottom staff"));
     894              : 
     895            0 :     score()->undoChangeStyleVal(Sid::systemObjectsBelowBottomStaff, false);
     896              : 
     897            0 :     apply();
     898            0 : }
     899              : 
     900            0 : void NotationParts::toggleStaveSharing(bool on)
     901              : {
     902            0 :     undoStack()->transaction(TranslatableString("undoableAction", "Toggle stave sharing"), [&](engraving::Transaction& tx) {
     903            0 :         EditStaveSharing::toggleStaveSharing(tx, score(), on);
     904            0 :     });
     905            0 :     m_partsChanged.notify();
     906            0 : }
     907              : 
     908            0 : Notification NotationParts::sharedPartsChanged() const
     909              : {
     910            0 :     return m_sharedPartsChanged;
     911              : }
     912              : 
     913            0 : Notification NotationParts::partsChanged() const
     914              : {
     915            0 :     return m_partsChanged;
     916              : }
     917              : 
     918            0 : Notification NotationParts::scoreOrderChanged() const
     919              : {
     920            0 :     return m_scoreOrderChanged;
     921              : }
     922              : 
     923            0 : mu::engraving::Score* NotationParts::score() const
     924              : {
     925            0 :     return m_getScore->score();
     926              : }
     927              : 
     928            0 : INotationUndoStackPtr NotationParts::undoStack() const
     929              : {
     930            0 :     return m_undoStack;
     931              : }
     932              : 
     933            0 : void NotationParts::startEdit(const muse::TranslatableString& actionName)
     934              : {
     935            0 :     undoStack()->prepareChanges(actionName);
     936            0 : }
     937              : 
     938            0 : void NotationParts::apply()
     939              : {
     940            0 :     undoStack()->commitChanges();
     941              : 
     942            0 :     m_partsChanged.notify();
     943            0 : }
     944              : 
     945            0 : void NotationParts::rollback()
     946              : {
     947            0 :     undoStack()->rollbackChanges();
     948            0 : }
     949              : 
     950            0 : void NotationParts::removeParts(const IDList& partsIds)
     951              : {
     952            0 :     TRACEFUNC;
     953              : 
     954            0 :     std::vector<Part*> partsToRemove = parts(partsIds);
     955            0 :     if (partsToRemove.empty()) {
     956            0 :         return;
     957              :     }
     958              : 
     959            0 :     endInteractionWithScore();
     960            0 :     startEdit(TranslatableString("undoableAction", "Remove instruments"));
     961              : 
     962            0 :     doRemoveParts(partsToRemove);
     963              : 
     964            0 :     PartInstrumentList parts;
     965            0 :     for (mu::engraving::Part* part: score()->parts()) {
     966            0 :         PartInstrument pi;
     967            0 :         pi.isExistingPart = true;
     968            0 :         pi.partId = part->id();
     969            0 :         parts << pi;
     970            0 :     }
     971              : 
     972            0 :     sortParts(parts);
     973              : 
     974            0 :     setBracketsAndBarlines();
     975              : 
     976            0 :     apply();
     977              : 
     978            0 :     for (const Part* part : partsToRemove) {
     979            0 :         notifyAboutPartRemoved(part);
     980              :     }
     981            0 : }
     982              : 
     983            0 : void NotationParts::doRemoveParts(const std::vector<Part*>& parts)
     984              : {
     985            0 :     TRACEFUNC;
     986              : 
     987            0 :     EditPart::removeParts(score(), parts);
     988              : 
     989            0 :     onPartsRemoved(parts);
     990            0 : }
     991              : 
     992            0 : void NotationParts::onPartsRemoved(const std::vector<Part*>&)
     993              : {
     994            0 : }
     995              : 
     996            0 : void NotationParts::doAppendStaff(Staff* staff, Part* destinationPart, bool createRests)
     997              : {
     998            0 :     staff_idx_t staffLocalIndex = destinationPart->nstaves();
     999            0 :     mu::engraving::KeyList keyList = *destinationPart->staff(staffLocalIndex - 1)->keyList();
    1000              : 
    1001            0 :     staff->setScore(score());
    1002            0 :     staff->setPart(destinationPart);
    1003              : 
    1004            0 :     insertStaff(staff, staffLocalIndex, createRests);
    1005              : 
    1006            0 :     staff_idx_t staffGlobalIndex = staff->idx();
    1007            0 :     score()->adjustKeySigs(staffGlobalIndex, staffGlobalIndex + 1, keyList);
    1008              : 
    1009            0 :     score()->updateBracesAndBarlines(destinationPart, staffLocalIndex);
    1010            0 :     setBracketsAndBarlines();
    1011              : 
    1012            0 :     destinationPart->instrument()->setClefType(staffLocalIndex, staff->defaultClefType());
    1013            0 : }
    1014              : 
    1015            0 : void NotationParts::doSetStaffConfig(Staff* staff, const StaffConfig& config, Fraction tick)
    1016              : {
    1017            0 :     mu::engraving::StaffType* staffType = staff->staffType(tick);
    1018            0 :     if (!staffType) {
    1019            0 :         return;
    1020              :     }
    1021              : 
    1022            0 :     score()->undo(new mu::engraving::ChangeStaff(staff, config.visible, config.clefTypeList, config.userDistance, config.cutaway,
    1023            0 :                                                  config.hideSystemBarline, config.mergeMatchingRests,
    1024            0 :                                                  config.reflectTranspositionInLinkedTab));
    1025              : 
    1026            0 :     score()->undo(new mu::engraving::ChangeStaffType(staff, config.staffType, tick));
    1027              : }
    1028              : 
    1029            0 : void NotationParts::doInsertPart(Part* part, size_t index)
    1030              : {
    1031            0 :     TRACEFUNC;
    1032              : 
    1033            0 :     std::vector<Staff*> stavesCopy(part->staves());
    1034            0 :     part->clearStaves();
    1035              : 
    1036            0 :     mu::engraving::InstrumentList instrumentsCopy = part->instruments();
    1037            0 :     part->setInstruments({});
    1038              : 
    1039            0 :     if (Excerpt* excerpt = score()->excerpt()) {
    1040            0 :         score()->undo(new AddPartToExcerpt(excerpt, part, index));
    1041              :     } else {
    1042            0 :         score()->undoInsertPart(part, index);
    1043              :     }
    1044              : 
    1045            0 :     for (auto it = instrumentsCopy.cbegin(); it != instrumentsCopy.cend(); ++it) {
    1046            0 :         part->setInstrument(new Instrument(*it->second), it->first);
    1047              :     }
    1048              : 
    1049            0 :     mu::engraving::Fraction startTick = score()->firstMeasure()->tick();
    1050            0 :     mu::engraving::Fraction endTick = score()->lastMeasure()->endTick();
    1051              : 
    1052            0 :     for (size_t staffIndex = 0; staffIndex < stavesCopy.size(); ++staffIndex) {
    1053            0 :         Staff* staff = stavesCopy[staffIndex];
    1054              : 
    1055            0 :         Staff* staffCopy = engraving::Factory::createStaff(part);
    1056            0 :         staffCopy->setId(staff->id());
    1057            0 :         staffCopy->setScore(score());
    1058            0 :         staffCopy->setPart(part);
    1059            0 :         staffCopy->init(staff);
    1060              : 
    1061            0 :         insertStaff(staffCopy, static_cast<int>(staffIndex), /* createRests = */ false);
    1062            0 :         score()->undo(new mu::engraving::Link(staffCopy, staff));
    1063              : 
    1064            0 :         mu::engraving::Excerpt::cloneStaff2(staff, staffCopy, startTick, endTick);
    1065              :     }
    1066              : 
    1067            0 :     score()->remapBracketsAndBarlines();
    1068            0 : }
    1069              : 
    1070            0 : void NotationParts::removeStaves(const IDList& stavesIds)
    1071              : {
    1072            0 :     TRACEFUNC;
    1073              : 
    1074            0 :     std::vector<Staff*> stavesToRemove = staves(stavesIds);
    1075            0 :     if (stavesToRemove.empty()) {
    1076            0 :         return;
    1077              :     }
    1078              : 
    1079            0 :     endInteractionWithScore();
    1080            0 :     startEdit(TranslatableString("undoableAction", "Remove staves"));
    1081              : 
    1082            0 :     EditPart::removeStaves(score(), stavesToRemove);
    1083              : 
    1084            0 :     apply();
    1085            0 : }
    1086              : 
    1087            0 : void NotationParts::moveParts(const IDList& sourcePartsIds, const ID& destinationPartId, InsertMode mode)
    1088              : {
    1089            0 :     TRACEFUNC;
    1090              : 
    1091            0 :     std::vector<Part*> sourceParts = parts(sourcePartsIds);
    1092            0 :     if (sourceParts.empty()) {
    1093            0 :         return;
    1094              :     }
    1095              : 
    1096            0 :     Part* destinationPart = partModifiable(destinationPartId);
    1097            0 :     if (!destinationPart) {
    1098            0 :         return;
    1099              :     }
    1100              : 
    1101            0 :     endInteractionWithScore();
    1102            0 :     startEdit(TranslatableString("undoableAction", "Move instruments"));
    1103              : 
    1104            0 :     EditPart::moveParts(score(), sourceParts, destinationPart, mode == InsertMode::After);
    1105              : 
    1106            0 :     apply();
    1107            0 : }
    1108              : 
    1109            0 : void NotationParts::moveStaves(const IDList& sourceStavesIds, const ID& destinationStaffId, InsertMode mode)
    1110              : {
    1111            0 :     TRACEFUNC;
    1112              : 
    1113            0 :     Staff* destinationStaff = staffModifiable(destinationStaffId);
    1114            0 :     if (!destinationStaff) {
    1115            0 :         return;
    1116              :     }
    1117              : 
    1118            0 :     std::vector<Staff*> staves = this->staves(sourceStavesIds);
    1119            0 :     if (staves.empty()) {
    1120            0 :         return;
    1121              :     }
    1122              : 
    1123            0 :     for (const Staff* staff : staves) {
    1124            0 :         IF_ASSERT_FAILED_X(staff->part() == destinationStaff->part(), "All staves must have the same part!") {
    1125            0 :             return;
    1126              :         }
    1127              :     }
    1128              : 
    1129            0 :     endInteractionWithScore();
    1130            0 :     startEdit(TranslatableString("undoableAction", "Move staves"));
    1131              : 
    1132            0 :     EditPart::moveStaves(score(), staves, destinationStaff, mode == InsertMode::After);
    1133              : 
    1134            0 :     apply();
    1135            0 : }
    1136              : 
    1137            0 : void NotationParts::appendStaves(Part* part, const InstrumentTemplate& templ, const mu::engraving::KeyList& keyList)
    1138              : {
    1139            0 :     TRACEFUNC;
    1140              : 
    1141            0 :     IF_ASSERT_FAILED(part) {
    1142            0 :         return;
    1143              :     }
    1144              : 
    1145            0 :     for (staff_idx_t staffIndex = 0; staffIndex < templ.staffCount; ++staffIndex) {
    1146            0 :         Staff* staff = engraving::Factory::createStaff(part);
    1147            0 :         const mu::engraving::StaffType* staffType = templ.staffTypePreset;
    1148            0 :         if (!staffType) {
    1149            0 :             staffType = mu::engraving::StaffType::preset(StaffTypeId::STANDARD);
    1150              :         }
    1151              : 
    1152            0 :         insertStaff(staff, staffIndex);
    1153            0 :         staff->init(&templ, staffType, staffIndex);
    1154              :     }
    1155              : 
    1156            0 :     if (!part->nstaves()) {
    1157            0 :         return;
    1158              :     }
    1159              : 
    1160            0 :     staff_idx_t firstStaffIndex = part->staff(0)->idx();
    1161            0 :     staff_idx_t endStaffIndex = firstStaffIndex + part->nstaves();
    1162            0 :     score()->adjustKeySigs(firstStaffIndex, endStaffIndex, keyList);
    1163            0 : }
    1164              : 
    1165            0 : void NotationParts::insertStaff(Staff* staff, staff_idx_t destinationStaffIndex, bool createRest)
    1166              : {
    1167            0 :     TRACEFUNC;
    1168              : 
    1169            0 :     score()->undoInsertStaff(staff, destinationStaffIndex, createRest);
    1170            0 : }
    1171              : 
    1172            0 : void NotationParts::removeMissingParts(const PartInstrumentList& newParts)
    1173              : {
    1174            0 :     TRACEFUNC;
    1175              : 
    1176            0 :     auto needRemove = [&newParts](const Part* part) {
    1177            0 :         for (const PartInstrument& pi : newParts) {
    1178            0 :             if (pi.partId == part->id()) {
    1179            0 :                 return false;
    1180              :             }
    1181              :         }
    1182              : 
    1183            0 :         return true;
    1184            0 :     };
    1185              : 
    1186            0 :     std::vector<Part*> partsToRemove;
    1187              : 
    1188            0 :     for (Part* part: score()->parts()) {
    1189            0 :         if (needRemove(part)) {
    1190            0 :             partsToRemove.push_back(part);
    1191              :         }
    1192              :     }
    1193              : 
    1194            0 :     doRemoveParts(partsToRemove);
    1195            0 : }
    1196              : 
    1197            0 : void NotationParts::insertNewParts(const PartInstrumentList& parts, const mu::engraving::KeyList& keyList)
    1198              : {
    1199            0 :     TRACEFUNC;
    1200              : 
    1201            0 :     size_t partIdx = 0;
    1202              : 
    1203            0 :     for (const PartInstrument& pi: parts) {
    1204            0 :         if (pi.isExistingPart) {
    1205              :             // Update numbering for existing parts of instruments being added
    1206            0 :             if (isNewInstrument(pi.instrumentTemplate, parts)) {
    1207            0 :                 int instrumentNumber = resolveExistingInstrumentNumber(pi.partId, pi.instrumentTemplate);
    1208            0 :                 if (Part* existingPart = score()->partById(pi.partId)) {
    1209            0 :                     score()->undo(new mu::engraving::ChangeInstrumentNumber(Fraction(-1, 1), existingPart, instrumentNumber));
    1210              :                 }
    1211              :             }
    1212            0 :             ++partIdx;
    1213            0 :             continue;
    1214            0 :         }
    1215              : 
    1216            0 :         Instrument instrument = Instrument::fromTemplate(&pi.instrumentTemplate);
    1217            0 :         const String& longN = instrument.longName();
    1218            0 :         const String& shortN = instrument.shortName();
    1219              : 
    1220            0 :         InstrumentTrait trait = instrument.trait();
    1221            0 :         const String& transp = trait.type == TraitType::Transposition && !trait.isHiddenOnScore ? trait.name : String();
    1222              : 
    1223            0 :         Part* part = new Part(score());
    1224            0 :         part->setSoloist(pi.isSoloist);
    1225            0 :         part->setInstrument(instrument);
    1226              : 
    1227            0 :         part->setLongName(muse::qtrc("notation", longN));
    1228            0 :         part->setShortName(muse::qtrc("notation", shortN));
    1229            0 :         part->setTransposition(muse::qtrc("notation", transp));
    1230              : 
    1231            0 :         int instrumentNumber = resolveNewInstrumentNumber(pi.instrumentTemplate, parts);
    1232            0 :         part->setNumber(instrumentNumber);
    1233              : 
    1234            0 :         if (Excerpt* excerpt = score()->excerpt()) {
    1235            0 :             score()->undo(new AddPartToExcerpt(excerpt, part, partIdx));
    1236              :         } else {
    1237            0 :             score()->undoInsertPart(part, partIdx);
    1238              :         }
    1239            0 :         appendStaves(part, pi.instrumentTemplate, keyList);
    1240            0 :         ++partIdx;
    1241              : 
    1242            0 :         m_partChangedNotifier.itemAdded(part);
    1243            0 :     }
    1244            0 : }
    1245              : 
    1246            0 : void NotationParts::insertNewParts(const PartInstrumentList& parts)
    1247              : {
    1248            0 :     TRACEFUNC;
    1249            0 :     insertNewParts(parts, score()->keyList());
    1250            0 : }
    1251              : 
    1252            0 : void NotationParts::updateSoloist(const PartInstrumentList& parts)
    1253              : {
    1254            0 :     TRACEFUNC;
    1255              : 
    1256            0 :     for (const PartInstrument& pi: parts) {
    1257            0 :         Part* part = partModifiable(pi.partId);
    1258              : 
    1259            0 :         if (pi.isExistingPart && (pi.isSoloist != part->soloist())) {
    1260            0 :             score()->undo(new mu::engraving::SetSoloist(part, pi.isSoloist));
    1261              :         }
    1262              :     }
    1263            0 : }
    1264              : 
    1265            0 : void NotationParts::sortParts(const PartInstrumentList& parts)
    1266              : {
    1267            0 :     TRACEFUNC;
    1268              : 
    1269            0 :     std::vector<mu::engraving::staff_idx_t> staffMapping;
    1270              : 
    1271            0 :     int partIndex = 0;
    1272            0 :     for (const PartInstrument& pi: parts) {
    1273            0 :         mu::engraving::Part* currentPart = pi.isExistingPart ? partModifiable(pi.partId) : score()->parts()[partIndex];
    1274              : 
    1275            0 :         for (mu::engraving::Staff* staff : currentPart->staves()) {
    1276            0 :             mu::engraving::staff_idx_t actualStaffIndex = muse::indexOf(score()->staves(), staff);
    1277            0 :             staffMapping.push_back(actualStaffIndex);
    1278              :         }
    1279            0 :         ++partIndex;
    1280              :     }
    1281              : 
    1282            0 :     score()->undo(new mu::engraving::SortStaves(score(), staffMapping));
    1283            0 : }
    1284              : 
    1285            0 : int NotationParts::countExistingInstruments(const InstrumentTemplate& instrument) const
    1286              : {
    1287            0 :     int count = 0;
    1288              : 
    1289            0 :     for (const Part* part : score()->parts()) {
    1290            0 :         const Instrument* partInstrument = part->instrument();
    1291              : 
    1292            0 :         if (partInstrument->id() == instrument.id) {
    1293            0 :             ++count;
    1294              :         }
    1295              :     }
    1296              : 
    1297            0 :     return count;
    1298              : }
    1299              : 
    1300            0 : bool NotationParts::isNewInstrument(const InstrumentTemplate& instrument, const PartInstrumentList& parts) const
    1301              : {
    1302            0 :     for (const PartInstrument& pi : parts) {
    1303            0 :         if (!pi.isExistingPart && pi.instrumentTemplate.id == instrument.id) {
    1304            0 :             return true;
    1305              :         }
    1306              :     }
    1307              : 
    1308            0 :     return false;
    1309              : }
    1310              : 
    1311            0 : int NotationParts::resolveExistingInstrumentNumber(const muse::ID& partId, const InstrumentTemplate& instrument) const
    1312              : {
    1313            0 :     int count = countExistingInstruments(instrument);
    1314              : 
    1315            0 :     int ordinal = 0;
    1316              : 
    1317            0 :     for (const Part* part : score()->parts()) {
    1318            0 :         const Instrument* partInstrument = part->instrument();
    1319              : 
    1320            0 :         if (partInstrument->id() != instrument.id) {
    1321            0 :             continue;
    1322              :         }
    1323              : 
    1324            0 :         ++ordinal;
    1325              : 
    1326            0 :         if (part->id() == partId) {
    1327            0 :             return ordinal;
    1328              :         }
    1329              :     }
    1330              : 
    1331            0 :     return count;
    1332              : }
    1333              : 
    1334            0 : int NotationParts::resolveNewInstrumentNumber(const InstrumentTemplate& instrument,
    1335              :                                               const PartInstrumentList& allNewInstruments) const
    1336              : {
    1337            0 :     int count = countExistingInstruments(instrument);
    1338              : 
    1339            0 :     if (count > 0) {
    1340            0 :         return count + 1;
    1341              :     }
    1342              : 
    1343            0 :     for (const PartInstrument& partInstrument: allNewInstruments) {
    1344            0 :         const InstrumentTemplate& templ = partInstrument.instrumentTemplate;
    1345              : 
    1346            0 :         if (templ.id == instrument.id) {
    1347            0 :             ++count;
    1348              :         }
    1349              :     }
    1350              : 
    1351            0 :     return count > 1 ? 1 : 0;
    1352              : }
    1353              : 
    1354            0 : void NotationParts::setBracketsAndBarlines()
    1355              : {
    1356            0 :     score()->setBracketsAndBarlines();
    1357            0 : }
    1358              : 
    1359            0 : void NotationParts::endInteractionWithScore()
    1360              : {
    1361            0 :     m_interaction->clearSelection();
    1362            0 :     m_interaction->noteInput()->endNoteInput(/*resetState=*/ true);
    1363            0 : }
    1364              : 
    1365            0 : void NotationParts::notifyAboutPartChanged(const Part* part) const
    1366              : {
    1367            0 :     IF_ASSERT_FAILED(part) {
    1368            0 :         return;
    1369              :     }
    1370              : 
    1371            0 :     m_partChangedNotifier.itemChanged(part);
    1372              : }
    1373              : 
    1374            0 : void NotationParts::notifyAboutPartAdded(const Part* part) const
    1375              : {
    1376            0 :     IF_ASSERT_FAILED(part) {
    1377            0 :         return;
    1378              :     }
    1379              : 
    1380            0 :     m_partChangedNotifier.itemAdded(part);
    1381              : }
    1382              : 
    1383            0 : void NotationParts::notifyAboutPartRemoved(const Part* part) const
    1384              : {
    1385            0 :     IF_ASSERT_FAILED(part) {
    1386            0 :         return;
    1387              :     }
    1388              : 
    1389            0 :     m_partChangedNotifier.itemRemoved(part);
    1390              : }
    1391              : 
    1392            0 : void NotationParts::notifyAboutPartReplaced(const Part* oldPart, const Part* newPart) const
    1393              : {
    1394            0 :     IF_ASSERT_FAILED(oldPart && newPart) {
    1395            0 :         return;
    1396              :     }
    1397              : 
    1398            0 :     m_partChangedNotifier.itemReplaced(oldPart, newPart);
    1399              : }
    1400              : 
    1401            0 : void NotationParts::notifyAboutStaffChanged(const Staff* staff) const
    1402              : {
    1403            0 :     IF_ASSERT_FAILED(staff && staff->part()) {
    1404            0 :         return;
    1405              :     }
    1406              : 
    1407            0 :     ChangedNotifier<const Staff*>& notifier = m_staffChangedNotifierMap[staff->part()->id()];
    1408            0 :     notifier.itemChanged(staff);
    1409              : }
    1410              : 
    1411            0 : void NotationParts::notifyAboutStaffAdded(const Staff* staff) const
    1412              : {
    1413            0 :     IF_ASSERT_FAILED(staff && staff->part()) {
    1414            0 :         return;
    1415              :     }
    1416              : 
    1417            0 :     ChangedNotifier<const Staff*>& notifier = m_staffChangedNotifierMap[staff->part()->id()];
    1418            0 :     notifier.itemAdded(staff);
    1419              : }
    1420              : 
    1421            0 : void NotationParts::notifyAboutStaffRemoved(const Staff* staff) const
    1422              : {
    1423            0 :     IF_ASSERT_FAILED(staff && staff->part()) {
    1424            0 :         return;
    1425              :     }
    1426              : 
    1427            0 :     ChangedNotifier<const Staff*>& notifier = m_staffChangedNotifierMap[staff->part()->id()];
    1428            0 :     notifier.itemRemoved(staff);
    1429              : }
        

Generated by: LCOV version 2.0-1