//============================================================================= // MuseScore // Music Composition & Notation // // Copyright (C) 2002-2016 Werner Schweer // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 // as published by the Free Software Foundation and appearing in // the file LICENCE.GPL //============================================================================= #include "accidental.h" #include "barline.h" #include "beam.h" #include "box.h" #include "chord.h" #include "clef.h" #include "element.h" #include "fingering.h" #include "glissando.h" #include "harmony.h" #include "key.h" #include "keysig.h" #include "layoutbreak.h" #include "layout.h" #include "lyrics.h" #include "marker.h" #include "measure.h" #include "mscore.h" #include "notedot.h" #include "note.h" #include "ottava.h" #include "page.h" #include "part.h" #include "repeat.h" #include "score.h" #include "segment.h" #include "sig.h" #include "slur.h" #include "staff.h" #include "stem.h" #include "stemslash.h" #include "sticking.h" #include "style.h" #include "sym.h" #include "system.h" #include "text.h" #include "tie.h" #include "timesig.h" #include "tremolo.h" #include "tuplet.h" #include "undo.h" #include "utils.h" #include "volta.h" #include "breath.h" #include "tempotext.h" #include "systemdivider.h" #include "hook.h" #include "ambitus.h" #include "hairpin.h" #include "stafflines.h" #include "articulation.h" #include "bracket.h" #include "spacer.h" #include "fermata.h" #include "measurenumber.h" namespace Ms { // #define PAGE_DEBUG #ifdef PAGE_DEBUG #define PAGEDBG(...) qDebug(__VA_ARGS__) #else #define PAGEDBG(...) ; #endif //--------------------------------------------------------- // rebuildBspTree //--------------------------------------------------------- void Score::rebuildBspTree() { for (Page* page : pages()) { page->rebuildBspTree(); } } //--------------------------------------------------------- // layoutSegmentElements //--------------------------------------------------------- static void layoutSegmentElements(Segment* segment, int startTrack, int endTrack) { for (int track = startTrack; track < endTrack; ++track) { if (Element* e = segment->element(track)) { e->layout(); } } } #if 0 //--------------------------------------------------------- // vUp // reurns true if chord should be treated as up // for purpose of setting horizontal position // for most chords, this is just chord->up() // but for notes on cross-staff beams, we take care to produce more consistent results // since the initial guess for up() may change during layout //--------------------------------------------------------- static bool vUp(Chord* chord) { if (!chord) { return true; } else if (!chord->beam() || !chord->beam()->cross()) { return chord->up(); } else { // cross-staff beam: we cannot know the actual direction of this chord until the beam layout, // but that's too late - it won't work to lay out as if the chord is up on pass one but then down on pass two // so just assign a logical direction based on attributes that won't change // so chords can be laid out consistently on both passes bool up; if (chord->stemDirection() != Direction::AUTO) { up = chord->stemDirection() == Direction::UP; } else if (chord->staffMove()) { up = chord->staffMove() > 0; } else if (chord->track() < chord->beam()->track()) { up = false; } else if (chord->track() > chord->beam()->track()) { up = true; } else if (chord->measure()->hasVoices(chord->staffIdx(), chord->tick(), chord->actualTicks())) { up = !(chord->track() % 2); } else { up = !chord->staff()->isTop(); } return up; } } #endif //--------------------------------------------------------- // layoutChords1 // - layout upstem and downstem chords // - offset as necessary to avoid conflict //--------------------------------------------------------- void Score::layoutChords1(Segment* segment, int staffIdx) { const Staff* staff = Score::staff(staffIdx); const int startTrack = staffIdx * VOICES; const int endTrack = startTrack + VOICES; const Fraction tick = segment->tick(); if (staff->isTabStaff(tick)) { layoutSegmentElements(segment, startTrack, endTrack); return; } bool crossBeamFound = false; std::vector upStemNotes; std::vector downStemNotes; int upVoices = 0; int downVoices = 0; qreal nominalWidth = noteHeadWidth() * staff->staffMag(tick); qreal maxUpWidth = 0.0; qreal maxDownWidth = 0.0; qreal maxUpMag = 0.0; qreal maxDownMag = 0.0; // dots and hooks can affect layout of notes as well as vice versa int upDots = 0; int downDots = 0; bool upHooks = false; bool downHooks = false; // also check for grace notes bool upGrace = false; bool downGrace = false; for (int track = startTrack; track < endTrack; ++track) { Element* e = segment->element(track); if (e && e->isChord()) { Chord* chord = toChord(e); if (chord->beam() && chord->beam()->cross()) { crossBeamFound = true; } bool hasGraceBefore = false; for (Chord* c : chord->graceNotes()) { if (c->isGraceBefore()) { hasGraceBefore = true; } layoutChords2(c->notes(), c->up()); // layout grace note noteheads layoutChords3(c->notes(), staff, 0); // layout grace note chords } if (chord->up()) { ++upVoices; upStemNotes.insert(upStemNotes.end(), chord->notes().begin(), chord->notes().end()); upDots = qMax(upDots, chord->dots()); maxUpMag = qMax(maxUpMag, chord->mag()); if (!upHooks) { upHooks = chord->hook(); } if (hasGraceBefore) { upGrace = true; } } else { ++downVoices; downStemNotes.insert(downStemNotes.end(), chord->notes().begin(), chord->notes().end()); downDots = qMax(downDots, chord->dots()); maxDownMag = qMax(maxDownMag, chord->mag()); if (!downHooks) { downHooks = chord->hook(); } if (hasGraceBefore) { downGrace = true; } } } } if (upVoices + downVoices) { // TODO: use track as secondary sort criteria? // otherwise there might be issues with unisons between voices // in some corner cases maxUpWidth = nominalWidth * maxUpMag; maxDownWidth = nominalWidth * maxDownMag; // layout upstem noteheads if (upVoices > 1) { std::sort(upStemNotes.begin(), upStemNotes.end(), [](Note* n1, const Note* n2) ->bool { return n1->line() > n2->line(); }); } if (upVoices) { qreal hw = layoutChords2(upStemNotes, true); maxUpWidth = qMax(maxUpWidth, hw); } // layout downstem noteheads if (downVoices > 1) { std::sort(downStemNotes.begin(), downStemNotes.end(), [](Note* n1, const Note* n2) ->bool { return n1->line() > n2->line(); }); } if (downVoices) { qreal hw = layoutChords2(downStemNotes, false); maxDownWidth = qMax(maxDownWidth, hw); } qreal sp = staff->spatium(tick); qreal upOffset = 0.0; // offset to apply to upstem chords qreal downOffset = 0.0; // offset to apply to downstem chords qreal dotAdjust = 0.0; // additional chord offset to account for dots qreal dotAdjustThreshold = 0.0; // if it exceeds this amount // centering adjustments for whole note, breve, and small chords qreal centerUp = 0.0; // offset to apply in order to center upstem chords qreal oversizeUp = 0.0; // adjustment to oversized upstem chord needed if laid out to the right qreal centerDown = 0.0; // offset to apply in order to center downstem chords qreal centerAdjustUp = 0.0; // adjustment to upstem chord needed after centering donwstem chord qreal centerAdjustDown = 0.0; // adjustment to downstem chord needed after centering upstem chord // only center chords if they differ from nominal by at least this amount // this avoids unnecessary centering on differences due only to floating point roundoff // it also allows for the possibility of disabling centering // for notes only "slightly" larger than nominal, like half notes // but this will result in them not being aligned with each other between voices // unless you change to left alignment as described in the comments below qreal centerThreshold = 0.01 * sp; // amount by which actual width exceeds nominal, adjusted for staff mag() only qreal headDiff = maxUpWidth - nominalWidth; // amount by which actual width exceeds nominal, adjusted for staff & chord/note mag() qreal headDiff2 = maxUpWidth - nominalWidth * (maxUpMag / staff->staffMag(tick)); if (headDiff > centerThreshold) { // larger than nominal centerUp = headDiff * -0.5; // maxUpWidth is true width, but we no longer will care about that // instead, we care only about portion to right of origin maxUpWidth += centerUp; // to left align rather than center, delete both of the above if (headDiff2 > centerThreshold) { // if max notehead is wider than nominal with chord/note mag() applied // then noteheads extend to left of origin // because stemPosX() is based on nominal width // so we need to correct for that too centerUp += headDiff2; oversizeUp = headDiff2; } } else if (-headDiff > centerThreshold) { // smaller than nominal centerUp = -headDiff * 0.5; if (headDiff2 > centerThreshold) { // max notehead is wider than nominal with chord/note mag() applied // perform same adjustment as above centerUp += headDiff2; oversizeUp = headDiff2; } centerAdjustDown = centerUp; } headDiff = maxDownWidth - nominalWidth; if (headDiff > centerThreshold) { // larger than nominal centerDown = headDiff * -0.5; // to left align rather than center, change the above to //centerAdjustUp = headDiff; maxDownWidth = nominalWidth - centerDown; } else if (-headDiff > centerThreshold) { // smaller than nominal centerDown = -headDiff * 0.5; centerAdjustUp = centerDown; } // handle conflict between upstem and downstem chords if (upVoices && downVoices) { Note* bottomUpNote = upStemNotes.front(); Note* topDownNote = downStemNotes.back(); int separation; // TODO: handle conflicts for cross-staff notes and notes on cross-staff beams // for now we simply treat these as though there is no conflict if (bottomUpNote->chord()->staffMove() == topDownNote->chord()->staffMove() && !crossBeamFound) { separation = topDownNote->line() - bottomUpNote->line(); } else { separation = 2; // no conflict } QVector overlapNotes; overlapNotes.reserve(8); if (separation == 1) { // second downOffset = maxUpWidth; // align stems if present, leave extra room if not if (topDownNote->chord()->stem() && bottomUpNote->chord()->stem()) { downOffset -= topDownNote->chord()->stem()->lineWidth(); } else { downOffset += 0.1 * sp; } } else if (separation < 1) { // overlap (possibly unison) // build list of overlapping notes for (size_t i = 0, n = upStemNotes.size(); i < n; ++i) { if (upStemNotes[i]->line() >= topDownNote->line() - 1) { overlapNotes.append(upStemNotes[i]); } else { break; } } for (size_t i = downStemNotes.size(); i > 0; --i) { // loop most probably needs to be in this reverse order if (downStemNotes[i - 1]->line() <= bottomUpNote->line() + 1) { overlapNotes.append(downStemNotes[i - 1]); } else { break; } } std::sort(overlapNotes.begin(), overlapNotes.end(), [](Note* n1, const Note* n2) ->bool { return n1->line() > n2->line(); }); // determine nature of overlap bool shareHeads = true; // can all overlapping notes share heads? bool matchPending = false; // looking for a unison match bool conflictUnison = false; // unison found bool conflictSecondUpHigher = false; // second found bool conflictSecondDownHigher = false; // second found int lastLine = 1000; Note* p = overlapNotes[0]; for (int i = 0, count = overlapNotes.size(); i < count; ++i) { Note* n = overlapNotes[i]; NoteHead::Type nHeadType; NoteHead::Type pHeadType; Chord* nchord = n->chord(); Chord* pchord = p->chord(); if (n->mirror()) { if (separation < 0) { // don't try to share heads if there is any mirroring shareHeads = false; // don't worry about conflicts involving mirrored notes continue; } } int line = n->line(); int d = lastLine - line; switch (d) { case 0: // unison conflictUnison = true; matchPending = false; nHeadType = (n->headType() == NoteHead::Type::HEAD_AUTO) ? n->chord()->durationType().headType() : n->headType(); pHeadType = (p->headType() == NoteHead::Type::HEAD_AUTO) ? p->chord()->durationType().headType() : p->headType(); // the most important rules for sharing noteheads on unisons between voices are // that notes must be one same line with same tpc // noteheads must be unmirrored and of same group // and chords must be same size (or else sharing code won't work) if (n->headGroup() != p->headGroup() || n->tpc() != p->tpc() || n->mirror() || p->mirror() || nchord->small() != pchord->small()) { shareHeads = false; } else { // noteheads are potentially shareable // it is more subjective at this point // current default is to require *either* of the following: // 1) both chords have same number of dots, both have stems, and both noteheads are same type and are full size (automatic match) // or 2) one or more of the noteheads is not of type AUTO, but is explicitly set to match the other (user-forced match) // or 3) exactly one of the noteheads is invisible (user-forced match) // thus user can force notes to be shared despite differing number of dots or either being stemless // by setting one of the notehead types to match the other or by making one notehead invisible // TODO: consider adding a style option, staff properties, or note property to control sharing if ((nchord->dots() != pchord->dots() || !nchord->stem() || !pchord->stem() || nHeadType != pHeadType || n->small() || p->small()) && ((n->headType() == NoteHead::Type::HEAD_AUTO && p->headType() == NoteHead::Type::HEAD_AUTO) || nHeadType != pHeadType) && (n->visible() == p->visible())) { shareHeads = false; } } break; case 1: // second // trust that this won't be a problem for single unison if (separation < 0) { if (n->chord()->up()) { conflictSecondUpHigher = true; } else { conflictSecondDownHigher = true; } shareHeads = false; } break; default: // no conflict if (matchPending) { shareHeads = false; } matchPending = true; } p = n; lastLine = line; } if (matchPending) { shareHeads = false; } // calculate offsets if (shareHeads) { for (int i = overlapNotes.size() - 1; i >= 1; i -= 2) { Note* previousNote = overlapNotes[i - 1]; Note* n = overlapNotes[i]; if (!(previousNote->chord()->isNudged() || n->chord()->isNudged())) { if (previousNote->chord()->dots() == n->chord()->dots()) { // hide one set dots bool onLine = !(previousNote->line() & 1); if (onLine) { // hide dots for lower voice if (previousNote->voice() & 1) { previousNote->setDotsHidden(true); } else { n->setDotsHidden(true); } } else { // hide dots for upper voice if (!(previousNote->voice() & 1)) { previousNote->setDotsHidden(true); } else { n->setDotsHidden(true); } } } // formerly we hid noteheads in an effort to fix playback // but this doesn't work for cases where noteheads cannot be shared // so better to solve the problem elsewhere } } } else if (conflictUnison && separation == 0 && (!downGrace || upGrace)) { downOffset = maxUpWidth + 0.3 * sp; } else if (conflictUnison) { upOffset = maxDownWidth + 0.3 * sp; } else if (conflictSecondUpHigher) { upOffset = maxDownWidth + 0.2 * sp; } else if ((downHooks && !upHooks) && !(upDots && !downDots)) { downOffset = maxUpWidth + 0.3 * sp; } else if (conflictSecondDownHigher) { if (downDots && !upDots) { downOffset = maxUpWidth + 0.3 * sp; } else { upOffset = maxDownWidth - 0.2 * sp; if (downHooks) { upOffset += 0.3 * sp; } } } else { // no direct conflict, so parts can overlap (downstem on left) // just be sure that stems clear opposing noteheads qreal clearLeft = 0.0, clearRight = 0.0; if (topDownNote->chord()->stem()) { clearLeft = topDownNote->chord()->stem()->lineWidth() + 0.3 * sp; } if (bottomUpNote->chord()->stem()) { clearRight = bottomUpNote->chord()->stem()->lineWidth() + qMax(maxDownWidth - maxUpWidth, 0.0) + 0.3 * sp; } else { downDots = 0; // no need to adjust for dots in this case } upOffset = qMax(clearLeft, clearRight); if (downHooks) { // we will need more space to avoid collision with hook // but we won't need as much dot adjustment upOffset = qMax(upOffset, maxDownWidth + 0.1 * sp); dotAdjustThreshold = maxUpWidth - 0.3 * sp; } // if downstem chord is small, don't center // and we might not need as much dot adjustment either if (centerDown > 0.0) { centerDown = 0.0; centerAdjustUp = 0.0; dotAdjustThreshold = (upOffset - maxDownWidth) + maxUpWidth - 0.3 * sp; } } } // adjust for dots if ((upDots && !downDots) || (downDots && !upDots)) { // only one sets of dots // place between chords int dots; qreal mag; if (upDots) { dots = upDots; mag = maxUpMag; } else { dots = downDots; mag = maxDownMag; } qreal dotWidth = segment->symWidth(SymId::augmentationDot); // first dot dotAdjust = styleP(Sid::dotNoteDistance) + dotWidth; // additional dots if (dots > 1) { dotAdjust += styleP(Sid::dotDotDistance) * (dots - 1); } dotAdjust *= mag; // only by amount over threshold dotAdjust = qMax(dotAdjust - dotAdjustThreshold, 0.0); } if (separation == 1) { dotAdjust += 0.1 * sp; } } // apply chord offsets for (int track = startTrack; track < endTrack; ++track) { Element* e = segment->element(track); if (e && e->isChord()) { Chord* chord = toChord(e); if (chord->up()) { if (upOffset != 0.0) { chord->rxpos() += upOffset + centerAdjustUp + oversizeUp; if (downDots && !upDots) { chord->rxpos() += dotAdjust; } } else { chord->rxpos() += centerUp; } } else { if (downOffset != 0.0) { chord->rxpos() += downOffset + centerAdjustDown; if (upDots && !downDots) { chord->rxpos() += dotAdjust; } } else { chord->rxpos() += centerDown; } } } } // layout chords std::vector notes; if (upVoices) { notes.insert(notes.end(), upStemNotes.begin(), upStemNotes.end()); } if (downVoices) { notes.insert(notes.end(), downStemNotes.begin(), downStemNotes.end()); } if (upVoices + downVoices > 1) { std::sort(notes.begin(), notes.end(), [](Note* n1, const Note* n2) ->bool { return n1->line() > n2->line(); }); } layoutChords3(notes, staff, segment); } layoutSegmentElements(segment, startTrack, endTrack); } //--------------------------------------------------------- // layoutChords2 // - determine which notes need mirroring // - this is called once for each stem direction // eg, once for voices 1&3, once for 2&4 // with all notes combined and sorted to resemble one chord // - return maximum non-mirrored notehead width //--------------------------------------------------------- qreal Score::layoutChords2(std::vector& notes, bool up) { int startIdx, endIdx, incIdx; qreal maxWidth = 0.0; // loop in correct direction so that first encountered notehead wins conflict if (up) { // loop bottom up startIdx = 0; endIdx = int(notes.size()); incIdx = 1; } else { // loop top down startIdx = int(notes.size()) - 1; endIdx = -1; incIdx = -1; } int ll = 1000; // line of previous notehead // hack: start high so first note won't show as conflict bool lvisible = false; // was last note visible? bool mirror = false; // should current notehead be mirrored? // value is retained and may be used on next iteration // to track mirror status of previous note bool isLeft = notes[startIdx]->chord()->up(); // is notehead on left? int lmove = notes[startIdx]->chord()->staffMove(); // staff offset of last note (for cross-staff beaming) for (int idx = startIdx; idx != endIdx; idx += incIdx) { Note* note = notes[idx]; // current note int line = note->line(); // line of current note Chord* chord = note->chord(); int move = chord->staffMove(); // staff offset of current note // there is a conflict // if this is same or adjacent line as previous note (and chords are on same staff!) // but no need to do anything about it if either note is invisible bool conflict = (qAbs(ll - line) < 2) && (lmove == move) && note->visible() && lvisible; // this note is on opposite side of stem as previous note // if there is a conflict // or if this the first note *after* a conflict if (conflict || (chord->up() != isLeft)) { isLeft = !isLeft; } // determine if we would need to mirror current note // to get it to the correct side // this would be needed to get a note to left or downstem or right of upstem // whether or not we actually do this is determined later (based on user mirror property) bool nmirror = (chord->up() != isLeft); // by default, notes and dots are not hidden // this may be changed later to allow unisons to share noteheads note->setHidden(false); note->setDotsHidden(false); // be sure chord position is initialized // chord may be moved to the right later // if there are conflicts between voices chord->rxpos() = 0.0; // let user mirror property override the default we calculated if (note->userMirror() == MScore::DirectionH::AUTO) { mirror = nmirror; } else { mirror = note->chord()->up(); if (note->userMirror() == MScore::DirectionH::LEFT) { mirror = !mirror; } } note->setMirror(mirror); // accumulate return value if (!mirror) { maxWidth = qMax(maxWidth, note->bboxRightPos()); } // prepare for next iteration lvisible = note->visible(); lmove = move; ll = line; } return maxWidth; } //--------------------------------------------------------- // AcEl //--------------------------------------------------------- struct AcEl { Note* note; qreal x; // actual x position of this accidental relative to origin qreal top; // top of accidental bbox relative to staff qreal bottom; // bottom of accidental bbox relative to staff int line; // line of note int next; // index of next accidental of same pitch class (ascending list) qreal width; // width of accidental qreal ascent; // amount (in sp) vertical strokes extend above body qreal descent; // amount (in sp) vertical strokes extend below body qreal rightClear; // amount (in sp) to right of last vertical stroke above body qreal leftClear; // amount (in sp) to left of last vertical stroke below body }; //--------------------------------------------------------- // resolveAccidentals // lx = calculated position of rightmost edge of left accidental relative to origin //--------------------------------------------------------- static bool resolveAccidentals(AcEl* left, AcEl* right, qreal& lx, qreal pd, qreal sp) { AcEl* upper; AcEl* lower; if (left->line >= right->line) { upper = right; lower = left; } else { upper = left; lower = right; } qreal gap = lower->top - upper->bottom; // no conflict at all if there is sufficient vertical gap between accidentals // the arrangement of accidentals into columns assumes accidentals an octave apart *do* clear if (gap >= pd || lower->line - upper->line >= 7) { return false; } qreal allowableOverlap = qMax(upper->descent, lower->ascent) - pd; // accidentals that are "close" (small gap or even slight overlap) if (qAbs(gap) <= 0.33 * sp) { // acceptable with slight offset // if one of the accidentals can subsume the overlap // and both accidentals allow it if (-gap <= allowableOverlap && qMin(upper->descent, lower->ascent) > 0.0) { qreal align = qMin(left->width, right->width); lx = qMin(lx, right->x + align - pd); return true; } } // amount by which overlapping accidentals will be separated // for example, the vertical stems of two flat signs // these need more space than we would need between non-overlapping accidentals qreal overlapShift = pd * 1.41; // accidentals with more significant overlap // acceptable if one accidental can subsume overlap if (left == lower && -gap <= allowableOverlap) { qreal offset = qMax(left->rightClear, right->leftClear); offset = qMin(offset, left->width) - overlapShift; lx = qMin(lx, right->x + offset); return true; } // accidentals with even more overlap // can work if both accidentals can subsume overlap if (left == lower && -gap <= upper->descent + lower->ascent - pd) { qreal offset = qMin(left->rightClear, right->leftClear) - overlapShift; if (offset > 0.0) { lx = qMin(lx, right->x + offset); return true; } } // otherwise, there is real conflict lx = qMin(lx, right->x - pd); return true; } //--------------------------------------------------------- // layoutAccidental //--------------------------------------------------------- static qreal layoutAccidental(AcEl* me, AcEl* above, AcEl* below, qreal colOffset, QVector& leftNotes, qreal pnd, qreal pd, qreal sp) { qreal lx = colOffset; Accidental* acc = me->note->accidental(); qreal mag = acc->mag(); pnd *= mag; pd *= mag; // extra space for ledger lines if (me->line <= -2 || me->line >= me->note->staff()->lines(me->note->chord()->tick()) * 2) { lx = qMin(lx, -0.2 * sp); } // clear left notes int lns = leftNotes.size(); for (int i = 0; i < lns; ++i) { Note* ln = leftNotes[i]; int lnLine = ln->line(); qreal lnTop = (lnLine - 1) * 0.5 * sp; qreal lnBottom = lnTop + sp; if (me->top - lnBottom <= pnd && lnTop - me->bottom <= pnd) { // undercut note above if possible if (lnBottom - me->top <= me->ascent - pnd) { lx = qMin(lx, ln->x() + ln->chord()->x() + me->rightClear); } else { lx = qMin(lx, ln->x() + ln->chord()->x()); } } else if (lnTop > me->bottom) { break; } } // clear other accidentals bool conflictAbove = false; bool conflictBelow = false; if (above) { conflictAbove = resolveAccidentals(me, above, lx, pd, sp); } if (below) { conflictBelow = resolveAccidentals(me, below, lx, pd, sp); } if (conflictAbove || conflictBelow) { me->x = lx - acc->width() - acc->bbox().x(); } else if (colOffset != 0.0) { me->x = lx - pd - acc->width() - acc->bbox().x(); } else { me->x = lx - pnd - acc->width() - acc->bbox().x(); } return me->x; } //--------------------------------------------------------- // layoutChords3 // - calculate positions of notes, accidentals, dots //--------------------------------------------------------- void Score::layoutChords3(std::vector& notes, const Staff* staff, Segment* segment) { //--------------------------------------------------- // layout accidentals // find column for dots //--------------------------------------------------- QVector leftNotes; // notes to left of origin leftNotes.reserve(8); QVector aclist; // accidentals aclist.reserve(8); // track columns of octave-separated accidentals int columnBottom[7] = { -1, -1, -1, -1, -1, -1, -1 }; Fraction tick = notes.front()->chord()->segment()->tick(); qreal sp = staff->spatium(tick); qreal stepDistance = sp * staff->lineDistance(tick) * .5; int stepOffset = staff->staffType(tick)->stepOffset(); qreal lx = 10000.0; // leftmost notehead position qreal upDotPosX = 0.0; qreal downDotPosX = 0.0; int nNotes = int(notes.size()); int nAcc = 0; for (int i = nNotes - 1; i >= 0; --i) { Note* note = notes[i]; Accidental* ac = note->accidental(); if (ac && !note->fixed()) { ac->layout(); AcEl acel; acel.note = note; int line = note->line(); acel.line = line; acel.x = 0.0; acel.top = line * 0.5 * sp + ac->bbox().top(); acel.bottom = line * 0.5 * sp + ac->bbox().bottom(); acel.width = ac->width(); QPointF bboxNE = ac->symBbox(ac->symbol()).topRight(); QPointF bboxSW = ac->symBbox(ac->symbol()).bottomLeft(); QPointF cutOutNE = ac->symCutOutNE(ac->symbol()); QPointF cutOutSW = ac->symCutOutSW(ac->symbol()); if (!cutOutNE.isNull()) { acel.ascent = cutOutNE.y() - bboxNE.y(); acel.rightClear = bboxNE.x() - cutOutNE.x(); } else { acel.ascent = 0.0; acel.rightClear = 0.0; } if (!cutOutSW.isNull()) { acel.descent = bboxSW.y() - cutOutSW.y(); acel.leftClear = cutOutSW.x() - bboxSW.x(); } else { acel.descent = 0.0; acel.leftClear = 0.0; } int pitchClass = (line + 700) % 7; acel.next = columnBottom[pitchClass]; columnBottom[pitchClass] = nAcc; aclist.append(acel); ++nAcc; } Chord* chord = note->chord(); bool _up = chord->up(); if (chord->stemSlash()) { chord->stemSlash()->layout(); } qreal overlapMirror; Stem* stem = chord->stem(); if (stem) { overlapMirror = stem->lineWidth(); } else if (chord->durationType().headType() == NoteHead::Type::HEAD_WHOLE) { overlapMirror = styleP(Sid::stemWidth) * chord->mag(); } else { overlapMirror = 0.0; } qreal x = 0.0; if (note->mirror()) { if (_up) { x = chord->stemPosX() - overlapMirror; } else { x = -note->headBodyWidth() + overlapMirror; } } else if (_up) { x = chord->stemPosX() - note->headBodyWidth(); } qreal ny = (note->line() + stepOffset) * stepDistance; if (note->rypos() != ny) { note->rypos() = ny; if (chord->stem()) { chord->stem()->layout(); if (chord->hook()) { chord->hook()->rypos() = chord->stem()->hookPos().y(); } } } note->rxpos() = x; // find leftmost non-mirrored note to set as X origin for accidental layout // a mirrored note that extends to left of segment X origin // will displace accidentals only if there is conflict qreal sx = x + chord->x(); // segment-relative X position of note if (note->mirror() && !chord->up() && sx < 0.0) { leftNotes.append(note); } else if (sx < lx) { lx = sx; } qreal xx = x + note->headBodyWidth() + chord->pos().x(); Direction dotPosition = note->userDotPosition(); if (chord->dots()) { if (chord->up()) { upDotPosX = qMax(upDotPosX, xx); } else { downDotPosX = qMax(downDotPosX, xx); } if (dotPosition == Direction::AUTO && nNotes > 1 && note->visible() && !note->dotsHidden()) { // resolve dot conflicts int line = note->line(); Note* above = (i < nNotes - 1) ? notes[i + 1] : 0; if (above && (!above->visible() || above->dotsHidden())) { above = 0; } int intervalAbove = above ? line - above->line() : 1000; Note* below = (i > 0) ? notes[i - 1] : 0; if (below && (!below->visible() || below->dotsHidden())) { below = 0; } int intervalBelow = below ? below->line() - line : 1000; if ((line & 1) == 0) { // line if (intervalAbove == 1 && intervalBelow != 1) { dotPosition = Direction::DOWN; } else if (intervalBelow == 1 && intervalAbove != 1) { dotPosition = Direction::UP; } else if (intervalAbove == 0 && above->chord()->dots()) { // unison if (((above->voice() & 1) == (note->voice() & 1))) { above->setDotY(Direction::UP); dotPosition = Direction::DOWN; } } } else { // space if (intervalAbove == 0 && above->chord()->dots()) { // unison if (!(note->voice() & 1)) { dotPosition = Direction::UP; } else { if (!(above->voice() & 1)) { above->setDotY(Direction::UP); } else { dotPosition = Direction::DOWN; } } } } } } note->setDotY(dotPosition); // also removes invalid dots } // if there are no non-mirrored notes in a downstem chord, // then use the stem X position as X origin for accidental layout if (nNotes && leftNotes.size() == nNotes) { lx = notes.front()->chord()->stemPosX(); } if (segment) { // align all dots for segment/staff // it would be possible to dots for up & down chords separately // this would require space to have been allocated previously // when calculating chord offsets segment->setDotPosX(staff->idx(), qMax(upDotPosX, downDotPosX)); } if (nAcc == 0) { return; } QVector umi; qreal pd = styleP(Sid::accidentalDistance); qreal pnd = styleP(Sid::accidentalNoteDistance); qreal colOffset = 0.0; if (nAcc >= 2 && aclist[nAcc - 1].line - aclist[0].line >= 7) { // accidentals spread over an octave or more // set up columns for accidentals with octave matches // these will start at right and work to the left // unmatched accidentals will use zig zag approach (see below) // starting to the left of the octave columns qreal minX = 0.0; int columnTop[7] = { -1, -1, -1, -1, -1, -1, -1 }; // find columns of octaves for (int pc = 0; pc < 7; ++pc) { if (columnBottom[pc] == -1) { continue; } // calculate column height for (int j = columnBottom[pc]; j != -1; j = aclist[j].next) { columnTop[pc] = j; } } // compute reasonable column order // use zig zag QVector column; QVector unmatched; int n = nAcc - 1; for (int i = 0; i <= n; ++i, --n) { int pc = (aclist[i].line + 700) % 7; if (aclist[columnTop[pc]].line != aclist[columnBottom[pc]].line) { if (!column.contains(pc)) { column.append(pc); } } else { unmatched.append(i); } if (i == n) { break; } pc = (aclist[n].line + 700) % 7; if (aclist[columnTop[pc]].line != aclist[columnBottom[pc]].line) { if (!column.contains(pc)) { column.append(pc); } } else { unmatched.append(n); } } int nColumns = column.size(); int nUnmatched = unmatched.size(); // handle unmatched accidentals for (int i = 0; i < nUnmatched; ++i) { // first try to slot it into an existing column AcEl* me = &aclist[unmatched[i]]; // find column bool found = false; for (int j = 0; j < nColumns; ++j) { int pc = column[j]; int above = -1; int below = -1; // find slot within column for (int k = columnBottom[pc]; k != -1; k = aclist[k].next) { if (aclist[k].line < me->line) { above = k; break; } below = k; } // check to see if accidental can fit in slot qreal myPd = pd * me->note->accidental()->mag(); bool conflict = false; if (above != -1 && me->top - aclist[above].bottom < myPd) { conflict = true; } else if (below != -1 && aclist[below].top - me->bottom < myPd) { conflict = true; } if (!conflict) { // insert into column found = true; me->next = above; if (above == -1) { columnTop[pc] = unmatched[i]; } if (below != -1) { aclist[below].next = unmatched[i]; } else { columnBottom[pc] = unmatched[i]; } break; } } // if no slot found, then add to list of unmatched accidental indices if (!found) { umi.push_back(unmatched[i]); } } nAcc = umi.size(); if (nAcc > 1) { std::sort(umi.begin(), umi.end()); } // lay out columns for (int i = 0; i < nColumns; ++i) { int pc = column[i]; AcEl* below = 0; // lay out accidentals for (int j = columnBottom[pc]; j != -1; j = aclist[j].next) { qreal x = layoutAccidental(&aclist[j], 0, below, colOffset, leftNotes, pnd, pd, sp); minX = qMin(minX, x); below = &aclist[j]; } // align within column int next = -1; for (int j = columnBottom[pc]; j != -1; j = next) { next = aclist[j].next; if (next != -1 && aclist[j].line == aclist[next].line) { continue; } aclist[j].x = minX; } // move to next column colOffset = minX; } } else { for (int i = 0; i < nAcc; ++i) { umi.push_back(i); } } if (nAcc) { // for accidentals with no octave matches, use zig zag approach // layout right to left in pairs, (next) highest then lowest AcEl* me = &aclist[umi[0]]; AcEl* above = 0; AcEl* below = 0; // layout top accidental layoutAccidental(me, above, below, colOffset, leftNotes, pnd, pd, sp); // layout bottom accidental int n = nAcc - 1; if (n > 0) { above = me; me = &aclist[umi[n]]; layoutAccidental(me, above, below, colOffset, leftNotes, pnd, pd, sp); } // layout middle accidentals if (n > 1) { for (int i = 1; i < n; ++i, --n) { // next highest below = me; me = &aclist[umi[i]]; layoutAccidental(me, above, below, colOffset, leftNotes, pnd, pd, sp); if (i == n - 1) { break; } // next lowest above = me; me = &aclist[umi[n - 1]]; layoutAccidental(me, above, below, colOffset, leftNotes, pnd, pd, sp); } } } for (const AcEl& e : aclist) { // even though we initially calculate accidental position relative to segment // we must record pos for accidental relative to note, // since pos is always interpreted relative to parent Note* note = e.note; qreal x = e.x + lx - (note->x() + note->chord()->x()); note->accidental()->setPos(x, 0); } } #define beamModeMid(a) (a == Beam::Mode::MID || a == Beam::Mode::BEGIN32 || a == Beam::Mode::BEGIN64) bool beamNoContinue(Beam::Mode mode) { return mode == Beam::Mode::END || mode == Beam::Mode::NONE || mode == Beam::Mode::INVALID; } //--------------------------------------------------------- // beamGraceNotes //--------------------------------------------------------- void Score::beamGraceNotes(Chord* mainNote, bool after) { ChordRest* a1 = 0; // start of (potential) beam Beam* beam = 0; // current beam Beam::Mode bm = Beam::Mode::AUTO; QVector graceNotes = after ? mainNote->graceNotesAfter() : mainNote->graceNotesBefore(); for (ChordRest* cr : graceNotes) { bm = Groups::endBeam(cr); if ((cr->durationType().type() <= TDuration::DurationType::V_QUARTER) || (bm == Beam::Mode::NONE)) { if (beam) { beam->layoutGraceNotes(); beam = 0; } if (a1) { a1->removeDeleteBeam(false); a1 = 0; } cr->removeDeleteBeam(false); continue; } if (beam) { bool beamEnd = bm == Beam::Mode::BEGIN; if (!beamEnd) { cr->replaceBeam(beam); cr = 0; beamEnd = (bm == Beam::Mode::END); } if (beamEnd) { beam->layoutGraceNotes(); beam = 0; } } if (!cr) { continue; } if (a1 == 0) { a1 = cr; } else { if (!beamModeMid(bm) && (bm == Beam::Mode::BEGIN)) { a1->removeDeleteBeam(false); a1 = cr; } else { beam = a1->beam(); if (beam == 0 || beam->elements().front() != a1) { beam = new Beam(this); beam->setGenerated(true); beam->setTrack(mainNote->track()); a1->replaceBeam(beam); } cr->replaceBeam(beam); a1 = 0; } } } if (beam) { beam->layoutGraceNotes(); } else if (a1) { a1->removeDeleteBeam(false); } } #if 0 // unused //--------------------------------------------------------- // layoutSpanner // called after dragging a staff //--------------------------------------------------------- void Score::layoutSpanner() { int tracks = ntracks(); for (int track = 0; track < tracks; ++track) { for (Segment* segment = firstSegment(SegmentType::All); segment; segment = segment->next1()) { if (track == tracks - 1) { size_t n = segment->annotations().size(); for (size_t i = 0; i < n; ++i) { segment->annotations().at(i)->layout(); } } Element* e = segment->element(track); if (e && e->isChord()) { Chord* c = toChord(segment->element(track)); c->layoutStem(); for (Note* n : c->notes()) { Tie* tie = n->tieFor(); if (tie) { tie->layout(); } for (Spanner* sp : n->spannerFor()) { sp->layout(); } } } } } rebuildBspTree(); } #endif //--------------------------------------------------------- // hideEmptyStaves //--------------------------------------------------------- void Score::hideEmptyStaves(System* system, bool isFirstSystem) { int staves = _staves.size(); int staffIdx = 0; bool systemIsEmpty = true; for (Staff* staff : _staves) { SysStaff* ss = system->staff(staffIdx); Staff::HideMode hideMode = staff->hideWhenEmpty(); if (hideMode == Staff::HideMode::ALWAYS || (styleB(Sid::hideEmptyStaves) && (staves > 1) && !(isFirstSystem && styleB(Sid::dontHideStavesInFirstSystem)) && hideMode != Staff::HideMode::NEVER)) { bool hideStaff = true; for (MeasureBase* m : system->measures()) { if (!m->isMeasure()) { continue; } Measure* measure = toMeasure(m); if (!measure->isEmpty(staffIdx)) { hideStaff = false; break; } } // check if notes moved into this staff Part* part = staff->part(); int n = part->nstaves(); if (hideStaff && (n > 1)) { int idx = part->staves()->front()->idx(); for (int i = 0; i < part->nstaves(); ++i) { int st = idx + i; for (MeasureBase* mb : system->measures()) { if (!mb->isMeasure()) { continue; } Measure* m = toMeasure(mb); if (staff->hideWhenEmpty() == Staff::HideMode::INSTRUMENT && !m->isEmpty(st)) { hideStaff = false; break; } for (Segment* s = m->first(SegmentType::ChordRest); s; s = s->next(SegmentType::ChordRest)) { for (int voice = 0; voice < VOICES; ++voice) { ChordRest* cr = s->cr(st * VOICES + voice); if (cr == 0 || cr->isRest()) { continue; } int staffMove = cr->staffMove(); if (staffIdx == st + staffMove) { hideStaff = false; break; } } } if (!hideStaff) { break; } } if (!hideStaff) { break; } } } ss->setShow(hideStaff ? false : staff->show()); if (ss->show()) { systemIsEmpty = false; } } else if (!staff->show()) { // TODO: OK to check this first and not bother with checking if empty? ss->setShow(false); } else { systemIsEmpty = false; ss->setShow(true); } ++staffIdx; } Staff* firstVisible = nullptr; if (systemIsEmpty) { for (Staff* staff : _staves) { SysStaff* ss = system->staff(staff->idx()); if (staff->showIfEmpty() && !ss->show()) { ss->setShow(true); systemIsEmpty = false; } else if (!firstVisible && staff->show()) { firstVisible = staff; } } } // don’t allow a complete empty system if (systemIsEmpty) { Staff* staff = firstVisible ? firstVisible : _staves.front(); SysStaff* ss = system->staff(staff->idx()); ss->setShow(true); } } //--------------------------------------------------------- // connectTies /// Rebuild tie connections. //--------------------------------------------------------- void Score::connectTies(bool silent) { int tracks = nstaves() * VOICES; Measure* m = firstMeasure(); if (!m) { return; } SegmentType st = SegmentType::ChordRest; for (Segment* s = m->first(st); s; s = s->next1(st)) { for (int i = 0; i < tracks; ++i) { Element* e = s->element(i); if (e == 0 || !e->isChord()) { continue; } Chord* c = toChord(e); for (Note* n : c->notes()) { // connect a tie without end note Tie* tie = n->tieFor(); if (tie && !tie->endNote()) { Note* nnote; if (_mscVersion <= 114) { nnote = searchTieNote114(n); } else { nnote = searchTieNote(n); } if (nnote == 0) { if (!silent) { qDebug("next note at %d track %d for tie not found (version %d)", s->tick().ticks(), i, _mscVersion); delete tie; n->setTieFor(0); } } else { tie->setEndNote(nnote); nnote->setTieBack(tie); } } // connect a glissando without initial note (old glissando format) for (Spanner* spanner : n->spannerBack()) { if (spanner->isGlissando() && !spanner->startElement()) { Note* initialNote = Glissando::guessInitialNote(n->chord()); n->removeSpannerBack(spanner); if (initialNote) { spanner->setStartElement(initialNote); spanner->setEndElement(n); spanner->setTick(initialNote->chord()->tick()); spanner->setTick2(n->chord()->tick()); spanner->setTrack(n->track()); spanner->setTrack2(n->track()); spanner->setParent(initialNote); initialNote->add(spanner); } else { delete spanner; } } } // spanner with no end element can happen during copy/paste for (Spanner* spanner : n->spannerFor()) { if (spanner->endElement() == nullptr) { n->removeSpannerFor(spanner); delete spanner; } } } #if 0 // chords are set in tremolo->layout() // connect two note tremolos Tremolo* tremolo = c->tremolo(); if (tremolo && tremolo->twoNotes() && !tremolo->chord2()) { for (Segment* ls = s->next1(st); ls; ls = ls->next1(st)) { Element* element = ls->element(i); if (!element) { continue; } if (!element->isChord()) { qDebug("cannot connect tremolo"); } else { Chord* nc = toChord(element); nc->setTremolo(tremolo); tremolo->setChords(c, nc); // cross-measure tremolos are not supported // but can accidentally result from copy & paste // remove them now if (c->measure() != nc->measure()) { c->remove(tremolo); } } break; } } #endif } } } //--------------------------------------------------------- // checkDivider //--------------------------------------------------------- static void checkDivider(bool left, System* s, qreal yOffset, bool remove = false) { SystemDivider* divider = left ? s->systemDividerLeft() : s->systemDividerRight(); if ((s->score()->styleB(left ? Sid::dividerLeft : Sid::dividerRight)) && !remove) { if (!divider) { divider = new SystemDivider(s->score()); divider->setDividerType(left ? SystemDivider::Type::LEFT : SystemDivider::Type::RIGHT); divider->setGenerated(true); s->add(divider); } divider->layout(); divider->rypos() = divider->height() * .5 + yOffset; if (left) { divider->rypos() += s->score()->styleD(Sid::dividerLeftY) * SPATIUM20; divider->rxpos() = s->score()->styleD(Sid::dividerLeftX) * SPATIUM20; } else { divider->rypos() += s->score()->styleD(Sid::dividerRightY) * SPATIUM20; divider->rxpos() = s->score()->styleD(Sid::pagePrintableWidth) * DPI - divider->width(); divider->rxpos() += s->score()->styleD(Sid::dividerRightX) * SPATIUM20; } } else if (divider) { if (divider->generated()) { s->remove(divider); delete divider; } else { s->score()->undoRemoveElement(divider); } } } //--------------------------------------------------------- // layoutPage // restHeight - vertical space which has to be distributed // between systems // The algorithm tries to produce most equally spaced // systems. //--------------------------------------------------------- static void layoutPage(Page* page, qreal restHeight) { if (restHeight < 0.0) { qDebug("restHeight < 0.0: %f\n", restHeight); restHeight = 0; } Score* score = page->score(); int gaps = page->systems().size() - 1; QList sList; // build list of systems (excluding last) // set initial distance for each to the unstretched minimum distance to next for (int i = 0; i < gaps; ++i) { System* s1 = page->systems().at(i); System* s2 = page->systems().at(i + 1); s1->setDistance(s2->y() - s1->y()); if (s1->vbox() || s2->vbox() || s1->hasFixedDownDistance()) { continue; } sList.push_back(s1); } // last systenm needs no divider System* lastSystem = page->systems().back(); checkDivider(true, lastSystem, 0.0, true); // remove checkDivider(false, lastSystem, 0.0, true); // remove if (sList.empty() || MScore::noVerticalStretch || score->layoutMode() == LayoutMode::SYSTEM) { if (score->layoutMode() == LayoutMode::FLOAT) { qreal y = restHeight * .5; for (System* system : page->systems()) { system->move(QPointF(0.0, y)); } } // system dividers for (int i = 0; i < gaps; ++i) { System* s1 = page->systems().at(i); System* s2 = page->systems().at(i + 1); if (!(s1->vbox() || s2->vbox())) { qreal yOffset = s1->height() + (s1->distance() - s1->height()) * .5; checkDivider(true, s1, yOffset); checkDivider(false, s1, yOffset); } } return; } qreal maxDist = score->styleP(Sid::maxSystemDistance); // allocate space as needed to normalize system distance (bottom of one system to top of next) std::sort(sList.begin(), sList.end(), [](System* a, System* b) { return a->distance() - a->height() < b->distance() - b->height(); }); System* s0 = sList[0]; qreal dist = s0->distance() - s0->height(); // distance for shortest system for (int i = 1; i < sList.size(); ++i) { System* si = sList[i]; qreal ndist = si->distance() - si->height(); // next taller system qreal fill = ndist - dist; // amount by which this system distance exceeds next shorter if (fill > 0.0) { qreal totalFill = fill * i; // space required to add this amount to all shorter systems if (totalFill > restHeight) { totalFill = restHeight; // too much; adjust amount fill = restHeight / i; } for (int k = 0; k < i; ++k) { // add amount to all shorter systems System* s = sList[k]; qreal d = s->distance() + fill; if ((d - s->height()) > maxDist) { // but don't exceed max system distance d = qMax(maxDist + s->height(), s->distance()); } s->setDistance(d); } restHeight -= totalFill; // reduce available space for next iteration if (restHeight <= 0) { break; // no space left } } dist = ndist; // set up for next iteration } if (restHeight > 0.0) { // space left? qreal fill = restHeight / sList.size(); for (System* s : sList) { // allocate it to systems equally qreal d = s->distance() + fill; if ((d - s->height()) > maxDist) { // but don't exceed max system distance d = qMax(maxDist + s->height(), s->distance()); } s->setDistance(d); } } qreal y = page->systems().at(0)->y(); for (int i = 0; i < gaps; ++i) { System* s1 = page->systems().at(i); System* s2 = page->systems().at(i + 1); s1->rypos() = y; y += s1->distance(); if (!(s1->vbox() || s2->vbox())) { qreal yOffset = s1->height() + (s1->distance() - s1->height()) * .5; checkDivider(true, s1, yOffset); checkDivider(false, s1, yOffset); } } page->systems().back()->rypos() = y; } //--------------------------------------------------------- // Spring //--------------------------------------------------------- struct Spring { int seg; qreal stretch; qreal fix; Spring(int i, qreal s, qreal f) : seg(i), stretch(s), fix(f) {} }; typedef std::multimap > SpringMap; //--------------------------------------------------------- // sff2 // compute 1/Force for a given Extend //--------------------------------------------------------- static qreal sff2(qreal width, qreal xMin, const SpringMap& springs) { if (width <= xMin) { return 0.0; } auto i = springs.begin(); qreal c = i->second.stretch; if (c == 0.0) { //DEBUG c = 1.1; } qreal f = 0.0; for (; i != springs.end();) { xMin -= i->second.fix; f = (width - xMin) / c; ++i; if (i == springs.end() || f <= i->first) { break; } c += i->second.stretch; } return f; } //--------------------------------------------------------- // respace //--------------------------------------------------------- void Score::respace(std::vector* elements) { ChordRest* cr1 = elements->front(); ChordRest* cr2 = elements->back(); int n = int(elements->size()); qreal x1 = cr1->segment()->pos().x(); qreal x2 = cr2->segment()->pos().x(); #if (!defined (_MSCVER) && !defined (_MSC_VER)) qreal width[n - 1]; int ticksList[n - 1]; #else // MSVC does not support VLA. Replace with std::vector. If profiling determines that the // heap allocation is slow, an optimization might be used. std::vector width(n - 1); std::vector ticksList(n - 1); #endif int minTick = 100000; for (int i = 0; i < n - 1; ++i) { ChordRest* cr = (*elements)[i]; ChordRest* ncr = (*elements)[i + 1]; width[i] = cr->shape().minHorizontalDistance(ncr->shape()); ticksList[i] = cr->ticks().ticks(); minTick = qMin(ticksList[i], minTick); } //--------------------------------------------------- // compute stretches //--------------------------------------------------- SpringMap springs; qreal minimum = 0.0; for (int i = 0; i < n - 1; ++i) { qreal w = width[i]; int t = ticksList[i]; qreal str = 1.0 + 0.865617 * log(qreal(t) / qreal(minTick)); qreal d = w / str; springs.insert(std::pair(d, Spring(i, str, w))); minimum += w; } //--------------------------------------------------- // distribute stretch to elements //--------------------------------------------------- qreal force = sff2(x2 - x1, minimum, springs); for (auto i = springs.begin(); i != springs.end(); ++i) { qreal stretch = force * i->second.stretch; if (stretch < i->second.fix) { stretch = i->second.fix; } width[i->second.seg] = stretch; } qreal x = x1; for (int i = 1; i < n - 1; ++i) { x += width[i - 1]; ChordRest* cr = (*elements)[i]; qreal dx = x - cr->segment()->pos().x(); cr->rxpos() += dx; } } //--------------------------------------------------------- // getNextPage //--------------------------------------------------------- void LayoutContext::getNextPage() { if (!page || curPage >= score->npages()) { page = new Page(score); score->pages().push_back(page); prevSystem = nullptr; pageOldMeasure = nullptr; } else { page = score->pages()[curPage]; QList& systems = page->systems(); pageOldMeasure = systems.isEmpty() ? nullptr : systems.back()->measures().back(); const int i = systems.indexOf(curSystem); if (i > 0 && systems[i - 1]->page() == page) { // Current and previous systems are on the current page. // Erase only the current and the following systems // as the previous one will not participate in layout. systems.erase(systems.begin() + i, systems.end()); } else { // system is not on the current page (or will be the first one) systems.clear(); } prevSystem = systems.empty() ? nullptr : systems.back(); } page->bbox().setRect(0.0, 0.0, score->loWidth(), score->loHeight()); page->setNo(curPage); qreal x = 0.0; qreal y = 0.0; if (curPage) { Page* prevPage = score->pages()[curPage - 1]; if (MScore::verticalOrientation()) { y = prevPage->pos().y() + page->height() + MScore::verticalPageGap; } else { qreal gap = (curPage + score->pageNumberOffset()) & 1 ? MScore::horizontalPageGapOdd : MScore::horizontalPageGapEven; x = prevPage->pos().x() + page->width() + gap; } } ++curPage; page->setPos(x, y); } //--------------------------------------------------------- // getNextSystem //--------------------------------------------------------- System* Score::getNextSystem(LayoutContext& lc) { bool isVBox = lc.curMeasure->isVBox(); System* system; if (lc.systemList.empty()) { system = new System(this); lc.systemOldMeasure = 0; } else { system = lc.systemList.takeFirst(); lc.systemOldMeasure = system->measures().empty() ? 0 : system->measures().back(); system->clear(); // remove measures from system } _systems.append(system); if (!isVBox) { int nstaves = Score::nstaves(); system->adjustStavesNumber(nstaves); } return system; } //--------------------------------------------------------- // createMMRest // create a multi measure rest from m to lm (inclusive) //--------------------------------------------------------- void Score::createMMRest(Measure* m, Measure* lm, const Fraction& len) { int n = 1; if (m != lm) { for (Measure* mm = m->nextMeasure(); mm; mm = mm->nextMeasure()) { ++n; mm->setMMRestCount(-1); if (mm->mmRest()) { undo(new ChangeMMRest(mm, 0)); } if (mm == lm) { break; } } } Measure* mmr = m->mmRest(); if (mmr) { // reuse existing mmrest if (mmr->ticks() != len) { Segment* s = mmr->findSegmentR(SegmentType::EndBarLine, mmr->ticks()); // adjust length mmr->setTicks(len); // move existing end barline if (s) { s->setRtick(len); } } mmr->removeSystemTrailer(); } else { mmr = new Measure(this); mmr->setTicks(len); mmr->setTick(m->tick()); undo(new ChangeMMRest(m, mmr)); } mmr->setTimesig(m->timesig()); mmr->setPageBreak(lm->pageBreak()); mmr->setLineBreak(lm->lineBreak()); mmr->setMMRestCount(n); mmr->setNo(m->no()); Segment* ss = lm->findSegmentR(SegmentType::EndBarLine, lm->ticks()); if (ss) { Segment* ds = mmr->undoGetSegmentR(SegmentType::EndBarLine, mmr->ticks()); for (int staffIdx = 0; staffIdx < nstaves(); ++staffIdx) { Element* e = ss->element(staffIdx * VOICES); if (e) { bool generated = e->generated(); if (!ds->element(staffIdx * VOICES)) { Element* ee = generated ? e->clone() : e->linkedClone(); ee->setGenerated(generated); ee->setParent(ds); undoAddElement(ee); } else { BarLine* bd = toBarLine(ds->element(staffIdx * VOICES)); BarLine* bs = toBarLine(e); if (!generated && !bd->links()) { undo(new Link(bd, bs)); } if (bd->barLineType() != bs->barLineType()) { // change directly when generating mmrests, do not change underlying measures or follow links undo(new ChangeProperty(bd, Pid::BARLINE_TYPE, QVariant::fromValue(bs->barLineType()), PropertyFlags::NOSTYLE)); undo(new ChangeProperty(bd, Pid::GENERATED, generated, PropertyFlags::NOSTYLE)); } } } } } Segment* clefSeg = lm->findSegmentR(SegmentType::Clef | SegmentType::HeaderClef, lm->ticks()); if (clefSeg) { Segment* mmrClefSeg = mmr->undoGetSegment(clefSeg->segmentType(), lm->endTick()); for (int staffIdx = 0; staffIdx < nstaves(); ++staffIdx) { const int track = staff2track(staffIdx); Element* e = clefSeg->element(track); if (e && e->isClef()) { Clef* clef = toClef(e); if (!mmrClefSeg->element(track)) { Clef* mmrClef = clef->generated() ? clef->clone() : toClef(clef->linkedClone()); mmrClef->setParent(mmrClefSeg); undoAddElement(mmrClef); } else { Clef* mmrClef = toClef(mmrClefSeg->element(track)); mmrClef->setClefType(clef->clefType()); mmrClef->setShowCourtesy(clef->showCourtesy()); } } } } mmr->setRepeatStart(m->repeatStart() || lm->repeatStart()); mmr->setRepeatEnd(m->repeatEnd() || lm->repeatEnd()); mmr->setSectionBreak(lm->sectionBreak()); ElementList oldList = mmr->takeElements(); ElementList newList = lm->el(); for (Element* e : m->el()) { if (e->isMarker()) { newList.push_back(e); } } for (Element* e : newList) { bool found = false; for (Element* ee : oldList) { if (ee->type() == e->type() && ee->subtype() == e->subtype()) { mmr->add(ee); auto i = std::find(oldList.begin(), oldList.end(), ee); if (i != oldList.end()) { oldList.erase(i); } found = true; break; } } if (!found) { mmr->add(e->clone()); } } for (Element* e : oldList) { delete e; } Segment* s = mmr->undoGetSegmentR(SegmentType::ChordRest, Fraction(0,1)); for (int staffIdx = 0; staffIdx < _staves.size(); ++staffIdx) { int track = staffIdx * VOICES; if (s->element(track) == 0) { Rest* r = new Rest(this); r->setDurationType(TDuration::DurationType::V_MEASURE); r->setTicks(mmr->ticks()); r->setTrack(track); r->setParent(s); undo(new AddElement(r)); } } // // check for clefs // Segment* cs = lm->findSegmentR(SegmentType::Clef, lm->ticks()); Segment* ns = mmr->findSegment(SegmentType::Clef, lm->endTick()); if (cs) { if (ns == 0) { ns = mmr->undoGetSegmentR(SegmentType::Clef, lm->ticks()); } ns->setEnabled(cs->enabled()); ns->setTrailer(cs->trailer()); for (int staffIdx = 0; staffIdx < _staves.size(); ++staffIdx) { int track = staffIdx * VOICES; Clef* clef = toClef(cs->element(track)); if (clef) { if (ns->element(track) == 0) { ns->add(clef->clone()); } else { //TODO: check if same clef } } } } else if (ns) { // TODO: remove elements from ns? undo(new RemoveElement(ns)); } // // check for time signature // cs = m->findSegmentR(SegmentType::TimeSig, Fraction(0,1)); ns = mmr->findSegment(SegmentType::TimeSig, m->tick()); if (cs) { if (ns == 0) { ns = mmr->undoGetSegmentR(SegmentType::TimeSig, Fraction(0,1)); } ns->setEnabled(cs->enabled()); ns->setHeader(cs->header()); for (int staffIdx = 0; staffIdx < _staves.size(); ++staffIdx) { int track = staffIdx * VOICES; TimeSig* ts = toTimeSig(cs->element(track)); if (ts) { TimeSig* nts = toTimeSig(ns->element(track)); if (!nts) { nts = ts->generated() ? ts->clone() : toTimeSig(ts->linkedClone()); nts->setParent(ns); undo(new AddElement(nts)); } else { nts->setSig(ts->sig(), ts->timeSigType()); nts->layout(); } } } } else if (ns) { // TODO: remove elements from ns? undo(new RemoveElement(ns)); } // // check for ambitus // cs = m->findSegmentR(SegmentType::Ambitus, Fraction(0,1)); ns = mmr->findSegment(SegmentType::Ambitus, m->tick()); if (cs) { if (ns == 0) { ns = mmr->undoGetSegmentR(SegmentType::Ambitus, Fraction(0,1)); } for (int staffIdx = 0; staffIdx < _staves.size(); ++staffIdx) { int track = staffIdx * VOICES; Ambitus* a = toAmbitus(cs->element(track)); if (a) { Ambitus* na = toAmbitus(ns->element(track)); if (!na) { na = a->clone(); na->setParent(ns); undo(new AddElement(na)); } else { na->initFrom(a); na->layout(); } } } } else if (ns) { // TODO: remove elements from ns? undo(new RemoveElement(ns)); } // // check for key signature // cs = m->findSegmentR(SegmentType::KeySig, Fraction(0,1)); ns = mmr->findSegmentR(SegmentType::KeySig, Fraction(0,1)); if (cs) { if (ns == 0) { ns = mmr->undoGetSegmentR(SegmentType::KeySig, Fraction(0,1)); } ns->setEnabled(cs->enabled()); ns->setHeader(cs->header()); for (int staffIdx = 0; staffIdx < _staves.size(); ++staffIdx) { int track = staffIdx * VOICES; KeySig* ks = toKeySig(cs->element(track)); if (ks) { KeySig* nks = toKeySig(ns->element(track)); if (!nks) { nks = ks->generated() ? ks->clone() : toKeySig(ks->linkedClone()); nks->setParent(ns); nks->setGenerated(true); undo(new AddElement(nks)); } else { if (!(nks->keySigEvent() == ks->keySigEvent())) { bool addKey = ks->isChange(); undo(new ChangeKeySig(nks, ks->keySigEvent(), nks->showCourtesy(), addKey)); } } } } } else if (ns) { ns->setEnabled(false); // TODO: remove elements from ns, then delete ns // previously we removed the segment if not empty, // but this resulted in "stale" keysig in mmrest after removed from underlying measure //undo(new RemoveElement(ns)); } mmr->checkHeader(); mmr->checkTrailer(); // // check for rehearsal mark etc. // cs = m->findSegmentR(SegmentType::ChordRest, Fraction(0,1)); if (cs) { // clone elements from underlying measure to mmr for (Element* e : cs->annotations()) { // look at elements in underlying measure if (!(e->isRehearsalMark() || e->isTempoText() || e->isHarmony() || e->isStaffText() || e->isSystemText() || e->isInstrumentChange())) { continue; } // try to find a match in mmr bool found = false; for (Element* ee : s->annotations()) { if (e->linkList().contains(ee)) { found = true; break; } } // add to mmr if no match found if (!found) { Element* ne = e->linkedClone(); ne->setParent(s); undo(new AddElement(ne)); } } // remove stray elements (possibly leftover from a previous layout of this mmr) // this should not happen since the elements are linked? for (Element* e : s->annotations()) { // look at elements in mmr if (!(e->isRehearsalMark() || e->isTempoText() || e->isHarmony() || e->isStaffText() || e->isSystemText() || e->isInstrumentChange())) { continue; } // try to find a match in underlying measure bool found = false; for (Element* ee : cs->annotations()) { if (e->linkList().contains(ee)) { found = true; break; } } // remove from mmr if no match found if (!found) { undo(new RemoveElement(e)); } } } MeasureBase* nm = _showVBox ? lm->next() : lm->nextMeasure(); mmr->setNext(nm); mmr->setPrev(m->prev()); } //--------------------------------------------------------- // validMMRestMeasure // return true if this might be a measure in a // multi measure rest //--------------------------------------------------------- static bool validMMRestMeasure(Measure* m) { if (m->irregular()) { return false; } int n = 0; for (Segment* s = m->first(); s; s = s->next()) { for (Element* e : s->annotations()) { if (!(e->isRehearsalMark() || e->isTempoText() || e->isHarmony() || e->isStaffText() || e->isSystemText() || e->isInstrumentChange())) { return false; } } if (s->isChordRestType()) { bool restFound = false; int tracks = m->score()->ntracks(); for (int track = 0; track < tracks; ++track) { if ((track % VOICES) == 0 && !m->score()->staff(track / VOICES)->show()) { track += VOICES - 1; continue; } if (s->element(track)) { if (!s->element(track)->isRest()) { return false; } restFound = true; } } for (Element* e : s->annotations()) { if (e->isFermata()) { return false; } } if (restFound) { ++n; } // measure is not empty if there is more than one rest if (n > 1) { return false; } } } return true; } //--------------------------------------------------------- // breakMultiMeasureRest // return true if this measure should start a new // multi measure rest //--------------------------------------------------------- static bool breakMultiMeasureRest(Measure* m) { if (m->breakMultiMeasureRest()) { return true; } if (m->repeatStart() || (m->prevMeasure() && m->prevMeasure()->repeatEnd()) || (m->isIrregular()) || (m->prevMeasure() && m->prevMeasure()->isIrregular()) || (m->prevMeasure() && (m->prevMeasure()->sectionBreak()))) { return true; } auto sl = m->score()->spannerMap().findOverlapping(m->tick().ticks(), m->endTick().ticks()); for (auto i : sl) { Spanner* s = i.value; // break for first measure of volta or textline and first measure *after* volta if ((s->isVolta() || s->isTextLine()) && (s->tick() == m->tick() || s->tick2() == m->tick())) { return true; } } // break for marker in this measure for (Element* e : m->el()) { if (e->isMarker()) { Marker* mark = toMarker(e); if (!(mark->align() & Align::RIGHT)) { return true; } } } // break for marker & jump in previous measure Measure* pm = m->prevMeasure(); if (pm) { for (Element* e : pm->el()) { if (e->isJump()) { return true; } else if (e->isMarker()) { Marker* mark = toMarker(e); if (mark->align() & Align::RIGHT) { return true; } } } } for (Segment* s = m->first(); s; s = s->next()) { for (Element* e : s->annotations()) { if (!e->visible()) { continue; } if (e->isRehearsalMark() || e->isTempoText() || ((e->isHarmony() || e->isStaffText() || e->isSystemText() || e->isInstrumentChange()) && (e->systemFlag() || m->score()->staff(e->staffIdx())->show()))) { return true; } } for (int staffIdx = 0; staffIdx < m->score()->nstaves(); ++staffIdx) { if (!m->score()->staff(staffIdx)->show()) { continue; } Element* e = s->element(staffIdx * VOICES); if (!e || e->generated()) { continue; } if (s->isStartRepeatBarLineType()) { return true; } if (s->isType(SegmentType::KeySig | SegmentType::TimeSig) && m->tick().isNotZero()) { return true; } if (s->isClefType()) { if (s->tick() != m->endTick() && m->tick().isNotZero()) { return true; } } } } if (pm) { Segment* s = pm->findSegmentR(SegmentType::EndBarLine, pm->ticks()); if (s) { for (int staffIdx = 0; staffIdx < s->score()->nstaves(); ++staffIdx) { BarLine* bl = toBarLine(s->element(staffIdx * VOICES)); if (bl) { BarLineType t = bl->barLineType(); if (t != BarLineType::NORMAL && t != BarLineType::BROKEN && t != BarLineType::DOTTED && !bl->generated()) { return true; } else { break; } } } } if (pm->findSegment(SegmentType::Clef, m->tick())) { return true; } } return false; } //--------------------------------------------------------- // adjustMeasureNo //--------------------------------------------------------- int LayoutContext::adjustMeasureNo(MeasureBase* m) { measureNo += m->noOffset(); m->setNo(measureNo); if (!m->irregular()) { // don’t count measure ++measureNo; } if (m->sectionBreakElement() && m->sectionBreakElement()->startWithMeasureOne()) { measureNo = 0; } return measureNo; } //--------------------------------------------------------- // createBeams // helper function //--------------------------------------------------------- void Score::createBeams(LayoutContext& lc, Measure* measure) { bool crossMeasure = styleB(Sid::crossMeasureValues); for (int track = 0; track < ntracks(); ++track) { Staff* stf = staff(track2staff(track)); // don’t compute beams for invisible staves and tablature without stems if (!stf->show() || (stf->isTabStaff(measure->tick()) && stf->staffType(measure->tick())->stemless())) { continue; } ChordRest* a1 = 0; // start of (potential) beam bool firstCR = true; Beam* beam = 0; // current beam Beam::Mode bm = Beam::Mode::AUTO; ChordRest* prev = 0; bool checkBeats = false; Fraction stretch = Fraction(1,1); QHash beatSubdivision; // if this measure is simple meter (actually X/4), // then perform a prepass to determine the subdivision of each beat beatSubdivision.clear(); TimeSig* ts = stf->timeSig(measure->tick()); checkBeats = false; stretch = ts ? ts->stretch() : Fraction(1,1); const SegmentType st = SegmentType::ChordRest; if (ts && ts->denominator() == 4) { checkBeats = true; for (Segment* s = measure->first(st); s; s = s->next(st)) { ChordRest* mcr = toChordRest(s->element(track)); if (mcr == 0) { continue; } int beat = (mcr->rtick() * stretch).ticks() / MScore::division; if (beatSubdivision.contains(beat)) { beatSubdivision[beat] = qMin(beatSubdivision[beat], mcr->durationType()); } else { beatSubdivision[beat] = mcr->durationType(); } } } for (Segment* segment = measure->first(st); segment; segment = segment->next(st)) { ChordRest* cr = segment->cr(track); if (cr == 0) { continue; } if (firstCR) { firstCR = false; // Handle cross-measure beams Beam::Mode mode = cr->beamMode(); if (mode == Beam::Mode::MID || mode == Beam::Mode::END) { ChordRest* prevCR = findCR(measure->tick() - Fraction::fromTicks(1), track); if (prevCR) { const Measure* pm = prevCR->measure(); if (!beamNoContinue(prevCR->beamMode()) && !pm->lineBreak() && !pm->pageBreak() && !pm->sectionBreak() && lc.prevMeasure && prevCR->durationType().type() >= TDuration::DurationType::V_EIGHTH && prevCR->durationType().type() <= TDuration::DurationType::V_1024TH) { beam = prevCR->beam(); //a1 = beam ? beam->elements().front() : prevCR; a1 = beam ? nullptr : prevCR; // when beam is found, a1 is no longer required. } } } } #if 0 for (Lyrics* l : cr->lyrics()) { if (l) { l->layout(); } } #endif // handle grace notes and cross-measure beaming // (tied chords?) if (cr->isChord()) { Chord* chord = toChord(cr); beamGraceNotes(chord, false); // grace before beamGraceNotes(chord, true); // grace after // set up for cross-measure values as soon as possible // to have all computations (stems, hooks, ...) consistent with it if (!chord->isGrace()) { chord->crossMeasureSetup(crossMeasure); } } if (cr->isRest() && cr->beamMode() == Beam::Mode::AUTO) { bm = Beam::Mode::NONE; // do not beam rests set to Beam::Mode::AUTO } else { bm = Groups::endBeam(cr, prev); // get defaults from time signature properties } // perform additional context-dependent checks if (bm == Beam::Mode::AUTO) { // check if we need to break beams according to minimum duration in current / previous beat if (checkBeats && cr->rtick().isNotZero()) { Fraction tick = cr->rtick() * stretch; // check if on the beat if ((tick.ticks() % MScore::division) == 0) { int beat = tick.ticks() / MScore::division; // get minimum duration for this & previous beat TDuration minDuration = qMin(beatSubdivision[beat], beatSubdivision[beat - 1]); // re-calculate beam as if this were the duration of current chordrest TDuration saveDuration = cr->actualDurationType(); TDuration saveCMDuration = cr->crossMeasureDurationType(); CrossMeasure saveCrossMeasVal = cr->crossMeasure(); cr->setDurationType(minDuration); bm = Groups::endBeam(cr, prev); cr->setDurationType(saveDuration); cr->setCrossMeasure(saveCrossMeasVal); cr->setCrossMeasureDurationType(saveCMDuration); } } } prev = cr; // if chord has hooks and is 2nd element of a cross-measure value // set beam mode to NONE (do not combine with following chord beam/hook, if any) if (cr->durationType().hooks() > 0 && cr->crossMeasure() == CrossMeasure::SECOND) { bm = Beam::Mode::NONE; } if ((cr->durationType().type() <= TDuration::DurationType::V_QUARTER) || (bm == Beam::Mode::NONE)) { bool removeBeam = true; if (beam) { beam->layout1(); removeBeam = (beam->elements().size() <= 1); beam = 0; } if (a1) { if (removeBeam) { a1->removeDeleteBeam(false); } a1 = 0; } cr->removeDeleteBeam(false); continue; } if (beam) { bool beamEnd = (bm == Beam::Mode::BEGIN); if (!beamEnd) { cr->replaceBeam(beam); cr = 0; beamEnd = (bm == Beam::Mode::END); } if (beamEnd) { beam->layout1(); beam = 0; } } if (!cr) { continue; } if (a1 == 0) { a1 = cr; } else { if (!beamModeMid(bm) && (bm == Beam::Mode::BEGIN || (a1->segment()->segmentType() != cr->segment()->segmentType()) || (a1->tick() + a1->actualTicks() < cr->tick()) ) ) { a1->removeDeleteBeam(false); a1 = cr; } else { beam = a1->beam(); if (beam == 0 || beam->elements().front() != a1) { beam = new Beam(this); beam->setGenerated(true); beam->setTrack(track); a1->replaceBeam(beam); } cr->replaceBeam(beam); a1 = 0; } } } if (beam) { beam->layout1(); } else if (a1) { Fraction nextTick = a1->tick() + a1->actualTicks(); Measure* m = (nextTick >= measure->endTick() ? measure->nextMeasure() : measure); ChordRest* nextCR = (m ? m->findChordRest(nextTick, track) : nullptr); Beam* b = a1->beam(); if (!(b && b->elements().startsWith(a1) && nextCR && beamModeMid(nextCR->beamMode()))) { a1->removeDeleteBeam(false); } } } } //--------------------------------------------------------- // breakCrossMeasureBeams //--------------------------------------------------------- static void breakCrossMeasureBeams(Measure* measure) { MeasureBase* mbNext = measure->next(); if (!mbNext || !mbNext->isMeasure()) { return; } Measure* next = toMeasure(mbNext); Score* score = measure->score(); const int ntracks = score->ntracks(); Segment* fstSeg = next->first(SegmentType::ChordRest); if (!fstSeg) { return; } for (int track = 0; track < ntracks; ++track) { Staff* stf = score->staff(track2staff(track)); // don’t compute beams for invisible staves and tablature without stems if (!stf->show() || (stf->isTabStaff(measure->tick()) && stf->staffType(measure->tick())->stemless())) { continue; } Element* e = fstSeg->element(track); if (!e || !e->isChordRest()) { continue; } ChordRest* cr = toChordRest(e); Beam* beam = cr->beam(); if (!beam || beam->elements().front()->measure() == next) { // no beam or not cross-measure beam continue; } std::vector mElements; std::vector nextElements; for (ChordRest* beamCR : beam->elements()) { if (beamCR->measure() == measure) { mElements.push_back(beamCR); } else { nextElements.push_back(beamCR); } } if (mElements.size() == 1) { mElements[0]->removeDeleteBeam(false); } Beam* newBeam = nullptr; if (nextElements.size() > 1) { newBeam = new Beam(score); newBeam->setGenerated(true); newBeam->setTrack(track); } const bool nextBeamed = bool(newBeam); for (ChordRest* nextCR : nextElements) { nextCR->removeDeleteBeam(nextBeamed); if (newBeam) { newBeam->add(nextCR); } } if (newBeam) { newBeam->layout1(); } } } //--------------------------------------------------------- // layoutDrumsetChord //--------------------------------------------------------- void layoutDrumsetChord(Chord* c, const Drumset* drumset, const StaffType* st, qreal spatium) { for (Note* note : c->notes()) { int pitch = note->pitch(); if (!drumset->isValid(pitch)) { // qDebug("unmapped drum note %d", pitch); } else if (!note->fixed()) { note->undoChangeProperty(Pid::HEAD_GROUP, int(drumset->noteHead(pitch))); int line = drumset->line(pitch); note->setLine(line); int off = st->stepOffset(); qreal ld = st->lineDistance().val(); note->rypos() = (line + off * 2.0) * spatium * .5 * ld; } } } //--------------------------------------------------------- // extendedStemLenWithTwoNotesTremolo // Goal: To extend stem of one of the chords to make the tremolo less steep // Returns a modified pair of stem lengths of two chords //--------------------------------------------------------- std::pair extendedStemLenWithTwoNoteTremolo(Tremolo* tremolo, qreal stemLen1, qreal stemLen2) { const qreal spatium = tremolo->score()->spatium(); Chord* c1 = tremolo->chord1(); Chord* c2 = tremolo->chord2(); Stem* s1 = c1->stem(); Stem* s2 = c2->stem(); const qreal sgn1 = c1->up() ? -1.0 : 1.0; const qreal sgn2 = c2->up() ? -1.0 : 1.0; const qreal stemTipDistance = (s1 && s2) ? (s2->pagePos().y() + stemLen2) - (s1->pagePos().y() + stemLen1) : (c2->stemPos().y() + stemLen2) - (c1->stemPos().y() + stemLen1); // same staff & same direction: extend one of the stems if (c1->staffMove() == c2->staffMove() && c1->up() == c2->up()) { const bool stem1Higher = stemTipDistance > 0.0; if (std::abs(stemTipDistance) > 1.0 * spatium) { if ((c1->up() && !stem1Higher) || (!c1->up() && stem1Higher)) { return { stemLen1 + sgn1 * (std::abs(stemTipDistance) - 1.0 * spatium), stemLen2 }; } else { /* if ((c1->up() && stem1Higher) || (!c1->up() && !stem1Higher)) */ return { stemLen1, stemLen2 + sgn2 * (std::abs(stemTipDistance) - 1.0 * spatium) }; } } } // TODO: cross-staff two-note tremolo. Currently doesn't generate the right result in some cases. #if 0 // cross-staff & beam between staves: extend both stems by the same length else if (tremolo->crossStaffBeamBetween()) { const qreal sw = tremolo->score()->styleS(Sid::tremoloStrokeWidth).val(); const qreal td = tremolo->score()->styleS(Sid::tremoloDistance).val(); const qreal tremoloMinHeight = ((tremolo->lines() - 1) * td + sw) * spatium; const qreal dy = c1->up() ? tremoloMinHeight - stemTipDistance : tremoloMinHeight + stemTipDistance; const bool tooShort = dy > 1.0 * spatium; const bool tooLong = dy < -1.0 * spatium; const qreal idealDistance = 1.0 * spatium - tremoloMinHeight; if (tooShort) { return { stemLen1 + sgn1 * (std::abs(stemTipDistance) - idealDistance) / 2.0, stemLen2 + sgn2 * (std::abs(stemTipDistance) - idealDistance) / 2.0 }; } else if (tooLong) { return { stemLen1 - sgn1 * (std::abs(stemTipDistance) + idealDistance) / 2.0, stemLen2 - sgn2 * (std::abs(stemTipDistance) + idealDistance) / 2.0 }; } } #endif return { stemLen1, stemLen2 }; } //--------------------------------------------------------- // getNextMeasure //--------------------------------------------------------- void Score::getNextMeasure(LayoutContext& lc) { lc.prevMeasure = lc.curMeasure; lc.curMeasure = lc.nextMeasure; if (!lc.curMeasure) { lc.nextMeasure = _showVBox ? first() : firstMeasure(); } else { lc.nextMeasure = _showVBox ? lc.curMeasure->next() : lc.curMeasure->nextMeasure(); } if (!lc.curMeasure) { return; } int mno = lc.adjustMeasureNo(lc.curMeasure); if (lc.curMeasure->isMeasure()) { if (score()->styleB(Sid::createMultiMeasureRests)) { Measure* m = toMeasure(lc.curMeasure); Measure* nm = m; Measure* lm = nm; int n = 0; Fraction len; while (validMMRestMeasure(nm)) { MeasureBase* mb = _showVBox ? nm->next() : nm->nextMeasure(); if (breakMultiMeasureRest(nm) && n) { break; } if (nm != m) { lc.adjustMeasureNo(nm); } ++n; len += nm->ticks(); lm = nm; if (!(mb && mb->isMeasure())) { break; } nm = toMeasure(mb); } if (n >= styleI(Sid::minEmptyMeasures)) { createMMRest(m, lm, len); lc.curMeasure = m->mmRest(); lc.nextMeasure = _showVBox ? lm->next() : lm->nextMeasure(); } else { if (m->mmRest()) { undo(new ChangeMMRest(m, 0)); } m->setMMRestCount(0); lc.measureNo = mno; } } else if (toMeasure(lc.curMeasure)->isMMRest()) { qDebug("mmrest: no %d += %d", lc.measureNo, toMeasure(lc.curMeasure)->mmRestCount()); lc.measureNo += toMeasure(lc.curMeasure)->mmRestCount() - 1; } } if (!lc.curMeasure->isMeasure()) { lc.curMeasure->setTick(lc.tick); return; } //----------------------------------------- // process one measure //----------------------------------------- Measure* measure = toMeasure(lc.curMeasure); measure->moveTicks(lc.tick - measure->tick()); if (lineMode() && (measure->tick() < lc.startTick || measure->tick() > lc.endTick)) { // needed to reset segment widths if they can change after measure width is computed //for (Segment& s : measure->segments()) // s.createShapes(); lc.tick += measure->ticks(); return; } measure->connectTremolo(); // // calculate accidentals and note lines, // create stem and set stem direction // for (int staffIdx = 0; staffIdx < score()->nstaves(); ++staffIdx) { const Staff* staff = Score::staff(staffIdx); const Drumset* drumset = staff->part()->instrument()->useDrumset() ? staff->part()->instrument()->drumset() : 0; AccidentalState as; // list of already set accidentals for this measure as.init(staff->keySigEvent(measure->tick()), staff->clef(measure->tick())); for (Segment& segment : measure->segments()) { // TODO? maybe we do need to process it here to make it possible to enable later //if (!segment.enabled()) // continue; if (segment.isKeySigType()) { KeySig* ks = toKeySig(segment.element(staffIdx * VOICES)); if (!ks) { continue; } Fraction tick = segment.tick(); as.init(staff->keySigEvent(tick), staff->clef(tick)); ks->layout(); } else if (segment.isChordRestType()) { const StaffType* st = staff->staffTypeForElement(&segment); int track = staffIdx * VOICES; int endTrack = track + VOICES; for (int t = track; t < endTrack; ++t) { ChordRest* cr = segment.cr(t); if (!cr) { continue; } qreal m = staff->staffMag(&segment); if (cr->small()) { m *= score()->styleD(Sid::smallNoteMag); } if (cr->isChord()) { Chord* chord = toChord(cr); chord->cmdUpdateNotes(&as); for (Chord* c : chord->graceNotes()) { c->setMag(m * score()->styleD(Sid::graceNoteMag)); c->computeUp(); if (c->stemDirection() != Direction::AUTO) { c->setUp(c->stemDirection() == Direction::UP); } else { c->setUp(!(t % 2)); } if (drumset) { layoutDrumsetChord(c, drumset, st, spatium()); } c->layoutStem1(); } if (drumset) { layoutDrumsetChord(chord, drumset, st, spatium()); } chord->computeUp(); chord->layoutStem1(); // create stems needed to calculate spacing // stem direction can change later during beam processing // if there is a two-note tremolo attached, and it is too steep, // extend stem of one of the chords (if not cross-staff) // or extend both stems (if cross-staff) // this should be done after the stem lengths of two notes are both calculated if (chord->tremolo() && chord == chord->tremolo()->chord2()) { Stem* stem1 = chord->tremolo()->chord1()->stem(); Stem* stem2 = chord->tremolo()->chord2()->stem(); if (stem1 && stem2) { std::pair extendedLen = extendedStemLenWithTwoNoteTremolo( chord->tremolo(), stem1->p2().y(), stem2->p2().y()); stem1->setLen(extendedLen.first); stem2->setLen(extendedLen.second); } } } cr->setMag(m); } } else if (segment.isClefType()) { Element* e = segment.element(staffIdx * VOICES); if (e) { toClef(e)->setSmall(true); e->layout(); } } else if (segment.isType(SegmentType::TimeSig | SegmentType::Ambitus | SegmentType::HeaderClef)) { Element* e = segment.element(staffIdx * VOICES); if (e) { e->layout(); } } } } createBeams(lc, measure); for (int staffIdx = 0; staffIdx < score()->nstaves(); ++staffIdx) { for (Segment& segment : measure->segments()) { if (segment.isChordRestType()) { layoutChords1(&segment, staffIdx); for (int voice = 0; voice < VOICES; ++voice) { ChordRest* cr = segment.cr(staffIdx * VOICES + voice); if (cr) { for (Lyrics* l : cr->lyrics()) { if (l) { l->layout(); } } } } } } } measure->computeTicks(); for (Segment& segment : measure->segments()) { if (segment.isBreathType()) { for (Element* e : segment.elist()) { if (e && e->isBreath()) { e->layout(); } } } else if (segment.isChordRestType()) { for (Element* e : segment.annotations()) { if (e->isSymbol()) { e->layout(); } } } } rebuildTempoAndTimeSigMaps(measure); Segment* seg = measure->findSegmentR(SegmentType::StartRepeatBarLine, Fraction(0,1)); if (measure->repeatStart()) { if (!seg) { seg = measure->getSegmentR(SegmentType::StartRepeatBarLine, Fraction(0,1)); } measure->barLinesSetSpan(seg); // this also creates necessary barlines for (int staffIdx = 0; staffIdx < nstaves(); ++staffIdx) { BarLine* b = toBarLine(seg->element(staffIdx * VOICES)); if (b) { b->setBarLineType(BarLineType::START_REPEAT); b->layout(); } } } else if (seg) { score()->undoRemoveElement(seg); } for (Segment& s : measure->segments()) { // TODO? maybe we do need to process it here to make it possible to enable later //if (!s.enabled()) // continue; // DEBUG: relayout grace notes as beaming/flags may have changed if (s.isChordRestType()) { for (Element* e : s.elist()) { if (e && e->isChord()) { Chord* chord = toChord(e); chord->layout(); // if (chord->tremolo()) // debug // chord->tremolo()->layout(); } } } else if (s.isEndBarLineType()) { continue; } s.createShapes(); } lc.tick += measure->ticks(); } //--------------------------------------------------------- // isTopBeam // returns true for the first CR of a beam that is not cross-staff //--------------------------------------------------------- bool isTopBeam(ChordRest* cr) { Beam* b = cr->beam(); if (b && b->elements().front() == cr) { // beam already considered cross? if (b->cross()) { return false; } // for beams not already considered cross, // consider them so here if any elements were moved up for (ChordRest* cr1 : b->elements()) { // some element moved up? if (cr1->staffMove() < 0) { return false; } } // not cross return true; } // no beam or not first element return false; } //--------------------------------------------------------- // notTopBeam // returns true for the first CR of a beam that is cross-staff //--------------------------------------------------------- bool notTopBeam(ChordRest* cr) { Beam* b = cr->beam(); if (b && b->elements().front() == cr) { // beam already considered cross? if (b->cross()) { return true; } // for beams not already considered cross, // consider them so here if any elements were moved up for (ChordRest* cr1 : b->elements()) { // some element moved up? if (cr1->staffMove() < 0) { return true; } } // not cross return false; } // no beam or not first element return false; } //--------------------------------------------------------- // isTopTuplet // returns true for the first CR of a tuplet that is not cross-staff //--------------------------------------------------------- bool isTopTuplet(ChordRest* cr) { Tuplet* t = cr->tuplet(); if (t && t->elements().front() == cr) { // find top level tuplet while (t->tuplet()) { t = t->tuplet(); } // consider tuplet cross if anything moved within it if (t->cross()) { return false; } else { return true; } } // no tuplet or not first element return false; } //--------------------------------------------------------- // notTopTuplet // returns true for the first CR of a tuplet that is cross-staff //--------------------------------------------------------- bool notTopTuplet(ChordRest* cr) { Tuplet* t = cr->tuplet(); if (t && t->elements().front() == cr) { // find top level tuplet while (t->tuplet()) { t = t->tuplet(); } // consider tuplet cross if anything moved within it if (t->cross()) { return true; } else { return false; } } // no tuplet or not first element return false; } //--------------------------------------------------------- // findLyricsMaxY //--------------------------------------------------------- static qreal findLyricsMaxY(Segment& s, int staffIdx) { qreal yMax = 0.0; if (!s.isChordRestType()) { return yMax; } qreal lyricsMinTopDistance = s.score()->styleP(Sid::lyricsMinTopDistance); for (int voice = 0; voice < VOICES; ++voice) { ChordRest* cr = s.cr(staffIdx * VOICES + voice); if (cr && !cr->lyrics().empty()) { SkylineLine sk(true); for (Lyrics* l : cr->lyrics()) { if (l->autoplace() && l->placeBelow()) { qreal yOff = l->offset().y(); QPointF offset = l->pos() + cr->pos() + s.pos() + s.measure()->pos(); QRectF r = l->bbox().translated(offset); r.translate(0.0, -yOff); sk.add(r.x(), r.top(), r.width()); } } SysStaff* ss = s.measure()->system()->staff(staffIdx); for (Lyrics* l : cr->lyrics()) { if (l->autoplace() && l->placeBelow()) { qreal y = ss->skyline().south().minDistance(sk); if (y > -lyricsMinTopDistance) { yMax = qMax(yMax, y + lyricsMinTopDistance); } } } } } return yMax; } //--------------------------------------------------------- // findLyricsMinY //--------------------------------------------------------- static qreal findLyricsMinY(Segment& s, int staffIdx) { qreal yMin = 0.0; if (!s.isChordRestType()) { return yMin; } qreal lyricsMinTopDistance = s.score()->styleP(Sid::lyricsMinTopDistance); for (int voice = 0; voice < VOICES; ++voice) { ChordRest* cr = s.cr(staffIdx * VOICES + voice); if (cr && !cr->lyrics().empty()) { SkylineLine sk(false); for (Lyrics* l : cr->lyrics()) { if (l->autoplace() && l->placeAbove()) { qreal yOff = l->offset().y(); QRectF r = l->bbox().translated(l->pos() + cr->pos() + s.pos() + s.measure()->pos()); r.translate(0.0, -yOff); sk.add(r.x(), r.bottom(), r.width()); } } SysStaff* ss = s.measure()->system()->staff(staffIdx); for (Lyrics* l : cr->lyrics()) { if (l->autoplace() && l->placeAbove()) { qreal y = sk.minDistance(ss->skyline().north()); if (y > -lyricsMinTopDistance) { yMin = qMin(yMin, -y - lyricsMinTopDistance); } } } } } return yMin; } static qreal findLyricsMaxY(Measure* m, int staffIdx) { qreal yMax = 0.0; for (Segment& s : m->segments()) { yMax = qMax(yMax, findLyricsMaxY(s, staffIdx)); } return yMax; } static qreal findLyricsMinY(Measure* m, int staffIdx) { qreal yMin = 0.0; for (Segment& s : m->segments()) { yMin = qMin(yMin, findLyricsMinY(s, staffIdx)); } return yMin; } //--------------------------------------------------------- // applyLyricsMax //--------------------------------------------------------- static void applyLyricsMax(Segment& s, int staffIdx, qreal yMax) { if (!s.isChordRestType()) { return; } Skyline& sk = s.measure()->system()->staff(staffIdx)->skyline(); for (int voice = 0; voice < VOICES; ++voice) { ChordRest* cr = s.cr(staffIdx * VOICES + voice); if (cr && !cr->lyrics().empty()) { qreal lyricsMinBottomDistance = s.score()->styleP(Sid::lyricsMinBottomDistance); for (Lyrics* l : cr->lyrics()) { if (l->autoplace() && l->placeBelow()) { l->rypos() += yMax - l->propertyDefault(Pid::OFFSET).toPointF().y(); if (l->addToSkyline()) { QPointF offset = l->pos() + cr->pos() + s.pos() + s.measure()->pos(); sk.add(l->bbox().translated(offset).adjusted(0.0, 0.0, 0.0, lyricsMinBottomDistance)); } } } } } } static void applyLyricsMax(Measure* m, int staffIdx, qreal yMax) { for (Segment& s : m->segments()) { applyLyricsMax(s, staffIdx, yMax); } } //--------------------------------------------------------- // applyLyricsMin //--------------------------------------------------------- static void applyLyricsMin(ChordRest* cr, int staffIdx, qreal yMin) { Skyline& sk = cr->measure()->system()->staff(staffIdx)->skyline(); for (Lyrics* l : cr->lyrics()) { if (l->autoplace() && l->placeAbove()) { l->rypos() += yMin - l->propertyDefault(Pid::OFFSET).toPointF().y(); if (l->addToSkyline()) { QPointF offset = l->pos() + cr->pos() + cr->segment()->pos() + cr->segment()->measure()->pos(); sk.add(l->bbox().translated(offset)); } } } } static void applyLyricsMin(Measure* m, int staffIdx, qreal yMin) { for (Segment& s : m->segments()) { if (s.isChordRestType()) { for (int voice = 0; voice < VOICES; ++voice) { ChordRest* cr = s.cr(staffIdx * VOICES + voice); if (cr) { applyLyricsMin(cr, staffIdx, yMin); } } } } } //--------------------------------------------------------- // restoreBeams //--------------------------------------------------------- static void restoreBeams(Measure* m) { for (Segment* s = m->first(SegmentType::ChordRest); s; s = s->next(SegmentType::ChordRest)) { for (Element* e : s->elist()) { if (e && e->isChordRest()) { ChordRest* cr = toChordRest(e); if (isTopBeam(cr)) { Beam* b = cr->beam(); b->layout(); b->addSkyline(m->system()->staff(b->staffIdx())->skyline()); } } } } } //--------------------------------------------------------- // layoutLyrics // // vertical align lyrics // //--------------------------------------------------------- void Score::layoutLyrics(System* system) { std::vector visibleStaves; for (int staffIdx = system->firstVisibleStaff(); staffIdx < nstaves(); staffIdx = system->nextVisibleStaff(staffIdx)) { visibleStaves.push_back(staffIdx); } //int nAbove[nstaves()]; std::vector VnAbove(nstaves()); for (int staffIdx : visibleStaves) { VnAbove[staffIdx] = 0; for (MeasureBase* mb : system->measures()) { if (!mb->isMeasure()) { continue; } Measure* m = toMeasure(mb); for (Segment& s : m->segments()) { if (s.isChordRestType()) { for (int voice = 0; voice < VOICES; ++voice) { ChordRest* cr = s.cr(staffIdx * VOICES + voice); if (cr) { int nA = 0; for (Lyrics* l : cr->lyrics()) { // user adjusted offset can possibly change placement if (l->offsetChanged() != OffsetChange::NONE) { Placement p = l->placement(); l->rebaseOffset(); if (l->placement() != p) { l->undoResetProperty(Pid::AUTOPLACE); //l->undoResetProperty(Pid::OFFSET); //l->layout(); } } l->setOffsetChanged(false); if (l->placeAbove()) { ++nA; } } VnAbove[staffIdx] = qMax(VnAbove[staffIdx], nA); } } } } } } for (int staffIdx : visibleStaves) { for (MeasureBase* mb : system->measures()) { if (!mb->isMeasure()) { continue; } Measure* m = toMeasure(mb); for (Segment& s : m->segments()) { if (s.isChordRestType()) { for (int voice = 0; voice < VOICES; ++voice) { ChordRest* cr = s.cr(staffIdx * VOICES + voice); if (cr) { for (Lyrics* l : cr->lyrics()) { l->layout2(VnAbove[staffIdx]); } } } } } } } VerticalAlignRange ar = VerticalAlignRange(styleI(Sid::autoplaceVerticalAlignRange)); switch (ar) { case VerticalAlignRange::MEASURE: for (MeasureBase* mb : system->measures()) { if (!mb->isMeasure()) { continue; } Measure* m = toMeasure(mb); for (int staffIdx : visibleStaves) { qreal yMax = findLyricsMaxY(m, staffIdx); applyLyricsMax(m, staffIdx, yMax); } } break; case VerticalAlignRange::SYSTEM: for (int staffIdx : visibleStaves) { qreal yMax = 0.0; qreal yMin = 0.0; for (MeasureBase* mb : system->measures()) { if (!mb->isMeasure()) { continue; } yMax = qMax(yMax, findLyricsMaxY(toMeasure(mb), staffIdx)); yMin = qMin(yMin, findLyricsMinY(toMeasure(mb), staffIdx)); } for (MeasureBase* mb : system->measures()) { if (!mb->isMeasure()) { continue; } applyLyricsMax(toMeasure(mb), staffIdx, yMax); applyLyricsMin(toMeasure(mb), staffIdx, yMin); } } break; case VerticalAlignRange::SEGMENT: for (MeasureBase* mb : system->measures()) { if (!mb->isMeasure()) { continue; } Measure* m = toMeasure(mb); for (int staffIdx : visibleStaves) { for (Segment& s : m->segments()) { qreal yMax = findLyricsMaxY(s, staffIdx); applyLyricsMax(s, staffIdx, yMax); } } } break; } } //--------------------------------------------------------- // layoutTies //--------------------------------------------------------- void layoutTies(Chord* ch, System* system, const Fraction& stick) { SysStaff* staff = system->staff(ch->staffIdx()); if (!staff->show()) { return; } for (Note* note : ch->notes()) { Tie* t = note->tieFor(); if (t) { TieSegment* ts = t->layoutFor(system); if (ts && ts->addToSkyline()) { staff->skyline().add(ts->shape().translated(ts->pos())); } } t = note->tieBack(); if (t) { if (t->startNote()->tick() < stick) { TieSegment* ts = t->layoutBack(system); if (ts && ts->addToSkyline()) { staff->skyline().add(ts->shape().translated(ts->pos())); } } } } } //--------------------------------------------------------- // layoutHarmonies //--------------------------------------------------------- void layoutHarmonies(const std::vector& sl) { for (const Segment* s : sl) { for (Element* e : s->annotations()) { if (e->isHarmony()) { Harmony* h = toHarmony(e); // For chord symbols that coincide with a chord or rest, // a partial layout can also happen (if needed) during ChordRest layout // in order to calculate a bbox and allocate its shape to the ChordRest. // But that layout (if it happens at all) does not do autoplace, // so we need the full layout here. h->layout(); h->autoplaceSegmentElement(); } } } } //--------------------------------------------------------- // processLines //--------------------------------------------------------- static void processLines(System* system, std::vector lines, bool align) { std::vector segments; for (Spanner* sp : lines) { SpannerSegment* ss = sp->layoutSystem(system); // create/layout spanner segment for this system if (ss->autoplace()) { segments.push_back(ss); } } if (align && segments.size() > 1) { const int nstaves = system->staves()->size(); constexpr qreal minY = -1000000.0; const qreal defaultY = segments[0]->rypos(); std::vector y(nstaves, minY); for (SpannerSegment* ss : segments) { if (ss->visible()) { qreal& staffY = y[ss->staffIdx()]; staffY = qMax(staffY, ss->rypos()); } } for (SpannerSegment* ss : segments) { const qreal staffY = y[ss->staffIdx()]; if (staffY > minY) { ss->rypos() = staffY; } else { ss->rypos() = defaultY; } } } // // add shapes to skyline // for (SpannerSegment* ss : segments) { if (ss->addToSkyline()) { system->staff(ss->staffIdx())->skyline().add(ss->shape().translated(ss->pos())); } } } //--------------------------------------------------------- // collectSystem //--------------------------------------------------------- System* Score::collectSystem(LayoutContext& lc) { if (!lc.curMeasure) { return 0; } Measure* measure = _systems.empty() ? 0 : _systems.back()->lastMeasure(); if (measure) { lc.firstSystem = measure->sectionBreak() && _layoutMode != LayoutMode::FLOAT; lc.startWithLongNames = lc.firstSystem && measure->sectionBreakElement()->startWithLongNames(); } System* system = getNextSystem(lc); Fraction lcmTick = lc.curMeasure ? lc.curMeasure->tick() : Fraction(0,1); system->setInstrumentNames(lc.startWithLongNames, lcmTick); qreal minWidth = 0; qreal layoutSystemMinWidth = 0; bool firstMeasure = true; bool createHeader = false; qreal systemWidth = styleD(Sid::pagePrintableWidth) * DPI; system->setWidth(systemWidth); // save state of measure qreal curWidth = lc.curMeasure->width(); bool curHeader = lc.curMeasure->header(); bool curTrailer = lc.curMeasure->trailer(); while (lc.curMeasure) { // collect measure for system System* oldSystem = lc.curMeasure->system(); system->appendMeasure(lc.curMeasure); qreal ww = 0; // width of current measure if (lc.curMeasure->isMeasure()) { Measure* m = toMeasure(lc.curMeasure); if (firstMeasure) { layoutSystemMinWidth = minWidth; system->layoutSystem(minWidth); minWidth += system->leftMargin(); if (m->repeatStart()) { Segment* s = m->findSegmentR(SegmentType::StartRepeatBarLine, Fraction(0,1)); if (!s->enabled()) { s->setEnabled(true); } } m->addSystemHeader(lc.firstSystem); firstMeasure = false; createHeader = false; } else { if (createHeader) { m->addSystemHeader(false); createHeader = false; } else if (m->header()) { m->removeSystemHeader(); } } m->createEndBarLines(true); m->addSystemTrailer(m->nextMeasure()); m->computeMinWidth(); ww = m->width(); } else if (lc.curMeasure->isHBox()) { lc.curMeasure->computeMinWidth(); ww = lc.curMeasure->width(); createHeader = toHBox(lc.curMeasure)->createSystemHeader(); } else { // vbox: getNextMeasure(lc); system->layout2(); // compute staff distances return system; } // check if lc.curMeasure fits, remove if not // collect at least one measure and the break bool doBreak = (system->measures().size() > 1) && ((minWidth + ww) > systemWidth); if (doBreak) { if (lc.prevMeasure->noBreak() && system->measures().size() > 2) { // remove last two measures // TODO: check more measures for noBreak() system->removeLastMeasure(); system->removeLastMeasure(); lc.curMeasure->setSystem(oldSystem); lc.prevMeasure->setSystem(oldSystem); lc.nextMeasure = lc.curMeasure; lc.curMeasure = lc.prevMeasure; lc.prevMeasure = lc.curMeasure->prevMeasure(); break; } else if (!lc.prevMeasure->noBreak()) { // remove last measure system->removeLastMeasure(); lc.curMeasure->setSystem(oldSystem); break; } } if (lc.prevMeasure && lc.prevMeasure->isMeasure() && lc.prevMeasure->system() == system) { // // now we know that the previous measure is not the last // measure in the system and we finally can create the end barline for it Measure* m = toMeasure(lc.prevMeasure); if (m->trailer()) { qreal ow = m->width(); m->removeSystemTrailer(); minWidth += m->width() - ow; } // if the prev measure is an end repeat and the cur measure // is an repeat, the createEndBarLines() created an start-end repeat barline // and we can remove the start repeat barline of the current barline if (lc.curMeasure->isMeasure()) { Measure* m1 = toMeasure(lc.curMeasure); if (m1->repeatStart()) { Segment* s = m1->findSegmentR(SegmentType::StartRepeatBarLine, Fraction(0,1)); if (!s->enabled()) { s->setEnabled(true); m1->computeMinWidth(); ww = m1->width(); } } } minWidth += m->createEndBarLines(false); // create final barLine } MeasureBase* mb = lc.curMeasure; bool lineBreak = false; switch (_layoutMode) { case LayoutMode::PAGE: case LayoutMode::SYSTEM: lineBreak = mb->pageBreak() || mb->lineBreak() || mb->sectionBreak(); break; case LayoutMode::FLOAT: case LayoutMode::LINE: lineBreak = false; break; } // preserve state of next measure (which is about to become current measure) if (lc.nextMeasure) { MeasureBase* nmb = lc.nextMeasure; if (nmb->isMeasure() && styleB(Sid::createMultiMeasureRests)) { Measure* nm = toMeasure(nmb); if (nm->hasMMRest()) { nmb = nm->mmRest(); } } curWidth = nmb->width(); curHeader = nmb->header(); curTrailer = nmb->trailer(); } getNextMeasure(lc); minWidth += ww; // ElementType nt = lc.curMeasure ? lc.curMeasure->type() : ElementType::INVALID; mb = lc.curMeasure; bool tooWide = false; // minWidth + minMeasureWidth > systemWidth; // TODO: noBreak if (lineBreak || !mb || mb->isVBox() || mb->isTBox() || mb->isFBox() || tooWide) { break; } } if (lc.endTick < lc.prevMeasure->tick()) { // we've processed the entire range // but we need to continue layout until we reach a system whose last measure is the same as previous layout if (lc.prevMeasure == lc.systemOldMeasure) { // this system ends in the same place as the previous layout // ok to stop if (lc.curMeasure && lc.curMeasure->isMeasure()) { // we may have previously processed first measure of next system // so now we must restore it to its original state Measure* m = toMeasure(lc.curMeasure); if (m->repeatStart()) { Segment* s = m->findSegmentR(SegmentType::StartRepeatBarLine, Fraction(0,1)); if (!s->enabled()) { s->setEnabled(true); } } bool firstSystem = lc.prevMeasure->sectionBreak() && _layoutMode != LayoutMode::FLOAT; if (curHeader) { m->addSystemHeader(firstSystem); } else { m->removeSystemHeader(); } if (curTrailer) { m->addSystemTrailer(m->nextMeasure()); } else { m->removeSystemTrailer(); } m->computeMinWidth(); m->stretchMeasure(curWidth); restoreBeams(m); } lc.rangeDone = true; } } // // now we have a complete set of measures for this system // // prevMeasure is the last measure in the system if (lc.prevMeasure && lc.prevMeasure->isMeasure()) { breakCrossMeasureBeams(toMeasure(lc.prevMeasure)); qreal w = toMeasure(lc.prevMeasure)->createEndBarLines(true); minWidth += w; } hideEmptyStaves(system, lc.firstSystem); bool allShown = true; for (const SysStaff* ss : *system->staves()) { if (!ss->show()) { allShown = false; break; } } if (!allShown) { // Relayout system decorations to reuse space properly for // hidden staves' instrument names or other hidden elements. minWidth -= system->leftMargin(); system->layoutSystem(layoutSystemMinWidth); minWidth += system->leftMargin(); } //------------------------------------------------------- // add system trailer if needed // (cautionary time/key signatures etc) //------------------------------------------------------- Measure* lm = system->lastMeasure(); if (lm) { Measure* nm = lm->nextMeasure(); if (nm) { qreal w = lm->width(); lm->addSystemTrailer(nm); if (lm->trailer()) { lm->computeMinWidth(); } minWidth += lm->width() - w; } } // // stretch incomplete row // qreal rest; if (MScore::noHorizontalStretch) { rest = 0; } else { qreal mw = system->leftMargin(); // DEBUG qreal totalWeight = 0.0; for (MeasureBase* mb : system->measures()) { if (mb->isHBox()) { mw += mb->width(); } else if (mb->isMeasure()) { Measure* m = toMeasure(mb); mw += m->width(); // measures are stretched already with basicStretch() int weight = m->layoutWeight(); totalWeight += weight * m->basicStretch(); } } #ifndef NDEBUG if (!qFuzzyCompare(mw, minWidth)) { qDebug("==layoutSystem %6d old %.1f new %.1f", system->measures().front()->tick().ticks(), minWidth, mw); } #endif rest = systemWidth - minWidth; // // don’t stretch last system row, if accumulated minWidth is <= lastSystemFillLimit // if (lc.curMeasure == 0 && ((minWidth / systemWidth) <= styleD(Sid::lastSystemFillLimit))) { if (minWidth > rest) { rest = rest * .5; } else { rest = minWidth; } } rest /= totalWeight; } QPointF pos; firstMeasure = true; bool createBrackets = false; for (MeasureBase* mb : system->measures()) { qreal ww = mb->width(); if (mb->isMeasure()) { if (firstMeasure) { pos.rx() += system->leftMargin(); firstMeasure = false; } mb->setPos(pos); Measure* m = toMeasure(mb); qreal stretch = m->basicStretch(); int weight = m->layoutWeight(); ww += rest * weight * stretch; m->stretchMeasure(ww); m->layoutStaffLines(); if (createBrackets) { system->addBrackets(toMeasure(mb)); createBrackets = false; } } else if (mb->isHBox()) { mb->setPos(pos + QPointF(toHBox(mb)->topGap(), 0.0)); mb->layout(); createBrackets = toHBox(mb)->createSystemHeader(); } else if (mb->isVBox()) { mb->setPos(pos); } pos.rx() += ww; } system->setWidth(pos.x()); layoutSystemElements(system, lc); system->layout2(); // compute staff distances lm = system->lastMeasure(); if (lm) { lc.firstSystem = lm->sectionBreak() && _layoutMode != LayoutMode::FLOAT; lc.startWithLongNames = lc.firstSystem && lm->sectionBreakElement()->startWithLongNames(); } return system; } //--------------------------------------------------------- // layoutSystemElements //--------------------------------------------------------- void Score::layoutSystemElements(System* system, LayoutContext& lc) { //------------------------------------------------------------- // create cr segment list to speed up computations //------------------------------------------------------------- std::vector sl; for (MeasureBase* mb : system->measures()) { if (!mb->isMeasure()) { continue; } Measure* m = toMeasure(mb); m->layoutMeasureNumber(); // in continuous view, entire score is one system // but we only need to process the range if (lineMode() && (m->tick() < lc.startTick || m->tick() > lc.endTick)) { continue; } for (Segment* s = m->first(); s; s = s->next()) { if (s->isChordRestType() || !s->annotations().empty()) { sl.push_back(s); } } } //------------------------------------------------------------- // layout beams // Needs to be done before creating skylines as stem lengths // may change. //------------------------------------------------------------- for (Segment* s : sl) { for (Element* e : s->elist()) { if (!e || !e->isChordRest() || !score()->staff(e->staffIdx())->show()) { // the beam and its system may still be referenced when selecting all, // even if the staff is invisible. The old system is invalid and does cause problems in #284012 if (e && e->isChordRest() && !score()->staff(e->staffIdx())->show() && toChordRest(e)->beam()) { toChordRest(e)->beam()->setParent(nullptr); } continue; } ChordRest* cr = toChordRest(e); // layout beam if (isTopBeam(cr)) { Beam* b = cr->beam(); b->layout(); } } } //------------------------------------------------------------- // create skylines //------------------------------------------------------------- for (int staffIdx = 0; staffIdx < nstaves(); ++staffIdx) { SysStaff* ss = system->staff(staffIdx); Skyline& skyline = ss->skyline(); skyline.clear(); for (MeasureBase* mb : system->measures()) { if (!mb->isMeasure()) { continue; } Measure* m = toMeasure(mb); MeasureNumber* mno = m->noText(staffIdx); // no need to build skyline outside of range in continuous view if (lineMode() && (m->tick() < lc.startTick || m->tick() > lc.endTick)) { continue; } if (mno && mno->addToSkyline()) { ss->skyline().add(mno->bbox().translated(m->pos() + mno->pos())); } if (m->staffLines(staffIdx)->addToSkyline()) { ss->skyline().add(m->staffLines(staffIdx)->bbox().translated(m->pos())); } for (Segment& s : m->segments()) { if (!s.enabled() || s.isTimeSigType()) { // hack: ignore time signatures continue; } QPointF p(s.pos() + m->pos()); if (s.segmentType() & (SegmentType::BarLine | SegmentType::EndBarLine | SegmentType::StartRepeatBarLine | SegmentType::BeginBarLine)) { BarLine* bl = toBarLine(s.element(staffIdx * VOICES)); if (bl && bl->addToSkyline()) { QRectF r = bl->layoutRect(); skyline.add(r.translated(bl->pos() + p)); } } else { int strack = staffIdx * VOICES; int etrack = strack + VOICES; for (Element* e : s.elist()) { if (!e) { continue; } int effectiveTrack = e->vStaffIdx() * VOICES + e->voice(); if (effectiveTrack < strack || effectiveTrack >= etrack) { continue; } // clear layout for chord-based fingerings // do this before adding chord to skyline if (e->isChord()) { Chord* c = toChord(e); std::list notes; for (auto gc : c->graceNotes()) { for (auto n : gc->notes()) { notes.push_back(n); } } for (auto n : c->notes()) { notes.push_back(n); } for (Note* note : notes) { for (Element* en : note->el()) { if (en->isFingering()) { Fingering* f = toFingering(en); if (f->layoutType() == ElementType::CHORD) { f->setPos(QPointF()); f->setbbox(QRectF()); } } } } } // add element to skyline if (e->addToSkyline()) { skyline.add(e->shape().translated(e->pos() + p)); } // add tremolo to skyline if (e->isChord() && toChord(e)->tremolo()) { Tremolo* t = toChord(e)->tremolo(); Chord* c1 = t->chord1(); Chord* c2 = t->chord2(); if (!t->twoNotes() || (c1 && !c1->staffMove() && c2 && !c2->staffMove())) { if (t->chord() == e && t->addToSkyline()) { skyline.add(t->shape().translated(t->pos() + e->pos() + p)); } } } } } } } } //------------------------------------------------------------- // layout fingerings, add beams to skylines //------------------------------------------------------------- for (Segment* s : sl) { std::set recreateShapes; for (Element* e : s->elist()) { if (!e || !e->isChordRest() || !score()->staff(e->staffIdx())->show()) { continue; } ChordRest* cr = toChordRest(e); // add beam to skyline if (isTopBeam(cr)) { Beam* b = cr->beam(); b->addSkyline(system->staff(b->staffIdx())->skyline()); } // layout chord-based fingerings if (e->isChord()) { Chord* c = toChord(e); std::list notes; for (auto gc : c->graceNotes()) { for (auto n : gc->notes()) { notes.push_back(n); } } for (auto n : c->notes()) { notes.push_back(n); } std::list fingerings; for (Note* note : notes) { for (Element* el : note->el()) { if (el->isFingering()) { Fingering* f = toFingering(el); if (f->layoutType() == ElementType::CHORD) { if (f->placeAbove()) { fingerings.push_back(f); } else { fingerings.push_front(f); } } } } } for (Fingering* f : fingerings) { f->layout(); if (f->addToSkyline()) { Note* n = f->note(); QRectF r = f->bbox().translated( f->pos() + n->pos() + n->chord()->pos() + s->pos() + s->measure()->pos()); system->staff(f->note()->chord()->vStaffIdx())->skyline().add(r); } recreateShapes.insert(f->staffIdx()); } } } for (auto staffIdx : recreateShapes) { s->createShape(staffIdx); } } //------------------------------------------------------------- // layout articulations //------------------------------------------------------------- for (Segment* s : sl) { for (Element* e : s->elist()) { if (!e || !e->isChordRest() || !score()->staff(e->staffIdx())->show()) { continue; } ChordRest* cr = toChordRest(e); // articulations if (cr->isChord()) { Chord* c = toChord(cr); c->layoutArticulations(); c->layoutArticulations2(); } } } //------------------------------------------------------------- // layout tuplets //------------------------------------------------------------- for (Segment* s : sl) { for (Element* e : s->elist()) { if (!e || !e->isChordRest() || !score()->staff(e->staffIdx())->show()) { continue; } ChordRest* cr = toChordRest(e); if (!isTopTuplet(cr)) { continue; } DurationElement* de = cr; while (de->tuplet() && de->tuplet()->elements().front() == de) { Tuplet* t = de->tuplet(); t->layout(); de = t; } } } //------------------------------------------------------------- // Drumline sticking //------------------------------------------------------------- for (const Segment* s : sl) { for (Element* e : s->annotations()) { if (e->isSticking()) { e->layout(); } } } //------------------------------------------------------------- // layout slurs //------------------------------------------------------------- bool useRange = false; // TODO: lineMode(); Fraction stick = useRange ? lc.startTick : system->measures().front()->tick(); Fraction etick = useRange ? lc.endTick : system->measures().back()->endTick(); auto spanners = score()->spannerMap().findOverlapping(stick.ticks(), etick.ticks()); std::vector spanner; for (auto interval : spanners) { Spanner* sp = interval.value; sp->computeStartElement(); sp->computeEndElement(); lc.processedSpanners.insert(sp); if (sp->tick() < etick && sp->tick2() >= stick) { if (sp->isSlur()) { // skip cross-staff slurs ChordRest* scr = sp->startCR(); ChordRest* ecr = sp->endCR(); int idx = sp->vStaffIdx(); if (scr && ecr && (scr->vStaffIdx() != idx || ecr->vStaffIdx() != idx)) { continue; } spanner.push_back(sp); } } } processLines(system, spanner, false); for (auto s : spanner) { Slur* slur = toSlur(s); ChordRest* scr = s->startCR(); ChordRest* ecr = s->endCR(); if (scr && scr->isChord()) { toChord(scr)->layoutArticulations3(slur); } if (ecr && ecr->isChord()) { toChord(ecr)->layoutArticulations3(slur); } } std::vector dynamics; for (Segment* s : sl) { for (Element* e : s->elist()) { if (!e) { continue; } if (e->isChord()) { Chord* c = toChord(e); for (Chord* ch : c->graceNotes()) { layoutTies(ch, system, stick); } layoutTies(c, system, stick); } } for (Element* e : s->annotations()) { if (e->isDynamic()) { Dynamic* d = toDynamic(e); d->layout(); if (d->autoplace()) { d->autoplaceSegmentElement(false); dynamics.push_back(d); } } else if (e->isFiguredBass()) { e->layout(); e->autoplaceSegmentElement(); } } } // add dynamics shape to skyline for (Dynamic* d : dynamics) { if (!d->addToSkyline()) { continue; } int si = d->staffIdx(); Segment* s = d->segment(); Measure* m = s->measure(); system->staff(si)->skyline().add(d->shape().translated(d->pos() + s->pos() + m->pos())); } //------------------------------------------------------------- // layout SpannerSegments for current system // ottavas, pedals, voltas are collected here, but layouted later //------------------------------------------------------------- spanner.clear(); std::vector hairpins; std::vector ottavas; std::vector pedal; std::vector voltas; for (auto interval : spanners) { Spanner* sp = interval.value; if (sp->tick() < etick && sp->tick2() > stick) { if (sp->isOttava()) { ottavas.push_back(sp); } else if (sp->isPedal()) { pedal.push_back(sp); } else if (sp->isVolta()) { voltas.push_back(sp); } else if (sp->isHairpin()) { hairpins.push_back(sp); } else if (!sp->isSlur() && !sp->isVolta()) { // slurs are already spanner.push_back(sp); } } } processLines(system, hairpins, false); processLines(system, spanner, false); //------------------------------------------------------------- // Fermata, TremoloBar //------------------------------------------------------------- for (const Segment* s : sl) { for (Element* e : s->annotations()) { if (e->isFermata() || e->isTremoloBar()) { e->layout(); } } } //------------------------------------------------------------- // Ottava, Pedal //------------------------------------------------------------- processLines(system, ottavas, false); processLines(system, pedal, true); //------------------------------------------------------------- // Lyric //------------------------------------------------------------- layoutLyrics(system); // here are lyrics dashes and melisma for (Spanner* sp : _unmanagedSpanner) { if (sp->tick() >= etick || sp->tick2() <= stick) { continue; } sp->layoutSystem(system); } // // We need to known if we have FretDiagrams in the system to decide when to layout the Harmonies // bool hasFretDiagram = false; for (const Segment* s : sl) { for (Element* e : s->annotations()) { if (e->isFretDiagram()) { hasFretDiagram = true; break; } } if (hasFretDiagram) { break; } } //------------------------------------------------------------- // Harmony, 1st place // If we have FretDiagrams, we want the Harmony above this and // above the volta, therefore we delay the layout. //------------------------------------------------------------- if (!hasFretDiagram) { layoutHarmonies(sl); } //------------------------------------------------------------- // StaffText, InstrumentChange //------------------------------------------------------------- for (const Segment* s : sl) { for (Element* e : s->annotations()) { if (e->isStaffText() || e->isSystemText() || e->isInstrumentChange()) { e->layout(); } } } //------------------------------------------------------------- // Jump, Marker //------------------------------------------------------------- for (MeasureBase* mb : system->measures()) { if (!mb->isMeasure()) { continue; } Measure* m = toMeasure(mb); for (Element* e : m->el()) { if (e->isJump() || e->isMarker()) { e->layout(); } } } //------------------------------------------------------------- // TempoText //------------------------------------------------------------- for (const Segment* s : sl) { for (Element* e : s->annotations()) { if (e->isTempoText()) { e->layout(); } } } //------------------------------------------------------------- // layout Voltas for current system //------------------------------------------------------------- processLines(system, voltas, false); // // vertical align volta segments // for (int staffIdx = 0; staffIdx < nstaves(); ++staffIdx) { std::vector voltaSegments; for (SpannerSegment* ss : system->spannerSegments()) { if (ss->isVoltaSegment() && ss->staffIdx() == staffIdx) { voltaSegments.push_back(ss); } } while (!voltaSegments.empty()) { // we assume voltas are sorted left to right (by tick values) qreal y = 0; int idx = 0; Volta* prevVolta = 0; for (SpannerSegment* ss : voltaSegments) { Volta* volta = toVolta(ss->spanner()); if (prevVolta && prevVolta != volta) { // check if volta is adjacent to prevVolta if (prevVolta->tick2() != volta->tick()) { break; } } y = qMin(y, ss->rypos()); ++idx; prevVolta = volta; } for (int i = 0; i < idx; ++i) { SpannerSegment* ss = voltaSegments[i]; ss->rypos() = y; if (ss->addToSkyline()) { system->staff(staffIdx)->skyline().add(ss->shape().translated(ss->pos())); } } voltaSegments.erase(voltaSegments.begin(), voltaSegments.begin() + idx); } } //------------------------------------------------------------- // FretDiagram //------------------------------------------------------------- if (hasFretDiagram) { for (const Segment* s : sl) { for (Element* e : s->annotations()) { if (e->isFretDiagram()) { e->layout(); } } } //------------------------------------------------------------- // Harmony, 2nd place // We have FretDiagrams, we want the Harmony above this and // above the volta. //------------------------------------------------------------- layoutHarmonies(sl); } //------------------------------------------------------------- // RehearsalMark //------------------------------------------------------------- for (const Segment* s : sl) { for (Element* e : s->annotations()) { if (e->isRehearsalMark()) { e->layout(); } } } } //--------------------------------------------------------- // collectPage //--------------------------------------------------------- void LayoutContext::collectPage() { const qreal slb = score->styleP(Sid::staffLowerBorder); bool breakPages = score->layoutMode() != LayoutMode::SYSTEM; //qreal y = prevSystem ? prevSystem->y() + prevSystem->height() : page->tm(); qreal ey = page->height() - page->bm(); System* nextSystem = 0; int systemIdx = -1; qreal y = page->systems().isEmpty() ? page->tm() : page->system(0)->y() + page->system(0)->height(); // re-calculate positions for systems before current // (they may have been filled on previous layout) int pSystems = page->systems().size(); for (int i = 1; i < pSystems; ++i) { System* cs = page->system(i); System* ps = page->system(i - 1); qreal distance = ps->minDistance(cs); y += distance; cs->setPos(page->lm(), y); y += cs->height(); } for (int k = 0;; ++k) { // // calculate distance to previous system // qreal distance; if (prevSystem) { distance = prevSystem->minDistance(curSystem); } else { // this is the first system on page if (curSystem->vbox()) { distance = 0.0; } else { distance = score->styleP(Sid::staffUpperBorder); bool fixedDistance = false; // TODO: curSystem->spacerDistance(true) for (MeasureBase* mb : curSystem->measures()) { if (mb->isMeasure()) { Measure* m = toMeasure(mb); Spacer* sp = m->vspacerUp(0); // TODO: first visible? if (sp) { if (sp->spacerType() == SpacerType::FIXED) { distance = sp->gap(); fixedDistance = true; break; } else { distance = qMax(distance, sp->gap()); } } //TODO::ws distance = qMax(distance, -m->staffShape(0).top()); } } if (!fixedDistance) { distance = qMax(distance, curSystem->minTop()); } } } //TODO-ws ?? // distance += score->staves().front()->userDist(); y += distance; curSystem->setPos(page->lm(), y); page->appendSystem(curSystem); y += curSystem->height(); // // check for page break or if next system will fit on page // bool collected = false; if (rangeDone) { // take next system unchanged if (systemIdx > 0) { nextSystem = score->systems().value(systemIdx++); if (!nextSystem) { // TODO: handle next movement } } else { nextSystem = systemList.empty() ? 0 : systemList.takeFirst(); if (nextSystem) { score->systems().append(nextSystem); } else if (score->isMaster()) { MasterScore* ms = static_cast(score)->next(); if (ms) { score = ms; systemIdx = 0; nextSystem = score->systems().value(systemIdx++); } } } } else { nextSystem = score->collectSystem(*this); if (nextSystem) { collected = true; } if (!nextSystem && score->isMaster()) { MasterScore* ms = static_cast(score)->next(); if (ms) { score = ms; QList& systems = ms->systems(); if (systems.empty() || systems.front()->measures().empty()) { systemList = systems; systems.clear(); measureNo = 0; startWithLongNames = true; firstSystem = true; tick = Fraction(0,1); prevMeasure = 0; curMeasure = 0; nextMeasure = ms->measures()->first(); ms->getNextMeasure(*this); nextSystem = ms->collectSystem(*this); ms->setScoreFont(ScoreFont::fontFactory(ms->styleSt(Sid::MusicalSymbolFont))); ms->setNoteHeadWidth(ms->scoreFont()->width(SymId::noteheadBlack, ms->spatium() / SPATIUM20)); } else { rangeDone = true; systemIdx = 0; nextSystem = score->systems().value(systemIdx++); } } } } prevSystem = curSystem; Q_ASSERT(curSystem != nextSystem); curSystem = nextSystem; bool breakPage = !curSystem || (breakPages && prevSystem->pageBreak()); if (!breakPage) { qreal dist = prevSystem->minDistance(curSystem) + curSystem->height(); Box* vbox = curSystem->vbox(); if (vbox) { dist += vbox->bottomGap(); } else if (!prevSystem->hasFixedDownDistance()) { qreal margin = qMax(curSystem->minBottom(), curSystem->spacerDistance(false)); dist += qMax(margin, slb); } breakPage = (y + dist) >= ey && breakPages; } if (breakPage) { qreal dist = qMax(prevSystem->minBottom(), prevSystem->spacerDistance(false)); dist = qMax(dist, slb); layoutPage(page, ey - (y + dist)); // if we collected a system we cannot fit onto this page, // we need to collect next page in order to correctly set system positions if (collected) { pageOldMeasure = nullptr; } break; } } Fraction stick = Fraction(-1,1); for (System* s : page->systems()) { Score* currentScore = s->score(); for (MeasureBase* mb : s->measures()) { if (!mb->isMeasure()) { continue; } Measure* m = toMeasure(mb); if (stick == Fraction(-1,1)) { stick = m->tick(); } for (int track = 0; track < currentScore->ntracks(); ++track) { for (Segment* segment = m->first(); segment; segment = segment->next()) { Element* e = segment->element(track); if (!e) { continue; } if (e->isChordRest()) { if (!currentScore->staff(track2staff(track))->show()) { continue; } ChordRest* cr = toChordRest(e); if (notTopBeam(cr)) { // layout cross staff beams cr->beam()->layout(); } if (notTopTuplet(cr)) { // fix layout of tuplets DurationElement* de = cr; while (de->tuplet() && de->tuplet()->elements().front() == de) { Tuplet* t = de->tuplet(); t->layout(); de = t; } } if (cr->isChord()) { Chord* c = toChord(cr); for (Chord* cc : c->graceNotes()) { if (cc->beam() && cc->beam()->elements().front() == cc) { cc->beam()->layout(); } cc->layoutSpanners(); for (Element* element : cc->el()) { if (element->isSlur()) { element->layout(); } } } c->layoutArpeggio2(); c->layoutSpanners(); if (c->tremolo()) { Tremolo* t = c->tremolo(); Chord* c1 = t->chord1(); Chord* c2 = t->chord2(); if (t->twoNotes() && c1 && c2 && (c1->staffMove() || c2->staffMove())) { t->layout(); } } } } else if (e->isBarLine()) { toBarLine(e)->layout2(); } } } m->layout2(); } } if (score->systemMode()) { System* s = page->systems().last(); qreal height = s ? s->pos().y() + s->height() + s->minBottom() : page->tm(); page->bbox().setRect(0.0, 0.0, score->loWidth(), height + page->bm()); } page->rebuildBspTree(); } //--------------------------------------------------------- // doLayout // do a complete (re-) layout //--------------------------------------------------------- void Score::doLayout() { doLayoutRange(Fraction(0,1), Fraction(-1,1)); } //--------------------------------------------------------- // CmdStateLocker //--------------------------------------------------------- class CmdStateLocker { Score* score; public: CmdStateLocker(Score* s) : score(s) { score->cmdState().lock(); } ~CmdStateLocker() { score->cmdState().unlock(); } }; //--------------------------------------------------------- // doLayoutRange //--------------------------------------------------------- void Score::doLayoutRange(const Fraction& st, const Fraction& et) { CmdStateLocker cmdStateLocker(this); LayoutContext lc(this); Fraction stick(st); Fraction etick(et); Q_ASSERT(!(stick == Fraction(-1,1) && etick == Fraction(-1,1))); if (!last() || (lineMode() && !firstMeasure())) { qDebug("empty score"); qDeleteAll(_systems); _systems.clear(); qDeleteAll(pages()); pages().clear(); lc.getNextPage(); return; } // if (!_systems.isEmpty()) // return; bool layoutAll = stick <= Fraction(0,1) && (etick < Fraction(0,1) || etick >= masterScore()->last()->endTick()); if (stick < Fraction(0,1)) { stick = Fraction(0,1); } if (etick < Fraction(0,1)) { etick = last()->endTick(); } lc.endTick = etick; _scoreFont = ScoreFont::fontFactory(style().value(Sid::MusicalSymbolFont).toString()); _noteHeadWidth = _scoreFont->width(SymId::noteheadBlack, spatium() / SPATIUM20); if (cmdState().layoutFlags & LayoutFlag::REBUILD_MIDI_MAPPING) { if (isMaster()) { masterScore()->rebuildMidiMapping(); } } if (cmdState().layoutFlags & LayoutFlag::FIX_PITCH_VELO) { updateVelo(); } #if 0 // TODO: needed? It was introduced in ab9774ec4098512068b8ef708167d9aa6e702c50 if (cmdState().layoutFlags & LayoutFlag::PLAY_EVENTS) { createPlayEvents(); } #endif //--------------------------------------------------- // initialize layout context lc //--------------------------------------------------- MeasureBase* m = tick2measure(stick); if (m == 0) { m = first(); } // start layout one measure earlier to handle clefs and cautionary elements if (m->prevMeasureMM()) { m = m->prevMeasureMM(); } else if (m->prev()) { m = m->prev(); } while (!m->isMeasure() && m->prev()) { m = m->prev(); } // if the first measure of the score is part of a multi measure rest // m->system() will return a nullptr. We need to find the multi measure // rest which replaces the measure range if (!m->system() && m->isMeasure() && toMeasure(m)->hasMMRest()) { qDebug(" don’t start with mmrest"); m = toMeasure(m)->mmRest(); } // qDebug("start <%s> tick %d, system %p", m->name(), m->tick(), m->system()); if (lineMode()) { lc.prevMeasure = 0; lc.nextMeasure = m; //_showVBox ? first() : firstMeasure(); lc.startTick = m->tick(); layoutLinear(layoutAll, lc); return; } if (!layoutAll && m->system()) { System* system = m->system(); int systemIndex = _systems.indexOf(system); lc.page = system->page(); lc.curPage = pageIdx(lc.page); if (lc.curPage == -1) { lc.curPage = 0; } lc.curSystem = system; lc.systemList = _systems.mid(systemIndex); if (systemIndex == 0) { lc.nextMeasure = _showVBox ? first() : firstMeasure(); } else { System* prevSystem = _systems[systemIndex - 1]; lc.nextMeasure = prevSystem->measures().back()->next(); } _systems.erase(_systems.begin() + systemIndex, _systems.end()); if (!lc.nextMeasure->prevMeasure()) { lc.measureNo = 0; lc.tick = Fraction(0,1); } else { LayoutBreak* sectionBreak = lc.nextMeasure->prevMeasure()->sectionBreakElement(); if (sectionBreak && sectionBreak->startWithMeasureOne()) { lc.measureNo = 0; } else { lc.measureNo = lc.nextMeasure->prevMeasure()->no() + 1; // will be adjusted later with respect } // to the user-defined offset. lc.tick = lc.nextMeasure->tick(); } } else { // qDebug("layoutAll, systems %p %d", &_systems, int(_systems.size())); //lc.measureNo = 0; //lc.tick = 0; // qDeleteAll(_systems); // _systems.clear(); // lc.systemList = _systems; // _systems.clear(); for (System* s : _systems) { for (Bracket* b : s->brackets()) { if (b->selected()) { _selection.remove(b); setSelectionChanged(true); } } // for (SpannerSegment* ss : s->spannerSegments()) // ss->setParent(0); s->setParent(nullptr); } for (MeasureBase* mb = first(); mb; mb = mb->next()) { mb->setSystem(0); if (mb->isMeasure() && toMeasure(mb)->mmRest()) { toMeasure(mb)->mmRest()->setSystem(0); } } qDeleteAll(_systems); _systems.clear(); qDeleteAll(pages()); pages().clear(); lc.nextMeasure = _showVBox ? first() : firstMeasure(); } lc.prevMeasure = 0; getNextMeasure(lc); lc.curSystem = collectSystem(lc); lc.layout(); } //--------------------------------------------------------- // layout //--------------------------------------------------------- void LayoutContext::layout() { MeasureBase* lmb; do{ getNextPage(); collectPage(); if (page && !page->systems().isEmpty()) { lmb = page->systems().back()->measures().back(); } else { lmb = nullptr; } // we can stop collecting pages when: // 1) we reach the end of score (curSystem is nullptr) // or // 2) we have fully processed the range and reached a point of stability: // a) we have completed layout for the range (rangeDone is true) // b) we haven't collected a system that will need to go on the next page // c) this page ends with the same measure as the previous layout // pageOldMeasure will be last measure from previous layout if range was completed on or before this page // it will be nullptr if this page was never laid out or if we collected a system for next page } while (curSystem && !(rangeDone && lmb == pageOldMeasure)); // && page->system(0)->measures().back()->tick() > endTick // FIXME: perhaps the first measure was meant? Or last system? if (!curSystem) { // The end of the score. The remaining systems are not needed... qDeleteAll(systemList); systemList.clear(); // ...and the remaining pages too while (score->npages() > curPage) { delete score->pages().takeLast(); } } else { Page* p = curSystem->page(); if (p && (p != page)) { p->rebuildBspTree(); } } score->systems().append(systemList); // TODO } //--------------------------------------------------------- // LayoutContext::~LayoutContext //--------------------------------------------------------- LayoutContext::~LayoutContext() { for (Spanner* s : processedSpanners) { s->layoutSystemsDone(); } for (MuseScoreView* v : score->getViewer()) { v->layoutChanged(); } } }