LCOV - code coverage report
Current view: top level - src/engraving/editing - edittimesig.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 78.7 % 362 285
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
       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 "edittimesig.h"
      24              : 
      25              : #include <map>
      26              : #include <tuple>
      27              : #include <vector>
      28              : 
      29              : #include "global/containers.h"
      30              : 
      31              : #include "../dom/excerpt.h"
      32              : #include "../dom/factory.h"
      33              : #include "../dom/layoutbreak.h"
      34              : #include "../dom/masterscore.h"
      35              : #include "../dom/measure.h"
      36              : #include "../dom/measurerepeat.h"
      37              : #include "../dom/range.h"
      38              : #include "../dom/score.h"
      39              : #include "../dom/segment.h"
      40              : #include "../dom/sig.h"
      41              : #include "../dom/slur.h"
      42              : #include "../dom/staff.h"
      43              : #include "../dom/system.h"
      44              : #include "../dom/timesig.h"
      45              : #include "../dom/tremolotwochord.h"
      46              : 
      47              : #include "addremoveelement.h"
      48              : #include "editmeasures.h"
      49              : #include "editspanner.h"
      50              : #include "edittremolo.h"
      51              : #include "transaction/transaction.h"
      52              : #include "transaction/undostack.h"
      53              : 
      54              : #include "log.h"
      55              : 
      56              : using namespace mu;
      57              : using namespace mu::engraving;
      58              : 
      59              : //---------------------------------------------------------
      60              : //   rewriteMeasures
      61              : //    rewrite all measures from startMeasure to endMeasure (including)
      62              : //    If staffIdx is valid (>= 0), then rewrite a local
      63              : //    timesig change.
      64              : //---------------------------------------------------------
      65              : 
      66           21 : bool EditTimeSig::rewriteMeasures(Transaction& tx, Score* score, Measure* startMeasure, Measure* endMeasure, const Fraction& newTimeSig,
      67              :                                   staff_idx_t staffIdx)
      68              : {
      69           21 :     if (staffIdx != muse::nidx) {
      70              :         // local timesig
      71              :         // don't actually rewrite, just update measure rest durations
      72              :         // abort if there is anything other than measure rests in range
      73            3 :         track_idx_t strack = staffIdx * VOICES;
      74            3 :         track_idx_t etrack = strack + VOICES;
      75           62 :         for (Measure* m = startMeasure;; m = m->nextMeasure()) {
      76          124 :             for (Segment* s = m->first(SegmentType::ChordRest); s; s = s->next(SegmentType::ChordRest)) {
      77          310 :                 for (track_idx_t track = strack; track < etrack; ++track) {
      78          248 :                     ChordRest* cr = toChordRest(s->element(track));
      79          248 :                     if (!cr) {
      80          186 :                         continue;
      81              :                     }
      82           62 :                     if (cr->isRest() && cr->durationType() == DurationType::V_MEASURE) {
      83           62 :                         cr->undoChangeProperty(Pid::DURATION, newTimeSig);
      84              :                     } else {
      85            0 :                         return false;
      86              :                     }
      87              :                 }
      88              :             }
      89           62 :             if (m == endMeasure) {
      90            3 :                 break;
      91              :             }
      92           59 :         }
      93            3 :         return true;
      94              :     }
      95              : 
      96           18 :     bool fmr = true;
      97              : 
      98              :     // Format: chord 1 tick, chord 2 tick, tremolo, track
      99           18 :     std::vector<std::tuple<Fraction, Fraction, TremoloTwoChord*, track_idx_t> > tremoloChordTicks;
     100              : 
     101              :     track_idx_t strack, etrack;
     102           18 :     if (staffIdx == muse::nidx) {
     103           18 :         strack = 0;
     104           18 :         etrack = score->ntracks();
     105              :     } else {
     106            0 :         strack = staffIdx * VOICES;
     107            0 :         etrack = strack + VOICES;
     108              :     }
     109              : 
     110           18 :     std::vector<Segment*> endOfMeasureTimeSigsToRemove;
     111              : 
     112           55 :     for (Measure* m = startMeasure; m; m = m->nextMeasure()) {
     113           55 :         if (!m->isFullMeasureRest()) {
     114           20 :             fmr = false;
     115              :         }
     116              : 
     117          284 :         for (Segment* s = m->first(); s; s = s->next()) {
     118          229 :             if (!s->isChordRestType() && !(s->isTimeSigType() && s->endOfMeasureChange())) {
     119           94 :                 continue;
     120              :             }
     121              : 
     122          135 :             if (s->isTimeSigType()) {
     123            0 :                 endOfMeasureTimeSigsToRemove.push_back(s);
     124            0 :                 continue;
     125              :             }
     126              : 
     127          935 :             for (track_idx_t track = strack; track < etrack; ++track) {
     128          800 :                 ChordRest* cr = toChordRest(s->element(track));
     129          800 :                 if (cr && cr->isChord()) {
     130          104 :                     Chord* chord = toChord(cr);
     131          104 :                     if (chord->tremoloTwoChord()) {
     132            4 :                         TremoloTwoChord* trem = chord->tremoloTwoChord();
     133              : 
     134              :                         // Don't add same chord twice
     135            4 :                         if (trem->chord2() == chord) {
     136            2 :                             continue;
     137              :                         }
     138              : 
     139              :                         std::tuple<Fraction, Fraction, TremoloTwoChord*, track_idx_t> newP
     140            2 :                             = { cr->tick(), trem->chord2()->segment()->tick(), trem, track };
     141            2 :                         tremoloChordTicks.push_back(newP);
     142              :                     }
     143              :                 }
     144              :             }
     145              :         }
     146              : 
     147           55 :         if (m == endMeasure) {
     148           18 :             break;
     149              :         }
     150              :     }
     151              : 
     152           18 :     if (!fmr) {
     153              :         // check for local time signatures
     154           64 :         for (Measure* m = startMeasure; m; m = m->nextMeasure()) {
     155          101 :             for (size_t si = 0; si < score->nstaves(); ++si) {
     156           61 :                 if (score->staff(si)->timeStretch(m->tick()) != Fraction(1, 1)) {
     157              :                     // we cannot change a staff with a local time signature
     158            0 :                     return false;
     159              :                 }
     160           61 :                 if (m == endMeasure) {
     161           12 :                     break;
     162              :                 }
     163              :             }
     164              :         }
     165              :     }
     166              : 
     167           18 :     for (Segment* seg : endOfMeasureTimeSigsToRemove) {
     168            0 :         score->doUndoRemoveElement(seg);
     169              :     }
     170              : 
     171           18 :     ScoreRange range;
     172           18 :     Measure* nextMeasure = endMeasure->nextMeasure();
     173           18 :     Segment* finalSeg = endMeasure->last();
     174           18 :     if (nextMeasure) {
     175            3 :         finalSeg = nextMeasure->first();
     176              :     }
     177           18 :     range.read(startMeasure->first(), finalSeg);
     178              : 
     179              :     //
     180              :     // calculate number of required measures = newMeasures
     181              :     //
     182           18 :     Fraction ticks = range.ticks().isNotZero() ? range.ticks() : score->endTick() - startMeasure->first()->tick();
     183           18 :     Fraction k = ticks / newTimeSig;
     184           18 :     int newMeasures     = (k.numerator() + k.denominator() - 1) / k.denominator();
     185              : 
     186           18 :     Fraction newDuration = newTimeSig * Fraction(newMeasures, 1);
     187              : 
     188              :     // evtl. we have to fill the last measure
     189           18 :     Fraction fill = newDuration - ticks;
     190           18 :     range.fill(fill);
     191              : 
     192           36 :     for (Score* s : score->scoreList()) {
     193           18 :         Measure* m1 = s->tick2measure(startMeasure->tick());
     194           18 :         Measure* m2 = s->tick2measure(endMeasure->tick());
     195              : 
     196           18 :         Fraction tick1 = m1->tick();
     197           18 :         Fraction tick2 = m2->endTick();
     198           18 :         auto spanners = s->spannerMap().findOverlapping(tick1.ticks(), tick2.ticks());
     199           22 :         for (auto i : spanners) {
     200            4 :             if (i.value->tick() >= tick1 && i.value->tick() < tick2) {
     201            4 :                 score->doUndoRemoveElement(i.value);
     202              :             }
     203              :         }
     204           18 :         s->undoRemoveMeasures(m1, m2, true);
     205              : 
     206           18 :         Measure* newFirstMeasure = nullptr;
     207           18 :         Measure* newLastMeasure = nullptr;
     208           18 :         Fraction tick     = startMeasure->tick();
     209           93 :         for (int i = 0; i < newMeasures; ++i) {
     210           75 :             Measure* m = Factory::createMeasure(s);
     211           75 :             m->setPrev(newLastMeasure);
     212           75 :             if (newLastMeasure) {
     213           57 :                 newLastMeasure->setNext(m);
     214              :             }
     215           75 :             m->setTimesig(newTimeSig);
     216           75 :             m->setTicks(newTimeSig);
     217           75 :             m->setTick(tick);
     218           75 :             tick += m->ticks();
     219           75 :             newLastMeasure = m;
     220           75 :             if (newFirstMeasure == 0) {
     221           18 :                 newFirstMeasure = m;
     222              :             }
     223              :         }
     224              :         //
     225              :         // insert new calculated measures
     226              :         //
     227           18 :         newFirstMeasure->setPrev(m1->prev());
     228           18 :         newLastMeasure->setNext(m2->next());
     229           18 :         tx.push(new InsertMeasures(newFirstMeasure, newLastMeasure));
     230           36 :     }
     231           18 :     if (!fill.isZero()) {
     232           12 :         score->undoInsertTime(endMeasure->endTick(), fill);
     233              :     }
     234              : 
     235           18 :     if (!range.write(score->masterScore(), startMeasure->tick())) {
     236            1 :         return false;
     237              :     }
     238              : 
     239           34 :     for (Score* s : score->scoreList()) {
     240           17 :         s->connectTies(true);
     241           17 :     }
     242              : 
     243              :     // reset start and end elements for slurs that overlap the rewritten measures
     244           20 :     for (auto spanner : score->spannerMap().findOverlapping(startMeasure->tick().ticks(), endMeasure->tick().ticks())) {
     245            3 :         Slur* slur = (spanner.value->isSlur() ? toSlur(spanner.value) : nullptr);
     246            3 :         if (slur) {
     247            1 :             EngravingItem* startEl = slur->startElement();
     248            1 :             EngravingItem* endEl = slur->endElement();
     249            1 :             if (!startEl || !endEl) {
     250            0 :                 continue;
     251              :             }
     252            1 :             tx.push(new ChangeStartEndSpanner(spanner.value, slur->findStartCR(), slur->findEndCR()));
     253              :         }
     254              :     }
     255              :     // Attempt to move tremolos to correct chords
     256           19 :     for (auto tremPair : tremoloChordTicks) {
     257            2 :         Fraction chord1Tick = std::get<0>(tremPair);
     258            2 :         Fraction chord2Tick = std::get<1>(tremPair);
     259            2 :         TremoloTwoChord* trem = std::get<2>(tremPair);
     260            2 :         track_idx_t track = std::get<3>(tremPair);
     261              : 
     262            2 :         tx.push(new MoveTremolo(trem->score(), chord1Tick, chord2Tick, trem, track));
     263              :     }
     264              : 
     265           17 :     if (score->noteEntryMode()) {
     266              :         // set input cursor to possibly re-written segment
     267            0 :         Fraction icTick = score->inputState().tick();
     268            0 :         Segment* icSegment = score->tick2segment(icTick, false, SegmentType::ChordRest);
     269            0 :         if (!icSegment) {
     270              :             // this can happen if cursor was on a rest
     271              :             // and in the rewriting it got subsumed into a full measure rest
     272            0 :             Measure* icMeasure = score->tick2measure(icTick);
     273            0 :             if (!icMeasure) {                         // shouldn't happen, but just in case
     274            0 :                 icMeasure = score->firstMeasure();
     275              :             }
     276            0 :             icSegment = icMeasure->first(SegmentType::ChordRest);
     277              :         }
     278            0 :         score->inputState().setSegment(icSegment);
     279              :     }
     280              : 
     281           17 :     return true;
     282           18 : }
     283              : 
     284              : //---------------------------------------------------------
     285              : //   rewriteMeasures
     286              : //    rewrite all measures up to the next time signature or section break
     287              : //---------------------------------------------------------
     288              : 
     289           21 : bool EditTimeSig::rewriteMeasures(Transaction& tx, Score* score, Measure* fm, const Fraction& ns, staff_idx_t staffIdx)
     290              : {
     291           21 :     Measure* lm  = fm;
     292           21 :     Measure* fm1 = fm;
     293           21 :     Measure* nm  = nullptr;
     294           21 :     LayoutBreak* sectionBreak = nullptr;
     295           21 :     Segment* endOfMeasureTimeSigSeg = nullptr;
     296              : 
     297              :     //
     298              :     // split into Measure segments fm-lm
     299              :     //
     300          138 :     auto foundLM = [fm, staffIdx, &endOfMeasureTimeSigSeg](MeasureBase* curMeas, Measure* curLM) {
     301          138 :         if (!curMeas || !curMeas->isMeasure() || curLM->sectionBreak()) {
     302           17 :             return true;
     303              :         }
     304          121 :         Segment* timeSigSeg = toMeasure(curMeas)->findSegmentR(SegmentType::TimeSig, Fraction(0, 1));
     305          121 :         if (!timeSigSeg && curMeas->prevMeasure()) {
     306              :             // find time sig at end of previous measure
     307           97 :             Measure* prevMeasure = curMeas->prevMeasure();
     308           97 :             timeSigSeg = prevMeasure->findSegmentR(SegmentType::TimeSig, prevMeasure->ticks());
     309           97 :             if (timeSigSeg) {
     310            0 :                 endOfMeasureTimeSigSeg = timeSigSeg;
     311              :             }
     312              :         }
     313          121 :         if (timeSigSeg && curMeas != fm) {
     314            5 :             return staffIdx == muse::nidx || timeSigSeg->element(staff2track(staffIdx));
     315              :         }
     316          116 :         return false;
     317           21 :     };
     318          138 :     for (MeasureBase* measure = fm;; measure = measure->next()) {
     319          138 :         if (foundLM(measure, lm)) {
     320              :             // save section break to reinstate after rewrite
     321           21 :             LayoutBreak* layoutBreak = lm->sectionBreakElement();
     322              : 
     323           21 :             if (lm && layoutBreak) {
     324            0 :                 sectionBreak = Factory::copyLayoutBreak(*layoutBreak);
     325              :             }
     326              : 
     327           21 :             if (!rewriteMeasures(tx, score, fm1, lm, ns, staffIdx)) {
     328            1 :                 if (staffIdx != muse::nidx) {
     329            0 :                     MScore::setError(MsError::CANNOT_CHANGE_LOCAL_TIMESIG_MEASURE_NOT_EMPTY);
     330              :                     // restore measure rests that were prematurely modified
     331            0 :                     Fraction fr(score->staff(staffIdx)->timeSig(fm->tick())->sig());
     332            0 :                     for (Measure* m = fm1; m; m = m->nextMeasure()) {
     333            0 :                         ChordRest* cr = m->findChordRest(m->tick(), staffIdx * VOICES);
     334            0 :                         if (cr && cr->isRest() && cr->durationType() == DurationType::V_MEASURE) {
     335            0 :                             cr->undoChangeProperty(Pid::DURATION, fr);
     336              :                         } else {
     337            0 :                             break;
     338              :                         }
     339              :                     }
     340              :                 }
     341            1 :                 for (Measure* m = fm1; m; m = m->nextMeasure()) {
     342            1 :                     if (m->first(SegmentType::TimeSig)) {
     343            1 :                         break;
     344              :                     }
     345            0 :                     Fraction fr(ns);
     346            0 :                     m->undoChangeProperty(Pid::TIMESIG_NOMINAL, fr);
     347              :                 }
     348            1 :                 return false;
     349              :             }
     350              : 
     351              :             // after rewrite, lm is not necessarily valid
     352              :             // m is first MeasureBase after rewritten range
     353              :             // m->prevMeasure () is new last measure of range
     354              :             // set nm to first true Measure after rewritten range
     355              :             // we may use this to reinstate time signatures
     356           20 :             if (measure && measure->prevMeasure()) {
     357            4 :                 nm = measure->prevMeasure()->nextMeasure();
     358              :             } else {
     359           16 :                 nm = nullptr;
     360              :             }
     361              : 
     362           20 :             if (sectionBreak) {
     363              :                 // reinstate section break, then stop rewriting
     364            0 :                 if (measure && measure->prevMeasure()) {
     365            0 :                     sectionBreak->setOwnershipParent(measure->prevMeasure());
     366            0 :                     score->undoAddElement(sectionBreak);
     367            0 :                 } else if (!measure) {
     368            0 :                     sectionBreak->setOwnershipParent(score->lastMeasure());
     369            0 :                     score->undoAddElement(sectionBreak);
     370              :                 } else {
     371            0 :                     LOGD("unable to restore section break");
     372            0 :                     nm = nullptr;
     373            0 :                     sectionBreak = nullptr;
     374              :                 }
     375            0 :                 break;
     376              :             }
     377              : 
     378              :             // stop rewriting at end of score
     379              :             // or at a measure (which means we found a time signature segment)
     380           20 :             if (!measure || measure->isMeasure()) {
     381           20 :                 break;
     382              :             }
     383              : 
     384              :             // skip frames
     385            0 :             while (!measure->isMeasure()) {
     386            0 :                 LayoutBreak* layoutBreak2 = measure->sectionBreakElement();
     387              : 
     388            0 :                 if (layoutBreak2) {
     389              :                     // frame has a section break; we can stop skipping ahead
     390            0 :                     sectionBreak = layoutBreak2;
     391            0 :                     break;
     392              :                 }
     393            0 :                 measure = measure->next();
     394            0 :                 if (!measure) {
     395            0 :                     break;
     396              :                 }
     397              :             }
     398              :             // stop rewriting if we encountered a section break on a frame
     399              :             // or if there is a time signature on first measure after the frame
     400            0 :             if (sectionBreak || (measure && toMeasure(measure)->first(SegmentType::TimeSig))) {
     401            0 :                 break;
     402              :             }
     403              : 
     404              :             // set up for next range to rewrite
     405            0 :             fm1 = toMeasure(measure);
     406            0 :             if (fm1 == 0) {
     407            0 :                 break;
     408              :             }
     409              :         }
     410              : 
     411              :         // if we didn't break the loop already,
     412              :         // we must have an ordinary measure
     413              :         // add measure to range to rewrite
     414          117 :         lm = toMeasure(measure);
     415          117 :     }
     416              : 
     417              :     // if any staves don't have time signatures at the point where we stopped,
     418              :     // we need to reinstate their previous time signatures
     419           20 :     if (!nm) {
     420           16 :         return true;
     421              :     }
     422            4 :     Segment* timeSigSeg = nm->undoGetSegment(SegmentType::TimeSig, nm->tick());
     423              : 
     424           11 :     for (size_t i = 0; i < score->nstaves(); ++i) {
     425            7 :         if (staffIdx != muse::nidx && i != staffIdx) {
     426            3 :             continue;
     427              :         }
     428            4 :         if (!timeSigSeg->element(staff2track(i))) {
     429            1 :             TimeSig* ots = endOfMeasureTimeSigSeg
     430            1 :                            ? toTimeSig(endOfMeasureTimeSigSeg->element(staff2track(i))) : score->staff(i)->timeSig(nm->tick());
     431            1 :             if (ots) {
     432            0 :                 TimeSig* nts = Factory::copyTimeSig(*ots);
     433            0 :                 nts->setOwnershipParent(timeSigSeg);
     434            0 :                 if (sectionBreak) {
     435            0 :                     nts->setGenerated(false);
     436            0 :                     nts->setShowCourtesySig(false);
     437              :                 }
     438            0 :                 score->undoAddElement(nts);
     439              :             }
     440              :         }
     441              :     }
     442              : 
     443            4 :     return true;
     444              : }
     445              : 
     446              : //---------------------------------------------------------
     447              : //   addTimeSig
     448              : //
     449              : //    Add or change time signature at measure in response
     450              : //    to gui command (drop timesig on measure or timesig)
     451              : //---------------------------------------------------------
     452              : 
     453           21 : void EditTimeSig::addTimeSig(Transaction& tx, Score* score, Measure* fm, staff_idx_t staffIdx, TimeSig* ts, bool local)
     454              : {
     455           21 :     score->deselectAll();
     456              : 
     457           21 :     if (fm->isMMRest()) {
     458            0 :         fm = fm->mmRestFirst();
     459              :     }
     460              : 
     461           21 :     Fraction ns   = ts->sig();
     462           21 :     Fraction tick = fm->tick();
     463           21 :     if (local) {
     464            3 :         Fraction stretch = (ns / fm->timesig()).reduced();
     465            3 :         ts->setStretch(stretch);
     466              :     }
     467              : 
     468           21 :     track_idx_t track = staffIdx * VOICES;
     469           21 :     Segment* seg = fm->findSegment(SegmentType::TimeSig, tick);
     470           21 :     TimeSig* ots = seg ? toTimeSig(seg->element(track)) : nullptr;
     471              : 
     472              :     // Check same tick at end of previous measure
     473           21 :     if (!ots) {
     474           10 :         Measure* prevMeasure = fm->prevMeasure();
     475           10 :         seg = prevMeasure ? prevMeasure->findSegmentR(SegmentType::TimeSig, prevMeasure->ticks()) : seg;
     476           10 :         ots = seg ? toTimeSig(seg->element(track)) : nullptr;
     477              :     }
     478              : 
     479           21 :     if (ots && (*ots == *ts)) {
     480              :         //
     481              :         //  ignore if there is already a timesig
     482              :         //  with same values
     483              :         //
     484            0 :         delete ts;
     485            1 :         return;
     486              :     }
     487              : 
     488           21 :     seg = fm->undoGetSegment(SegmentType::TimeSig, tick);
     489              : 
     490           51 :     auto getStaffIdxRange = [score, local, staffIdx](const Score* lscore) -> std::pair<staff_idx_t /*start*/, staff_idx_t /*end*/> {
     491              :         staff_idx_t startStaffIdx, endStaffIdx;
     492           51 :         if (local) {
     493           18 :             if (lscore == score) {
     494            6 :                 startStaffIdx = staffIdx;
     495            6 :                 endStaffIdx = startStaffIdx + 1;
     496              :             } else {
     497           12 :                 const Staff* thisStaff = score->staff(staffIdx);
     498           12 :                 const Staff* linkedStaff = thisStaff->findLinkedInScore(lscore);
     499           12 :                 if (linkedStaff) {
     500            3 :                     startStaffIdx = linkedStaff->idx();
     501            3 :                     endStaffIdx = startStaffIdx + 1;
     502              :                 } else {
     503            9 :                     startStaffIdx = 0;
     504            9 :                     endStaffIdx = 0;
     505              :                 }
     506              :             }
     507              :         } else {
     508           33 :             startStaffIdx = 0;
     509           33 :             endStaffIdx = lscore->nstaves();
     510              :         }
     511          102 :         return std::make_pair(startStaffIdx, endStaffIdx);
     512           21 :     };
     513              : 
     514           21 :     if (ots && ots->sig() == ns && ots->stretch() == ts->stretch()) {
     515              :         //
     516              :         // the measure duration does not change,
     517              :         // so its ok to just update the time signatures
     518              :         //
     519            2 :         TimeSig* nts = score->staff(staffIdx)->nextTimeSig(tick + Fraction::eps());
     520            2 :         const Fraction lmTick = nts ? nts->segment()->tick() : Fraction(-1, 1);
     521            4 :         for (Score* lscore : score->scoreList()) {
     522            2 :             Measure* mf = lscore->tick2measure(tick);
     523            2 :             Measure* lm = (lmTick != Fraction(-1, 1)) ? lscore->tick2measure(lmTick) : nullptr;
     524            5 :             for (Measure* m = mf; m != lm; m = m->nextMeasure()) {
     525            3 :                 bool changeActual = m->ticks() == m->timesig();
     526            3 :                 m->undoChangeProperty(Pid::TIMESIG_NOMINAL, ns);
     527            3 :                 if (changeActual) {
     528            3 :                     m->undoChangeProperty(Pid::TIMESIG_ACTUAL, ns);
     529              :                 }
     530              :             }
     531            2 :             std::pair<staff_idx_t, staff_idx_t> staffIdxRange = getStaffIdxRange(lscore);
     532            4 :             for (staff_idx_t si = staffIdxRange.first; si < staffIdxRange.second; ++si) {
     533            2 :                 TimeSig* nsig = toTimeSig(seg->element(si * VOICES));
     534            2 :                 if (!nsig) {
     535            0 :                     continue;
     536              :                 }
     537            2 :                 nsig->undoChangeProperty(Pid::SHOW_COURTESY, ts->showCourtesySig());
     538            2 :                 nsig->undoChangeProperty(Pid::TIMESIG, ts->sig());
     539            2 :                 nsig->undoChangeProperty(Pid::TIMESIG_TYPE, int(ts->timeSigType()));
     540            2 :                 nsig->undoChangeProperty(Pid::NUMERATOR_STRING, ts->numeratorString());
     541            2 :                 nsig->undoChangeProperty(Pid::DENOMINATOR_STRING, ts->denominatorString());
     542            2 :                 nsig->undoChangeProperty(Pid::TIMESIG_STRETCH, ts->stretch());
     543            2 :                 nsig->undoChangeProperty(Pid::GROUP_NODES, ts->groups().nodes());
     544            2 :                 nsig->setSelected(false);
     545            2 :                 nsig->setDropTarget(false);
     546              :             }
     547            2 :         }
     548              :     } else {
     549           19 :         Score* mScore = score->masterScore();
     550           19 :         Measure* mf  = mScore->tick2measure(tick);
     551              : 
     552              :         //
     553              :         // rewrite all measures up to the next time signature
     554              :         //
     555           19 :         if (mf == mScore->firstMeasure() && mf->nextMeasure() && (mf->ticks() != mf->timesig())) {
     556              :             // handle upbeat
     557            0 :             mf->undoChangeProperty(Pid::TIMESIG_NOMINAL, ns);
     558            0 :             Measure* m = mf->nextMeasure();
     559            0 :             Segment* s = m->findSegment(SegmentType::TimeSig, m->tick());
     560            0 :             mf = s ? 0 : mf->nextMeasure();
     561              :         } else {
     562           19 :             if (score->sigmap()->timesig(seg->tick().ticks()).nominal().identical(ns)) {
     563              :                 // no change to global time signature,
     564              :                 // but we need to rewrite any staves with local time signatures
     565            0 :                 for (size_t i = 0; i < score->nstaves(); ++i) {
     566            0 :                     if (score->staff(i)->timeSig(tick) && score->staff(i)->timeSig(tick)->isLocal()) {
     567            0 :                         if (!rewriteMeasures(tx, mScore, mf, ns, i)) {
     568            0 :                             score->undoStack()->activeTransaction()->unwind();
     569            1 :                             return;
     570              :                         }
     571              :                     }
     572              :                 }
     573            0 :                 mf = 0;
     574              :             }
     575              :         }
     576              : 
     577              :         // try to rewrite the measures first
     578              :         // we will only add time signatures if this succeeds
     579              :         // this means, however, that the rewrite cannot depend on the time signatures being in place
     580           19 :         if (mf) {
     581           19 :             auto staffIdxRangeOnMaster = getStaffIdxRange(mScore);
     582           38 :             if (staffIdxRangeOnMaster.second != staffIdxRangeOnMaster.first
     583           19 :                 && !rewriteMeasures(tx, mScore, mf, ns, local ? staffIdxRangeOnMaster.first : muse::nidx)) {
     584            1 :                 score->undoStack()->activeTransaction()->unwind();
     585            1 :                 return;
     586              :             }
     587              :         }
     588              :         // add the time signatures
     589           18 :         std::map<track_idx_t, TimeSig*> masterTimeSigs;
     590           48 :         for (Score* lscore : score->scoreList()) {
     591           30 :             Measure* nfm = lscore->tick2measure(tick);
     592           30 :             seg = nfm->undoGetSegment(SegmentType::TimeSig, nfm->tick());
     593           30 :             std::pair<staff_idx_t, staff_idx_t> staffIdxRange = getStaffIdxRange(lscore);
     594           56 :             for (staff_idx_t si = staffIdxRange.first; si < staffIdxRange.second; ++si) {
     595           26 :                 if (fm->isMeasureRepeatGroup(si)) {
     596            0 :                     score->deleteItem(fm->measureRepeatElement(si));
     597              :                 }
     598           26 :                 TimeSig* nsig = toTimeSig(seg->element(si * VOICES));
     599           26 :                 if (nsig == 0) {
     600           14 :                     nsig = Factory::copyTimeSig(*ts);
     601           14 :                     nsig->setScore(lscore);
     602           14 :                     nsig->setTrack(si * VOICES);
     603           14 :                     nsig->setOwnershipParent(seg);
     604           14 :                     nsig->styleChanged();
     605           14 :                     score->undoAddElement(nsig);
     606           14 :                     if (lscore->excerpt()) {
     607            3 :                         const track_idx_t masterTrack = muse::key(lscore->excerpt()->tracksMapping(), nsig->track());
     608            3 :                         TimeSig* masterTimeSig = masterTimeSigs[masterTrack];
     609            3 :                         if (masterTimeSig) {
     610            3 :                             tx.push(new Link(masterTimeSig, nsig));
     611              :                         }
     612              :                     }
     613              :                 } else {
     614           12 :                     nsig->undoChangeProperty(Pid::SHOW_COURTESY, ts->showCourtesySig());
     615           12 :                     nsig->undoChangeProperty(Pid::TIMESIG_TYPE, int(ts->timeSigType()));
     616           12 :                     nsig->undoChangeProperty(Pid::TIMESIG, ts->sig());
     617           12 :                     nsig->undoChangeProperty(Pid::NUMERATOR_STRING, ts->numeratorString());
     618           12 :                     nsig->undoChangeProperty(Pid::DENOMINATOR_STRING, ts->denominatorString());
     619              : 
     620              :                     // HACK do it twice to accommodate undo
     621           12 :                     nsig->undoChangeProperty(Pid::TIMESIG_TYPE, int(ts->timeSigType()));
     622           12 :                     nsig->undoChangeProperty(Pid::TIMESIG_STRETCH, ts->stretch());
     623           12 :                     nsig->undoChangeProperty(Pid::GROUP_NODES, ts->groups().nodes());
     624           12 :                     nsig->setSelected(false);
     625           12 :                     nsig->setDropTarget(false);                 // DEBUG
     626              :                 }
     627              : 
     628           26 :                 if (lscore->isMaster()) {
     629           23 :                     masterTimeSigs[nsig->track()] = nsig;
     630              :                 }
     631              :             }
     632           18 :         }
     633           18 :     }
     634           20 :     delete ts;
     635              : }
     636              : 
     637              : //---------------------------------------------------------
     638              : //   removeTimeSig
     639              : //---------------------------------------------------------
     640              : 
     641            2 : void EditTimeSig::removeTimeSig(Transaction& tx, Score* score, TimeSig* ts)
     642              : {
     643            2 :     Measure* m = ts->measure();
     644            2 :     Segment* s = ts->segment();
     645              : 
     646              :     //
     647              :     // we cannot remove a courtesy time signature
     648              :     //
     649            2 :     if (s->isCourtesySegment()) {
     650            0 :         return;
     651              :     }
     652            2 :     Fraction tick = m->tick();
     653              : 
     654              :     // if we remove all time sigs from segment, segment will be already removed by now
     655              :     // but this would leave us no means of detecting that we have measures in a local timesig
     656              :     // in cases where we try deleting the local time sig
     657              :     // known bug: this means we do not correctly detect non-empty measures when deleting global timesig change after a local one
     658              :     // see http://musescore.org/en/node/51596
     659              :     // Delete the time sig segment from the root score, we will rewriteMeasures from it
     660              :     // since it contains all the music while the part doesn't
     661            2 :     Score* rScore = score->masterScore();
     662            2 :     Measure* rm = rScore->tick2measure(m->tick());
     663            2 :     Segment* rs = rm->findSegment(SegmentType::TimeSig, s->tick());
     664            2 :     if (rs) {
     665            2 :         rScore->undoRemoveElement(rs);
     666              :     }
     667              :     // Measure can contain mmRest that can have its own timesig. We need to delete it too
     668            2 :     if (rm->mmRest()) {
     669            2 :         Segment* mmRestTimesig = rm->mmRest()->findSegment(SegmentType::TimeSig, s->tick());
     670            2 :         if (mmRestTimesig) {
     671            2 :             rScore->undoRemoveElement(mmRestTimesig);
     672              :         }
     673              :     }
     674              : 
     675            2 :     Measure* pm = m->prevMeasure();
     676            2 :     Fraction ns(pm ? pm->timesig() : Fraction(4, 4));
     677              : 
     678            2 :     if (!rewriteMeasures(tx, rScore, rm, ns, muse::nidx)) {
     679            0 :         score->undoStack()->activeTransaction()->unwind();
     680              :     } else {
     681            2 :         m = score->tick2measure(tick);           // old m may have been replaced
     682              :         // hack: fix measure rest durations for staves with local time signatures
     683              :         // if a time signature was deleted to reveal a previous local one,
     684              :         // then rewriteMeasures() got the measure rest durations wrong
     685              :         // (if we fixed it to work for delete, it would fail for add)
     686              :         // so we will fix measure rest durations here
     687              :         // TODO: fix rewriteMeasures() to get this right
     688            4 :         for (size_t i = 0; i < score->nstaves(); ++i) {
     689            2 :             TimeSig* tsig = score->staff(i)->timeSig(tick);
     690            2 :             if (tsig && tsig->isLocal()) {
     691            0 :                 for (Measure* nm = m; nm; nm = nm->nextMeasure()) {
     692              :                     // stop when time signature changes
     693            0 :                     if (score->staff(i)->timeSig(nm->tick()) != tsig) {
     694            0 :                         break;
     695              :                     }
     696              :                     // fix measure rest duration
     697            0 :                     ChordRest* cr = nm->findChordRest(nm->tick(), i * VOICES);
     698            0 :                     if (cr && cr->isRest() && cr->durationType() == DurationType::V_MEASURE) {
     699            0 :                         cr->undoChangeProperty(Pid::DURATION, nm->stretchedLen(score->staff(i)));
     700              :                     }
     701              :                     //cr->setTicks(nm->stretchedLen(staff(i)));
     702              :                 }
     703              :             }
     704              :         }
     705              :     }
     706              : }
        

Generated by: LCOV version 2.0-1