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 : }
|