4557 lines
194 KiB
C++
4557 lines
194 KiB
C++
//=============================================================================
|
||
// 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()))
|
||
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;
|
||
|
||
if (staff->isTabStaff(segment->tick())) {
|
||
layoutSegmentElements(segment, startTrack, endTrack);
|
||
return;
|
||
}
|
||
|
||
bool crossBeamFound = false;
|
||
std::vector<Note*> upStemNotes;
|
||
std::vector<Note*> downStemNotes;
|
||
int upVoices = 0;
|
||
int downVoices = 0;
|
||
qreal nominalWidth = noteHeadWidth() * staff->mag(segment->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) {
|
||
qSort(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) {
|
||
qSort(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(segment->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->mag(segment->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<Note*> 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;
|
||
}
|
||
qSort(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<Note*> 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)
|
||
qSort(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<Note*>& 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<Note*>& 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<Note*>& notes, const Staff* staff, Segment* segment)
|
||
{
|
||
//---------------------------------------------------
|
||
// layout accidentals
|
||
// find column for dots
|
||
//---------------------------------------------------
|
||
|
||
QVector<Note*> leftNotes; // notes to left of origin
|
||
leftNotes.reserve(8);
|
||
QVector<AcEl> 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<int> 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<int> column;
|
||
QVector<int> 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)
|
||
qSort(umi);
|
||
|
||
// 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<Chord*> 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<System*> 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<qreal, Spring, std::less<qreal> > 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<ChordRest*>* 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<qreal> width(n-1);
|
||
std::vector<int> 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<qreal, Spring>(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<System*>& 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 and first measure *after* volta
|
||
if (s->isVolta() && (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(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 staffs 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<int, TDuration> 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()
|
||
&& 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 staffs 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<ChordRest*> mElements;
|
||
std::vector<ChordRest*> 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;
|
||
}
|
||
}
|
||
}
|
||
|
||
//---------------------------------------------------------
|
||
// 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->staffType(segment.tick());
|
||
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->mag(segment.tick());
|
||
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
|
||
}
|
||
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(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<int> visibleStaves;
|
||
for (int staffIdx = system->firstVisibleStaff(); staffIdx < nstaves(); staffIdx = system->nextVisibleStaff(staffIdx))
|
||
visibleStaves.push_back(staffIdx);
|
||
|
||
//int nAbove[nstaves()];
|
||
std::vector<int> 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<qreal>(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<Segment*>& 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<Spanner*> lines, bool align)
|
||
{
|
||
std::vector<SpannerSegment*> 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<qreal> 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<Segment*> 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<Note*> 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) {
|
||
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<Note*> 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<Fingering*> 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);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
//-------------------------------------------------------------
|
||
// 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*> 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<Dynamic*> 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<Spanner*> hairpins;
|
||
std::vector<Spanner*> ottavas;
|
||
std::vector<Spanner*> pedal;
|
||
std::vector<Spanner*> 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<SpannerSegment*> 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<MasterScore*>(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<MasterScore*>(score)->next();
|
||
if (ms) {
|
||
score = ms;
|
||
QList<System*>& 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();
|
||
}
|
||
}
|