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 "chordrest.h"
24 :
25 : #include "translation.h"
26 :
27 : #include "types/typesconv.h"
28 :
29 : #include "actionicon.h"
30 : #include "barline.h"
31 : #include "beam.h"
32 : #include "breath.h"
33 : #include "chord.h"
34 : #include "clef.h"
35 : #include "factory.h"
36 : #include "figuredbass.h"
37 : #include "harmony.h"
38 : #include "harppedaldiagram.h"
39 : #include "hook.h"
40 : #include "instrchange.h"
41 : #include "keysig.h"
42 : #include "lyrics.h"
43 : #include "marker.h"
44 : #include "measure.h"
45 : #include "note.h"
46 : #include "page.h"
47 : #include "part.h"
48 : #include "rehearsalmark.h"
49 : #include "score.h"
50 : #include "segment.h"
51 : #include "slur.h"
52 : #include "staff.h"
53 : #include "stafftype.h"
54 : #include "system.h"
55 : #include "tabdurationsymbol.h"
56 : #include "tuplet.h"
57 : #include "utils.h"
58 : #include "volta.h"
59 :
60 : #include "editing/editclef.h"
61 : #include "editing/editkeysig.h"
62 : #include "editing/editrehearsalmark.h"
63 : #include "editing/navigation.h"
64 : #include "editing/splitjoinmeasure.h"
65 : #include "editing/transaction/transaction.h"
66 : #include "editing/transpose.h"
67 :
68 : #include "rendering/iscorerenderer.h"
69 :
70 : #include "log.h"
71 :
72 : using namespace mu;
73 : using namespace mu::engraving;
74 :
75 : namespace mu::engraving {
76 : //---------------------------------------------------------
77 : // ChordRest
78 : //---------------------------------------------------------
79 :
80 52096 : ChordRest::ChordRest(const ElementType& type, DummyParentOr<Segment> parent)
81 52096 : : DurationElement(type, parent)
82 : {
83 52096 : m_staffMove = 0;
84 52096 : m_beam = 0;
85 52096 : m_tabDur = 0;
86 52096 : m_beamMode = BeamMode::AUTO;
87 52096 : m_isSmall = false;
88 52096 : m_melismaEnd = false;
89 52096 : m_crossMeasure = CrossMeasure::UNKNOWN;
90 52096 : }
91 :
92 5029 : ChordRest::ChordRest(const ChordRest& cr, bool link)
93 5029 : : DurationElement(cr, link)
94 : {
95 5029 : m_durationType = cr.m_durationType;
96 5029 : m_staffMove = cr.m_staffMove;
97 5029 : m_beam = 0;
98 5029 : m_tabDur = 0; // tab sur. symb. depends upon context: can't be
99 : // simply copied from another CR
100 :
101 5029 : m_beamMode = cr.m_beamMode;
102 5029 : m_isSmall = cr.m_isSmall;
103 5029 : m_melismaEnd = cr.m_melismaEnd;
104 5029 : m_crossMeasure = cr.m_crossMeasure;
105 :
106 5031 : for (Lyrics* l : cr.m_lyrics) { // make deep copy
107 2 : Lyrics* nl = Factory::copyLyrics(*l);
108 2 : if (link) {
109 0 : nl->linkTo(l);
110 : }
111 2 : nl->setOwnershipParent(this);
112 2 : nl->setTrack(track());
113 2 : m_lyrics.push_back(nl);
114 : }
115 5029 : }
116 :
117 : //---------------------------------------------------------
118 : // undoUnlink
119 : //---------------------------------------------------------
120 :
121 445 : void ChordRest::undoUnlink()
122 : {
123 445 : DurationElement::undoUnlink();
124 451 : for (Lyrics* l : m_lyrics) {
125 6 : l->undoUnlink();
126 : }
127 445 : }
128 :
129 : //---------------------------------------------------------
130 : // ChordRest
131 : //---------------------------------------------------------
132 :
133 51060 : ChordRest::~ChordRest()
134 : {
135 51060 : muse::DeleteAll(m_lyrics);
136 51060 : muse::DeleteAll(m_el);
137 51060 : delete m_tabDur;
138 :
139 51060 : if (m_beam) {
140 6869 : muse::remove(m_beam->elements(), this);
141 6869 : m_beam = nullptr;
142 : }
143 :
144 51060 : if (m_beamlet) {
145 95 : m_beamlet = nullptr;
146 : }
147 51060 : }
148 :
149 : //---------------------------------------------------------
150 : // drop
151 : //---------------------------------------------------------
152 :
153 9 : bool ChordRest::acceptDrop(EditData& data) const
154 : {
155 9 : EngravingItem* e = data.dropElement;
156 :
157 9 : switch (e->type()) {
158 9 : case ElementType::FERMATA:
159 : case ElementType::CLEF:
160 : case ElementType::KEYSIG:
161 : case ElementType::TIMESIG:
162 : case ElementType::SYSTEM_TEXT:
163 : case ElementType::TRIPLET_FEEL:
164 : case ElementType::STAFF_TEXT:
165 : case ElementType::PLAYTECH_ANNOTATION:
166 : case ElementType::CAPO:
167 : case ElementType::BAR_LINE:
168 : case ElementType::BREATH:
169 : case ElementType::STAFF_STATE:
170 : case ElementType::INSTRUMENT_CHANGE:
171 : case ElementType::DYNAMIC:
172 : case ElementType::EXPRESSION:
173 : case ElementType::HARMONY:
174 : case ElementType::TEMPO_TEXT:
175 : case ElementType::REHEARSAL_MARK:
176 : case ElementType::FRET_DIAGRAM:
177 : case ElementType::TREMOLOBAR:
178 : case ElementType::HARP_DIAGRAM:
179 9 : return true;
180 0 : case ElementType::ACTION_ICON: {
181 0 : switch (toActionIcon(e)->actionType()) {
182 0 : case ActionIconType::BEAM_AUTO:
183 : case ActionIconType::BEAM_NONE:
184 : case ActionIconType::BEAM_BREAK_LEFT:
185 : case ActionIconType::BEAM_BREAK_INNER_8TH:
186 : case ActionIconType::BEAM_BREAK_INNER_16TH:
187 : case ActionIconType::BEAM_JOIN:
188 0 : return true;
189 0 : default: break;
190 : }
191 0 : break;
192 : }
193 0 : default: break;
194 : }
195 0 : return measure()->acceptDrop(data);
196 : }
197 :
198 30 : EngravingItem* ChordRest::drop(Transaction& tx, EditData& data)
199 : {
200 30 : EngravingItem* e = data.dropElement;
201 30 : Measure* m = measure();
202 30 : bool fromPalette = (e->track() == muse::nidx);
203 30 : switch (e->type()) {
204 13 : case ElementType::BREATH:
205 : {
206 13 : Breath* b = toBreath(e);
207 13 : b->setPos(PointF());
208 : // allow breath marks in voice > 1
209 13 : b->setTrack(track());
210 13 : b->setPlacement(b->track() & 1 ? PlacementV::BELOW : PlacementV::ABOVE);
211 :
212 : // TODO: insert automatically in all staves?
213 :
214 13 : Segment* seg = m->undoGetSegment(SegmentType::Breath, endTick());
215 :
216 13 : if (seg->element(b->track())) {
217 : // Already a breath present, change its symbol
218 0 : Breath* existingBreath = toBreath(seg->element(b->track()));
219 0 : existingBreath->undoChangeProperty(Pid::SYMBOL, b->symId());
220 0 : delete b;
221 0 : return existingBreath;
222 : } else {
223 13 : b->setOwnershipParent(seg);
224 13 : score()->undoAddElement(b);
225 13 : return e;
226 : }
227 : }
228 :
229 0 : case ElementType::BAR_LINE:
230 : {
231 0 : BarLine* bl = toBarLine(e);
232 0 : Fraction barLineTick = bl->barLineType() == BarLineType::START_REPEAT ? tick() : endTick();
233 :
234 0 : if (data.control()) {
235 0 : SplitJoinMeasure::splitMeasure(tx, masterScore(), barLineTick);
236 0 : m = score()->tick2measure(tick());
237 : // consume the ControlModifier flag
238 0 : data.modifiers &= ~ControlModifier;
239 : }
240 :
241 0 : if (barLineTick == m->tick() || barLineTick == m->endTick()) {
242 0 : return m->drop(tx, data);
243 : }
244 :
245 0 : bl->setPos(PointF());
246 0 : bl->setTrack(staffIdx() * VOICES);
247 0 : bl->setGenerated(false);
248 :
249 0 : BarLine* obl = nullptr;
250 0 : for (Staff* st : staff()->staffList()) {
251 0 : Score* score = st->score();
252 0 : Measure* measure = score->tick2measure(m->tick());
253 0 : Segment* seg = measure->undoGetSegment(SegmentType::BarLine, barLineTick);
254 : BarLine* l;
255 0 : if (!obl) {
256 0 : obl = l = bl->clone();
257 : } else {
258 0 : l = toBarLine(obl->linkedClone());
259 : }
260 0 : l->setTrack(st->idx() * VOICES);
261 0 : l->setOwnershipParent(seg);
262 0 : score->undoAddElement(l);
263 0 : }
264 :
265 0 : delete e;
266 0 : return nullptr;
267 : }
268 :
269 0 : case ElementType::CLEF:
270 0 : EditClef::insertClef(tx, score(), toClef(e), this);
271 0 : return nullptr;
272 :
273 0 : case ElementType::FERMATA:
274 0 : e->setPlacement(track() & 1 ? PlacementV::BELOW : PlacementV::ABOVE);
275 0 : for (EngravingItem* el: segment()->annotations()) {
276 0 : if (el->isFermata() && (el->track() == track())) {
277 0 : if (el->subtype() == e->subtype()) {
278 0 : delete e;
279 0 : return el;
280 : } else {
281 0 : e->setPlacement(el->placement());
282 0 : e->setTrack(track());
283 0 : e->setOwnershipParent(segment());
284 0 : score()->undoChangeElement(el, e);
285 0 : return e;
286 : }
287 : }
288 : }
289 : [[fallthrough]];
290 : case ElementType::TEMPO_TEXT:
291 : case ElementType::EXPRESSION:
292 : case ElementType::FRET_DIAGRAM:
293 : case ElementType::TREMOLOBAR:
294 : case ElementType::SYMBOL:
295 : case ElementType::IMAGE:
296 0 : e->setTrack(track());
297 0 : e->setOwnershipParent(segment());
298 0 : score()->undoAddElement(e);
299 0 : return e;
300 :
301 5 : case ElementType::DYNAMIC:
302 5 : e->setTrack(track());
303 5 : e->checkVoiceAssignmentCompatibleWithTrack();
304 5 : e->setOwnershipParent(segment());
305 5 : score()->undoAddElement(e);
306 5 : return e;
307 :
308 1 : case ElementType::NOTE: {
309 1 : Note* note = toNote(e);
310 : // calculate correct transposed tpc
311 1 : Interval v = staff()->transpose(tick());
312 1 : v.flip();
313 1 : note->setTpc2(Transpose::transposeTpc(note->tpc1(), v, true));
314 :
315 1 : Segment* seg = segment();
316 1 : score()->undoRemoveElement(this);
317 1 : Chord* chord = Factory::createChord(score()->dummy());
318 1 : chord->setTrack(track());
319 1 : chord->setDurationType(durationType());
320 1 : chord->setTicks(ticks());
321 1 : chord->setTuplet(tuplet());
322 1 : chord->add(note);
323 1 : score()->undoAddCR(chord, seg->measure(), seg->tick());
324 1 : return note;
325 : }
326 :
327 0 : case ElementType::HARMONY:
328 : {
329 : // transpose
330 0 : Harmony* harmony = toHarmony(e);
331 0 : Interval interval = staff()->transpose(tick());
332 0 : if (!style().styleB(Sid::concertPitch) && !interval.isZero()) {
333 0 : interval.flip();
334 0 : Transpose::undoTransposeHarmony(tx, harmony, interval);
335 : }
336 : }
337 : [[fallthrough]];
338 10 : case ElementType::TEXT:
339 : case ElementType::STAFF_TEXT:
340 : case ElementType::SYSTEM_TEXT:
341 : case ElementType::TRIPLET_FEEL:
342 : case ElementType::PLAYTECH_ANNOTATION:
343 : case ElementType::CAPO:
344 : case ElementType::STICKING:
345 : case ElementType::STAFF_STATE:
346 : case ElementType::HARP_DIAGRAM:
347 : case ElementType::REHEARSAL_MARK:
348 : {
349 10 : e->setOwnershipParent(segment());
350 10 : e->setTrack(trackZeroVoice(track()));
351 10 : if (e->isRehearsalMark() && fromPalette) {
352 0 : RehearsalMark* r = toRehearsalMark(e);
353 0 : r->setXmlText(EditRehearsalMark::createRehearsalMarkText(score(), r));
354 10 : } else if (e->isHarpPedalDiagram() && fromPalette && originPart()) {
355 : // Match pedal config with previous diagram's
356 5 : if (HarpPedalDiagram* prevDiagram = originPart()->prevHarpDiagram(segment()->tick())) {
357 4 : toHarpPedalDiagram(e)->setPedalState(prevDiagram->getPedalState());
358 : }
359 : }
360 10 : score()->undoAddElement(e);
361 10 : return e;
362 : }
363 1 : case ElementType::INSTRUMENT_CHANGE:
364 1 : if (part()->instruments().find(tick().ticks()) != part()->instruments().end()) {
365 0 : LOGD() << "InstrumentChange already exists at tick = " << tick().ticks();
366 0 : delete e;
367 0 : return nullptr;
368 : } else {
369 1 : InstrumentChange* ic = toInstrumentChange(e);
370 1 : ic->setOwnershipParent(segment());
371 1 : ic->setTrack(trackZeroVoice(track()));
372 :
373 1 : const Instrument* prevInstr = part()->instrument(tick());
374 1 : const Instrument instr = *ic->instrument();
375 :
376 1 : IF_ASSERT_FAILED(prevInstr) {
377 0 : delete e;
378 0 : return nullptr;
379 : }
380 :
381 : // temporarily set previous instrument, for correct transposition calculation
382 1 : ic->setInstrument(*prevInstr);
383 1 : score()->undoAddElement(ic);
384 :
385 1 : if (!fromPalette) {
386 1 : ic->setupInstrument(&instr, /*updateText*/ false);
387 : }
388 1 : return e;
389 1 : }
390 0 : case ElementType::FIGURED_BASS:
391 : {
392 : bool bNew;
393 0 : FiguredBass* fb = toFiguredBass(e);
394 0 : fb->setOwnershipParent(segment());
395 0 : fb->setTrack(trackZeroVoice(track()));
396 0 : fb->setTicks(ticks());
397 0 : fb->setOnNote(true);
398 0 : FiguredBass::addFiguredBassToSegment(segment(), fb->track(), fb->ticks(), &bNew);
399 0 : if (bNew) {
400 0 : score()->undoAddElement(e);
401 : }
402 0 : return e;
403 : }
404 :
405 0 : case ElementType::ACTION_ICON:
406 : {
407 0 : ActionIconType actionType = toActionIcon(e)->actionType();
408 : static const std::unordered_map<ActionIconType, BeamMode> beamModeTable = {
409 : { ActionIconType::BEAM_AUTO, BeamMode::AUTO },
410 : { ActionIconType::BEAM_NONE, BeamMode::NONE },
411 : { ActionIconType::BEAM_BREAK_LEFT, BeamMode::BEGIN },
412 : { ActionIconType::BEAM_BREAK_INNER_8TH, BeamMode::BEGIN16 },
413 : { ActionIconType::BEAM_BREAK_INNER_16TH, BeamMode::BEGIN32 },
414 : { ActionIconType::BEAM_JOIN, BeamMode::MID },
415 0 : };
416 0 : if (muse::contains(beamModeTable, actionType)) {
417 0 : undoChangeProperty(Pid::BEAM_MODE, muse::value(beamModeTable, actionType));
418 0 : delete e;
419 0 : return nullptr;
420 : }
421 0 : break;
422 : }
423 :
424 0 : case ElementType::KEYSIG:
425 : {
426 0 : if (data.modifiers & ControlModifier) {
427 : // apply only to this stave
428 0 : KeySigEvent k = toKeySig(e)->keySigEvent();
429 0 : EditKeySig::undoChangeKeySig(tx, score(), staff(), tick(), k);
430 0 : delete e;
431 0 : return nullptr;
432 0 : }
433 0 : break;
434 : }
435 :
436 0 : default:
437 0 : if (e->isSpanner()) {
438 0 : Spanner* spanner = toSpanner(e);
439 0 : spanner->setTick(tick());
440 0 : if (spanner->systemFlag()) {
441 0 : spanner->setTrack(0);
442 0 : spanner->setTrack2(0);
443 : } else {
444 0 : spanner->setTrack(track());
445 0 : spanner->setTrack2(track());
446 : }
447 0 : score()->undoAddElement(spanner);
448 0 : return e;
449 : }
450 0 : break;
451 : }
452 0 : return m->drop(tx, data);
453 : }
454 :
455 : //---------------------------------------------------------
456 : // beam
457 : //---------------------------------------------------------
458 :
459 2604771 : Beam* ChordRest::beam() const
460 : {
461 2604771 : return !(measure() && measure()->stemless(staffIdx())) ? m_beam : nullptr;
462 : }
463 :
464 : //---------------------------------------------------------
465 : // setBeam
466 : //---------------------------------------------------------
467 :
468 11178 : void ChordRest::setBeam(Beam* b)
469 : {
470 11178 : m_beam = b;
471 11178 : }
472 :
473 196164 : void ChordRest::setBeamlet(BeamSegment* b)
474 : {
475 196164 : m_beamlet = b;
476 196164 : }
477 :
478 : //---------------------------------------------------------
479 : // setDurationType
480 : //---------------------------------------------------------
481 :
482 45662 : void ChordRest::setDurationType(DurationType t)
483 : {
484 45662 : m_durationType.setType(t);
485 45662 : m_crossMeasure = CrossMeasure::UNKNOWN;
486 45662 : }
487 :
488 41 : void ChordRest::setDurationType(const Fraction& ticks)
489 : {
490 41 : m_durationType.setVal(ticks.ticks());
491 41 : m_crossMeasure = CrossMeasure::UNKNOWN;
492 41 : }
493 :
494 26663 : void ChordRest::setDurationType(TDuration v)
495 : {
496 26663 : m_durationType = v;
497 26663 : m_crossMeasure = CrossMeasure::UNKNOWN;
498 26663 : }
499 :
500 : //---------------------------------------------------------
501 : // durationUserName
502 : //---------------------------------------------------------
503 :
504 0 : String ChordRest::durationUserName() const
505 : {
506 0 : String tupletType;
507 0 : if (tuplet()) {
508 0 : switch (tuplet()->ratio().numerator()) {
509 0 : case 2:
510 0 : tupletType = muse::mtrc("engraving", "Duplet");
511 0 : break;
512 0 : case 3:
513 0 : tupletType = muse::mtrc("engraving", "Triplet");
514 0 : break;
515 0 : case 4:
516 0 : tupletType = muse::mtrc("engraving", "Quadruplet");
517 0 : break;
518 0 : case 5:
519 0 : tupletType = muse::mtrc("engraving", "Quintuplet");
520 0 : break;
521 0 : case 6:
522 0 : tupletType = muse::mtrc("engraving", "Sextuplet");
523 0 : break;
524 0 : case 7:
525 0 : tupletType = muse::mtrc("engraving", "Septuplet");
526 0 : break;
527 0 : case 8:
528 0 : tupletType = muse::mtrc("engraving", "Octuplet");
529 0 : break;
530 0 : case 9:
531 0 : tupletType = muse::mtrc("engraving", "Nonuplet");
532 0 : break;
533 0 : default:
534 : //: %1 is tuplet ratio numerator (i.e. the number of notes in the tuplet)
535 0 : tupletType = muse::mtrc("engraving", "%1 note tuplet").arg(tuplet()->ratio().numerator());
536 : }
537 : }
538 0 : String dotString;
539 0 : if (!tupletType.isEmpty()) {
540 0 : dotString += u' ';
541 : }
542 :
543 0 : switch (dots()) {
544 0 : case 1:
545 0 : dotString += muse::mtrc("engraving", "Dotted %1").arg(TConv::translatedUserName(durationType().type()));
546 0 : break;
547 0 : case 2:
548 0 : dotString += muse::mtrc("engraving", "Double dotted %1").arg(TConv::translatedUserName(durationType().type()));
549 0 : break;
550 0 : case 3:
551 0 : dotString += muse::mtrc("engraving", "Triple dotted %1").arg(TConv::translatedUserName(durationType().type()));
552 0 : break;
553 0 : case 4:
554 0 : dotString += muse::mtrc("engraving", "Quadruple dotted %1").arg(TConv::translatedUserName(durationType().type()));
555 0 : break;
556 0 : default:
557 0 : dotString += TConv::translatedUserName(durationType().type());
558 : }
559 0 : return String(u"%1%2").arg(tupletType, dotString);
560 0 : }
561 :
562 : //---------------------------------------------------------
563 : // add
564 : //---------------------------------------------------------
565 :
566 1789 : void ChordRest::add(EngravingItem* e)
567 : {
568 1789 : e->setOwnershipParent(this);
569 1789 : e->setTrack(track());
570 1789 : switch (e->type()) {
571 0 : case ElementType::ARTICULATION: // for backward compatibility
572 0 : LOGD("ChordRest::add: unknown element %s", e->typeName());
573 0 : break;
574 1789 : case ElementType::LYRICS:
575 1789 : m_lyrics.push_back(toLyrics(e));
576 1789 : e->added();
577 1789 : break;
578 0 : default:
579 0 : EngravingItem::add(e);
580 0 : break;
581 : }
582 1789 : }
583 :
584 : //---------------------------------------------------------
585 : // remove
586 : //---------------------------------------------------------
587 :
588 18 : void ChordRest::remove(EngravingItem* e)
589 : {
590 18 : switch (e->type()) {
591 18 : case ElementType::LYRICS: {
592 18 : toLyrics(e)->removeFromScore();
593 18 : auto i = std::find(m_lyrics.begin(), m_lyrics.end(), toLyrics(e));
594 18 : if (i != m_lyrics.end()) {
595 18 : m_lyrics.erase(i);
596 18 : e->removed();
597 : } else {
598 0 : LOGD("ChordRest::remove: %s %p not found", e->typeName(), e);
599 : }
600 : }
601 18 : break;
602 0 : default:
603 0 : EngravingItem::remove(e);
604 : }
605 18 : }
606 :
607 : //---------------------------------------------------------
608 : // removeDeleteBeam
609 : /// Remove ChordRest from beam, delete beam if empty.
610 : /// \param beamed - if the chordrest is beamed (will get
611 : /// a (new) beam)
612 : //---------------------------------------------------------
613 :
614 79177 : void ChordRest::removeDeleteBeam(bool beamed)
615 : {
616 79177 : setBeamlet(nullptr);
617 79177 : if (m_beam) {
618 184 : Beam* b = m_beam;
619 184 : m_beam->remove(this);
620 184 : if (b->empty()) {
621 75 : score()->doUndoRemoveElement(b);
622 : } else {
623 109 : renderer()->layoutBeam1(b);
624 : }
625 : }
626 79177 : if (!beamed && isChord()) {
627 34216 : Chord* c = toChord(this);
628 34216 : if (c->shouldHaveHook()) {
629 2360 : if (!c->hook()) {
630 1269 : c->createHook();
631 : }
632 31856 : } else if (c->hook()) {
633 21 : score()->doUndoRemoveElement(c->hook());
634 : }
635 34216 : renderer()->layoutStem(c);
636 : }
637 79177 : }
638 :
639 0 : void ChordRest::computeUp()
640 : {
641 0 : UNREACHABLE;
642 : }
643 :
644 : //---------------------------------------------------------
645 : // replaceBeam
646 : //---------------------------------------------------------
647 :
648 11458 : void ChordRest::replaceBeam(Beam* newBeam)
649 : {
650 11458 : if (m_beam == newBeam) {
651 4870 : return;
652 : }
653 6588 : removeDeleteBeam(true);
654 6588 : newBeam->add(this);
655 : }
656 :
657 0 : Slur* ChordRest::slur(const ChordRest* secondChordRest) const
658 : {
659 0 : if (secondChordRest == nullptr) {
660 0 : ChordRestNavigateOptions options;
661 0 : options.disableOverRepeats = true;
662 0 : secondChordRest = Navigation::nextChordRest(const_cast<ChordRest*>(this), options);
663 : }
664 0 : int currentTick = tick().ticks();
665 0 : Slur* result = nullptr;
666 0 : for (auto it : score()->spannerMap().findOverlapping(currentTick, currentTick + 1)) {
667 0 : Spanner* spanner = it.value;
668 0 : if (!spanner->isSlur()) {
669 0 : continue;
670 : }
671 0 : Slur* slur = toSlur(spanner);
672 0 : if (slur->startElement() == this && slur->endElement() == secondChordRest) {
673 0 : if (slur->slurDirection() == DirectionV::AUTO) {
674 0 : return slur;
675 : }
676 0 : result = slur;
677 : }
678 : }
679 0 : return result;
680 : }
681 :
682 718 : void ChordRest::undoChangeProperty(Pid id, const PropertyValue& newValue, PropertyFlags ps)
683 : {
684 718 : if (id == Pid::BEAM_MODE) {
685 46 : if (isChord() && m_durationType.hooks() == 0) {
686 43 : const StaffType* tab = staffType();
687 43 : if (!tab || !tab->isTabStaff() || !tab->genDurations()) {
688 37 : return;
689 : }
690 : }
691 9 : BeamMode newBeamMode = newValue.value<BeamMode>();
692 1 : if ((newBeamMode == BeamMode::BEGIN16 && m_durationType.hooks() < 2)
693 10 : || (newBeamMode == BeamMode::BEGIN32 && m_durationType.hooks() < 3)) {
694 2 : return;
695 : }
696 : }
697 :
698 679 : EngravingItem::undoChangeProperty(id, newValue, ps);
699 : }
700 :
701 : //---------------------------------------------------------
702 : // localSpatiumChanged
703 : //---------------------------------------------------------
704 :
705 0 : void ChordRest::localSpatiumChanged(double oldValue, double newValue)
706 : {
707 0 : DurationElement::localSpatiumChanged(oldValue, newValue);
708 0 : for (EngravingItem* e : lyrics()) {
709 0 : e->localSpatiumChanged(oldValue, newValue);
710 : }
711 0 : for (EngravingItem* e : el()) {
712 0 : e->localSpatiumChanged(oldValue, newValue);
713 : }
714 0 : }
715 :
716 : //---------------------------------------------------------
717 : // getProperty
718 : //---------------------------------------------------------
719 :
720 407515 : PropertyValue ChordRest::getProperty(Pid propertyId) const
721 : {
722 407515 : switch (propertyId) {
723 33464 : case Pid::SMALL: return PropertyValue::fromValue(isSmall());
724 35 : case Pid::BEAM_MODE: return beamMode();
725 33352 : case Pid::STAFF_MOVE: return staffMove();
726 212 : case Pid::DURATION_TYPE_WITH_DOTS: return actualDurationType().typeWithDots();
727 340452 : default: return DurationElement::getProperty(propertyId);
728 : }
729 : }
730 :
731 : //---------------------------------------------------------
732 : // setProperty
733 : //---------------------------------------------------------
734 :
735 980 : bool ChordRest::setProperty(Pid propertyId, const PropertyValue& v)
736 : {
737 980 : switch (propertyId) {
738 56 : case Pid::SMALL:
739 56 : setSmall(v.toBool());
740 56 : break;
741 18 : case Pid::BEAM_MODE:
742 18 : setBeamMode(v.value<BeamMode>());
743 18 : break;
744 0 : case Pid::STAFF_MOVE:
745 0 : setStaffMove(v.toInt());
746 0 : break;
747 1 : case Pid::VISIBLE:
748 1 : setVisible(v.toBool());
749 1 : measure()->checkMultiVoices(staffIdx());
750 1 : break;
751 74 : case Pid::DURATION_TYPE_WITH_DOTS:
752 74 : setDurationType(v.value<DurationTypeWithDots>());
753 74 : break;
754 831 : default:
755 831 : return DurationElement::setProperty(propertyId, v);
756 : }
757 149 : triggerLayout();
758 149 : return true;
759 : }
760 :
761 : //---------------------------------------------------------
762 : // propertyDefault
763 : //---------------------------------------------------------
764 :
765 367669 : PropertyValue ChordRest::propertyDefault(Pid propertyId) const
766 : {
767 367669 : switch (propertyId) {
768 33352 : case Pid::SMALL:
769 33352 : return false;
770 0 : case Pid::BEAM_MODE:
771 0 : return BeamMode::AUTO;
772 33352 : case Pid::STAFF_MOVE:
773 33352 : return 0;
774 300965 : default:
775 300965 : return DurationElement::propertyDefault(propertyId);
776 : }
777 : // Prevent unreachable code warning
778 : // triggerLayout();
779 : }
780 :
781 : //---------------------------------------------------------
782 : // isGrace
783 : //---------------------------------------------------------
784 :
785 199975 : bool ChordRest::isGrace() const
786 : {
787 199975 : return isChord() && toChord(this)->isGrace();
788 : }
789 :
790 : //---------------------------------------------------------
791 : // isGraceBefore
792 : //---------------------------------------------------------
793 :
794 7975 : bool ChordRest::isGraceBefore() const
795 : {
796 7975 : return isChord()
797 7975 : && (toChord(this)->noteType() & (
798 : NoteType::ACCIACCATURA | NoteType::APPOGGIATURA | NoteType::GRACE4 | NoteType::GRACE16 | NoteType::GRACE32
799 7975 : ));
800 : }
801 :
802 : //---------------------------------------------------------
803 : // isGraceAfter
804 : //---------------------------------------------------------
805 :
806 21969 : bool ChordRest::isGraceAfter() const
807 : {
808 21969 : return isChord()
809 21969 : && (toChord(this)->noteType() & (NoteType::GRACE8_AFTER | NoteType::GRACE16_AFTER | NoteType::GRACE32_AFTER));
810 : }
811 :
812 : //---------------------------------------------------------
813 : // hasBreathMark - determine if chordrest has breath-mark
814 : //---------------------------------------------------------
815 8231 : Breath* ChordRest::hasBreathMark() const
816 : {
817 8231 : Segment* s = measure()->findSegment(SegmentType::Breath, endTick());
818 8231 : return s ? toBreath(s->element(track())) : nullptr;
819 : }
820 :
821 : //---------------------------------------------------------
822 : // nextSegmentAfterCR
823 : // returns first segment at tick CR->tick + CR->actualTicks
824 : // of given types
825 : //---------------------------------------------------------
826 :
827 248 : Segment* ChordRest::nextSegmentAfterCR(SegmentType types) const
828 : {
829 248 : Fraction end = tick() + actualTicks();
830 248 : for (Segment* s = segment()->next1MM(types); s; s = s->next1MM(types)) {
831 248 : if (s->tick() >= end) {
832 248 : return s;
833 : }
834 : }
835 0 : return nullptr;
836 : }
837 :
838 : //---------------------------------------------------------
839 : // setTrack
840 : //---------------------------------------------------------
841 :
842 64344 : void ChordRest::setTrack(track_idx_t val)
843 : {
844 64344 : EngravingItem::setTrack(val);
845 65507 : processSiblings([val](EngravingItem* e) { e->setTrack(val); });
846 64344 : }
847 :
848 : //---------------------------------------------------------
849 : // setScore
850 : //---------------------------------------------------------
851 :
852 7758 : void ChordRest::setScore(Score* s)
853 : {
854 7758 : EngravingItem::setScore(s);
855 8289 : processSiblings([s](EngravingItem* e) { e->setScore(s); });
856 7758 : }
857 :
858 : //---------------------------------------------------------
859 : // processSiblings
860 : //---------------------------------------------------------
861 :
862 72102 : void ChordRest::processSiblings(std::function<void(EngravingItem*)> func)
863 : {
864 72102 : if (m_beam) {
865 399 : func(m_beam);
866 : }
867 72102 : if (m_tabDur) {
868 0 : func(m_tabDur);
869 : }
870 72109 : for (Lyrics* l : m_lyrics) {
871 7 : func(l);
872 : }
873 72102 : if (tuplet()) {
874 1278 : func(tuplet());
875 : }
876 72112 : for (EngravingItem* e : m_el) {
877 10 : func(e);
878 : }
879 72102 : }
880 :
881 : //---------------------------------------------------------
882 : // nextArticulationOrLyric
883 : //---------------------------------------------------------
884 :
885 0 : EngravingItem* ChordRest::nextArticulationOrLyric(EngravingItem* e)
886 : {
887 0 : if (e->isLyrics()) {
888 : // The next element after Lyrics is the LyricsLine (if it exists)...
889 0 : if (LyricsLine* line = toLyrics(e)->separator()) {
890 0 : return line;
891 : }
892 : }
893 0 : if (e->isLyricsLine()) {
894 0 : LyricsLine* lyricsLine = toLyricsLine(e);
895 0 : Lyrics* lyrics = lyricsLine->lyrics();
896 0 : IF_ASSERT_FAILED(lyrics) {
897 0 : return nullptr;
898 : }
899 : // We were on a LyricsLine - now we'll try to move to the next Lyrics...
900 0 : e = lyrics;
901 : }
902 :
903 0 : if (!isChord() || !e->isArticulationFamily()) {
904 : // Move to the next Lyrics...
905 0 : auto i = std::find(m_lyrics.begin(), m_lyrics.end(), e);
906 0 : if (i == m_lyrics.end() || i == m_lyrics.end() - 1) {
907 0 : return nullptr;
908 : }
909 0 : return *(i + 1);
910 : }
911 :
912 : // Cycle through articulations...
913 0 : Chord* c = toChord(this);
914 0 : auto i = std::find(c->articulations().begin(), c->articulations().end(), e);
915 0 : if (i == c->articulations().end()) {
916 0 : return nullptr;
917 : }
918 0 : if (i != c->articulations().end() - 1) {
919 0 : return *(i + 1);
920 : }
921 0 : if (!m_lyrics.empty()) {
922 0 : return m_lyrics[0];
923 : }
924 :
925 0 : return nullptr;
926 : }
927 :
928 : //---------------------------------------------------------
929 : // prevArticulationOrLyric
930 : //---------------------------------------------------------
931 :
932 0 : EngravingItem* ChordRest::prevArticulationOrLyric(EngravingItem* e)
933 : {
934 0 : const std::vector<Articulation*> artics = isChord() ? toChord(this)->articulations() : std::vector<Articulation*>();
935 :
936 0 : if (e->isLyricsLine()) {
937 : // The previous element to a LyricsLine is the associated Lyrics...
938 0 : return toLyricsLine(e)->lyrics();
939 : }
940 :
941 0 : auto i = std::find(m_lyrics.begin(), m_lyrics.end(), e);
942 0 : if (i == m_lyrics.end()) {
943 : // Cycle through articulations...
944 0 : if (artics.empty()) {
945 0 : return nullptr;
946 : }
947 0 : auto j = std::find(artics.begin(), artics.end(), e);
948 0 : if (j == artics.end() || j == artics.begin()) {
949 0 : return nullptr;
950 : }
951 0 : return *(j - 1);
952 : }
953 0 : if (i == m_lyrics.begin()) {
954 0 : return !artics.empty() ? artics.back() : nullptr;
955 : }
956 :
957 : // Move to the previous Lyrics...
958 0 : Lyrics* lyrics = *(i - 1);
959 0 : IF_ASSERT_FAILED(lyrics) {
960 0 : return nullptr;
961 : }
962 0 : if (lyrics->separator()) {
963 : // Move to the LyricsLine of the previous Lyrics (if it exists)...
964 0 : return lyrics->separator();
965 : }
966 0 : return lyrics;
967 0 : }
968 :
969 : //---------------------------------------------------------
970 : // nextElement
971 : //---------------------------------------------------------
972 :
973 0 : EngravingItem* ChordRest::nextElement()
974 : {
975 0 : EngravingItem* e = score()->selection().element();
976 0 : if (!e && !score()->selection().elements().empty()) {
977 0 : e = score()->selection().elements().front();
978 : }
979 0 : switch (e->type()) {
980 0 : case ElementType::ARTICULATION:
981 : case ElementType::ORNAMENT:
982 : case ElementType::TAPPING:
983 : case ElementType::LYRICS:
984 : case ElementType::LYRICSLINE: {
985 0 : EngravingItem* next = nextArticulationOrLyric(e);
986 0 : if (next) {
987 0 : return next;
988 : } else {
989 0 : break;
990 : }
991 : }
992 0 : default: {
993 0 : if (isChord() && !toChord(this)->articulations().empty()) {
994 0 : return toChord(this)->articulations()[0];
995 0 : } else if (!m_lyrics.empty()) {
996 0 : return m_lyrics[0];
997 : } else {
998 0 : break;
999 : }
1000 : }
1001 : }
1002 0 : staff_idx_t staffId = e->staffIdx();
1003 0 : return segment()->nextElement(staffId);
1004 : }
1005 :
1006 : //---------------------------------------------------------
1007 : // prevElement
1008 : //---------------------------------------------------------
1009 :
1010 0 : EngravingItem* ChordRest::prevElement()
1011 : {
1012 0 : EngravingItem* e = score()->selection().element();
1013 0 : if (!e && !score()->selection().elements().empty()) {
1014 0 : e = score()->selection().elements().back();
1015 : }
1016 0 : switch (e->type()) {
1017 0 : case ElementType::ARTICULATION:
1018 : case ElementType::ORNAMENT:
1019 : case ElementType::TAPPING:
1020 : case ElementType::LYRICS:
1021 : case ElementType::LYRICSLINE: {
1022 0 : EngravingItem* prev = prevArticulationOrLyric(e);
1023 0 : if (prev) {
1024 0 : return prev;
1025 : } else {
1026 0 : if (isChord()) {
1027 0 : return toChord(this)->lastElementBeforeSegment();
1028 : }
1029 : }
1030 : // fall through
1031 : }
1032 : default: {
1033 0 : break;
1034 : }
1035 : }
1036 :
1037 0 : Tuplet* tuplet = this->tuplet();
1038 0 : if (tuplet && this == tuplet->elements().front()) {
1039 0 : return tuplet;
1040 : }
1041 :
1042 0 : staff_idx_t staffId = e->staffIdx();
1043 0 : EngravingItem* prevItem = segment()->prevElement(staffId);
1044 0 : if (prevItem && prevItem->isNote()) {
1045 0 : const Chord* prevChord = toNote(prevItem)->chord();
1046 0 : if (prevChord && !prevChord->articulations().empty()) {
1047 0 : return prevChord->articulations().back();
1048 : }
1049 : }
1050 0 : return prevItem;
1051 : }
1052 :
1053 : //---------------------------------------------------------
1054 : // lastElementBeforeSegment
1055 : //---------------------------------------------------------
1056 :
1057 0 : EngravingItem* ChordRest::lastElementBeforeSegment()
1058 : {
1059 0 : if (m_lyrics.empty()) {
1060 0 : return nullptr;
1061 : }
1062 0 : Lyrics* lyrics = m_lyrics.back();
1063 0 : IF_ASSERT_FAILED(lyrics) {
1064 0 : return nullptr;
1065 : }
1066 0 : if (LyricsLine* lyricsLine = lyrics->separator()) {
1067 0 : return lyricsLine;
1068 : }
1069 0 : return lyrics;
1070 : }
1071 :
1072 : //---------------------------------------------------------
1073 : // nextSegmentElement
1074 : //---------------------------------------------------------
1075 :
1076 0 : EngravingItem* ChordRest::nextSegmentElement()
1077 : {
1078 0 : return segment()->firstInNextSegments(staffIdx());
1079 : }
1080 :
1081 : //---------------------------------------------------------
1082 : // scanElements
1083 : //---------------------------------------------------------
1084 :
1085 20849 : void ChordRest::scanElements(std::function<void(EngravingItem*)> func)
1086 : {
1087 5739 : if (m_beam && (m_beam->elements().front() == this)
1088 26588 : && !measure()->stemless(staffIdx())) {
1089 2601 : m_beam->scanElements(func);
1090 : }
1091 21077 : for (Lyrics* l : m_lyrics) {
1092 228 : l->scanElements(func);
1093 : }
1094 20849 : DurationElement* de = this;
1095 22233 : while (de->tuplet() && de->tuplet()->elements().front() == de) {
1096 1384 : de->tuplet()->scanElements(func);
1097 1384 : de = de->tuplet();
1098 : }
1099 20849 : if (m_tabDur) {
1100 0 : func(m_tabDur);
1101 : }
1102 20849 : }
1103 :
1104 : //---------------------------------------------------------
1105 : // prevSegmentElement
1106 : //---------------------------------------------------------
1107 :
1108 0 : EngravingItem* ChordRest::prevSegmentElement()
1109 : {
1110 0 : return segment()->lastInPrevSegments(staffIdx());
1111 : }
1112 :
1113 0 : String ChordRest::accessibleExtraInfo() const
1114 : {
1115 0 : String rez;
1116 0 : for (EngravingItem* l : lyrics()) {
1117 0 : if (!score()->selectionFilter().canSelect(l)) {
1118 0 : continue;
1119 : }
1120 0 : rez = String(u"%1 %2").arg(rez, l->screenReaderInfo());
1121 : }
1122 :
1123 0 : if (segment()) {
1124 0 : for (EngravingItem* e : segment()->annotations()) {
1125 0 : if (!score()->selectionFilter().canSelect(e)) {
1126 0 : continue;
1127 : }
1128 0 : if (e->track() == track()) {
1129 0 : rez = String(u"%1 %2").arg(rez, e->screenReaderInfo());
1130 : }
1131 : }
1132 :
1133 0 : SpannerMap& smap = score()->spannerMap();
1134 0 : auto spanners = smap.findOverlapping(tick().ticks(), tick().ticks());
1135 0 : for (auto interval : spanners) {
1136 0 : Spanner* s = interval.value;
1137 0 : if (!score()->selectionFilter().canSelect(s)) {
1138 0 : continue;
1139 : }
1140 : // voltas are added for barlines, ties are added in notes
1141 0 : if (s->isVolta() || s->isTie()) {
1142 0 : continue;
1143 : }
1144 :
1145 0 : if (s->isSlur()) {
1146 0 : if (s->tick() == tick() && s->track() == track()) {
1147 0 : rez += u" " + muse::mtrc("engraving", "Start of %1").arg(s->screenReaderInfo());
1148 : }
1149 0 : if (s->tick2() == tick() && s->track2() == track()) {
1150 0 : rez += u" " + muse::mtrc("engraving", "End of %1").arg(s->screenReaderInfo());
1151 : }
1152 0 : } else if (s->staffIdx() == staffIdx()) {
1153 0 : bool start = s->tick() == tick();
1154 0 : bool end = s->tick2() == tick() + ticks();
1155 0 : if (start && end) {
1156 0 : rez += u" " + muse::mtrc("engraving", "Start and end of %1").arg(s->screenReaderInfo());
1157 0 : } else if (start) {
1158 0 : rez += u" " + muse::mtrc("engraving", "Start of %1").arg(s->screenReaderInfo());
1159 0 : } else if (end) {
1160 0 : rez += u" " + muse::mtrc("engraving", "End of %1").arg(s->screenReaderInfo());
1161 : }
1162 : }
1163 : }
1164 0 : }
1165 0 : return rez;
1166 0 : }
1167 :
1168 : //---------------------------------------------------------
1169 : // isMelismaEnd
1170 : // returns true if chordrest represents the end of a melisma
1171 : //---------------------------------------------------------
1172 :
1173 9925 : bool ChordRest::isMelismaEnd() const
1174 : {
1175 9925 : return m_melismaEnd;
1176 : }
1177 :
1178 : //---------------------------------------------------------
1179 : // setMelismaEnd
1180 : //---------------------------------------------------------
1181 :
1182 106 : void ChordRest::setMelismaEnd(bool v)
1183 : {
1184 106 : m_melismaEnd = v;
1185 : // TODO: don't take "false" at face value
1186 : // check to see if some other melisma ends here,
1187 : // in which case we can leave this set to true
1188 : // for now, rely on the fact that we'll generate the value correctly on layout
1189 106 : }
1190 :
1191 : //---------------------------------------------------------
1192 : // lyrics
1193 : //---------------------------------------------------------
1194 :
1195 34 : Lyrics* ChordRest::lyrics(int verse) const
1196 : {
1197 49 : for (Lyrics* l : m_lyrics) {
1198 46 : if (l->verse() == verse) {
1199 31 : return l;
1200 : }
1201 : }
1202 3 : return 0;
1203 : }
1204 :
1205 749 : Lyrics* ChordRest::lyrics(int verse, PlacementV p) const
1206 : {
1207 1000 : for (Lyrics* l : m_lyrics) {
1208 792 : if (l->placement() == p && l->verse() == verse) {
1209 541 : return l;
1210 : }
1211 : }
1212 208 : return 0;
1213 : }
1214 :
1215 : //---------------------------------------------------------
1216 : // lastVerse
1217 : // return last verse number (starting from 0)
1218 : // return -1 if there are no lyrics;
1219 : //---------------------------------------------------------
1220 :
1221 0 : int ChordRest::lastVerse(PlacementV p) const
1222 : {
1223 0 : int lastVerse = -1;
1224 :
1225 0 : for (Lyrics* l : m_lyrics) {
1226 0 : if (l->placement() == p && l->verse() > lastVerse) {
1227 0 : lastVerse = l->verse();
1228 : }
1229 : }
1230 :
1231 0 : return lastVerse;
1232 : }
1233 :
1234 : //---------------------------------------------------------
1235 : // removeMarkings
1236 : // - this is normally called after cloning a chord to tie a note over the barline
1237 : // - there is no special undo handling; the assumption is that undo will simply remove the cloned chord
1238 : // - two note tremolos are converted into simple notes
1239 : // - single note tremolos are optionally retained
1240 : //---------------------------------------------------------
1241 :
1242 133 : void ChordRest::removeMarkings(bool /* keepTremolo */)
1243 : {
1244 133 : muse::DeleteAll(el());
1245 133 : clearEls();
1246 133 : muse::DeleteAll(lyrics());
1247 133 : lyrics().clear();
1248 133 : }
1249 :
1250 : //---------------------------------------------------------
1251 : // isBefore
1252 : //---------------------------------------------------------
1253 :
1254 0 : bool ChordRest::isBefore(const EngravingItem* o) const
1255 : {
1256 0 : if (!o || this == o) {
1257 0 : return false;
1258 : }
1259 :
1260 0 : const ChordRest* otherCr = nullptr;
1261 0 : if (o->isChordRest()) {
1262 0 : otherCr = toChordRest(o);
1263 0 : } else if (o->isNote()) {
1264 0 : otherCr = toNote(o)->chord();
1265 : }
1266 :
1267 0 : if (!otherCr) {
1268 0 : return EngravingItem::isBefore(o);
1269 : }
1270 :
1271 0 : Fraction thisTick = tick();
1272 0 : Fraction otherTick = otherCr->tick();
1273 0 : if (thisTick != otherTick) {
1274 0 : return thisTick < otherTick;
1275 : }
1276 :
1277 0 : bool thisIsGraceBefore = isGraceBefore();
1278 0 : bool otherIsGraceBefore = otherCr->isGraceBefore();
1279 0 : if (thisIsGraceBefore != otherIsGraceBefore) {
1280 0 : return thisIsGraceBefore;
1281 0 : } else if (thisIsGraceBefore) {
1282 0 : return toChord(this)->graceIndex() < toChord(otherCr)->graceIndex();
1283 : }
1284 :
1285 0 : bool thisIsGraceAfter = isGraceAfter();
1286 0 : bool otherIsGraceAfter = otherCr->isGraceAfter();
1287 0 : if (thisIsGraceAfter != otherIsGraceAfter) {
1288 0 : return !thisIsGraceAfter;
1289 0 : } else if (thisIsGraceAfter) {
1290 0 : return toChord(this)->graceIndex() > toChord(otherCr)->graceIndex();
1291 : }
1292 :
1293 0 : return false;
1294 : }
1295 :
1296 30771 : bool ChordRest::isBelowCrossBeam(const BeamBase* beamBase) const
1297 : {
1298 30771 : return staffMove() >= beamBase->crossStaffIdx();
1299 : }
1300 :
1301 703 : void ChordRest::checkStaffMoveValidity()
1302 : {
1303 703 : if (!staff()) {
1304 0 : return;
1305 : }
1306 703 : staff_idx_t idx = m_staffMove ? vStaffIdx() : staffIdx() + m_storedStaffMove;
1307 703 : const Staff* baseStaff = staff();
1308 703 : const StaffType* baseStaffType = baseStaff->staffTypeForElement(this);
1309 703 : const Staff* targetStaff = score()->staff(idx);
1310 703 : const StaffType* targetStaffType = targetStaff ? targetStaff->staffTypeForElement(this) : nullptr;
1311 : // check that destination staff makes sense
1312 703 : const TrackRange trackRange = part()->trackRange();
1313 703 : staff_idx_t minStaff = track2staff(trackRange.startTrack);
1314 703 : staff_idx_t maxStaff = track2staff(trackRange.endTrack);
1315 703 : bool isDestinationValid = targetStaff && targetStaff->visible() && idx >= minStaff && idx < maxStaff
1316 1406 : && targetStaffType->group() == baseStaffType->group() && targetStaff->isLinked() == baseStaff->isLinked();
1317 703 : if (!isDestinationValid) {
1318 1 : LOGD("staffMove out of scope %zu + %d min %zu max %zu",
1319 : staffIdx(), m_staffMove, minStaff, maxStaff);
1320 : // If destination staff is invalid, reset to base staff
1321 1 : if (m_staffMove) {
1322 : // Remember the intended staff move, so it can be re-applied if
1323 : // destination staff becomes valid (e.g. unihidden)
1324 1 : m_storedStaffMove = m_staffMove;
1325 : }
1326 1 : setStaffMove(0);
1327 702 : } else if (!m_staffMove && m_storedStaffMove) {
1328 1 : setStaffMove(m_storedStaffMove);
1329 1 : m_storedStaffMove = 0;
1330 : }
1331 :
1332 703 : if (isDestinationValid) {
1333 : // Move valid, clear stored move
1334 702 : m_storedStaffMove = 0;
1335 : }
1336 : }
1337 :
1338 64976 : bool ChordRest::hasFollowingJumpItem() const
1339 : {
1340 64976 : const Segment* seg = segment();
1341 64976 : const Measure* measure = seg ? seg->measure() : nullptr;
1342 64976 : if (!measure) {
1343 0 : return false;
1344 : }
1345 :
1346 64976 : if (endTick() != measure->endTick()) {
1347 32204 : return false;
1348 : }
1349 :
1350 32772 : std::vector<Measure*> followingRepeatMeasures = findFollowingRepeatMeasures(measure);
1351 :
1352 32772 : return !followingRepeatMeasures.empty();
1353 32772 : }
1354 :
1355 687 : bool ChordRest::hasPrecedingJumpItem() const
1356 : {
1357 693 : TRACEFUNC;
1358 687 : const Segment* seg = segment();
1359 687 : const Measure* measure = seg->measure();
1360 :
1361 687 : if (tick() != measure->tick()) {
1362 422 : return false;
1363 : }
1364 :
1365 265 : std::vector<Measure*> precedingRepeatMeasures = findPreviousRepeatMeasures(measure);
1366 :
1367 265 : return !precedingRepeatMeasures.empty();
1368 687 : }
1369 : }
|