LCOV - code coverage report
Current view: top level - src/engraving/editing - editparentheses.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 81.7 % 82 67
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 "editparentheses.h"
      24              : 
      25              : #include <algorithm>
      26              : #include <map>
      27              : #include <set>
      28              : 
      29              : #include "editchord.h"
      30              : 
      31              : #include "../dom/accidental.h"
      32              : #include "../dom/chord.h"
      33              : #include "../dom/engravingitem.h"
      34              : #include "../dom/note.h"
      35              : #include "../dom/score.h"
      36              : #include "../dom/select.h"
      37              : #include "../dom/timesig.h"
      38              : #include "../dom/utils.h"
      39              : 
      40              : #include "log.h"
      41              : 
      42              : using namespace mu::engraving;
      43              : 
      44              : namespace {
      45              : struct NoteComparator {
      46          173 :     bool operator()(const Note* n1, const Note* n2) const
      47              :     {
      48          173 :         if (noteIsBefore(n1, n2)) {
      49           25 :             return true;
      50              :         }
      51          148 :         if (noteIsBefore(n2, n1)) {
      52           98 :             return false;
      53              :         }
      54              :         // tiebreak by pointer address so we can have unison notes in the set
      55           50 :         return n1 < n2;
      56              :     }
      57              : };
      58              : 
      59           14 : static std::map<Chord*, std::set<Note*, NoteComparator> > getNotesByChord(const std::vector<Note*>& notes)
      60              : {
      61              :     // Return map of notes by chord
      62              :     // Include all notes between highest and lowest in each chord
      63           14 :     std::map<Chord*, std::set<Note*, NoteComparator> > notesByChord;
      64           55 :     for (Note* noteToAdd : notes) {
      65           41 :         Chord* chord = noteToAdd->chord();
      66           41 :         auto notesByChordIt = notesByChord.find(chord);
      67              : 
      68           41 :         if (notesByChordIt == notesByChord.end()) {
      69           32 :             notesByChord.emplace(chord, std::set<Note*, NoteComparator> { noteToAdd });
      70           16 :             continue;
      71              :         }
      72              : 
      73              :         // Add all notes between last note and this one
      74           25 :         const Note* prevNote = *notesByChordIt->second.rbegin();
      75              : 
      76           25 :         const Note* firstNote = noteIsBefore(noteToAdd, prevNote) ? noteToAdd : prevNote;
      77           25 :         const Note* secondNote = firstNote == noteToAdd ? prevNote : noteToAdd;
      78              : 
      79           25 :         const std::vector<Note*>& chordNotes = chord->notes();
      80              : 
      81           25 :         std::vector<Note*>::const_iterator firstNoteIt = std::find(chordNotes.begin(), chordNotes.end(), firstNote);
      82           25 :         std::vector<Note*>::const_iterator secondNoteIt = std::find(chordNotes.begin(), chordNotes.end(), secondNote);
      83              : 
      84           25 :         assert(firstNoteIt != chordNotes.end() && secondNoteIt != chordNotes.end());
      85              : 
      86          100 :         for (std::vector<Note*>::const_iterator chordNoteIt = firstNoteIt; chordNoteIt != std::next(secondNoteIt);
      87           50 :              chordNoteIt = std::next(chordNoteIt)) {
      88           50 :             Note* note = *chordNoteIt;
      89           50 :             notesByChordIt->second.insert(note);
      90              :         }
      91              :     }
      92              : 
      93           14 :     return notesByChord;
      94            0 : }
      95              : }
      96              : 
      97            9 : void EditParentheses::toggleParenthesesOnNotes(Transaction& tx, Score* score)
      98              : {
      99            9 :     std::vector<Note*> notes = score->selection().uniqueNotes(muse::nidx, false);
     100              : 
     101            9 :     bool add = false;
     102              : 
     103           11 :     for (Note* note : notes) {
     104            9 :         if (note->getProperty(Pid::HAS_PARENTHESES).value<ParenthesesMode>() == ParenthesesMode::NONE) {
     105            7 :             add = true;
     106            7 :             break;
     107              :         }
     108              :     }
     109              : 
     110            9 :     if (add) {
     111            7 :         addParenthesesToNotes(tx, notes);
     112              :     } else {
     113            2 :         removeParenthesesFromNotes(tx, notes);
     114              :     }
     115            9 : }
     116              : 
     117           11 : void EditParentheses::addParenthesesToNotes(Transaction&, const std::vector<Note*>& notes)
     118              : {
     119           11 :     std::map<Chord*, std::set<Note*, NoteComparator> > notesByChord = getNotesByChord(notes);
     120              : 
     121           24 :     for (auto& [chord, noteSet] : notesByChord) {
     122           13 :         std::vector<Note*> noteVec(noteSet.begin(), noteSet.end());
     123              : 
     124           47 :         for (Note* note : noteVec) {
     125              :             // User has overriden generated parentheses
     126           34 :             note->undoChangeProperty(Pid::HIDE_GENERATED_PARENTHESES, true);
     127           34 :             note->undoChangeProperty(Pid::HAS_PARENTHESES, ParenthesesMode::BOTH);
     128              :         }
     129              : 
     130           13 :         EditChord::removeChordParentheses(chord, noteVec);
     131           13 :         EditChord::addChordParentheses(chord, std::move(noteVec));
     132           13 :     }
     133           11 : }
     134              : 
     135            0 : void EditParentheses::addParenthesesToNotes(Transaction& tx, Score* score)
     136              : {
     137            0 :     addParenthesesToNotes(tx, score->selection().uniqueNotes(muse::nidx, false));
     138            0 : }
     139              : 
     140            3 : void EditParentheses::removeParenthesesFromNotes(Transaction&, const std::vector<Note*>& notes)
     141              : {
     142            3 :     std::map<Chord*, std::set<Note*, NoteComparator> > notesByChord = getNotesByChord(notes);
     143              : 
     144            6 :     for (auto& [chord, noteSet] : notesByChord) {
     145            3 :         std::vector<Note*> noteVec(noteSet.begin(), noteSet.end());
     146              : 
     147           10 :         for (Note* note : noteVec) {
     148            7 :             note->undoChangeProperty(Pid::HAS_PARENTHESES, ParenthesesMode::NONE);
     149              : 
     150              :             // User has overriden generated parentheses
     151            7 :             note->undoChangeProperty(Pid::HIDE_GENERATED_PARENTHESES, true);
     152              :         }
     153              : 
     154            3 :         EditChord::removeChordParentheses(chord, std::move(noteVec));
     155            3 :     }
     156            3 : }
     157              : 
     158            1 : void EditParentheses::removeParenthesesFromNotes(Transaction& tx, Score* score)
     159              : {
     160            1 :     removeParenthesesFromNotes(tx, score->selection().uniqueNotes(muse::nidx, false));
     161            1 : }
     162              : 
     163            9 : void EditParentheses::toggleParentheses(Transaction& tx, Score* score)
     164              : {
     165            9 :     toggleParenthesesOnNotes(tx, score);
     166              : 
     167           38 :     for (EngravingItem* el : score->selection().elements()) {
     168           29 :         if (el->isNote()) {
     169           29 :             continue;
     170              :         }
     171            0 :         toggleParenthesesOnItem(tx, el);
     172              :     }
     173            9 : }
     174              : 
     175            0 : void EditParentheses::toggleParenthesesOnItem(Transaction&, EngravingItem* el)
     176              : {
     177            0 :     if (el->isAccidental()) {
     178            0 :         Accidental* acc = toAccidental(el);
     179            0 :         acc->undoChangeProperty(Pid::ACCIDENTAL_BRACKET, int(AccidentalBracket::PARENTHESIS));
     180            0 :     } else if (el->type() == ElementType::TIMESIG) {
     181            0 :         TimeSig* ts = toTimeSig(el);
     182            0 :         ts->setLargeParentheses(true);
     183              :     } else {
     184            0 :         ParenthesesMode p = el->leftParen() || el->rightParen() ? ParenthesesMode::NONE : ParenthesesMode::BOTH;
     185            0 :         el->undoChangeProperty(Pid::HAS_PARENTHESES, p);
     186              :     }
     187            0 : }
        

Generated by: LCOV version 2.0-1