LCOV - code coverage report
Current view: top level - src/engraving/editing - addremoveelement.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 76.7 % 257 197
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 "addremoveelement.h"
      24              : 
      25              : #include "containers.h"
      26              : 
      27              : #include "../dom/arpeggio.h"
      28              : #include "../dom/chord.h"
      29              : #include "../dom/engravingitem.h"
      30              : #include "../dom/keysig.h"
      31              : #include "../dom/linkedobjects.h"
      32              : #include "../dom/measure.h"
      33              : #include "../dom/note.h"
      34              : #include "../dom/score.h"
      35              : #include "../dom/segment.h"
      36              : #include "../dom/spanner.h"
      37              : #include "../dom/staff.h"
      38              : #include "../dom/system.h"
      39              : #include "../dom/tie.h"
      40              : #include "../dom/tremolotwochord.h"
      41              : #include "../dom/tuplet.h"
      42              : 
      43              : using namespace mu::engraving;
      44              : 
      45              : //---------------------------------------------------------
      46              : //   undoRemoveTuplet
      47              : //---------------------------------------------------------
      48              : 
      49         5664 : static void undoRemoveTuplet(DurationElement* cr)
      50              : {
      51         5664 :     if (cr->tuplet()) {
      52          495 :         cr->tuplet()->remove(cr);
      53          495 :         if (cr->tuplet()->elements().empty()) {
      54           91 :             undoRemoveTuplet(cr->tuplet());
      55              :         }
      56              :     }
      57         5664 : }
      58              : 
      59              : //---------------------------------------------------------
      60              : //   undoAddTuplet
      61              : //---------------------------------------------------------
      62              : 
      63         6785 : static void undoAddTuplet(DurationElement* cr)
      64              : {
      65         6785 :     if (cr->tuplet()) {
      66          560 :         cr->tuplet()->add(cr);
      67          560 :         if (cr->tuplet()->elements().size() == 1) {
      68          134 :             undoAddTuplet(cr->tuplet());
      69              :         }
      70              :     }
      71         6785 : }
      72              : 
      73              : //---------------------------------------------------------
      74              : //   AddElement
      75              : //---------------------------------------------------------
      76              : 
      77        14309 : AddElement::AddElement(EngravingItem* e)
      78              : {
      79        14309 :     DO_ASSERT_X(!e->generated(), String(u"Generated item %1 passed to AddElement").arg(String::fromAscii(e->typeName())));
      80        14309 :     DO_ASSERT_X(e->parent()->score() == e->score(),
      81              :                 String(u"Item %1 is in a different score to its parent %2").arg(String::fromAscii(e->typeName()),
      82              :                                                                                 String::fromAscii(e->parent()->typeName())));
      83        14309 :     element = e;
      84        14309 : }
      85              : 
      86         7495 : void AddElement::cleanup(bool wasDone)
      87              : {
      88         7495 :     if (!wasDone) {
      89          689 :         delete element;
      90          689 :         element = nullptr;
      91              :     }
      92         7495 : }
      93              : 
      94        15273 : void AddElement::endUndoRedo(bool isUndo) const
      95              : {
      96        15273 :     if (element->isChordRest()) {
      97         6801 :         if (isUndo) {
      98          508 :             undoRemoveTuplet(toChordRest(element));
      99              :         } else {
     100         6293 :             undoAddTuplet(toChordRest(element));
     101              :         }
     102         8472 :     } else if (element->isClef()) {
     103           38 :         element->triggerLayout();
     104           38 :         element->score()->setLayout(element->staff()->nextClefTick(element->tick()), element->staffIdx());
     105         8434 :     } else if (element->isKeySig()) {
     106          235 :         element->triggerLayout();
     107          235 :         element->score()->setLayout(element->staff()->nextKeyTick(element->tick()), element->staffIdx());
     108              :     }
     109        15273 : }
     110              : 
     111          831 : void AddElement::undo()
     112              : {
     113          831 :     Score* score = element->score();
     114          831 :     EngravingItem::disconnectSharedTree(element);
     115              : 
     116          831 :     if (!element->isTuplet()) {
     117          831 :         score->removeElement(element);
     118              :     }
     119              : 
     120          831 :     endUndoRedo(true);
     121          831 : }
     122              : 
     123        14442 : void AddElement::redo()
     124              : {
     125        14442 :     Score* score = element->score();
     126              : 
     127        14442 :     if (!element->isTuplet()) {
     128        14439 :         score->addElement(element);
     129              :     }
     130              : 
     131        14442 :     endUndoRedo(false);
     132        14442 : }
     133              : 
     134            0 : const char* AddElement::name() const
     135              : {
     136              :     static char buffer[64];
     137            0 :     if (element->isTextBase()) {
     138            0 :         snprintf(buffer, 64, "Add:    %s <%s> %p", element->typeName(),
     139            0 :                  muPrintable(toTextBase(element)->plainText()), element);
     140            0 :     } else if (element->isSegment()) {
     141            0 :         snprintf(buffer, 64, "Add:    <%s-%s> %p", element->typeName(), toSegment(element)->subTypeName(), element);
     142              :     } else {
     143            0 :         snprintf(buffer, 64, "Add:    <%s> %p", element->typeName(), element);
     144              :     }
     145            0 :     return buffer;
     146              : }
     147              : 
     148            0 : bool AddElement::matchesFilter(UndoableCommandFilter f, const EngravingItem* target) const
     149              : {
     150            0 :     switch (f) {
     151            0 :     case UndoableCommandFilter::AddElement:
     152            0 :         return target == element;
     153            0 :     case UndoableCommandFilter::AddElementLinked:
     154            0 :         return muse::contains(target->linkList(), static_cast<EngravingObject*>(element));
     155            0 :     default:
     156            0 :         break;
     157              :     }
     158            0 :     return false;
     159              : }
     160              : 
     161         9348 : std::vector<EngravingObject*> AddElement::objectItems() const
     162              : {
     163         9348 :     return compoundObjects(element);
     164              : }
     165              : 
     166              : //---------------------------------------------------------
     167              : //   removeNote
     168              : //    Helper function for RemoveElement class
     169              : //---------------------------------------------------------
     170              : 
     171         2662 : static void removeNote(const Note* note)
     172              : {
     173         2662 :     Score* score = note->score();
     174         2662 :     Tie* tieFor = note->tieFor();
     175         2662 :     Tie* tieBack = note->tieBack();
     176         2662 :     if (tieFor && tieFor->endNote()) {
     177          822 :         score->doUndoRemoveElement(tieFor);
     178              :     }
     179         2662 :     if (tieBack) {
     180           92 :         if (tieBack->tieJumpPoints() && tieBack->tieJumpPoints()->size() > 1) {
     181            1 :             Tie::changeTieType(tieBack);
     182              :         } else {
     183           91 :             score->doUndoRemoveElement(tieBack);
     184              :         }
     185              :     }
     186         2662 :     for (Spanner* s : note->spannerBack()) {
     187            0 :         score->doUndoRemoveElement(s);
     188              :     }
     189         2662 :     for (Spanner* s : note->spannerFor()) {
     190            0 :         score->doUndoRemoveElement(s);
     191              :     }
     192         2662 : }
     193              : 
     194              : //---------------------------------------------------------
     195              : //   RemoveElement
     196              : //---------------------------------------------------------
     197              : 
     198        11016 : RemoveElement::RemoveElement(EngravingItem* e)
     199              : {
     200        11016 :     DO_ASSERT_X(!e->generated(), String(u"Generated item %1 passed to RemoveElement").arg(String::fromAscii(e->typeName())));
     201        11016 :     element = e;
     202              : 
     203        11016 :     Score* score = element->score();
     204        11016 :     if (element->isChordRest()) {
     205         5025 :         ChordRest* cr = toChordRest(element);
     206         5025 :         if (cr->tuplet() && cr->tuplet()->elements().size() <= 1) {
     207           61 :             score->doUndoRemoveElement(cr->tuplet());
     208              :         }
     209         5025 :         if (e->isChord()) {
     210         2224 :             Chord* chord = toChord(e);
     211              :             // remove tremolo between 2 notes
     212         2224 :             if (chord->tremoloTwoChord()) {
     213            0 :                 TremoloTwoChord* tremolo = chord->tremoloTwoChord();
     214            0 :                 score->doUndoRemoveElement(tremolo);
     215              :             }
     216              :             // Move arpeggio down to next available note
     217         2224 :             if (chord->arpeggio()) {
     218            1 :                 chord->arpeggio()->rebaseStartAnchor(AnchorRebaseDirection::DOWN);
     219              :             } else {
     220              :                 // If this chord is the end of an arpeggio, move the end of the arpeggio upwards to the next available chord
     221         2223 :                 Arpeggio* spanArp = chord->spanArpeggio();
     222         2223 :                 if (spanArp && chord->track() == spanArp->endTrack()) {
     223            0 :                     spanArp->rebaseEndAnchor(AnchorRebaseDirection::UP);
     224              :                 }
     225              :             }
     226         4549 :             for (const Note* note : chord->notes()) {
     227         2325 :                 removeNote(note);
     228              :             }
     229              :         }
     230         5991 :     } else if (element->isNote()) {
     231              :         // Removing an individual note within a chord
     232          337 :         const Note* note = toNote(element);
     233          337 :         removeNote(note);
     234              :     }
     235        11016 : }
     236              : 
     237         5762 : void RemoveElement::cleanup(bool wasDone)
     238              : {
     239         5762 :     if (wasDone) {
     240         5115 :         delete element;
     241         5115 :         element = nullptr;
     242              :     }
     243         5762 : }
     244              : 
     245          738 : void RemoveElement::undo()
     246              : {
     247          738 :     Score* score = element->score();
     248              : 
     249          738 :     if (!element->isTuplet()) {
     250          729 :         score->addElement(element);
     251              :     }
     252              : 
     253          738 :     if (element->isChordRest()) {
     254          358 :         if (element->isChord()) {
     255          118 :             Chord* chord = toChord(element);
     256          237 :             for (Note* note : chord->notes()) {
     257          119 :                 note->connectTiedNotes();
     258              :             }
     259              :         }
     260          358 :         undoAddTuplet(toChordRest(element));
     261          380 :     } else if (element->isClef()) {
     262            4 :         score->setLayout(element->staff()->nextClefTick(element->tick()), element->staffIdx());
     263          376 :     } else if (element->isKeySig()) {
     264            9 :         score->setLayout(element->staff()->nextKeyTick(element->tick()), element->staffIdx());
     265              :     }
     266          738 : }
     267              : 
     268        11105 : void RemoveElement::redo()
     269              : {
     270        11105 :     Score* score = element->score();
     271              : 
     272        11105 :     EngravingItem::disconnectSharedTree(element);
     273        11105 :     if (!element->isTuplet()) {
     274        11044 :         score->removeElement(element);
     275              :     }
     276              : 
     277        11105 :     if (element->isChordRest()) {
     278         5065 :         undoRemoveTuplet(toChordRest(element));
     279         5065 :         if (element->isChord()) {
     280         2226 :             Chord* chord = toChord(element);
     281         4553 :             for (Note* note : chord->notes()) {
     282         2327 :                 note->disconnectTiedNotes();
     283              :             }
     284              :         }
     285         6040 :     } else if (element->isClef()) {
     286           13 :         score->setLayout(element->staff()->nextClefTick(element->tick()), element->staffIdx());
     287         6027 :     } else if (element->isKeySig()) {
     288           41 :         score->setLayout(element->staff()->nextKeyTick(element->tick()), element->staffIdx());
     289              :     }
     290        11105 : }
     291              : 
     292            0 : const char* RemoveElement::name() const
     293              : {
     294              :     static char buffer[64];
     295            0 :     if (element->isTextBase()) {
     296            0 :         snprintf(buffer, 64, "Remove: %s <%s> %p", element->typeName(),
     297            0 :                  muPrintable(toTextBase(element)->plainText()), element);
     298            0 :     } else if (element->isSegment()) {
     299            0 :         snprintf(buffer, 64, "Remove: <%s-%s> %p", element->typeName(), toSegment(element)->subTypeName(), element);
     300              :     } else {
     301            0 :         snprintf(buffer, 64, "Remove: %s %p", element->typeName(), element);
     302              :     }
     303            0 :     return buffer;
     304              : }
     305              : 
     306            0 : bool RemoveElement::matchesFilter(UndoableCommandFilter f, const EngravingItem* target) const
     307              : {
     308            0 :     switch (f) {
     309            0 :     case UndoableCommandFilter::RemoveElement:
     310            0 :         return target == element;
     311            0 :     case UndoableCommandFilter::RemoveElementLinked:
     312            0 :         return muse::contains(target->linkList(), static_cast<EngravingObject*>(element));
     313            0 :     default:
     314            0 :         break;
     315              :     }
     316            0 :     return false;
     317              : }
     318              : 
     319         7374 : std::vector<EngravingObject*> RemoveElement::objectItems() const
     320              : {
     321         7374 :     return compoundObjects(element);
     322              : }
     323              : 
     324              : //---------------------------------------------------------
     325              : //   ChangeElement
     326              : //---------------------------------------------------------
     327              : 
     328          115 : ChangeElement::ChangeElement(EngravingItem* oe, EngravingItem* ne)
     329              : {
     330          115 :     oldElement = oe;
     331          115 :     newElement = ne;
     332          115 : }
     333              : 
     334          115 : void ChangeElement::flip()
     335              : {
     336          115 :     const LinkedObjects* links = oldElement->links();
     337          115 :     if (links) {
     338            0 :         newElement->linkTo(oldElement);
     339            0 :         oldElement->unlink();
     340              :     }
     341              : 
     342          115 :     newElement->setTrack(oldElement->track());
     343          115 :     if (newElement->isSpanner()) {
     344            0 :         toSpanner(newElement)->setTrack2(toSpanner(oldElement)->track2());
     345              :     }
     346              : 
     347          115 :     Score* score = oldElement->score();
     348          115 :     if (!score->selection().isRange()) {
     349           14 :         if (oldElement->selected()) {
     350            0 :             score->deselect(oldElement);
     351              :         }
     352           14 :         if (newElement->selected()) {
     353            0 :             score->select(newElement, SelectType::ADD);
     354              :         }
     355              :     }
     356              : 
     357          115 :     if (oldElement->ownershipParent() == nullptr) {
     358            0 :         newElement->setScore(score);
     359            0 :         score->removeElement(oldElement);
     360            0 :         score->addElement(newElement);
     361              :     } else {
     362          115 :         oldElement->ownershipParentItem()->change(oldElement, newElement);
     363              :     }
     364              : 
     365          115 :     if (newElement->isKeySig()) {
     366            0 :         KeySig* ks = toKeySig(newElement);
     367            0 :         if (!ks->generated()) {
     368            0 :             ks->staff()->setKey(ks->tick(), ks->keySigEvent());
     369              :         }
     370              :     }
     371              : 
     372          115 :     if (newElement->isSpannerSegment()) {
     373            0 :         SpannerSegment* os = toSpannerSegment(oldElement);
     374            0 :         SpannerSegment* ns = toSpannerSegment(newElement);
     375            0 :         if (os->system()) {
     376            0 :             os->system()->remove(os);
     377              :         }
     378            0 :         if (ns->system()) {
     379            0 :             ns->system()->add(ns);
     380              :         }
     381              :     }
     382              : 
     383          115 :     std::swap(oldElement, newElement);
     384          115 :     oldElement->triggerLayout();
     385          115 :     newElement->triggerLayout();
     386          115 : }
     387              : 
     388              : //---------------------------------------------------------
     389              : //   ChangeParent
     390              : //---------------------------------------------------------
     391              : 
     392          187 : void ChangeParent::flip()
     393              : {
     394          187 :     EngravingItem* p = element->ownershipParentItem();
     395          187 :     staff_idx_t si = element->staffIdx();
     396          187 :     p->remove(element);
     397          187 :     element->setOwnershipParent(parent);
     398          187 :     element->setTrack(staffIdx * VOICES + element->voice());
     399          187 :     parent->add(element);
     400          187 :     staffIdx = si;
     401          187 :     parent = p;
     402              : 
     403          187 :     element->triggerLayout();
     404          187 : }
     405              : 
     406              : //---------------------------------------------------------
     407              : //   LinkUnlink
     408              : //---------------------------------------------------------
     409              : 
     410         5363 : LinkUnlink::~LinkUnlink()
     411              : {
     412         5363 :     if (le && mustDelete) {
     413          381 :         assert(le->size() <= 1);
     414          381 :         delete le;
     415              :     }
     416         5363 : }
     417              : 
     418         4967 : void LinkUnlink::link()
     419              : {
     420         4967 :     if (le->size() == 1) {
     421         4186 :         le->front()->setLinks(le);
     422              :     }
     423         4967 :     mustDelete = false;
     424         4967 :     le->push_back(e);
     425         4967 :     e->setLinks(le);
     426         4967 : }
     427              : 
     428          610 : void LinkUnlink::unlink()
     429              : {
     430          610 :     assert(le->contains(e));
     431          610 :     le->remove(e);
     432          610 :     if (le->size() == 1) {
     433          409 :         le->front()->setLinks(nullptr);
     434          409 :         mustDelete = true;
     435              :     }
     436              : 
     437          610 :     e->setLinks(nullptr);
     438          610 : }
     439              : 
     440              : //---------------------------------------------------------
     441              : //   Link
     442              : //    link e1 to e2
     443              : //---------------------------------------------------------
     444              : 
     445         4941 : Link::Link(EngravingObject* e1, EngravingObject* e2)
     446              : {
     447         4941 :     assert(e1->links() == nullptr);
     448         4941 :     le = e2->links();
     449         4941 :     if (!le) {
     450         4067 :         le = new LinkedObjects();
     451         4067 :         le->push_back(e2);
     452              :     }
     453         4941 :     e = e1;
     454         4941 : }
     455              : 
     456            0 : bool Link::matchesFilter(UndoableCommandFilter f, const EngravingItem* target) const
     457              : {
     458            0 :     if (f == UndoableCommandFilter::Link) {
     459            0 :         return e == target || le->contains(const_cast<EngravingItem*>(target));
     460              :     }
     461            0 :     return false;
     462              : }
     463              : 
     464              : //---------------------------------------------------------
     465              : //   Unlink
     466              : //---------------------------------------------------------
     467              : 
     468          536 : Unlink::Unlink(EngravingObject* _e)
     469              : {
     470          536 :     e  = _e;
     471          536 :     le = e->links();
     472          536 :     assert(le);
     473          536 : }
     474              : 
     475            8 : void ChangeSegmentParent::flip()
     476              : {
     477            8 :     Measure* p = segment->measure();
     478            8 :     Fraction oldTick = segment->rtick();
     479            8 :     p->remove(segment);
     480            8 :     segment->setOwnershipParent(parent);
     481            8 :     segment->setRtick(tick);
     482            8 :     parent->add(segment);
     483              : 
     484            8 :     parent = p;
     485            8 :     tick = oldTick;
     486              : 
     487            8 :     segment->triggerLayout();
     488            8 : }
        

Generated by: LCOV version 2.0-1