LCOV - code coverage report
Current view: top level - src/engraving/editing - paste.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 65.2 % 425 277
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) 2021 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 "paste.h"
      24              : 
      25              : #include "draw/iimageprovider.h"
      26              : #include "io/buffer.h"
      27              : #include "modularity/ioc.h"
      28              : 
      29              : #include "imimedata.h"
      30              : 
      31              : #include "editmeasures.h"
      32              : #include "editparentheses.h"
      33              : #include "editstaff.h"
      34              : #include "mscoreview.h"
      35              : #include "noteinput.h"
      36              : #include "transaction/transaction.h"
      37              : #include "transpose.h"
      38              : 
      39              : #include "rw/read400/tread.h"
      40              : #include "rw/rwregister.h"
      41              : 
      42              : #include "../dom/articulation.h"
      43              : #include "../dom/beam.h"
      44              : #include "../dom/breath.h"
      45              : #include "../dom/chord.h"
      46              : #include "../dom/drumset.h"
      47              : #include "../dom/dynamic.h"
      48              : #include "../dom/factory.h"
      49              : #include "../dom/figuredbass.h"
      50              : #include "../dom/fret.h"
      51              : #include "../dom/harmony.h"
      52              : #include "../dom/image.h"
      53              : #include "../dom/measure.h"
      54              : #include "../dom/measurerepeat.h"
      55              : #include "../dom/note.h"
      56              : #include "../dom/part.h"
      57              : #include "../dom/rest.h"
      58              : #include "../dom/score.h"
      59              : #include "../dom/sig.h"
      60              : #include "../dom/staff.h"
      61              : #include "../dom/tie.h"
      62              : #include "../dom/timesig.h"
      63              : #include "../dom/tuplet.h"
      64              : #include "../dom/tremolosinglechord.h"
      65              : #include "../dom/utils.h"
      66              : 
      67              : #include "log.h"
      68              : 
      69              : using namespace mu;
      70              : using namespace muse::io;
      71              : using namespace mu::engraving;
      72              : 
      73              : namespace mu::engraving {
      74              : //---------------------------------------------------------
      75              : //   pasteStaff
      76              : //    return false if paste fails
      77              : //---------------------------------------------------------
      78           42 : bool Paste::pasteStaff(Transaction&, Score*, XmlReader& e, Segment* dst, staff_idx_t dstStaff, Fraction scale)
      79              : {
      80              :     //! NOTE Needs refactoring - reading should be separated from insertion
      81              :     //! (we read the elements into some structure, then inserted them)
      82           42 :     return rw::RWRegister::reader()->pasteStaff(e, dst, dstStaff, scale);
      83              : }
      84              : 
      85              : //---------------------------------------------------------
      86              : //   pasteChordRest
      87              : //---------------------------------------------------------
      88              : 
      89          459 : void Paste::pasteChordRest(Transaction& tx, Score* score, ChordRest* cr, const Fraction& t)
      90              : {
      91          459 :     Fraction tick(t);
      92              : // LOGD("pasteChordRest %s at %d, len %d/%d", cr->typeName(), tick, cr->ticks().numerator(), cr->ticks().denominator() );
      93              : 
      94          459 :     Measure* measure = score->tick2measure(tick);
      95          459 :     if (!measure) {
      96          454 :         return;
      97              :     }
      98              : 
      99          459 :     int twoNoteTremoloFactor = 1;
     100          459 :     if (cr->isChord()) {
     101          322 :         Chord* chord = toChord(cr);
     102          322 :         if (chord->vStaffIdx() >= chord->score()->nstaves()) {
     103              :             // check if staffMove moves a note to a
     104              :             // nonexistent staff
     105            0 :             chord->setStaffMove(0);
     106              :         }
     107          322 :         Transpose::transposeChord(chord, tick);
     108          322 :         if (chord->tremoloTwoChord()) {
     109            2 :             twoNoteTremoloFactor = 2;
     110          320 :         } else if (cr->durationTypeTicks() == (cr->ticks() * 2)) {
     111              :             // this could be the 2nd note of a two-note tremolo
     112              :             // check previous CR on same track, if it has a two-note tremolo, then set twoNoteTremoloFactor to 2
     113            0 :             Segment* seg = measure->undoGetSegment(SegmentType::ChordRest, tick);
     114            0 :             ChordRest* crt = seg->nextChordRest(cr->track(), true);
     115            0 :             if (crt && crt->isChord()) {
     116            0 :                 Chord* chrt = toChord(crt);
     117            0 :                 TremoloTwoChord* tr = chrt->tremoloTwoChord();
     118            0 :                 if (tr) {
     119            0 :                     twoNoteTremoloFactor = 2;
     120              :                 }
     121              :             }
     122              :         }
     123              :     }
     124              : 
     125              :     // we can paste a measure rest as such only at start of measure
     126              :     // and only if the lengths of the rest and measure match
     127              :     // otherwise, we need to convert to duration rest(s)
     128              :     // and potentially split the rest up (eg, 5/4 => whole + quarter)
     129          596 :     bool convertMeasureRest = cr->isRest() && cr->durationType().type() == DurationType::V_MEASURE
     130          596 :                               && (tick != measure->tick() || cr->actualTicksAt(tick) != measure->ticks());
     131              : 
     132          459 :     Fraction measureEnd = measure->endTick();
     133          459 :     bool isGrace = cr->isChord() && toChord(cr)->noteType() != NoteType::NORMAL;
     134              : 
     135              :     // adjust measures for measure repeat
     136          459 :     if (cr->isMeasureRepeat()) {
     137            0 :         MeasureRepeat* mr = toMeasureRepeat(cr);
     138            0 :         Measure* m = (mr->numMeasures() == 4 ? measure->prevMeasure() : measure);
     139            0 :         for (int i = 1; i <= mr->numMeasures(); ++i) {
     140            0 :             tx.push(new ChangeMeasureRepeatCount(m, i, mr->staffIdx()));
     141            0 :             if (i < mr->numMeasures()) {
     142            0 :                 m->undoSetNoBreak(true);
     143              :             }
     144            0 :             m = m->nextMeasure();
     145              :         }
     146              :     }
     147              : 
     148              :     // find out if the chordrest was only partially contained in the copied range
     149          459 :     bool partialCopy = false;
     150          459 :     if (cr->isMeasureRepeat()) {
     151            0 :         partialCopy = toMeasureRepeat(cr)->actualTicks() != measure->ticks();
     152          459 :     } else if (!isGrace && !cr->tuplet()) {
     153          337 :         partialCopy = cr->durationTypeTicks() != (cr->ticks() * twoNoteTremoloFactor);
     154              :     }
     155              : 
     156              :     // if note is too long to fit in measure, split it up with a tie across the barline
     157              :     // exclude tuplets from consideration
     158              :     // we have already disallowed a tuplet from crossing the barline, so there is no problem here
     159              :     // but due to rounding, it might appear from actualTicks() that the last note is too long by a couple of ticks
     160              : 
     161          459 :     Staff* stf = cr->staff();
     162          459 :     const bool shouldSplit = tick + cr->actualTicksAt(tick) > measureEnd || partialCopy || convertMeasureRest;
     163          459 :     if (isGrace || cr->tuplet() || !shouldSplit) {
     164          454 :         score->undoAddCR(cr, measure, tick);
     165          454 :         return;
     166              :     }
     167            5 :     if (cr->isChord()) {
     168              :         // split Chord
     169            2 :         Chord* c = toChord(cr);
     170            2 :         Fraction rest = c->ticks();
     171            2 :         bool firstpart = true;
     172            6 :         while (rest.isNotZero()) {
     173            4 :             measure = score->tick2measure(tick);
     174            4 :             Chord* c2 = firstpart ? c : toChord(c->clone());
     175            4 :             if (!firstpart) {
     176            2 :                 c2->removeMarkings(true);
     177              :             }
     178            4 :             Fraction timeStretch = stf->timeStretch(tick);
     179            4 :             Fraction mlen = (measure->endTick() - tick) * timeStretch;
     180            4 :             Fraction len = mlen > rest ? rest : mlen;
     181            4 :             std::vector<TDuration> dl = toRhythmicDurationList(len, false, (tick - measure->tick()) * timeStretch,
     182            8 :                                                                score->sigmap()->timesig(tick).nominal(), measure, MAX_DOTS, timeStretch);
     183            4 :             if (dl.empty()) {
     184            0 :                 LOGD("Could not make durations for: %d/%d", len.numerator(), len.denominator());
     185            0 :                 return;
     186              :             }
     187            4 :             TDuration d = dl[0];
     188            4 :             c2->setDurationType(d);
     189            4 :             c2->setTicks(d.fraction());
     190            4 :             score->undoAddCR(c2, measure, tick);
     191              : 
     192            4 :             std::vector<Note*> nl1 = c->notes();
     193            4 :             std::vector<Note*> nl2 = c2->notes();
     194              : 
     195            4 :             if (!firstpart) {
     196            4 :                 for (unsigned i = 0; i < nl1.size(); ++i) {
     197            2 :                     Tie* tie = Factory::createTie(nl1[i]);
     198            2 :                     tie->setStartNote(nl1[i]);
     199            2 :                     tie->setEndNote(nl2[i]);
     200            2 :                     tie->setTick(tie->startNote()->tick());
     201            2 :                     tie->setTick2(tie->endNote()->tick());
     202            2 :                     tie->setTrack(c->track());
     203            2 :                     Tie* tie2 = nl1[i]->tieFor();
     204            2 :                     if (tie2) {
     205            0 :                         nl2[i]->setTieFor(nl1[i]->tieFor());
     206            0 :                         tie2->setStartNote(nl2[i]);
     207              :                     }
     208            2 :                     nl1[i]->setTieFor(tie);
     209            2 :                     nl2[i]->setTieBack(tie);
     210              :                 }
     211              :             }
     212            4 :             rest -= c2->ticks();
     213            4 :             tick += c2->actualTicksAt(tick);
     214            4 :             firstpart = false;
     215            4 :             c = c2;
     216            4 :         }
     217            3 :     } else if (cr->isRest()) {
     218              :         // split Rest
     219            3 :         Rest* r       = toRest(cr);
     220            3 :         Fraction rest = r->ticks();
     221              : 
     222            3 :         bool firstpart = true;
     223           10 :         while (!rest.isZero()) {
     224            7 :             Rest* r2      = firstpart ? r : toRest(r->clone());
     225            7 :             measure       = score->tick2measure(tick);
     226            7 :             Fraction timeStretch = stf->timeStretch(tick);
     227            7 :             Fraction mlen = (measure->endTick() - tick) * timeStretch;
     228            7 :             Fraction len  = rest > mlen ? mlen : rest;
     229            7 :             std::vector<TDuration> dl = toRhythmicDurationList(len, true, (tick - measure->tick()) * timeStretch,
     230           14 :                                                                score->sigmap()->timesig(tick).nominal(), measure, MAX_DOTS, timeStretch);
     231            7 :             if (dl.empty()) {
     232            0 :                 LOGD("Could not make durations for: %d/%d", len.numerator(), len.denominator());
     233            0 :                 return;
     234              :             }
     235            7 :             TDuration d = dl[0];
     236            7 :             r2->setDurationType(d);
     237            7 :             r2->setTicks(d.isMeasure() ? measure->stretchedLen(stf) : d.fraction());
     238            7 :             score->undoAddCR(r2, measure, tick);
     239            7 :             rest -= r2->ticks();
     240            7 :             tick += r2->actualTicksAt(tick);
     241            7 :             firstpart = false;
     242            7 :         }
     243            0 :     } else if (cr->isMeasureRepeat()) {
     244            0 :         MeasureRepeat* mr = toMeasureRepeat(cr);
     245            0 :         std::vector<TDuration> list = toDurationList(mr->ticks(), true);
     246            0 :         for (auto dur : list) {
     247            0 :             Rest* r = Factory::createRest(score->dummy(), dur);
     248            0 :             r->setTrack(cr->track());
     249            0 :             Fraction rest = r->ticks();
     250            0 :             while (!rest.isZero()) {
     251            0 :                 Rest* r2      = toRest(r->clone());
     252            0 :                 measure       = score->tick2measure(tick);
     253            0 :                 Fraction timeStretch = stf->timeStretch(tick);
     254            0 :                 Fraction mlen = (measure->endTick() - tick) * timeStretch;
     255            0 :                 Fraction len  = rest > mlen ? mlen : rest;
     256            0 :                 std::vector<TDuration> dl = toDurationList(len, false);
     257            0 :                 if (dl.empty()) {
     258            0 :                     LOGD("Could not make durations for: %d/%d", len.numerator(), len.denominator());
     259            0 :                     return;
     260              :                 }
     261            0 :                 TDuration d = dl[0];
     262            0 :                 r2->setTicks(d.fraction());
     263            0 :                 r2->setDurationType(d);
     264            0 :                 score->undoAddCR(r2, measure, tick);
     265            0 :                 rest -= r2->ticks();
     266            0 :                 tick += r2->actualTicksAt(tick);
     267            0 :             }
     268            0 :             delete r;
     269              :         }
     270            0 :         delete cr;
     271            0 :     }
     272              : }
     273              : 
     274              : //---------------------------------------------------------
     275              : //   pasteSymbols
     276              : //
     277              : //    pastes a list of symbols into cr and following ChordRest's
     278              : //
     279              : //    (Note: info about delta ticks is currently ignored)
     280              : //---------------------------------------------------------
     281           11 : void Paste::pasteSymbols(Transaction&, XmlReader& e, ChordRest* dst)
     282              : {
     283              :     //! NOTE Needs refactoring - reading should be separated from insertion
     284              :     //! (we read the elements into some structure, then inserted them)
     285           11 :     rw::RWRegister::reader()->pasteSymbols(e, dst);
     286           11 : }
     287              : 
     288            6 : bool Paste::repeatListSelection(Transaction& tx, Score* score)
     289              : {
     290            7 :     TRACEFUNC;
     291              : 
     292            6 :     InputState& is = score->inputState();
     293              : 
     294            6 :     std::vector<Note*> notes = score->selection().noteList();
     295            6 :     std::sort(notes.begin(), notes.end(), [](const Note* a, const Note* b) {
     296           24 :         return std::make_tuple(a->track(), a->tick(), a->pitch())
     297           48 :                < std::make_tuple(b->track(), b->tick(), b->pitch());
     298              :     });
     299              : 
     300            6 :     std::vector<EngravingItem*> toSelect;
     301            6 :     std::unordered_set<const Chord*> foundChords;
     302              : 
     303              :     // Parenthesis logic: group new notes by the left parenthesis (if any) of their old equivalent. Once all
     304              :     // new notes have been created we can call cmdAddParentheses on each group then set their offset & visibility
     305              :     // Use a vector of pairs to preserve insertion order
     306              :     using NoteList = std::vector<Note*>;
     307              :     struct ParenGroup {
     308              :         NoteList notes;
     309              :         bool leftVisible = true;
     310              :         bool rightVisible = true;
     311              :         PointF leftOffset;
     312              :         PointF rightOffset;
     313              :     };
     314            6 :     std::vector<std::pair<const Parenthesis*, ParenGroup> > parenEntries;
     315              : 
     316           24 :     for (Note* n : notes) {
     317           18 :         if (n->isGrace() || n->incomingPartialTie() || n->outgoingPartialTie()) {
     318           14 :             continue;
     319              :         }
     320              : 
     321           18 :         const Chord* sourceChord = n->chord();
     322           18 :         is.setTrack(sourceChord->track());
     323              : 
     324           18 :         const bool addFlag = muse::contains(foundChords, sourceChord);
     325           18 :         if (!addFlag) {
     326              :             // If the note doesn't belong to a chord we've seen before...
     327            9 :             foundChords.emplace(sourceChord);
     328            9 :             is.setSegment(sourceChord->segment());
     329            9 :             is.moveToNextInputPos();
     330              : 
     331            9 :             if (is.noteEntryMode()) {
     332              :                 // In note input mode - use the duration of from the note input panel (InputState)...
     333            0 :                 IF_ASSERT_FAILED(is.duration().isValid()) {
     334            0 :                     LOGE() << "Invalid InputState duration";
     335            0 :                     is.setDuration(sourceChord->durationType());
     336              :                 }
     337              :             } else {
     338              :                 // Otherwise set it based on the duration of the previous chord...
     339            9 :                 is.setDuration(sourceChord->durationType());
     340              :             }
     341              :         }
     342              : 
     343           18 :         NoteVal nval = n->noteVal();
     344           18 :         Note* newNote = NoteInput::addPitch(tx, score, nval, addFlag);
     345           18 :         IF_ASSERT_FAILED(newNote) {
     346            0 :             continue;
     347              :         }
     348              : 
     349           18 :         newNote->undoChangeProperty(Pid::VISIBLE, n->visible());
     350              : 
     351           18 :         Chord* newChord = newNote->chord();
     352              : 
     353           18 :         newChord->updateArticulations(sourceChord->articulationSymbolIds());
     354              : 
     355           18 :         const TremoloSingleChord* oldTsc = sourceChord->tremoloSingleChord();
     356           18 :         if (!newChord->tremoloSingleChord() && oldTsc) {
     357            1 :             TremoloSingleChord* newTsc = oldTsc->clone();
     358            1 :             newTsc->setOwnershipParent(newChord);
     359            1 :             newTsc->setTrack(newChord->track());
     360            1 :             score->doUndoAddElement(newTsc);
     361              :         }
     362              : 
     363           18 :         toSelect.push_back(newNote);
     364              : 
     365           18 :         const NoteParenthesisInfo* parenInfo = n->parenthesisInfo();
     366           18 :         const Parenthesis* leftParen = parenInfo ? parenInfo->leftParen() : nullptr;
     367           18 :         if (!leftParen) {
     368           13 :             continue;
     369              :         }
     370            5 :         const Parenthesis* rightParen = parenInfo->rightParen();
     371            5 :         IF_ASSERT_FAILED(rightParen) {
     372            0 :             continue;
     373              :         }
     374              : 
     375            5 :         NoteList notesForParen;
     376            5 :         notesForParen.emplace_back(newNote);
     377              : 
     378              :         // All tied notes should be parenthesized...
     379            5 :         Tie* tie = newNote->tieBack();
     380            7 :         while (tie) {
     381            2 :             Note* tiedNote = tie->startNote();
     382            2 :             if (!tiedNote) {
     383            0 :                 break;
     384              :             }
     385            2 :             notesForParen.emplace_back(tiedNote);
     386            2 :             tie = tiedNote->tieBack();
     387              :         }
     388              : 
     389            5 :         auto search = std::find_if(parenEntries.begin(), parenEntries.end(),
     390            2 :                                    [leftParen](const std::pair<const Parenthesis*, ParenGroup>& p) {
     391            2 :             return p.first == leftParen;
     392              :         });
     393            5 :         if (search != parenEntries.end()) {
     394            1 :             NoteList& nl = search->second.notes;
     395            1 :             nl.insert(nl.end(), notesForParen.begin(), notesForParen.end());
     396            1 :             continue;
     397            1 :         }
     398              : 
     399            4 :         ParenGroup group;
     400            4 :         group.notes = std::move(notesForParen);
     401            4 :         group.leftVisible = leftParen->visible();
     402            4 :         group.rightVisible = rightParen->visible();
     403            4 :         group.leftOffset = leftParen->offset();
     404            4 :         group.rightOffset = rightParen->offset();
     405            4 :         parenEntries.emplace_back(leftParen, std::move(group));
     406            5 :     }
     407              : 
     408           10 :     for (auto& [paren, group] : parenEntries) {
     409            4 :         EditParentheses::addParenthesesToNotes(tx, group.notes);
     410              : 
     411              :         // Restore the original parentheses' visibility and offset on each newly-created pair...
     412            4 :         std::unordered_set<const Parenthesis*> processedParens;
     413           11 :         for (Note* note : group.notes) {
     414            7 :             const NoteParenthesisInfo* newParenInfo = note->parenthesisInfo();
     415            7 :             Parenthesis* newLeftParen = newParenInfo ? newParenInfo->leftParen() : nullptr;
     416            7 :             if (!newLeftParen || muse::contains(processedParens, static_cast<const Parenthesis*>(newLeftParen))) {
     417            1 :                 continue;
     418              :             }
     419            6 :             processedParens.insert(newLeftParen);
     420              : 
     421            6 :             newLeftParen->undoSetVisible(group.leftVisible);
     422            6 :             newLeftParen->undoChangeProperty(Pid::OFFSET, group.leftOffset);
     423              : 
     424            6 :             Parenthesis* newRightParen = newParenInfo->rightParen();
     425            6 :             IF_ASSERT_FAILED(newRightParen) {
     426            0 :                 continue;
     427              :             }
     428              : 
     429            6 :             newRightParen->undoSetVisible(group.rightVisible);
     430            6 :             newRightParen->undoChangeProperty(Pid::OFFSET, group.rightOffset);
     431              :         }
     432            4 :     }
     433              : 
     434            6 :     score->select(toSelect, SelectType::ADD);
     435           12 :     return !toSelect.empty();
     436            6 : }
     437              : 
     438            1 : static ChordRest* replaceWithRest(ChordRest* target)
     439              : {
     440            1 :     target->score()->undoRemoveElement(target);
     441            1 :     return target->score()->addRest(target->segment(), target->track(), target->ticks(), target->tuplet());
     442              : }
     443              : 
     444           10 : static Note* prepareTarget(ChordRest* target, Note* with, const Fraction& duration)
     445              : {
     446           10 :     if (!target->segment()->element(target->track())) {
     447            0 :         return nullptr; // target was removed by previous operation, ignore this
     448              :     }
     449           10 :     if (target->isChord() && target->ticks() > duration) {
     450            1 :         target = replaceWithRest(target); // prevent unexpected note splitting
     451              :     }
     452           10 :     Segment* segment = target->segment();
     453           10 :     if (segment->measure()->isMMRest()) {
     454            0 :         Measure* m = segment->measure()->mmRestFirst();
     455            0 :         segment = m->findSegment(SegmentType::ChordRest, m->tick());
     456              :     }
     457              : 
     458           10 :     const Staff* staff = target->staff();
     459           10 :     const StaffGroup staffGroup = staff->staffType(segment->tick())->group();
     460           10 :     DirectionV stemDirection = DirectionV::AUTO;
     461           10 :     if (staffGroup == StaffGroup::PERCUSSION) {
     462            1 :         const Drumset* ds = staff->part()->instrument(segment->tick())->drumset();
     463            1 :         DO_ASSERT(ds);
     464              : 
     465            1 :         if (ds) {
     466            1 :             stemDirection = ds->stemDirection(with->noteVal().pitch);
     467              :         }
     468              :     }
     469              : 
     470           20 :     segment = target->score()->setNoteRest(segment, target->track(),
     471           10 :                                            with->noteVal(), duration, stemDirection, false, {}, false, &target->score()->inputState());
     472           10 :     return toChord(segment->nextChordRest(target->track()))->upNote();
     473              : }
     474              : 
     475           15 : static EngravingItem* prepareTarget(EngravingItem* target, Note* with, const Fraction& duration)
     476              : {
     477           15 :     if (target->isNote() && toNote(target)->chord()->ticks() != duration) {
     478            6 :         return prepareTarget(toNote(target)->chord(), with, duration);
     479              :     }
     480            9 :     if (target->isChordRest()
     481            9 :         && (toChordRest(target)->ticks() != duration || toChordRest(target)->durationType().type() == DurationType::V_MEASURE)) {
     482            4 :         return prepareTarget(toChordRest(target), with, duration);
     483              :     }
     484            5 :     return target;
     485              : }
     486              : 
     487            0 : static EngravingItem* pasteSystemObject(Transaction& tx, EditData& srcData, EngravingItem* target)
     488              : {
     489            0 :     if (!target) {
     490            0 :         return nullptr;
     491              :     }
     492              : 
     493            0 :     if (srcData.element && srcData.element->isTimeSig()) {
     494              :         // Don't allow copypasting time signatures
     495            0 :         return nullptr;
     496              :     }
     497              : 
     498            0 :     Score* targetScore = target->score();
     499            0 :     Staff* targetStaff = target->staff();
     500            0 :     if (!targetScore || !targetStaff) {
     501            0 :         return nullptr;
     502              :     }
     503              : 
     504              :     // System objects can only be pasted on the *top* staff of an instrument
     505            0 :     Part* targetPart = targetStaff->part();
     506            0 :     targetStaff = targetPart ? targetPart->staves().front() : nullptr;
     507            0 :     if (!targetStaff) {
     508            0 :         return nullptr;
     509              :     }
     510              : 
     511            0 :     if (targetStaff == targetScore->staff(0) || targetStaff->isSystemObjectStaff()) {
     512            0 :         return target->drop(tx, srcData);
     513              :     }
     514              : 
     515            0 :     tx.push(new AddSystemObjectStaff(targetStaff));
     516              : 
     517            0 :     const std::vector<EngravingItem*> topSystemObjects = collectSystemObjects(targetScore);
     518            0 :     const staff_idx_t staffIdx = targetStaff->idx();
     519            0 :     const Fraction targetTick = target->tick();
     520              : 
     521            0 :     EngravingItem* pastedItem = nullptr;
     522              : 
     523            0 :     for (EngravingItem* obj : topSystemObjects) {
     524            0 :         const bool visible = obj->type() == srcData.dropElement->type() && obj->tick() == targetTick;
     525              : 
     526            0 :         EngravingItem* copy = obj->linkedClone();
     527            0 :         copy->setVisible(visible);
     528            0 :         copy->setStaffIdx(staffIdx);
     529            0 :         targetScore->undoAddElement(copy, false /*addToLinkedStaves*/);
     530              : 
     531            0 :         if (visible) {
     532            0 :             pastedItem = copy;
     533              :         }
     534              :     }
     535              : 
     536            0 :     if (!pastedItem) {
     537            0 :         pastedItem = target->drop(tx, srcData);
     538              :     }
     539              : 
     540            0 :     return pastedItem;
     541            0 : }
     542              : 
     543              : //---------------------------------------------------------
     544              : //   cmdPaste
     545              : //---------------------------------------------------------
     546              : 
     547           61 : bool Paste::paste(Transaction& tx, Score* score, const IMimeData* ms, MuseScoreView* view, Fraction scale)
     548              : {
     549           61 :     if (!ms) {
     550            0 :         LOGE() << "No MIME data given";
     551            0 :         return false;
     552              :     }
     553              : 
     554           61 :     if (score->selection().isNone()) {
     555            0 :         LOGE() << "No target selection";
     556            0 :         MScore::setError(MsError::NO_DEST);
     557            0 :         return false;
     558              :     }
     559              : 
     560          183 :     if (ms->hasFormat(mimeSymbolFormat)) {
     561           32 :         muse::ByteArray data = ms->data(mimeSymbolFormat);
     562           16 :         return pasteSymbol(tx, score, data, view, scale);
     563           16 :     }
     564              : 
     565          135 :     if (ms->hasFormat(mimeStaffListFormat)) {
     566           68 :         muse::ByteArray data = ms->data(mimeStaffListFormat);
     567           34 :         return pasteStaffList(tx, score, data, scale);
     568           34 :     }
     569              : 
     570           33 :     if (ms->hasFormat(mimeSymbolListFormat)) {
     571           22 :         muse::ByteArray data = ms->data(mimeSymbolListFormat);
     572           11 :         return pasteSymbolList(tx, score, data);
     573           11 :     }
     574              : 
     575            0 :     if (ms->hasImage()) {
     576            0 :         muse::ByteArray ba;
     577            0 :         auto buffer = Buffer::opened(IODevice::WriteOnly, &ba);
     578              : 
     579            0 :         muse::GlobalInject<muse::draw::IImageProvider> imageProvider;
     580            0 :         auto px = ms->imageData();
     581            0 :         imageProvider()->saveAsPng(px, &buffer);
     582              : 
     583            0 :         std::unique_ptr<Image> image(new Image(score->dummy()));
     584            0 :         image->setImageType(ImageType::RASTER);
     585            0 :         image->loadFromData("paste", ba);
     586              : 
     587            0 :         std::vector<EngravingItem*> droppedElements;
     588            0 :         std::vector<EngravingItem*> targetElements = score->selection().elements();
     589            0 :         for (EngravingItem* target : targetElements) {
     590            0 :             score->addRefresh(target->pageBoundingRect()); // layout() ?!
     591              : 
     592            0 :             EngravingItem* nel = image->clone();
     593            0 :             EditData ddata(view);
     594            0 :             ddata.dropElement = nel;
     595              : 
     596            0 :             if (target->acceptDrop(ddata)) {
     597            0 :                 EngravingItem* dropped = target->drop(tx, ddata);
     598            0 :                 if (dropped) {
     599            0 :                     droppedElements.emplace_back(dropped);
     600              :                 }
     601              : 
     602            0 :                 if (score->selection().element()) {
     603            0 :                     score->addRefresh(score->selection().element()->pageBoundingRect());
     604              :                 }
     605              :             }
     606            0 :         }
     607              : 
     608            0 :         score->select(droppedElements);
     609            0 :         return true;
     610            0 :     }
     611              : 
     612            0 :     LOGE() << "Unsupported MIME data (formats: " << ms->formats() << ")";
     613            0 :     return false;
     614              : }
     615              : }
     616              : 
     617           16 : bool Paste::pasteSymbol(Transaction& tx, Score* score, muse::ByteArray& data, MuseScoreView* view, Fraction scale)
     618              : {
     619           16 :     std::vector<EngravingItem*> droppedElements;
     620              : 
     621           16 :     PointF dragOffset;
     622           16 :     Fraction duration(1, 4);
     623              : 
     624           16 :     std::unique_ptr<EngravingItem> el(EngravingItem::readMimeData(score, data, &dragOffset, &duration));
     625           16 :     if (!el) {
     626            0 :         return false;
     627              :     }
     628              : 
     629           16 :     duration *= scale;
     630           16 :     if (!TDuration(duration).isValid()) {
     631            0 :         return false;
     632              :     }
     633              : 
     634           16 :     std::vector<EngravingItem*> targetElements;
     635           16 :     if (score->selection().isNone()) {
     636            0 :         UNREACHABLE;
     637              :         return false;
     638              :     }
     639              : 
     640              :     // TODO: make this as smart as `NotationInteraction::applyPaletteElement`,
     641              :     // without duplicating logic. (Currently, for range selections, we only
     642              :     // paste onto the "top-left corner" for non-measure based elements.)
     643              :     bool unique;
     644           16 :     targetElements = filterTargetElements(score->selection(), el.get(), unique);
     645           16 :     if (!unique && score->selection().isRange()) {
     646              :         // The usage of `firstElementForNavigation` is inspired by `NotationInteraction::applyPaletteElement`.
     647            0 :         Segment* firstSegment = score->selection().startSegment();
     648            0 :         targetElements = { firstSegment->firstElementForNavigation(score->selection().staffStart()) };
     649              :     }
     650              : 
     651           16 :     if (targetElements.empty()) {
     652            0 :         LOGE() << "No valid target elements in selection";
     653            0 :         MScore::setError(MsError::NO_DEST);
     654            0 :         return false;
     655              :     }
     656              : 
     657           16 :     const bool systemObj = el->systemFlag();
     658              : 
     659           32 :     for (EngravingItem* target : targetElements) {
     660           16 :         score->addRefresh(target->pageBoundingRect()); // layout() ?!
     661           16 :         el->setTrack(target->track());
     662              : 
     663           16 :         EditData ddata(view);
     664           16 :         ddata.pos = target->pagePos();
     665           16 :         ddata.dropElement = el.get();
     666           16 :         ddata.track = target->track();
     667              : 
     668           16 :         if (!target->acceptDrop(ddata)) {
     669            0 :             continue;
     670              :         }
     671              : 
     672           16 :         if (!el->isNote() || (target = prepareTarget(target, toNote(el.get()), duration))) {
     673           16 :             ddata.dropElement = el->clone();
     674              : 
     675           16 :             EngravingItem* dropped = systemObj ? pasteSystemObject(tx, ddata, target) : target->drop(tx, ddata);
     676           16 :             if (dropped) {
     677           16 :                 droppedElements.emplace_back(dropped);
     678              :             }
     679              :         }
     680           16 :     }
     681              : 
     682           16 :     score->select(droppedElements);
     683           16 :     return true;
     684           16 : }
     685              : 
     686           39 : bool Paste::pasteStaffList(Transaction& tx, Score* score, muse::ByteArray& data, Fraction scale)
     687              : {
     688           39 :     if (MScore::debugMode) {
     689            0 :         LOGD() << "Pasting staff list: " << data.data();
     690              :     }
     691              : 
     692           39 :     ChordRest* cr = nullptr;
     693           39 :     if (score->selection().isRange()) {
     694            8 :         cr = score->selection().firstChordRest();
     695           31 :     } else if (score->selection().isSingle()) {
     696           31 :         EngravingItem* e = score->selection().element();
     697           31 :         Measure* measure = e->findMeasure();
     698           31 :         cr = measure ? measure->findChordRest(e->tick(), e->track()) : nullptr;
     699           31 :         if (!cr) {
     700            0 :             LOGE() << "Cannot paste staff list onto " << e->typeName();
     701            0 :             MScore::setError(MsError::DEST_NO_CR);
     702            0 :             return false;
     703              :         }
     704              :     }
     705              : 
     706           39 :     if (!cr) {
     707            0 :         MScore::setError(MsError::NO_DEST);
     708            0 :         return false;
     709              :     }
     710              : 
     711           39 :     if (cr->tuplet() && cr->tick() != cr->topTuplet()->tick()) {
     712            0 :         MScore::setError(MsError::DEST_TUPLET);
     713            0 :         return false;
     714              :     }
     715              : 
     716           39 :     XmlReader xmlReader(data);
     717           39 :     return pasteStaff(tx, score, xmlReader, cr->segment(), cr->staffIdx(), scale);
     718           39 : }
     719              : 
     720           11 : bool Paste::pasteSymbolList(Transaction& tx, Score* score, muse::ByteArray& data)
     721              : {
     722           11 :     if (MScore::debugMode) {
     723            0 :         LOGD() << "Pasting element list: " << data.data();
     724              :     }
     725              : 
     726           11 :     ChordRest* cr = nullptr;
     727           11 :     if (score->selection().isRange()) {
     728            0 :         cr = score->selection().firstChordRest();
     729           11 :     } else if (score->selection().isSingle()) {
     730           11 :         EngravingItem* e = score->selection().element();
     731           11 :         Measure* measure = e->findMeasure();
     732           11 :         cr = measure ? measure->findChordRest(e->tick(), e->track()) : nullptr;
     733           11 :         if (!cr) {
     734            0 :             LOGE() << "Cannot paste element list onto " << e->typeName();
     735            0 :             MScore::setError(MsError::DEST_NO_CR);
     736            0 :             return false;
     737              :         }
     738              :     }
     739              : 
     740           11 :     if (!cr) {
     741            0 :         MScore::setError(MsError::NO_DEST);
     742            0 :         return false;
     743              :     }
     744              : 
     745           11 :     XmlReader xmlReader(data);
     746           11 :     pasteSymbols(tx, xmlReader, cr);
     747           11 :     return true;
     748           11 : }
        

Generated by: LCOV version 2.0-1