LCOV - code coverage report
Current view: top level - src/engraving/editing - editrehearsalmark.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 45.6 % 79 36
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 "editrehearsalmark.h"
      24              : 
      25              : #include "../dom/measure.h"
      26              : #include "../dom/rehearsalmark.h"
      27              : #include "../dom/score.h"
      28              : #include "../dom/segment.h"
      29              : #include "../dom/select.h"
      30              : 
      31              : using namespace mu;
      32              : using namespace mu::engraving;
      33              : 
      34              : //---------------------------------------------------------
      35              : //   resequenceRehearsalMarks
      36              : ///   resequences rehearsal marks within a range selection
      37              : ///   or, if nothing is selected, the entire score
      38              : //---------------------------------------------------------
      39              : 
      40            4 : void EditRehearsalMark::resequenceRehearsalMarks(Transaction&, Score* score)
      41              : {
      42            4 :     bool noSelection = !score->selection().isRange();
      43              : 
      44            4 :     if (noSelection) {
      45            4 :         score->cmdSelectAll();
      46            0 :     } else if (!score->selection().isRange()) {
      47            0 :         return;
      48              :     }
      49              : 
      50            4 :     RehearsalMark* last = nullptr;
      51          153 :     for (Segment* s = score->selection().startSegment(); s && s != score->selection().endSegment(); s = s->next1()) {
      52          161 :         for (EngravingItem* e : s->annotations()) {
      53           12 :             if (!e->isRehearsalMark()) {
      54            0 :                 continue;
      55              :             }
      56           12 :             RehearsalMark* rm = toRehearsalMark(e);
      57           12 :             if (!rm->isTopSystemObject()) {
      58            0 :                 continue;
      59              :             }
      60           12 :             if (last) {
      61            8 :                 String rmText = nextRehearsalMarkText(last, rm);
      62           18 :                 for (EngravingObject* le : rm->linkList()) {
      63           10 :                     le->undoChangeProperty(Pid::TEXT, rmText);
      64            8 :                 }
      65            8 :             }
      66           12 :             last = rm;
      67              :         }
      68              :     }
      69              : 
      70            4 :     if (noSelection) {
      71            4 :         score->deselectAll();
      72              :     }
      73              : }
      74              : 
      75              : //---------------------------------------------------------
      76              : //   createRehearsalMarkText
      77              : //---------------------------------------------------------
      78              : 
      79            0 : String EditRehearsalMark::createRehearsalMarkText(const Score* score, RehearsalMark* current)
      80              : {
      81            0 :     Fraction tick = current->segment()->tick();
      82            0 :     RehearsalMark* before = 0;
      83            0 :     RehearsalMark* after = 0;
      84            0 :     for (Segment* s = score->firstSegment(SegmentType::All); s; s = s->next1()) {
      85            0 :         for (EngravingItem* e : s->annotations()) {
      86            0 :             if (e && e->isRehearsalMark()) {
      87            0 :                 if (s->tick() < tick) {
      88            0 :                     before = toRehearsalMark(e);
      89            0 :                 } else if (s->tick() > tick) {
      90            0 :                     after = toRehearsalMark(e);
      91            0 :                     break;
      92              :                 }
      93              :             }
      94              :         }
      95            0 :         if (after) {
      96            0 :             break;
      97              :         }
      98              :     }
      99            0 :     String s = u"A";
     100            0 :     String s1 = before ? before->xmlText() : u"";
     101            0 :     String s2 = after ? after->xmlText() : u"";
     102            0 :     if (s1.isEmpty()) {
     103            0 :         return s;
     104              :     }
     105            0 :     s = nextRehearsalMarkText(before, current);       // try to sequence
     106            0 :     if (s == current->xmlText()) {
     107              :         // no sequence detected (or current happens to be correct)
     108            0 :         return s;
     109            0 :     } else if (s == s2) {
     110              :         // next in sequence already present
     111            0 :         if (s1.at(0).isLetter()) {
     112            0 :             if (s1.size() == 2) {
     113            0 :                 s = String(s1.at(0)) + Char::fromAscii(s1.at(1).toAscii() + 1);          // BB, BC, CC
     114              :             } else {
     115            0 :                 s = s1 + u'1';                              // B, B1, C
     116              :             }
     117              :         } else {
     118            0 :             s = s1 + u'A';                                  // 2, 2A, 3
     119              :         }
     120              :     }
     121            0 :     return s;
     122            0 : }
     123              : 
     124              : //---------------------------------------------------------
     125              : //   nextRehearsalMarkText
     126              : //    finds next rehearsal in sequence established by previous
     127              : //     Alphabetic sequences:
     128              : //      A, B, ..., Y, Z, AA, BB, ..., YY, ZZ
     129              : //      a, b, ..., y, z, aa, bb, ..., yy, zz
     130              : //     Numeric sequences:
     131              : //      1, 2, 3, ...
     132              : //      If number of previous rehearsal mark matches measure number, assume use of measure numbers throughout
     133              : //---------------------------------------------------------
     134              : 
     135            8 : String EditRehearsalMark::nextRehearsalMarkText(const RehearsalMark* previous, const RehearsalMark* current)
     136              : {
     137            8 :     String previousText = previous->xmlText();
     138            8 :     String fallback = current ? current->xmlText() : previousText + u"'";
     139              : 
     140            8 :     if (previousText.size() == 1 && previousText.at(0).isLetter()) {
     141              :         // single letter sequence
     142            4 :         if (previousText == "Z") {
     143            1 :             return u"AA";
     144            3 :         } else if (previousText == "z") {
     145            0 :             return u"aa";
     146              :         } else {
     147            3 :             return String(Char::fromAscii(previousText.at(0).toAscii() + 1));
     148              :         }
     149            4 :     } else if (previousText.size() == 2 && previousText.at(0).isLetter() && previousText.at(1).isLetter()) {
     150              :         // double letter sequence
     151            0 :         if (previousText.at(0) == previousText.at(1)) {
     152              :             // repeated letter sequence
     153            0 :             if (previousText.toUpper() != "ZZ") {
     154            0 :                 String c = Char::fromAscii(previousText.at(0).toAscii() + 1);
     155            0 :                 return c + c;
     156            0 :             } else {
     157            0 :                 return fallback;
     158              :             }
     159              :         } else {
     160            0 :             return fallback;
     161              :         }
     162              :     } else {
     163              :         // try to interpret as number
     164              :         bool ok;
     165            4 :         int n = previousText.toInt(&ok);
     166            4 :         if (!ok) {
     167            0 :             return fallback;
     168            4 :         } else if (current && n == previous->segment()->measure()->measureNumber() + 1) {
     169              :             // use measure number
     170            2 :             n = current->segment()->measure()->measureNumber() + 1;
     171            2 :             return String::number(n);
     172              :         } else {
     173              :             // use number sequence
     174            2 :             n = previousText.toInt() + 1;
     175            2 :             return String::number(n);
     176              :         }
     177              :     }
     178            8 : }
        

Generated by: LCOV version 2.0-1