LCOV - code coverage report
Current view: top level - src/engraving/editing - editchord.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 67.2 % 271 182
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) 2025 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : 
      23              : #include "editchord.h"
      24              : 
      25              : #include "../dom/arpeggio.h"
      26              : #include "../dom/articulation.h"
      27              : #include "../dom/chord.h"
      28              : #include "../dom/chordrest.h"
      29              : #include "../dom/factory.h"
      30              : #include "../dom/note.h"
      31              : #include "../dom/score.h"
      32              : #include "../dom/segment.h"
      33              : #include "../dom/tapping.h"
      34              : #include "../dom/tremolotwochord.h"
      35              : #include "../dom/utils.h"
      36              : 
      37              : using namespace mu::engraving;
      38              : 
      39        50788 : void EditChord::addChordParentheses(Chord* chord, std::vector<Note*> notes, bool addToLinked, bool generated)
      40              : {
      41        50788 :     if (notes.empty()) {
      42        50610 :         return;
      43              :     }
      44          178 :     undoAddParenthesesToNotes(chord, notes, addToLinked, generated);
      45              : }
      46              : 
      47              : //---------------------------------------------------------
      48              : //   toggleArticulation
      49              : ///   Toggle attribute \a attr for all selected notes/rests.
      50              : //---------------------------------------------------------
      51              : 
      52            0 : void EditChord::toggleArticulation(Score* score, SymId attr)
      53              : {
      54            0 :     std::set<Chord*> set;
      55            0 :     for (EngravingItem* el : score->selection().elements()) {
      56            0 :         if (el->isNote() || el->isChord()) {
      57            0 :             Chord* cr = nullptr;
      58            0 :             if (el->isNote()) {
      59            0 :                 cr = toNote(el)->chord();
      60            0 :                 if (muse::contains(set, cr)) {
      61            0 :                     continue;
      62              :                 }
      63              :             }
      64            0 :             Articulation* na = Factory::createArticulation(score->dummy());
      65            0 :             na->setSymId(attr);
      66            0 :             if (!EditChord::toggleArticulation(score, el, na)) {
      67            0 :                 delete na;
      68              :             }
      69            0 :             if (cr) {
      70            0 :                 set.insert(cr);
      71              :             }
      72              :         }
      73              :     }
      74            0 : }
      75              : 
      76           24 : bool EditChord::toggleArticulation(Score* score, EngravingItem* el, Articulation* a)
      77              : {
      78              :     Chord* c;
      79           24 :     if (el->isNote()) {
      80           24 :         c = toNote(el)->chord();
      81            0 :     } else if (el->isChord()) {
      82            0 :         c = toChord(el);
      83              :     } else {
      84            0 :         return false;
      85              :     }
      86           24 :     Articulation* oa = c->hasArticulation(a);
      87           24 :     if (oa) {
      88            0 :         score->undoRemoveElement(oa);
      89            0 :         return false;
      90              :     }
      91              : 
      92           24 :     Tapping* tap = c->tapping();
      93           24 :     if (tap) {
      94            0 :         score->undoRemoveElement(tap);
      95              :     }
      96              : 
      97           24 :     if (!a->isDouble()) {
      98           24 :         a->setOwnershipParent(c);
      99           24 :         a->setTrack(c->track());
     100           24 :         score->undoAddElement(a);
     101           24 :         return true;
     102              :     }
     103              : 
     104            0 :     std::set<SymId> newSubComponentIds = splitArticulations({ a->symId() });
     105            0 :     for (const SymId& id : newSubComponentIds) {
     106            0 :         Articulation* articCopy = a->clone();
     107            0 :         articCopy->setSymId(id);
     108              : 
     109            0 :         if (!c->hasArticulation(articCopy)) {
     110            0 :             articCopy->setOwnershipParent(c);
     111            0 :             articCopy->setTrack(c->track());
     112            0 :             score->undoAddElement(articCopy);
     113            0 :             continue;
     114              :         }
     115            0 :         delete articCopy;
     116              :     }
     117            0 :     return true;
     118            0 : }
     119              : 
     120        51075 : void EditChord::removeChordParentheses(Chord* chord, std::vector<Note*> notes, bool addToLinked, bool generated)
     121              : {
     122        51075 :     if (notes.empty()) {
     123        50677 :         return;
     124              :     }
     125              :     // Split based on parentheses group
     126          398 :     std::map<const NoteParenthesisInfo*, std::vector<Note*> > notesByGroup;
     127              : 
     128          821 :     for (Note* n : notes) {
     129          423 :         const NoteParenthesisInfo* noteParenInfo = chord->findNoteParenthesisInfo(n);
     130          423 :         if (!noteParenInfo) {
     131          419 :             continue;
     132              :         }
     133              : 
     134           13 :         auto groupIt = notesByGroup.find(noteParenInfo);
     135              : 
     136           13 :         if (groupIt == notesByGroup.end()) {
     137           18 :             std::vector<Note*> noteVec { n };
     138            9 :             notesByGroup.insert(std::make_pair(noteParenInfo, noteVec));
     139            9 :             continue;
     140            9 :         }
     141              : 
     142            4 :         groupIt->second.push_back(n);
     143              :     }
     144              : 
     145          416 :     for (auto pairIterator = notesByGroup.rbegin(); pairIterator != notesByGroup.rend(); pairIterator = std::next(pairIterator)) {
     146            9 :         auto groupNotesPair = *pairIterator;
     147            9 :         const NoteParenthesisInfo* parenInfo = groupNotesPair.first;
     148            9 :         if (parenInfo->notes().size() == groupNotesPair.second.size()) {
     149              :             // All notes marked for paren removal, clear parentheses group
     150            4 :             undoClearParenthesisGroup(chord, parenInfo->notes(), parenInfo->leftParen(), parenInfo->rightParen(), addToLinked);
     151            4 :             continue;
     152              :         }
     153              : 
     154              :         // Only some notes marked for removal. Remove from lowest note up. This keeps the paren group splitting logic safe
     155           10 :         for (auto noteIterator = groupNotesPair.second.begin(); noteIterator != groupNotesPair.second.end();
     156            5 :              noteIterator = std::next(noteIterator)) {
     157              :             // Remove paren from single note and create new paren group for all notes below
     158            5 :             Note* note = *noteIterator;
     159            5 :             const NoteParenthesisInfo* curParenInfo = note->parenthesisInfo();
     160            5 :             const std::vector<Note*> notesList = curParenInfo->notes();
     161            5 :             Parenthesis* leftParen = curParenInfo->leftParen();
     162            5 :             Parenthesis* rightParen = curParenInfo->rightParen();
     163            5 :             auto notePos = std::find(notesList.begin(), notesList.end(), note);
     164              : 
     165           10 :             if (notePos != notesList.end() && std::next(notePos) != notesList.end() && notePos != notesList.begin()) {
     166              :                 // Create new group
     167            2 :                 std::vector<Note*> newNoteGroup(notePos + 1, notesList.end());
     168              : 
     169            6 :                 for (Note* noteToRemove : newNoteGroup) {
     170            4 :                     if (curParenInfo->notes().size() == 1) {
     171            0 :                         undoClearParenthesisGroup(chord, { noteToRemove }, leftParen, rightParen, addToLinked);
     172              :                     } else {
     173            4 :                         undoRemoveParenthesesFromNote(chord, noteToRemove, leftParen, addToLinked);
     174              :                     }
     175              :                 }
     176              : 
     177            2 :                 undoAddParenthesesToNotes(chord, newNoteGroup, addToLinked, generated);
     178            2 :             }
     179            5 :             if (curParenInfo->notes().size() == 1) {
     180            0 :                 undoClearParenthesisGroup(chord, { note }, leftParen, rightParen, addToLinked);
     181              :             } else {
     182            5 :                 undoRemoveParenthesesFromNote(chord, note, leftParen, addToLinked);
     183              :             }
     184            5 :         }
     185            9 :     }
     186          398 : }
     187              : 
     188          180 : void EditChord::undoAddParenthesesToNotes(Chord* chord, std::vector<Note*> notes, bool addToLinked, bool generated)
     189              : {
     190          180 :     track_idx_t track = chord->track();
     191          180 :     Parenthesis* leftParen = Factory::createParenthesis(chord);
     192          180 :     leftParen->setTrack(track);
     193          180 :     leftParen->setDirection(DirectionH::LEFT);
     194          180 :     leftParen->setGenerated(generated);
     195          180 :     Parenthesis* rightParen = Factory::createParenthesis(chord);
     196          180 :     rightParen->setTrack(track);
     197          180 :     rightParen->setDirection(DirectionH::RIGHT);
     198          180 :     rightParen->setGenerated(generated);
     199              : 
     200          180 :     if (!addToLinked) {
     201           67 :         doAddNoteParentheses(chord, notes, leftParen, rightParen);
     202           67 :         return;
     203              :     }
     204              : 
     205          231 :     for (EngravingObject* linkedObject : chord->linkList()) {
     206          118 :         if (linkedObject == chord) {
     207          113 :             doAddNoteParentheses(chord, notes, leftParen, rightParen);
     208          113 :             continue;
     209              :         }
     210              : 
     211            5 :         Chord* linkedChord = toChord(linkedObject);
     212            5 :         Score* linkedScore = linkedChord->score();
     213            5 :         Staff* linkedStaff = linkedChord->staff();
     214              : 
     215            5 :         std::vector<Note*> linkedNotes;
     216            5 :         linkedNotes.reserve(notes.size());
     217           19 :         for (Note* note : notes) {
     218           14 :             Note* linkedNote = toNote(note->findLinkedInStaff(linkedStaff));
     219           14 :             if (!linkedNote) {
     220            0 :                 continue;
     221              :             }
     222           14 :             linkedNotes.push_back(linkedNote);
     223              :         }
     224            5 :         if (linkedNotes.empty()) {
     225            0 :             continue;
     226              :         }
     227              : 
     228            5 :         Parenthesis* linkedParenLeft = toParenthesis(leftParen->linkedClone());
     229            5 :         linkedParenLeft->setScore(linkedScore);
     230            5 :         linkedParenLeft->setOwnershipParent(linkedChord);
     231            5 :         linkedParenLeft->setTrack(linkedChord->track());
     232            5 :         Parenthesis* linkedParenRight = toParenthesis(rightParen->linkedClone());
     233            5 :         linkedParenRight->setScore(linkedScore);
     234            5 :         linkedParenRight->setOwnershipParent(linkedChord);
     235            5 :         linkedParenRight->setTrack(linkedChord->track());
     236              : 
     237            5 :         doAddNoteParentheses(linkedChord, linkedNotes, linkedParenLeft, linkedParenRight);
     238          118 :     }
     239              : }
     240              : 
     241            9 : void EditChord::undoRemoveParenthesesFromNote(Chord* chord, Note* note, Parenthesis* leftParen, bool removeFromLinked)
     242              : {
     243            9 :     if (!removeFromLinked) {
     244            3 :         doRemoveSingleNoteParenthesis(chord, note, leftParen);
     245            3 :         return;
     246              :     }
     247              : 
     248           15 :     for (EngravingObject* linkedObject : chord->linkList()) {
     249            9 :         if (linkedObject == chord) {
     250            6 :             doRemoveSingleNoteParenthesis(chord, note, leftParen);
     251            6 :             continue;
     252              :         }
     253              : 
     254            3 :         Chord* linkedChord = toChord(linkedObject);
     255            3 :         Staff* linkedStaff = linkedChord->staff();
     256            3 :         Parenthesis* linkedLeftParen = toParenthesis(leftParen->findLinkedInStaff(linkedStaff));
     257            3 :         if (!linkedLeftParen) {
     258            0 :             continue;
     259              :         }
     260              : 
     261            3 :         Note* linkedNote = toNote(note->findLinkedInStaff(linkedStaff));
     262            3 :         if (!linkedNote) {
     263            0 :             continue;
     264              :         }
     265              : 
     266            3 :         const NoteParenthesisInfo* noteParenInfo = linkedChord->findNoteParenthesisInfo(linkedNote);
     267            3 :         if (noteParenInfo) {
     268            3 :             doRemoveSingleNoteParenthesis(linkedChord, linkedNote, linkedLeftParen);
     269              :         }
     270            6 :     }
     271              : }
     272              : 
     273            4 : void EditChord::undoClearParenthesisGroup(Chord* chord, std::vector<Note*> notes, Parenthesis* leftParen, Parenthesis* rightParen,
     274              :                                           bool removeFromLinked)
     275              : {
     276            4 :     if (!removeFromLinked) {
     277            3 :         doRemoveAllNoteParentheses(chord, leftParen);
     278            3 :         return;
     279              :     }
     280              : 
     281            2 :     for (EngravingObject* linkedObject : chord->linkList()) {
     282            1 :         if (linkedObject == chord) {
     283            1 :             doRemoveAllNoteParentheses(chord, leftParen);
     284            1 :             continue;
     285              :         }
     286              : 
     287            0 :         Chord* linkedChord = toChord(linkedObject);
     288            0 :         Staff* linkedStaff = linkedChord->staff();
     289            0 :         Parenthesis* linkedLeftParen = toParenthesis(leftParen->findLinkedInStaff(linkedStaff));
     290            0 :         Parenthesis* linkedRightParen = toParenthesis(rightParen->findLinkedInStaff(linkedStaff));
     291              : 
     292            0 :         if (!linkedLeftParen && !linkedRightParen) {
     293            0 :             continue;
     294              :         }
     295              : 
     296            0 :         std::vector<Note*> linkedNotes;
     297            0 :         linkedNotes.reserve(notes.size());
     298            0 :         for (Note* note : notes) {
     299            0 :             Note* linkedNote = toNote(note->findLinkedInStaff(linkedStaff));
     300            0 :             if (!linkedNote) {
     301            0 :                 continue;
     302              :             }
     303            0 :             linkedNotes.push_back(linkedNote);
     304              :         }
     305              : 
     306            0 :         doRemoveAllNoteParentheses(linkedChord, linkedLeftParen);
     307            1 :     }
     308              : }
     309              : 
     310          185 : void EditChord::doAddNoteParentheses(Chord* chord, std::vector<Note*> notes, Parenthesis* leftParen, Parenthesis* rightParen)
     311              : {
     312          185 :     NoteParenthesisInfo* parenInfo = new NoteParenthesisInfo(leftParen, rightParen, notes);
     313          185 :     if (leftParen->generated()) {
     314           67 :         chord->addNoteParenthesisInfo(parenInfo);
     315              :     } else {
     316          118 :         Score* score = chord->score();
     317          118 :         score->undo(new AddNoteParenthesisInfo(chord, parenInfo));
     318              :     }
     319          185 : }
     320              : 
     321           12 : void EditChord::doRemoveSingleNoteParenthesis(Chord* chord, Note* note, Parenthesis* leftParen)
     322              : {
     323           12 :     if (leftParen->generated()) {
     324            3 :         chord->removeNoteFromParenthesisInfo(note, leftParen);
     325              : 
     326            3 :         chord->triggerLayout();
     327              :     } else {
     328            9 :         Score* score = chord->score();
     329            9 :         score->undo(new RemoveSingleNoteParentheses(chord, note, leftParen));
     330              :     }
     331           12 : }
     332              : 
     333            4 : void EditChord::doRemoveAllNoteParentheses(Chord* chord, Parenthesis* leftParen)
     334              : {
     335            4 :     if (leftParen->generated()) {
     336            3 :         const NoteParenthesisInfo* noteParenInfo = chord->findNoteParenthesisInfo(leftParen);
     337              : 
     338            3 :         chord->removeNoteParenthesisInfo(noteParenInfo);
     339              :         //! HACK: don't delete as it may still be used in PropertiesPanel - see Score::doUndoRemoveElement
     340              :         // delete noteParenInfo
     341              : 
     342            3 :         chord->triggerLayout();
     343              :     } else {
     344            1 :         Score* score = chord->score();
     345            1 :         NoteParenthesisInfo* noteParenInfo = chord->findNoteParenthesisInfo(leftParen);
     346              : 
     347            1 :         score->undo(new RemoveNoteParenthesisInfo(chord, noteParenInfo));
     348              :     }
     349            4 : }
     350              : 
     351              : //---------------------------------------------------------
     352              : //   ChangeChordStaffMove
     353              : //---------------------------------------------------------
     354              : 
     355            4 : void ChangeChordStaffMove::flip()
     356              : {
     357            4 :     int v = chordRest->staffMove();
     358            4 :     staff_idx_t oldStaff = chordRest->vStaffIdx();
     359              : 
     360            4 :     chordRest->setStaffMove(staffMove);
     361            4 :     chordRest->checkStaffMoveValidity();
     362            4 :     chordRest->triggerLayout();
     363            4 :     if (chordRest->vStaffIdx() == oldStaff) {
     364            0 :         return;
     365              :     }
     366              : 
     367            8 :     for (EngravingObject* e : chordRest->linkList()) {
     368            4 :         ChordRest* cr = toChordRest(e);
     369            4 :         if (cr == chordRest) {
     370            4 :             continue;
     371              :         }
     372            0 :         cr->setStaffMove(staffMove);
     373            0 :         cr->checkStaffMoveValidity();
     374            0 :         cr->triggerLayout();
     375            4 :     }
     376            4 :     staffMove = v;
     377              : }
     378              : 
     379              : //---------------------------------------------------------
     380              : //   SwapCR
     381              : //---------------------------------------------------------
     382              : 
     383            0 : void SwapCR::flip()
     384              : {
     385            0 :     Segment* s1 = cr1->segment();
     386            0 :     Segment* s2 = cr2->segment();
     387            0 :     track_idx_t track = cr1->track();
     388              : 
     389            0 :     if (cr1->isChord() && cr2->isChord() && toChord(cr1)->tremoloTwoChord()
     390            0 :         && (toChord(cr1)->tremoloTwoChord() == toChord(cr2)->tremoloTwoChord())) {
     391            0 :         TremoloTwoChord* t = toChord(cr1)->tremoloTwoChord();
     392            0 :         Chord* c1 = t->chord1();
     393            0 :         Chord* c2 = t->chord2();
     394            0 :         t->setOwnershipParent(toChord(c2));
     395            0 :         t->setChords(toChord(c2), toChord(c1));
     396              :     }
     397              : 
     398            0 :     EngravingItem* cr = s1->element(track);
     399            0 :     s1->setElement(track, s2->element(track));
     400            0 :     s2->setElement(track, cr);
     401            0 :     cr1->score()->setLayout(s1->tick(), cr1->staffIdx(), cr1);
     402            0 :     cr1->score()->setLayout(s2->tick(), cr1->staffIdx(), cr1);
     403            0 : }
     404              : 
     405              : //---------------------------------------------------------
     406              : //   ChangeSpanArpeggio
     407              : //---------------------------------------------------------
     408              : 
     409           81 : void ChangeSpanArpeggio::flip()
     410              : {
     411           81 :     Arpeggio* f_spanArp = m_chord->spanArpeggio();
     412              : 
     413           81 :     m_chord->setSpanArpeggio(m_spanArpeggio);
     414           81 :     m_spanArpeggio = f_spanArp;
     415           81 : }
     416              : 
     417          118 : void AddNoteParenthesisInfo::redo()
     418              : {
     419          118 :     m_chord->addNoteParenthesisInfo(m_noteParenInfo);
     420              : 
     421          118 :     m_chord->triggerLayout();
     422          118 : }
     423              : 
     424            9 : void mu::engraving::AddNoteParenthesisInfo::undo()
     425              : {
     426            9 :     m_chord->removeNoteParenthesisInfo(m_noteParenInfo);
     427              : 
     428            9 :     m_chord->triggerLayout();
     429            9 : }
     430              : 
     431            0 : void AddNoteParenthesisInfo::cleanup(bool wasDone)
     432              : {
     433            0 :     if (!wasDone) {
     434            0 :         delete m_noteParenInfo;
     435            0 :         m_noteParenInfo = nullptr;
     436              :     }
     437            0 : }
     438              : 
     439            1 : void RemoveNoteParenthesisInfo::redo()
     440              : {
     441            1 :     m_chord->removeNoteParenthesisInfo(m_noteParenInfo);
     442              : 
     443            1 :     m_chord->triggerLayout();
     444            1 : }
     445              : 
     446            0 : void RemoveNoteParenthesisInfo::undo()
     447              : {
     448            0 :     m_chord->addNoteParenthesisInfo(m_noteParenInfo);
     449              : 
     450            0 :     m_chord->triggerLayout();
     451            0 : }
     452              : 
     453            0 : void RemoveNoteParenthesisInfo::cleanup(bool wasDone)
     454              : {
     455            0 :     if (wasDone) {
     456            0 :         delete m_noteParenInfo;
     457            0 :         m_noteParenInfo = nullptr;
     458              :     }
     459            0 : }
     460              : 
     461            9 : void RemoveSingleNoteParentheses::redo()
     462              : {
     463            9 :     m_chord->removeNoteFromParenthesisInfo(m_note, m_paren);
     464              : 
     465            9 :     m_chord->triggerLayout();
     466            9 : }
     467              : 
     468            9 : void RemoveSingleNoteParentheses::undo()
     469              : {
     470            9 :     m_chord->addNoteToParenthesisInfo(m_note, m_paren);
     471              : 
     472            9 :     m_chord->triggerLayout();
     473            9 : }
        

Generated by: LCOV version 2.0-1