MuseScore/libmscore/layout.cpp

3834 lines
160 KiB
C++
Raw Normal View History

2012-05-26 14:26:10 +02:00
//=============================================================================
// MuseScore
// Music Composition & Notation
//
2016-01-04 14:48:58 +01:00
// Copyright (C) 2002-2016 Werner Schweer
2012-05-26 14:26:10 +02:00
//
// 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"
2012-05-26 14:26:10 +02:00
#include "barline.h"
#include "beam.h"
#include "box.h"
#include "chord.h"
#include "clef.h"
#include "element.h"
2012-05-26 14:26:10 +02:00
#include "fingering.h"
#include "glissando.h"
#include "harmony.h"
#include "key.h"
#include "keysig.h"
2012-05-26 14:26:10 +02:00
#include "layoutbreak.h"
#include "layout.h"
2012-08-01 18:00:27 +02:00
#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 "style.h"
#include "sym.h"
#include "system.h"
#include "text.h"
#include "tie.h"
#include "timesig.h"
2014-05-15 13:42:03 +02:00
#include "tremolo.h"
#include "tuplet.h"
#include "undo.h"
#include "utils.h"
#include "volta.h"
2016-01-04 14:48:58 +01:00
#include "breath.h"
#include "tempotext.h"
2016-02-10 13:40:34 +01:00
#include "systemdivider.h"
2016-05-19 13:15:34 +02:00
#include "hook.h"
#include "ambitus.h"
#include "hairpin.h"
2016-12-23 12:05:18 +01:00
#include "stafflines.h"
2017-01-05 11:23:47 +01:00
#include "articulation.h"
2017-03-31 13:03:15 +02:00
#include "bracket.h"
2017-10-27 13:06:22 +02:00
#include "spacer.h"
2018-01-17 13:25:23 +01:00
#include "fermata.h"
2012-05-26 14:26:10 +02:00
2013-05-13 18:49:17 +02:00
namespace Ms {
// #define PAGE_DEBUG
#ifdef PAGE_DEBUG
#define PAGEDBG(...) qDebug(__VA_ARGS__)
#else
#define PAGEDBG(...) ;
#endif
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
// rebuildBspTree
//---------------------------------------------------------
void Score::rebuildBspTree()
{
2017-01-05 11:23:47 +01:00
for (Page* page : pages())
2014-11-23 13:34:03 +01:00
page->rebuildBspTree();
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
// layoutChords1
// - layout upstem and downstem chords
// - offset as necessary to avoid conflict
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
void Score::layoutChords1(Segment* segment, int staffIdx)
{
Staff* staff = Score::staff(staffIdx);
2016-12-13 13:16:17 +01:00
if (staff->isTabStaff(segment->tick()))
2012-05-26 14:26:10 +02:00
return;
std::vector<Note*> upStemNotes;
std::vector<Note*> downStemNotes;
int upVoices = 0;
int downVoices = 0;
int startTrack = staffIdx * VOICES;
int endTrack = startTrack + VOICES;
2016-12-23 12:05:18 +01:00
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;
2014-03-08 07:43:24 +01:00
2012-05-26 14:26:10 +02:00
for (int track = startTrack; track < endTrack; ++track) {
2016-02-10 18:35:35 +01:00
Element* e = segment->element(track);
if (e && e->isChord()) {
2016-02-17 14:54:23 +01:00
Chord* chord = toChord(e);
bool hasGraceBefore = false;
for (Chord* c : chord->graceNotes()) {
if (c->isGraceBefore())
hasGraceBefore = true;
2016-02-04 17:06:32 +01:00
layoutChords2(c->notes(), c->up()); // layout grace note noteheads
layoutChords3(c->notes(), staff, 0); // layout grace note chords
}
2014-03-07 03:43:41 +01:00
if (chord->up()) {
++upVoices;
2016-02-06 22:03:43 +01:00
upStemNotes.insert(upStemNotes.end(), chord->notes().begin(), chord->notes().end());
2016-02-04 17:06:32 +01:00
upDots = qMax(upDots, chord->dots());
2014-04-08 23:18:15 +02:00
maxUpMag = qMax(maxUpMag, chord->mag());
if (!upHooks)
upHooks = chord->hook();
if (hasGraceBefore)
upGrace = true;
2014-03-07 03:43:41 +01:00
}
else {
++downVoices;
2016-02-06 22:03:43 +01:00
downStemNotes.insert(downStemNotes.end(), chord->notes().begin(), chord->notes().end());
2014-03-08 07:43:24 +01:00
downDots = qMax(downDots, chord->dots());
2014-04-08 23:18:15 +02:00
maxDownMag = qMax(maxDownMag, chord->mag());
if (!downHooks)
downHooks = chord->hook();
if (hasGraceBefore)
downGrace = true;
2014-03-07 03:43:41 +01:00
}
2012-05-26 14:26:10 +02:00
}
}
2016-01-04 14:48:58 +01:00
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;
2016-01-04 14:48:58 +01:00
maxDownWidth = nominalWidth * maxDownMag;
2016-01-04 14:48:58 +01:00
// layout upstem noteheads
if (upVoices > 1) {
qSort(upStemNotes.begin(), upStemNotes.end(),
[](Note* n1, const Note* n2) ->bool {return n1->line() > n2->line(); } );
}
2016-01-04 14:48:58 +01:00
if (upVoices) {
qreal hw = layoutChords2(upStemNotes, true);
maxUpWidth = qMax(maxUpWidth, hw);
}
2016-01-04 14:48:58 +01:00
// layout downstem noteheads
if (downVoices > 1) {
qSort(downStemNotes.begin(), downStemNotes.end(),
[](Note* n1, const Note* n2) ->bool {return n1->line() > n2->line(); } );
}
2016-01-04 14:48:58 +01:00
if (downVoices) {
qreal hw = layoutChords2(downStemNotes, false);
maxDownWidth = qMax(maxDownWidth, hw);
}
2016-12-23 12:05:18 +01:00
qreal sp = staff->spatium(segment->tick());
2016-02-06 11:41:16 +01:00
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
2016-01-04 14:48:58 +01:00
// 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()
2016-12-23 12:05:18 +01:00
qreal headDiff2 = maxUpWidth - nominalWidth * (maxUpMag / staff->mag(segment->tick()));
2016-01-04 14:48:58 +01:00
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) {
2016-02-06 22:03:43 +01:00
Note* bottomUpNote = upStemNotes.front();
Note* topDownNote = downStemNotes.back();
2016-01-04 14:48:58 +01:00
int separation;
if (bottomUpNote->chord()->staffMove() == topDownNote->chord()->staffMove())
separation = topDownNote->line() - bottomUpNote->line();
else
separation = 2; // no conflict
2016-02-06 22:03:43 +01:00
QVector<Note*> overlapNotes;
overlapNotes.reserve(8);
2016-01-04 14:48:58 +01:00
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
2016-01-04 14:48:58 +01:00
downOffset += 0.1 * sp;
}
2016-01-04 14:48:58 +01:00
else if (separation < 1) {
// overlap (possibly unison)
// build list of overlapping notes
for (int i = 0, n = upStemNotes.size(); i < n; ++i) {
if (upStemNotes[i]->line() >= topDownNote->line() - 1)
overlapNotes.append(upStemNotes[i]);
else
break;
}
2016-01-04 14:48:58 +01:00
for (int i = downStemNotes.size() - 1; i >= 0; --i) {
if (downStemNotes[i]->line() <= bottomUpNote->line() + 1)
overlapNotes.append(downStemNotes[i]);
else
break;
2016-01-04 14:48:58 +01:00
}
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) {
2016-01-04 14:48:58 +01:00
// don't try to share heads if there is any mirroring
shareHeads = false;
2016-01-04 14:48:58 +01:00
// don't worry about conflicts involving mirrored notes
continue;
}
2016-01-04 14:48:58 +01:00
}
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 {
2016-01-04 14:48:58 +01:00
// 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
2016-01-04 14:48:58 +01:00
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* p = overlapNotes[i-1];
Note* n = overlapNotes[i];
if (!(p->chord()->isNudged() || n->chord()->isNudged())) {
if (p->chord()->dots() == n->chord()->dots()) {
// hide one set dots
bool onLine = !(p->line() & 1);
if (onLine) {
// hide dots for lower voice
if (p->voice() & 1)
p->setDotsHidden(true);
else
n->setDotsHidden(true);
}
else {
// hide dots for upper voice
if (!(p->voice() & 1))
p->setDotsHidden(true);
else
n->setDotsHidden(true);
}
}
2016-01-04 14:48:58 +01:00
// 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
}
}
}
2016-01-04 14:48:58 +01:00
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))
2014-03-08 07:43:24 +01:00
downOffset = maxUpWidth + 0.3 * sp;
2016-01-04 14:48:58 +01:00
else if (conflictSecondDownHigher) {
if (downDots && !upDots)
downOffset = maxUpWidth + 0.3 * sp;
else {
upOffset = maxDownWidth - 0.2 * sp;
if (downHooks)
upOffset += 0.3 * sp;
}
}
else {
2016-01-04 14:48:58 +01:00
// 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;
}
}
2016-01-04 14:48:58 +01:00
2014-03-08 07:43:24 +01:00
}
2016-01-04 14:48:58 +01:00
// 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;
}
2016-01-04 14:48:58 +01:00
else {
dots = downDots;
mag = maxDownMag;
2014-08-11 06:57:11 +02:00
}
2016-01-04 14:48:58 +01:00
qreal dotWidth = segment->symWidth(SymId::augmentationDot);
// first dot
2018-03-27 15:36:00 +02:00
dotAdjust = styleP(Sid::dotNoteDistance) + dotWidth;
2016-01-04 14:48:58 +01:00
// additional dots
if (dots > 1)
2018-03-27 15:36:00 +02:00
dotAdjust += styleP(Sid::dotDotDistance) * (dots - 1);
2016-01-04 14:48:58 +01:00
dotAdjust *= mag;
// only by amount over threshold
dotAdjust = qMax(dotAdjust - dotAdjustThreshold, 0.0);
2014-03-07 03:43:41 +01:00
}
2016-01-04 14:48:58 +01:00
if (separation == 1)
dotAdjust += 0.1 * sp;
2014-03-08 07:43:24 +01:00
}
2016-01-04 14:48:58 +01:00
// apply chord offsets
for (int track = startTrack; track < endTrack; ++track) {
2016-02-10 18:35:35 +01:00
Element* e = segment->element(track);
if (e && e->isChord()) {
2016-02-17 14:54:23 +01:00
Chord* chord = toChord(e);
2016-01-04 14:48:58 +01:00
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;
}
2014-04-08 23:18:15 +02:00
}
}
2016-01-04 14:48:58 +01:00
// layout chords
2016-02-06 22:03:43 +01:00
std::vector<Note*> notes;
2016-01-04 14:48:58 +01:00
if (upVoices)
2016-02-06 22:03:43 +01:00
notes.insert(notes.end(), upStemNotes.begin(), upStemNotes.end());
2016-01-04 14:48:58 +01:00
if (downVoices)
2016-02-06 22:03:43 +01:00
notes.insert(notes.end(), downStemNotes.begin(), downStemNotes.end());
2016-01-04 14:48:58 +01:00
if (upVoices + downVoices > 1)
qSort(notes.begin(), notes.end(),
[](Note* n1, const Note* n2) ->bool {return n1->line() > n2->line(); } );
layoutChords3(notes, staff, segment);
}
for (int track = startTrack; track < endTrack; ++track) {
Element* e = segment->element(track);
2016-01-04 14:48:58 +01:00
if (e)
e->layout();
}
2013-06-10 21:13:04 +02:00
}
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
// 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
//---------------------------------------------------------
2016-02-06 22:03:43 +01:00
qreal Score::layoutChords2(std::vector<Note*>& notes, bool up)
2013-06-10 21:13:04 +02:00
{
2012-05-26 14:26:10 +02:00
int startIdx, endIdx, incIdx;
qreal maxWidth = 0.0;
2012-05-26 14:26:10 +02:00
// loop in correct direction so that first encountered notehead wins conflict
if (up) {
// loop bottom up
2012-05-26 14:26:10 +02:00
startIdx = 0;
endIdx = notes.size();
incIdx = 1;
2012-05-26 14:26:10 +02:00
}
else {
// loop top down
2012-05-26 14:26:10 +02:00
startIdx = notes.size() - 1;
endIdx = -1;
incIdx = -1;
2012-05-26 14:26:10 +02:00
}
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)
2012-05-26 14:26:10 +02:00
for (int idx = startIdx; idx != endIdx; idx += incIdx) {
Note* note = notes[idx]; // current note
int line = note->line(); // line of current note
2012-05-26 14:26:10 +02:00
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))
2012-05-26 14:26:10 +02:00
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);
2014-05-09 18:17:52 +02:00
// by default, notes and dots are not hidden
// this may be changed later to allow unisons to share noteheads
2012-05-26 14:26:10 +02:00
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
2012-05-26 14:26:10 +02:00
chord->rxpos() = 0.0;
// let user mirror property override the default we calculated
if (note->userMirror() == MScore::DirectionH::AUTO) {
2012-05-26 14:26:10 +02:00
mirror = nmirror;
}
else {
mirror = note->chord()->up();
if (note->userMirror() == MScore::DirectionH::LEFT)
2012-05-26 14:26:10 +02:00
mirror = !mirror;
}
note->setMirror(mirror);
// accumulate return value
if (!mirror)
maxWidth = qMax(maxWidth, note->bboxRightPos());
// prepare for next iteration
2016-02-06 22:03:43 +01:00
lvisible = note->visible();
lmove = move;
ll = line;
2012-05-26 14:26:10 +02:00
}
return maxWidth;
}
2014-03-12 22:52:59 +01:00
//---------------------------------------------------------
// 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
2014-03-12 22:52:59 +01:00
};
//---------------------------------------------------------
// resolveAccidentals
// lx = calculated position of rightmost edge of left accidental relative to origin
2014-03-12 22:52:59 +01:00
//---------------------------------------------------------
static bool resolveAccidentals(AcEl* left, AcEl* right, qreal& lx, qreal pd, qreal sp)
2014-03-12 22:52:59 +01:00
{
AcEl* upper;
AcEl* lower;
if (left->line >= right->line) {
upper = right;
lower = left;
}
else {
upper = left;
lower = right;
}
qreal gap = lower->top - upper->bottom;
2014-03-12 22:52:59 +01:00
// 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)
2014-03-12 22:52:59 +01:00
return false;
2014-05-12 05:00:54 +02:00
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;
}
2014-03-12 22:52:59 +01:00
}
// 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;
2014-03-12 22:52:59 +01:00
}
// accidentals with even more overlap
// can work if both accidentals can subsume overlap
2014-05-12 05:00:54 +02:00
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);
2014-03-12 22:52:59 +01:00
return true;
}
//---------------------------------------------------------
// layoutAccidental
//---------------------------------------------------------
2016-02-06 22:03:43 +01:00
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
2016-12-13 13:16:17 +01:00
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
2014-05-12 05:00:54 +02:00
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;
2012-05-26 14:26:10 +02:00
}
// 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
//---------------------------------------------------------
2016-02-06 22:03:43 +01:00
void Score::layoutChords3(std::vector<Note*>& notes, Staff* staff, Segment* segment)
{
2012-05-26 14:26:10 +02:00
//---------------------------------------------------
// layout accidentals
// find column for dots
//---------------------------------------------------
2016-02-06 22:03:43 +01:00
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 };
2013-05-22 14:12:47 +02:00
2016-12-13 13:16:17 +01:00
int tick = notes.front()->tick();
2016-12-23 12:05:18 +01:00
qreal sp = staff->spatium(tick);
qreal stepDistance = sp * staff->lineDistance(tick) * .5;
2016-12-13 13:16:17 +01:00
int stepOffset = staff->staffType(tick)->stepOffset();
2012-05-26 14:26:10 +02:00
2016-12-13 13:16:17 +01:00
qreal lx = 10000.0; // leftmost notehead position
qreal upDotPosX = 0.0;
qreal downDotPosX = 0.0;
2013-06-27 15:00:08 +02:00
2012-05-26 14:26:10 +02:00
int nNotes = notes.size();
int nAcc = 0;
2012-05-26 14:26:10 +02:00
for (int i = nNotes-1; i >= 0; --i) {
Note* note = notes[i];
Accidental* ac = note->accidental();
if (ac && !note->fixed()) {
2012-05-26 14:26:10 +02:00
ac->layout();
AcEl acel;
2014-03-12 22:52:59 +01:00
acel.note = note;
int line = note->line();
acel.line = line;
2014-03-12 22:52:59 +01:00
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();
2014-05-12 05:00:54 +02:00
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;
2012-05-26 14:26:10 +02:00
}
2013-05-22 14:12:47 +02:00
Chord* chord = note->chord();
2013-06-04 18:29:14 +02:00
bool _up = chord->up();
qreal overlapMirror;
Stem* stem = chord->stem();
if (stem)
overlapMirror = stem->lineWidth();
else if (chord->durationType().headType() == NoteHead::Type::HEAD_WHOLE)
2018-03-27 15:36:00 +02:00
overlapMirror = styleP(Sid::stemWidth) * chord->mag();
2013-05-22 14:12:47 +02:00
else
overlapMirror = 0.0;
2013-05-22 14:12:47 +02:00
qreal x = 0.0;
if (note->mirror())
2013-05-22 14:12:47 +02:00
if (_up)
x = chord->stemPosX() - overlapMirror;
2013-05-22 14:12:47 +02:00
else
x = -note->headBodyWidth() + overlapMirror;
else if (_up)
x = chord->stemPosX() - note->headBodyWidth();
2013-05-22 14:12:47 +02:00
note->rypos() = (note->line() + stepOffset) * stepDistance;
note->rxpos() = x;
2014-03-12 22:52:59 +01:00
// 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)
2014-03-12 22:52:59 +01:00
leftNotes.append(note);
else if (sx < lx)
lx = sx;
2013-06-04 18:29:14 +02:00
qreal xx = x + chord->stemPosX() + chord->pos().x();
2014-04-02 20:31:37 +02:00
2016-08-18 10:58:59 +02:00
Direction dotPosition = note->userDotPosition();
if (chord->dots()) {
if (chord->up())
upDotPosX = qMax(upDotPosX, xx);
else {
qreal noteheadShift = note->headBodyWidth();
downDotPosX = qMax(downDotPosX, xx + noteheadShift);
}
2014-04-02 20:31:37 +02:00
2016-03-02 13:20:19 +01:00
if (dotPosition == Direction::AUTO && nNotes > 1 && note->visible() && !note->dotsHidden()) {
2014-04-02 20:31:37 +02:00
// resolve dot conflicts
int line = note->line();
Note* above = (i < nNotes - 1) ? notes[i+1] : 0;
if (above && (!above->visible() || above->dotsHidden()))
above = 0;
2014-04-02 20:31:37 +02:00
int intervalAbove = above ? line - above->line() : 1000;
Note* below = (i > 0) ? notes[i-1] : 0;
if (below && (!below->visible() || below->dotsHidden()))
below = 0;
2014-04-02 20:31:37 +02:00
int intervalBelow = below ? below->line() - line : 1000;
if ((line & 1) == 0) {
// line
if (intervalAbove == 1 && intervalBelow != 1)
2016-03-02 13:20:19 +01:00
dotPosition = Direction::DOWN;
else if (intervalBelow == 1 && intervalAbove != 1)
2016-03-02 13:20:19 +01:00
dotPosition = Direction::UP;
2014-04-02 20:31:37 +02:00
else if (intervalAbove == 0 && above->chord()->dots()) {
// unison
if (((above->voice() & 1) == (note->voice() & 1))) {
2016-03-02 13:20:19 +01:00
above->setDotY(Direction::UP);
dotPosition = Direction::DOWN;
2014-04-02 20:31:37 +02:00
}
}
}
else {
// space
if (intervalAbove == 0 && above->chord()->dots()) {
// unison
if (!(note->voice() & 1))
2016-03-02 13:20:19 +01:00
dotPosition = Direction::UP;
2014-04-02 20:31:37 +02:00
else {
if (!(above->voice() & 1))
2016-03-02 13:20:19 +01:00
above->setDotY(Direction::UP);
2014-04-02 20:31:37 +02:00
else
2016-03-02 13:20:19 +01:00
dotPosition = Direction::DOWN;
2014-04-02 20:31:37 +02:00
}
}
}
}
}
2016-08-18 10:58:59 +02:00
note->setDotY(dotPosition); // also removes invalid dots
2012-05-26 14:26:10 +02:00
}
2013-05-22 14:12:47 +02:00
2014-03-08 07:43:24 +01:00
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));
2014-03-08 07:43:24 +01:00
}
2012-05-26 14:26:10 +02:00
if (nAcc == 0)
return;
2016-02-06 22:03:43 +01:00
QVector<int> umi;
2018-03-27 15:36:00 +02:00
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;
}
2013-06-27 15:00:08 +02:00
// compute reasonable column order
// use zig zag
2016-02-06 22:03:43 +01:00
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)
2016-02-06 22:03:43 +01:00
umi.push_back(unmatched[i]);
}
nAcc = umi.size();
if (nAcc > 1)
qSort(umi);
2012-05-26 14:26:10 +02:00
// 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;
}
2012-05-26 14:26:10 +02:00
}
else {
for (int i = 0; i < nAcc; ++i)
2016-02-06 22:03:43 +01:00
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);
}
2012-05-26 14:26:10 +02:00
}
2012-05-26 14:26:10 +02:00
}
2013-05-22 14:12:47 +02:00
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
2013-05-22 14:12:47 +02:00
Note* note = e.note;
qreal x = e.x + lx - (note->x() + note->chord()->x());
2012-05-26 14:26:10 +02:00
note->accidental()->setPos(x, 0);
}
}
#define beamModeMid(a) (a == Beam::Mode::MID || a == Beam::Mode::BEGIN32 || a == Beam::Mode::BEGIN64)
2013-06-16 23:33:37 +02:00
//---------------------------------------------------------
// beamGraceNotes
//---------------------------------------------------------
2014-05-15 13:42:03 +02:00
void Score::beamGraceNotes(Chord* mainNote, bool after)
2013-06-16 23:33:37 +02:00
{
2013-11-24 20:39:58 +01:00
ChordRest* a1 = 0; // start of (potential) beam
Beam* beam = 0; // current beam
Beam::Mode bm = Beam::Mode::AUTO;
2016-02-06 22:03:43 +01:00
QVector<Chord*> graceNotes = after ? mainNote->graceNotesAfter() : mainNote->graceNotesBefore();
2015-02-19 10:28:25 +01:00
2016-02-04 11:27:47 +01:00
for (ChordRest* cr : graceNotes) {
2013-11-24 20:39:58 +01:00
bm = Groups::endBeam(cr);
if ((cr->durationType().type() <= TDuration::DurationType::V_QUARTER) || (bm == Beam::Mode::NONE)) {
2013-11-24 20:39:58 +01:00
if (beam) {
beam->layoutGraceNotes();
beam = 0;
}
if (a1) {
2016-01-04 14:48:58 +01:00
a1->removeDeleteBeam(false);
2013-11-24 20:39:58 +01:00
a1 = 0;
}
2016-01-04 14:48:58 +01:00
cr->removeDeleteBeam(false);
2013-11-24 20:39:58 +01:00
continue;
}
if (beam) {
bool beamEnd = bm == Beam::Mode::BEGIN;
2013-11-24 20:39:58 +01:00
if (!beamEnd) {
cr->removeDeleteBeam(true);
beam->add(cr);
cr = 0;
beamEnd = (bm == Beam::Mode::END);
2013-11-24 20:39:58 +01:00
}
if (beamEnd) {
beam->layoutGraceNotes();
beam = 0;
}
}
if (!cr)
continue;
if (a1 == 0)
a1 = cr;
else {
if (!beamModeMid(bm) && (bm == Beam::Mode::BEGIN)) {
2016-01-04 14:48:58 +01:00
a1->removeDeleteBeam(false);
2013-11-24 20:39:58 +01:00
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->removeDeleteBeam(true);
beam->add(a1);
}
cr->removeDeleteBeam(true);
beam->add(cr);
a1 = 0;
}
}
2013-07-10 18:01:37 +02:00
}
2013-11-24 20:39:58 +01:00
if (beam)
beam->layoutGraceNotes();
else if (a1)
2016-01-04 14:48:58 +01:00
a1->removeDeleteBeam(false);
2013-06-16 23:33:37 +02:00
}
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
2016-01-04 14:48:58 +01:00
// layoutSpanner
// called after dragging a staff
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
2016-01-04 14:48:58 +01:00
void Score::layoutSpanner()
2012-05-26 14:26:10 +02:00
{
2016-01-04 14:48:58 +01:00
int tracks = ntracks();
2012-05-26 14:26:10 +02:00
for (int track = 0; track < tracks; ++track) {
2017-03-08 13:12:26 +01:00
for (Segment* segment = firstSegment(SegmentType::All); segment; segment = segment->next1()) {
2016-01-04 14:48:58 +01:00
if (track == tracks-1) {
int n = segment->annotations().size();
for (int 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));
2016-01-04 14:48:58 +01:00
c->layoutStem();
for (Note* n : c->notes()) {
Tie* tie = n->tieFor();
if (tie) {
printf("tie layout ??\n");
2016-01-04 14:48:58 +01:00
tie->layout();
}
2016-01-04 14:48:58 +01:00
for (Spanner* sp : n->spannerFor())
sp->layout();
}
}
}
}
rebuildBspTree();
}
2013-07-23 18:49:26 +02:00
//---------------------------------------------------------
2016-01-04 14:48:58 +01:00
// hideEmptyStaves
2013-07-23 18:49:26 +02:00
//---------------------------------------------------------
2016-01-04 14:48:58 +01:00
void Score::hideEmptyStaves(System* system, bool isFirstSystem)
2013-07-23 18:49:26 +02:00
{
int staves = _staves.size();
2016-01-04 14:48:58 +01:00
int staffIdx = 0;
bool systemIsEmpty = true;
for (Staff* staff : _staves) {
SysStaff* ss = system->staff(staffIdx);
// bool oldShow = ss->show();
2016-01-04 14:48:58 +01:00
Staff::HideMode hideMode = staff->hideWhenEmpty();
2016-01-04 14:48:58 +01:00
if (hideMode == Staff::HideMode::ALWAYS
2018-03-27 15:36:00 +02:00
|| (styleB(Sid::hideEmptyStaves)
2016-01-04 14:48:58 +01:00
&& (staves > 1)
2018-03-27 15:36:00 +02:00
&& !(isFirstSystem && styleB(Sid::dontHideStavesInFirstSystem))
2016-01-04 14:48:58 +01:00
&& hideMode != Staff::HideMode::NEVER)) {
bool hideStaff = true;
for (MeasureBase* m : system->measures()) {
2016-02-06 11:41:16 +01:00
if (!m->isMeasure())
2016-01-04 14:48:58 +01:00
continue;
2016-02-17 14:54:23 +01:00
Measure* measure = toMeasure(m);
2016-01-04 14:48:58 +01:00
if (!measure->isMeasureRest(staffIdx)) {
hideStaff = false;
2012-05-26 14:26:10 +02:00
break;
}
2012-05-26 14:26:10 +02:00
}
2016-01-04 14:48:58 +01:00
// 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;
2016-02-04 17:06:32 +01:00
foreach (MeasureBase* mb, system->measures()) {
2016-02-06 11:41:16 +01:00
if (!mb->isMeasure())
2016-01-04 14:48:58 +01:00
continue;
2016-02-17 14:54:23 +01:00
Measure* m = toMeasure(mb);
2017-03-08 13:12:26 +01:00
for (Segment* s = m->first(SegmentType::ChordRest); s; s = s->next(SegmentType::ChordRest)) {
2016-01-04 14:48:58 +01:00
for (int voice = 0; voice < VOICES; ++voice) {
2016-02-04 17:06:32 +01:00
ChordRest* cr = s->cr(st * VOICES + voice);
2016-02-06 11:41:16 +01:00
if (cr == 0 || cr->isRest())
2016-01-04 14:48:58 +01:00
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())
2016-01-04 14:48:58 +01:00
systemIsEmpty = false;
}
else {
systemIsEmpty = false;
ss->setShow(true);
2016-01-04 14:48:58 +01:00
}
2013-10-28 15:52:32 +01:00
2016-01-04 14:48:58 +01:00
++staffIdx;
}
if (systemIsEmpty) {
for (Staff* staff : _staves) {
SysStaff* ss = system->staff(staff->idx());
if (staff->showIfEmpty() && !ss->show()) {
ss->setShow(true);
systemIsEmpty = false;
2013-09-20 17:21:12 +02:00
}
2016-01-04 14:48:58 +01:00
}
}
// dont allow a complete empty system
if (systemIsEmpty) {
Staff* staff = _staves.front();
SysStaff* ss = system->staff(staff->idx());
ss->setShow(true);
}
2016-01-04 14:48:58 +01:00
}
2013-09-20 17:21:12 +02:00
2016-01-04 14:48:58 +01:00
//---------------------------------------------------------
// connectTies
/// Rebuild tie connections.
//---------------------------------------------------------
2014-05-31 12:03:21 +02:00
2016-01-04 14:48:58 +01:00
void Score::connectTies(bool silent)
{
int tracks = nstaves() * VOICES;
Measure* m = firstMeasure();
if (!m)
return;
2017-03-08 13:12:26 +01:00
SegmentType st = SegmentType::ChordRest;
2016-01-04 14:48:58 +01:00
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())
2016-01-04 14:48:58 +01:00
continue;
Chord* c = toChord(e);
2016-01-04 14:48:58 +01:00
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(), i, _mscVersion);
delete tie;
n->setTieFor(0);
}
}
2016-01-04 14:48:58 +01:00
else {
tie->setEndNote(nnote);
nnote->setTieBack(tie);
}
}
2016-01-04 14:48:58 +01:00
// connect a glissando without initial note (old glissando format)
for (Spanner* spanner : n->spannerBack()) {
2016-02-06 11:41:16 +01:00
if (spanner->isGlissando() && !spanner->startElement()) {
2016-01-04 14:48:58 +01:00
Note* initialNote = Glissando::guessInitialNote(n->chord());
n->removeSpannerBack(spanner);
2016-02-15 12:23:28 +01:00
if (initialNote) {
2016-01-04 14:48:58 +01:00
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);
2014-05-31 12:03:21 +02:00
}
2013-09-19 17:17:22 +02:00
else {
2016-01-04 14:48:58 +01:00
delete spanner;
2013-09-19 17:17:22 +02:00
}
}
}
2016-01-04 14:48:58 +01:00
// spanner with no end element can happen during copy/paste
for (Spanner* spanner : n->spannerFor()) {
if (spanner->endElement() == nullptr) {
n->removeSpannerFor(spanner);
delete spanner;
2013-10-30 14:21:08 +01:00
}
}
}
2016-01-04 14:48:58 +01:00
// 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)) {
2016-10-20 11:32:07 +02:00
Chord* nc = toChord(ls->element(i));
2016-01-04 14:48:58 +01:00
if (nc == 0)
continue;
2016-02-06 11:41:16 +01:00
if (!nc->isChord())
2016-01-04 14:48:58 +01:00
qDebug("cannot connect tremolo");
else {
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);
2013-10-30 14:21:08 +01:00
}
2016-01-04 14:48:58 +01:00
break;
2013-10-25 12:17:42 +02:00
}
}
2014-08-17 12:41:44 +02:00
}
2012-05-26 14:26:10 +02:00
}
}
2016-02-10 13:40:34 +01:00
//---------------------------------------------------------
// checkDivider
//---------------------------------------------------------
static void checkDivider(bool left, System* s, qreal yOffset)
2016-02-10 13:40:34 +01:00
{
SystemDivider* divider = left ? s->systemDividerLeft() : s->systemDividerRight();
2018-03-27 15:36:00 +02:00
if (s->score()->styleB(left ? Sid::dividerLeft : Sid::dividerRight)) {
2016-02-10 13:40:34 +01:00
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) {
2018-03-27 15:36:00 +02:00
divider->rypos() += s->score()->styleD(Sid::dividerLeftY) * SPATIUM20;
divider->rxpos() = s->score()->styleD(Sid::dividerLeftX) * SPATIUM20;
}
else {
2018-03-27 15:36:00 +02:00
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;
}
2016-02-10 13:40:34 +01:00
}
else if (divider) {
if (divider->generated()) {
2016-02-10 13:40:34 +01:00
s->remove(divider);
delete divider;
}
else
s->score()->undoRemoveElement(divider);
2016-02-10 13:40:34 +01:00
}
}
2016-01-04 14:48:58 +01:00
//---------------------------------------------------------
// layoutPage
2016-03-24 12:39:18 +01:00
// restHeight - vertical space which has to be distributed
// between systems
2017-07-17 15:09:37 +02:00
// The algorithm tries to produce most equally spaced
// systems.
2016-01-04 14:48:58 +01:00
//---------------------------------------------------------
2016-03-24 12:39:18 +01:00
static void layoutPage(Page* page, qreal restHeight)
2016-01-04 14:48:58 +01:00
{
2017-07-17 15:09:37 +02:00
Score* score = page->score();
int nsystems = page->systems().size() - 1;
QList<System*> sList;
for (int i = 0; i < nsystems; ++i) {
2016-01-04 14:48:58 +01:00
System* s1 = page->systems().at(i);
System* s2 = page->systems().at(i+1);
2017-07-17 15:09:37 +02:00
s1->setDistance(s2->y() - s1->y());
2016-10-12 20:06:46 +02:00
if (s1->vbox() || s2->vbox() || s1->hasFixedDownDistance())
2016-01-04 14:48:58 +01:00
continue;
2017-07-17 15:09:37 +02:00
sList.push_back(s1);
2016-01-04 14:48:58 +01:00
}
2016-03-31 09:57:52 +02:00
2017-08-10 14:55:04 +02:00
if (sList.empty() || MScore::noVerticalStretch || score->layoutMode() == LayoutMode::SYSTEM) {
2016-03-24 12:39:18 +01:00
if (score->layoutMode() == LayoutMode::FLOAT) {
2016-01-04 14:48:58 +01:00
qreal y = restHeight * .5;
for (System* system : page->systems())
system->move(QPointF(0.0, y));
}
// remove system dividers
for (System* s : page->systems()) {
2016-02-10 13:40:34 +01:00
SystemDivider* sd = s->systemDividerLeft();
if (sd) {
s->remove(sd);
delete sd;
}
sd = s->systemDividerRight();
if (sd) {
s->remove(sd);
delete sd;
2012-05-26 14:26:10 +02:00
}
}
2016-01-04 14:48:58 +01:00
return;
}
2017-07-17 15:09:37 +02:00
std::sort(sList.begin(), sList.end(), [](System* a, System* b) { return a->distance() < b->distance(); });
2018-03-27 15:36:00 +02:00
qreal maxDist = score->styleP(Sid::maxSystemDistance);
2017-08-11 10:41:30 +02:00
2017-07-17 15:09:37 +02:00
qreal dist = sList[0]->distance();
2017-08-10 14:55:04 +02:00
for (int i = 1; i < sList.size(); ++i) {
2017-07-17 15:09:37 +02:00
qreal ndist = sList[i]->distance();
qreal fill = ndist - dist;
dist = ndist;
if (fill > 0.0) {
qreal totalFill = fill * i;
if (totalFill > restHeight) {
totalFill = restHeight;
fill = restHeight / i;
}
2017-07-17 15:09:37 +02:00
for (int k = 0; k < i; ++k) {
System* s = sList[k];
2017-08-11 10:41:30 +02:00
qreal d = s->distance() + fill;
if ((d - s->height()) > maxDist)
d = qMax(maxDist + s->height(), s->distance());
s->setDistance(d);
2017-07-17 15:09:37 +02:00
}
restHeight -= totalFill;
2017-08-10 14:55:04 +02:00
if (restHeight <= 0)
break;
}
}
if (restHeight > 0.0) {
qreal fill = restHeight / sList.size();
for (int i = 0; i < sList.size(); ++i) {
System* s = sList[i];
2017-08-11 10:41:30 +02:00
qreal d = s->distance() + fill;
if ((d - s->height()) > maxDist)
d = qMax(maxDist + s->height(), s->distance());
s->setDistance(d);
2016-01-04 14:48:58 +01:00
}
2017-07-17 15:09:37 +02:00
}
qreal y = page->systems().at(0)->y();
for (int i = 0; i < nsystems; ++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() || s1->hasFixedDownDistance())) {
qreal yOffset = s1->height() + (s1->distance()-s1->height()) * .5;
checkDivider(true, s1, yOffset);
checkDivider(false, s1, yOffset);
}
2016-01-04 14:48:58 +01:00
}
page->systems().back()->rypos() = y;
2016-01-04 14:48:58 +01:00
}
2014-10-22 17:17:26 +02:00
2016-01-04 14:48:58 +01:00
//---------------------------------------------------------
2016-05-02 13:41:41 +02:00
// Spring
2016-01-04 14:48:58 +01:00
//---------------------------------------------------------
2016-05-02 13:41:41 +02:00
struct Spring {
2016-01-04 14:48:58 +01:00
int seg;
qreal stretch;
qreal fix;
2016-05-02 13:41:41 +02:00
Spring(int i, qreal s, qreal f) : seg(i), stretch(s), fix(f) {}
2016-01-04 14:48:58 +01:00
};
2016-05-02 13:41:41 +02:00
typedef std::multimap<qreal, Spring, std::less<qreal> > SpringMap;
2014-10-21 18:57:43 +02:00
2016-01-04 14:48:58 +01:00
//---------------------------------------------------------
// sff2
// compute 1/Force for a given Extend
//---------------------------------------------------------
2016-05-02 13:41:41 +02:00
static qreal sff2(qreal width, qreal xMin, const SpringMap& springs)
2016-01-04 14:48:58 +01:00
{
2016-05-02 13:41:41 +02:00
if (width <= xMin)
2016-01-04 14:48:58 +01:00
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;
2016-05-02 13:41:41 +02:00
f = (width - xMin) / c;
2016-01-04 14:48:58 +01:00
++i;
if (i == springs.end() || f <= i->first)
2012-05-26 14:26:10 +02:00
break;
2016-01-04 14:48:58 +01:00
c += i->second.stretch;
}
return f;
}
2016-01-04 14:48:58 +01:00
//---------------------------------------------------------
// respace
//---------------------------------------------------------
2016-04-07 11:18:34 +02:00
void Score::respace(std::vector<ChordRest*>* elements)
2016-01-04 14:48:58 +01:00
{
ChordRest* cr1 = elements->front();
ChordRest* cr2 = elements->back();
int n = elements->size();
qreal x1 = cr1->segment()->pos().x();
qreal x2 = cr2->segment()->pos().x();
qreal width[n-1];
int ticksList[n-1];
int minTick = 100000;
for (int i = 0; i < n-1; ++i) {
ChordRest* cr = (*elements)[i];
2016-04-07 11:18:34 +02:00
ChordRest* ncr = (*elements)[i+1];
width[i] = cr->shape().minHorizontalDistance(ncr->shape());
ticksList[i] = cr->duration().ticks();
2016-01-04 14:48:58 +01:00
minTick = qMin(ticksList[i], minTick);
2012-05-26 14:26:10 +02:00
}
2016-01-04 14:48:58 +01:00
//---------------------------------------------------
// compute stretches
//---------------------------------------------------
2016-05-02 13:41:41 +02:00
SpringMap springs;
2016-01-04 14:48:58 +01:00
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;
2012-09-05 12:14:58 +02:00
2016-05-02 13:41:41 +02:00
springs.insert(std::pair<qreal, Spring>(d, Spring(i, str, w)));
2016-01-04 14:48:58 +01:00
minimum += w;
}
2012-05-26 14:26:10 +02:00
2016-01-04 14:48:58 +01:00
//---------------------------------------------------
// distribute stretch to elements
//---------------------------------------------------
2013-03-29 15:01:21 +01:00
2016-01-04 14:48:58 +01:00
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;
}
2013-03-29 15:01:21 +01:00
}
2016-01-04 14:48:58 +01:00
//---------------------------------------------------------
// getEmptyPage
//---------------------------------------------------------
2012-08-02 18:33:43 +02:00
2017-01-05 11:23:47 +01:00
void LayoutContext::getEmptyPage()
2016-01-04 14:48:58 +01:00
{
2017-01-05 11:23:47 +01:00
if (curPage >= score->npages()) {
Page* page = new Page(score);
page->setNo(score->pages().size());
score->pages().push_back(page);
2016-03-02 13:20:19 +01:00
}
else {
2017-01-05 11:23:47 +01:00
page = score->pages()[curPage];
2016-03-02 13:20:19 +01:00
}
2017-01-05 11:23:47 +01:00
page->setNo(curPage);
2016-01-04 14:48:58 +01:00
page->layout();
qreal x, y;
if (MScore::verticalOrientation()) {
x = 0.0;
2017-01-05 11:23:47 +01:00
y = (curPage == 0) ? 0.0 : score->pages()[curPage - 1]->pos().y() + page->height() + MScore::verticalPageGap;
2016-01-04 14:48:58 +01:00
}
else {
y = 0.0;
2017-01-05 11:23:47 +01:00
x = (curPage == 0) ? 0.0 : score->pages()[curPage - 1]->pos().x()
2016-01-04 14:48:58 +01:00
+ page->width()
2017-01-05 11:23:47 +01:00
+ (((curPage+score->pageNumberOffset()) & 1) ? MScore::horizontalPageGapOdd : MScore::horizontalPageGapEven);
2012-08-02 18:33:43 +02:00
}
2017-01-05 11:23:47 +01:00
++curPage;
2016-01-04 14:48:58 +01:00
page->setPos(x, y);
page->systems().clear();
}
2012-08-02 18:33:43 +02:00
2016-01-04 14:48:58 +01:00
//---------------------------------------------------------
// getNextSystem
//---------------------------------------------------------
2012-08-02 18:33:43 +02:00
2016-01-04 14:48:58 +01:00
System* Score::getNextSystem(LayoutContext& lc)
{
2016-02-06 11:41:16 +01:00
bool isVBox = lc.curMeasure->isVBox();
2016-01-04 14:48:58 +01:00
System* system;
2016-03-02 13:20:19 +01:00
if (lc.systemList.empty()) {
2016-01-04 14:48:58 +01:00
system = new System(this);
2016-03-02 13:20:19 +01:00
lc.systemOldMeasure = 0;
}
2016-01-04 14:48:58 +01:00
else {
system = lc.systemList.takeFirst();
2016-03-02 13:20:19 +01:00
lc.systemOldMeasure = system->measures().empty() ? 0 : system->measures().back();
2016-01-04 14:48:58 +01:00
system->clear(); // remove measures from system
}
2016-02-04 11:27:47 +01:00
_systems.append(system);
2016-01-04 14:48:58 +01:00
if (!isVBox) {
int nstaves = Score::nstaves();
for (int i = system->staves()->size(); i < nstaves; ++i)
system->insertStaff(i);
int dn = system->staves()->size() - nstaves;
for (int i = 0; i < dn; ++i)
system->removeStaff(system->staves()->size()-1);
}
return system;
2012-08-02 18:33:43 +02:00
}
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
2016-01-04 14:48:58 +01:00
// createMMRest
// create a multi measure rest from m to lm (inclusive)
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
2016-01-04 14:48:58 +01:00
void Score::createMMRest(Measure* m, Measure* lm, const Fraction& len)
2012-05-26 14:26:10 +02:00
{
2016-01-04 14:48:58 +01:00
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;
}
2016-01-04 14:48:58 +01:00
}
2016-01-04 14:48:58 +01:00
Measure* mmr = m->mmRest();
if (mmr) {
if (mmr->len() != len) {
2017-03-08 13:12:26 +01:00
Segment* s = mmr->findSegmentR(SegmentType::EndBarLine, mmr->ticks());
2016-01-04 14:48:58 +01:00
mmr->setLen(len);
if (s)
s->setTick(mmr->endTick());
}
}
else {
mmr = new Measure(this);
mmr->setLen(len);
mmr->setTick(m->tick());
mmr->setPageBreak(lm->pageBreak());
mmr->setLineBreak(lm->lineBreak());
2016-01-04 14:48:58 +01:00
undo(new ChangeMMRest(m, mmr));
}
mmr->setMMRestCount(n);
mmr->setNo(m->no());
2017-03-08 13:12:26 +01:00
Segment* ss = lm->findSegmentR(SegmentType::EndBarLine, lm->ticks());
if (ss) {
2017-03-08 13:12:26 +01:00
Segment* ds = mmr->undoGetSegment(SegmentType::EndBarLine, lm->endTick());
for (int staffIdx = 0; staffIdx < nstaves(); ++staffIdx) {
Element* e = ss->element(staffIdx * VOICES);
if (e) {
if (!ds->element(staffIdx * VOICES)) {
Element* ee = e->clone();
ee->setParent(ds);
undoAddElement(ee);
}
else {
BarLine* bd = toBarLine(ds->element(staffIdx * VOICES));
BarLine* bs = toBarLine(e);
if (bd->barLineType() != bs->barLineType()) {
2018-03-27 15:36:00 +02:00
bd->undoChangeProperty(Pid::BARLINE_TYPE, QVariant::fromValue(bs->barLineType()));
bd->undoChangeProperty(Pid::GENERATED, true);
}
}
}
}
}
2016-02-04 11:27:47 +01:00
mmr->setRepeatStart(m->repeatStart() || lm->repeatStart());
mmr->setRepeatEnd(m->repeatEnd() || lm->repeatEnd());
2016-01-04 14:48:58 +01:00
ElementList oldList = mmr->takeElements();
ElementList newList = lm->el();
for (Element* e : m->el()) {
2016-02-06 11:41:16 +01:00
if (e->isMarker())
2016-02-06 22:03:43 +01:00
newList.push_back(e);
2016-01-04 14:48:58 +01:00
}
for (Element* e : newList) {
bool found = false;
for (Element* ee : oldList) {
if (ee->type() == e->type()) {
mmr->add(ee);
2016-02-06 22:03:43 +01:00
auto i = std::find(oldList.begin(), oldList.end(), ee);
if (i != oldList.end())
oldList.erase(i);
2016-01-04 14:48:58 +01:00
found = true;
break;
}
}
if (!found)
mmr->add(e->clone());
}
for (Element* e : oldList)
delete e;
2017-03-08 13:12:26 +01:00
Segment* s = mmr->undoGetSegmentR(SegmentType::ChordRest, 0);
2016-01-04 14:48:58 +01:00
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->setDuration(mmr->len());
r->setTrack(track);
r->setParent(s);
undo(new AddElement(r));
}
}
//
// check for clefs
//
2017-03-08 13:12:26 +01:00
Segment* cs = lm->findSegmentR(SegmentType::Clef, lm->ticks());
Segment* ns = mmr->findSegment(SegmentType::Clef, lm->endTick());
2016-01-04 14:48:58 +01:00
if (cs) {
if (ns == 0)
2017-03-08 13:12:26 +01:00
ns = mmr->undoGetSegmentR(SegmentType::Clef, lm->ticks());
2016-01-04 14:48:58 +01:00
for (int staffIdx = 0; staffIdx < _staves.size(); ++staffIdx) {
int track = staffIdx * VOICES;
2016-10-20 11:32:07 +02:00
Clef* clef = toClef(cs->element(track));
2016-01-04 14:48:58 +01:00
if (clef) {
if (ns->element(track) == 0)
ns->add(clef->clone());
else {
//TODO: check if same clef
}
}
}
}
else if (ns)
undo(new RemoveElement(ns));
2016-01-04 14:48:58 +01:00
//
// check for time signature
//
2017-03-08 13:12:26 +01:00
cs = m->findSegmentR(SegmentType::TimeSig, 0);
ns = mmr->findSegment(SegmentType::TimeSig, m->tick());
2016-01-04 14:48:58 +01:00
if (cs) {
if (ns == 0)
2017-03-08 13:12:26 +01:00
ns = mmr->undoGetSegmentR(SegmentType::TimeSig, 0);
2016-01-04 14:48:58 +01:00
for (int staffIdx = 0; staffIdx < _staves.size(); ++staffIdx) {
int track = staffIdx * VOICES;
2016-10-20 11:32:07 +02:00
TimeSig* ts = toTimeSig(cs->element(track));
2016-01-04 14:48:58 +01:00
if (ts) {
2016-10-20 11:32:07 +02:00
TimeSig* nts = toTimeSig(ns->element(track));
2016-01-04 14:48:58 +01:00
if (!nts) {
nts = ts->clone();
nts->setParent(ns);
undo(new AddElement(nts));
}
else {
nts->setSig(ts->sig(), ts->timeSigType());
nts->layout();
}
2012-08-02 18:33:43 +02:00
}
2016-01-04 14:48:58 +01:00
}
}
else if (ns)
undo(new RemoveElement(ns));
//
// check for ambitus
//
2017-03-08 13:12:26 +01:00
cs = m->findSegmentR(SegmentType::Ambitus, 0);
ns = mmr->findSegment(SegmentType::Ambitus, m->tick());
if (cs) {
if (ns == 0)
2017-03-08 13:12:26 +01:00
ns = mmr->undoGetSegmentR(SegmentType::Ambitus, 0);
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)
undo(new RemoveElement(ns));
2016-01-04 14:48:58 +01:00
//
// check for key signature
//
2017-03-08 13:12:26 +01:00
cs = m->findSegmentR(SegmentType::KeySig, 0);
ns = mmr->findSegmentR(SegmentType::KeySig, 0);
2016-01-04 14:48:58 +01:00
if (cs) {
if (ns == 0)
2017-03-08 13:12:26 +01:00
ns = mmr->undoGetSegmentR(SegmentType::KeySig, 0);
2016-01-04 14:48:58 +01:00
for (int staffIdx = 0; staffIdx < _staves.size(); ++staffIdx) {
int track = staffIdx * VOICES;
2016-10-20 11:32:07 +02:00
KeySig* ts = toKeySig(cs->element(track));
KeySig* nts = toKeySig(ns->element(track));
2016-01-04 14:48:58 +01:00
if (ts) {
if (!nts) {
KeySig* nks = ts->clone();
nks->setParent(ns);
undo(new AddElement(nks));
2012-05-26 14:26:10 +02:00
}
2016-01-04 14:48:58 +01:00
else {
if (!(nts->keySigEvent() == ts->keySigEvent())) {
undo(new ChangeKeySig(nts, ts->keySigEvent(), nts->showCourtesy()));
2012-05-26 14:26:10 +02:00
}
}
}
}
}
2016-02-06 22:03:43 +01:00
else if (ns && ns->empty())
2016-01-04 14:48:58 +01:00
undo(new RemoveElement(ns));
2012-05-26 14:26:10 +02:00
2016-01-04 14:48:58 +01:00
//
// check for rehearsal mark etc.
//
2017-03-08 13:12:26 +01:00
cs = m->findSegmentR(SegmentType::ChordRest, 0);
2016-01-04 14:48:58 +01:00
if (cs) {
for (Element* e : cs->annotations()) {
2016-02-10 18:35:35 +01:00
if (!(e->isRehearsalMark() || e->isTempoText() || e->isHarmony() || e->isStaffText()))
2016-01-04 14:48:58 +01:00
continue;
2012-05-26 14:26:10 +02:00
2016-01-04 14:48:58 +01:00
bool found = false;
for (Element* ee : s->annotations()) {
if (ee->type() == e->type() && ee->track() == e->track()) {
found = true;
break;
}
}
if (!found) {
Element* ne = e->linkedClone();
ne->setParent(s);
undo(new AddElement(ne));
}
}
}
2016-01-04 14:48:58 +01:00
for (Element* e : s->annotations()) {
2016-02-10 18:35:35 +01:00
if (!(e->isRehearsalMark() || e->isTempoText() || e->isHarmony() || e->isStaffText()))
2016-01-04 14:48:58 +01:00
continue;
bool found = false;
for (Element* ee : cs->annotations()) {
if (ee->type() == e->type() && ee->track() == e->track()) {
found = true;
break;
}
}
if (!found)
undo(new RemoveElement(e));
}
MeasureBase* nm = _showVBox ? lm->next() : lm->nextMeasure();
mmr->setNext(nm);
mmr->setPrev(m->prev());
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
2016-01-04 14:48:58 +01:00
// validMMRestMeasure
// return true if this might be a measure in a
// multi measure rest
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
2016-01-04 14:48:58 +01:00
static bool validMMRestMeasure(Measure* m)
2012-05-26 14:26:10 +02:00
{
2016-01-04 14:48:58 +01:00
if (m->irregular())
return false;
int n = 0;
for (Segment* s = m->first(); s; s = s->next()) {
for (Element* e : s->annotations()) {
2016-02-10 18:35:35 +01:00
if (!(e->isRehearsalMark() || e->isTempoText() || e->isHarmony() || e->isStaffText()))
2016-01-04 14:48:58 +01:00
return false;
}
2016-03-02 13:20:19 +01:00
if (s->isChordRestType()) {
2016-01-04 14:48:58 +01:00
bool restFound = false;
2016-12-12 14:55:35 +01:00
int tracks = m->score()->ntracks();
2016-01-04 14:48:58 +01:00
for (int track = 0; track < tracks; ++track) {
if ((track % VOICES) == 0 && !m->score()->staff(track/VOICES)->show()) {
track += VOICES-1;
continue;
}
2016-01-04 14:48:58 +01:00
if (s->element(track)) {
2016-12-12 14:55:35 +01:00
if (!s->element(track)->isRest())
2016-01-04 14:48:58 +01:00
return false;
restFound = true;
2014-05-15 13:42:03 +02:00
}
}
2018-01-17 13:25:23 +01:00
for (Element* e : s->annotations()) {
if (e->isFermata())
return false;
}
2016-01-04 14:48:58 +01:00
if (restFound)
++n;
// measure is not empty if there is more than one rest
if (n > 1)
return false;
2012-05-26 14:26:10 +02:00
}
}
2016-01-04 14:48:58 +01:00
return true;
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
2016-01-04 14:48:58 +01:00
// breakMultiMeasureRest
// return true if this measure should start a new
// multi measure rest
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
2016-01-04 14:48:58 +01:00
static bool breakMultiMeasureRest(Measure* m)
2012-05-26 14:26:10 +02:00
{
2016-01-04 14:48:58 +01:00
if (m->breakMultiMeasureRest())
return true;
2016-02-04 11:27:47 +01:00
if (m->repeatStart()
|| (m->prevMeasure() && m->prevMeasure()->repeatEnd())
2016-01-04 14:48:58 +01:00
|| (m->prevMeasure() && (m->prevMeasure()->sectionBreak())))
return true;
2016-01-04 14:48:58 +01:00
auto sl = m->score()->spannerMap().findOverlapping(m->tick(), m->endTick());
for (auto i : sl) {
Spanner* s = i.value;
2016-02-06 11:41:16 +01:00
if (s->isVolta() && (s->tick() == m->tick() || s->tick2() == m->tick()))
2016-01-04 14:48:58 +01:00
return true;
}
2014-10-17 03:43:54 +02:00
2016-01-04 14:48:58 +01:00
// break for marker in this measure
for (Element* e : m->el()) {
2016-02-06 11:41:16 +01:00
if (e->isMarker()) {
2016-02-17 14:54:23 +01:00
Marker* mark = toMarker(e);
2017-01-16 20:51:12 +01:00
if (!(mark->align() & Align::RIGHT))
2016-01-04 14:48:58 +01:00
return true;
}
}
2012-05-26 14:26:10 +02:00
2016-01-04 14:48:58 +01:00
// break for marker & jump in previous measure
Measure* pm = m->prevMeasure();
if (pm) {
for (Element* e : pm->el()) {
2016-02-06 11:41:16 +01:00
if (e->isJump())
2016-01-04 14:48:58 +01:00
return true;
2016-02-06 11:41:16 +01:00
else if (e->isMarker()) {
2016-10-20 11:32:07 +02:00
Marker* mark = toMarker(e);
2017-01-16 20:51:12 +01:00
if (mark->align() & Align::RIGHT)
2016-01-04 14:48:58 +01:00
return true;
2012-05-26 14:26:10 +02:00
}
}
2016-01-04 14:48:58 +01:00
}
// break for end of volta
auto l = m->score()->spannerMap().findOverlapping(m->tick(), m->endTick());
for (auto isp : l) {
Spanner* s = isp.value;
2016-02-06 11:41:16 +01:00
if (s->isVolta() && (s->tick2() == m->endTick()))
2016-01-04 14:48:58 +01:00
return true;
}
for (Segment* s = m->first(); s; s = s->next()) {
for (Element* e : s->annotations()) {
2016-02-06 11:41:16 +01:00
if (e->isRehearsalMark() ||
e->isTempoText() ||
((e->isHarmony() || e->isStaffText()) && (e->systemFlag() || m->score()->staff(e->staffIdx())->show())))
2016-01-04 14:48:58 +01:00
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;
2016-03-02 13:20:19 +01:00
if (s->isStartRepeatBarLineType())
2016-01-04 14:48:58 +01:00
return true;
2017-03-08 13:12:26 +01:00
if (s->isType(SegmentType::KeySig | SegmentType::TimeSig) && m->tick())
2016-01-04 14:48:58 +01:00
return true;
2016-03-02 13:20:19 +01:00
if (s->isClefType()) {
2016-01-04 14:48:58 +01:00
if (s->tick() != m->endTick() && m->tick())
return true;
2012-05-26 14:26:10 +02:00
}
}
}
2016-01-04 14:48:58 +01:00
if (pm) {
2017-03-08 13:12:26 +01:00
Segment* s = pm->findSegmentR(SegmentType::EndBarLine, pm->ticks());
if (s) {
for (int staffIdx = 0; staffIdx < s->score()->nstaves(); ++staffIdx) {
2016-02-17 14:54:23 +01:00
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;
}
}
}
2017-03-08 13:12:26 +01:00
if (pm->findSegment(SegmentType::Clef, m->tick()))
2016-01-04 14:48:58 +01:00
return true;
2014-10-17 03:43:54 +02:00
}
2016-01-04 14:48:58 +01:00
return false;
2012-05-26 14:26:10 +02:00
}
2016-04-01 09:28:40 +02:00
//---------------------------------------------------------
// adjustMeasureNo
//---------------------------------------------------------
int LayoutContext::adjustMeasureNo(MeasureBase* m)
{
measureNo += m->noOffset();
m->setNo(measureNo);
if (!m->irregular()) // dont count measure
++measureNo;
if (m->sectionBreak())
measureNo = 0;
return measureNo;
}
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
2016-05-19 13:15:34 +02:00
// createBeams
// helper function
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
2016-05-19 13:15:34 +02:00
void Score::createBeams(Measure* measure)
2012-05-26 14:26:10 +02:00
{
2018-03-27 15:36:00 +02:00
bool crossMeasure = styleB(Sid::crossMeasureValues);
2012-05-26 14:26:10 +02:00
2016-01-04 14:48:58 +01:00
for (int track = 0; track < ntracks(); ++track) {
Staff* stf = staff(track2staff(track));
2016-01-04 14:48:58 +01:00
// dont compute beams for invisible staffs and tablature without stems
2016-12-13 13:16:17 +01:00
if (!stf->show() || (stf->isTabStaff(measure->tick()) && stf->staffType(measure->tick())->slashStyle()))
2012-05-26 14:26:10 +02:00
continue;
2016-01-04 14:48:58 +01:00
ChordRest* a1 = 0; // start of (potential) beam
Beam* beam = 0; // current beam
Beam::Mode bm = Beam::Mode::AUTO;
ChordRest* prev = 0;
2016-02-11 18:20:16 +01:00
bool checkBeats = false;
Fraction stretch = 1;
2016-01-04 14:48:58 +01:00
QHash<int, TDuration> beatSubdivision;
2016-02-11 18:20:16 +01:00
// 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() : 1;
2017-03-08 13:12:26 +01:00
const SegmentType st = SegmentType::ChordRest;
2016-02-11 18:20:16 +01:00
if (ts && ts->denominator() == 4) {
checkBeats = true;
for (Segment* s = measure->first(st); s; s = s->next(st)) {
2016-10-20 11:32:07 +02:00
ChordRest* mcr = toChordRest(s->element(track));
2016-02-11 18:20:16 +01:00
if (mcr == 0)
continue;
int beat = ((mcr->rtick() * stretch.numerator()) / stretch.denominator()) / MScore::division;
if (beatSubdivision.contains(beat))
beatSubdivision[beat] = qMin(beatSubdivision[beat], mcr->durationType());
else
beatSubdivision[beat] = mcr->durationType();
}
}
2012-05-26 14:26:10 +02:00
2016-01-04 14:48:58 +01:00
for (Segment* segment = measure->first(st); segment; segment = segment->next(st)) {
2016-02-06 11:41:16 +01:00
ChordRest* cr = segment->cr(track);
2016-01-04 14:48:58 +01:00
if (cr == 0)
continue;
2016-08-17 12:52:35 +02:00
for (Lyrics* l : cr->lyrics()) {
2016-02-06 11:41:16 +01:00
if (l)
l->layout();
}
2012-05-26 14:26:10 +02:00
2016-01-04 14:48:58 +01:00
// handle grace notes and cross-measure beaming
2016-02-06 11:41:16 +01:00
if (cr->isChord()) {
2016-02-17 14:54:23 +01:00
Chord* chord = toChord(cr);
2016-01-04 14:48:58 +01:00
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);
}
2012-05-26 14:26:10 +02:00
2016-01-04 14:48:58 +01:00
// get defaults from time signature properties
2016-02-11 18:20:16 +01:00
bm = Groups::endBeam(cr, prev);
// 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()) {
int tick = (cr->rtick() * stretch.numerator()) / stretch.denominator();
// check if on the beat
if (tick % MScore::division == 0) {
int beat = tick / 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);
}
}
}
2016-01-04 14:48:58 +01:00
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)
2016-02-11 18:20:16 +01:00
2016-01-04 14:48:58 +01:00
if (cr->durationType().hooks() > 0 && cr->crossMeasure() == CrossMeasure::SECOND)
bm = Beam::Mode::NONE;
2012-05-26 14:26:10 +02:00
2016-01-04 14:48:58 +01:00
if ((cr->durationType().type() <= TDuration::DurationType::V_QUARTER) || (bm == Beam::Mode::NONE)) {
if (beam) {
beam->layout1();
beam = 0;
}
if (a1) {
a1->removeDeleteBeam(false);
a1 = 0;
}
cr->removeDeleteBeam(false);
continue;
}
2012-05-26 14:26:10 +02:00
2016-01-04 14:48:58 +01:00
if (beam) {
bool beamEnd = (bm == Beam::Mode::BEGIN);
if (!beamEnd) {
cr->removeDeleteBeam(true);
beam->add(cr);
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->removeDeleteBeam(true);
beam->add(a1);
}
cr->removeDeleteBeam(true);
beam->add(cr);
a1 = 0;
}
}
2015-03-10 23:22:00 +01:00
}
2016-01-04 14:48:58 +01:00
if (beam)
beam->layout1();
else if (a1)
a1->removeDeleteBeam(false);
2015-03-10 23:22:00 +01:00
}
2016-05-19 13:15:34 +02:00
}
2017-12-20 10:49:54 +01:00
//---------------------------------------------------------
// layoutDrumsetChord
//---------------------------------------------------------
static void layoutDrumsetChord(Chord* c, const Drumset* drumset, 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()) {
2018-03-27 15:36:00 +02:00
note->undoChangeProperty(Pid::HEAD_GROUP, int(drumset->noteHead(pitch)));
2017-12-20 10:49:54 +01:00
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;
}
}
}
2016-05-19 13:15:34 +02:00
//---------------------------------------------------------
// 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;
2016-10-20 11:32:07 +02:00
int mno = lc.adjustMeasureNo(lc.curMeasure);
2016-05-19 13:15:34 +02:00
2016-10-20 11:32:07 +02:00
if (lineMode()) {
2016-05-19 13:15:34 +02:00
while (lc.curMeasure && lc.curMeasure->isVBox()) {
lc.curMeasure = lc.nextMeasure;
if (lc.curMeasure)
lc.nextMeasure = lc.curMeasure->next();
2016-05-19 13:15:34 +02:00
}
if (!lc.curMeasure)
return;
}
2016-10-20 11:32:07 +02:00
else if (lc.curMeasure->isMeasure()) {
2018-03-27 15:36:00 +02:00
if (score()->styleB(Sid::createMultiMeasureRests)) {
2016-10-20 11:32:07 +02:00
Measure* m = toMeasure(lc.curMeasure);
Measure* nm = m;
Measure* lm = nm;
int n = 0;
Fraction len;
2016-05-19 13:15:34 +02:00
2016-10-20 11:32:07 +02:00
lc.measureNo = m->no();
2016-05-19 13:15:34 +02:00
2016-10-20 11:32:07 +02:00
while (validMMRestMeasure(nm)) {
MeasureBase* mb = _showVBox ? nm->next() : nm->nextMeasure();
if (breakMultiMeasureRest(nm) && n)
break;
lc.adjustMeasureNo(nm);
++n;
len += nm->len();
lm = nm;
if (!(mb && mb->isMeasure()))
break;
nm = toMeasure(mb);
}
2018-03-27 15:36:00 +02:00
if (n >= styleI(Sid::minEmptyMeasures)) {
2016-10-20 11:32:07 +02:00
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;
}
2016-05-19 13:15:34 +02:00
}
2016-10-20 11:32:07 +02:00
else if (toMeasure(lc.curMeasure)->isMMRest()) {
qDebug("mmrest: no %d += %d", lc.measureNo, toMeasure(lc.curMeasure)->mmRestCount());
lc.measureNo += toMeasure(lc.curMeasure)->mmRestCount() - 1;
2016-05-19 13:15:34 +02:00
}
}
if (!lc.curMeasure->isMeasure()) {
lc.curMeasure->setTick(lc.tick);
return;
}
//-----------------------------------------
// process one measure
//-----------------------------------------
Measure* measure = toMeasure(lc.curMeasure);
measure->moveTicks(lc.tick - measure->tick());
//
// implement section break rest
//
if (measure->sectionBreak() && measure->pause() != 0.0)
setPause(measure->endTick(), measure->pause());
2016-05-19 13:15:34 +02:00
//
// calculate accidentals and note lines,
// create stem and set stem direction
//
for (int staffIdx = 0; staffIdx < score()->nstaves(); ++staffIdx) {
2016-12-23 12:05:18 +01:00
Staff* staff = Score::staff(staffIdx);
const Drumset* drumset = staff->part()->instrument()->useDrumset() ? staff->part()->instrument()->drumset() : 0;
2016-05-19 13:15:34 +02:00
AccidentalState as; // list of already set accidentals for this measure
2017-07-06 09:59:29 +02:00
as.init(staff->keySigEvent(measure->tick()), staff->clef(measure->tick()));
2016-05-19 13:15:34 +02:00
for (Segment& segment : measure->segments()) {
if (segment.isKeySigType()) {
KeySig* ks = toKeySig(segment.element(staffIdx * VOICES));
if (!ks)
continue;
2017-02-13 12:42:54 +01:00
int tick = segment.tick();
as.init(staff->keySigEvent(tick), staff->clef(tick));
2016-05-19 13:15:34 +02:00
ks->layout();
}
else if (segment.isChordRestType()) {
2017-12-20 10:49:54 +01:00
StaffType* st = staff->staffType(segment.tick());
int track = staffIdx * VOICES;
int endTrack = track + VOICES;
2016-12-23 12:05:18 +01:00
2016-05-19 13:15:34 +02:00
for (int t = track; t < endTrack; ++t) {
ChordRest* cr = segment.cr(t);
2017-07-06 09:59:29 +02:00
if (!cr)
continue;
qreal m = staff->mag(segment.tick());
if (cr->small())
2018-03-27 15:36:00 +02:00
m *= score()->styleD(Sid::smallNoteMag);
2017-07-06 09:59:29 +02:00
if (cr->isChord()) {
Chord* chord = toChord(cr);
chord->cmdUpdateNotes(&as);
for (Chord* c : chord->graceNotes()) {
2018-03-27 15:36:00 +02:00
c->setMag(m * score()->styleD(Sid::graceNoteMag));
2017-07-06 09:59:29 +02:00
c->computeUp();
if (c->stemDirection() != Direction::AUTO)
c->setUp(c->stemDirection() == Direction::UP);
else
c->setUp(!(t % 2));
2017-12-20 10:49:54 +01:00
if (drumset)
layoutDrumsetChord(c, drumset, st, spatium());
2017-07-06 09:59:29 +02:00
c->layoutStem1();
}
2017-12-20 10:49:54 +01:00
if (drumset)
layoutDrumsetChord(chord, drumset, st, spatium());
2017-07-06 09:59:29 +02:00
chord->computeUp();
chord->layoutStem1(); // create stems needed to calculate spacing
// stem direction can change later during beam processing
2016-12-23 12:05:18 +01:00
}
2017-07-06 09:59:29 +02:00
cr->setMag(m);
2016-05-19 13:15:34 +02:00
}
}
else if (segment.isClefType()) {
Element* e = segment.element(staffIdx * VOICES);
if (e) {
toClef(e)->setSmall(true);
e->layout();
}
}
2017-03-08 13:12:26 +01:00
else if (segment.isType(SegmentType::TimeSig | SegmentType::Ambitus)) {
2016-05-19 13:15:34 +02:00
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()) {
2016-11-16 13:32:22 +01:00
if (segment.isChordRestType()) {
2016-05-19 13:15:34 +02:00
layoutChords1(&segment, staffIdx);
2016-11-16 13:32:22 +01:00
for (int voice = 0; voice < VOICES; ++voice) {
ChordRest* cr = segment.cr(staffIdx * VOICES + voice);
2018-01-17 13:25:23 +01:00
if (cr && cr->isChord())
toChord(cr)->layoutArticulations();
2016-11-16 13:32:22 +01:00
}
}
2016-05-19 13:15:34 +02:00
}
}
for (Segment& segment : measure->segments()) {
if (segment.isBreathType()) {
qreal length = 0.0;
int tick = segment.tick();
// find longest pause
for (int i = 0, n = ntracks(); i < n; ++i) {
2016-06-02 11:02:18 +02:00
Element* e = segment.element(i);
if (e && e->isBreath()) {
Breath* b = toBreath(e);
b->layout();
2016-05-19 13:15:34 +02:00
length = qMax(length, b->pause());
}
2016-05-19 13:15:34 +02:00
}
if (length != 0.0)
setPause(tick, length);
}
else if (segment.isTimeSigType()) {
for (int staffIdx = 0; staffIdx < _staves.size(); ++staffIdx) {
TimeSig* ts = toTimeSig(segment.element(staffIdx * VOICES));
if (ts)
staff(staffIdx)->addTimeSig(ts);
}
}
else if (isMaster() && segment.isChordRestType()) {
for (Element* e : segment.annotations()) {
2018-01-16 13:38:17 +01:00
if (!(e->isTempoText()
|| e->isDynamic()
|| e->isFermata()
|| e->isRehearsalMark()
|| e->isFretDiagram()
|| e->isStaffText()
|| e->isFiguredBass()))
2016-05-19 13:15:34 +02:00
e->layout();
}
2016-06-24 11:58:22 +02:00
// TODO, this is not going to work, we just cleaned the tempomap
// it breaks the test midi/testBaroqueOrnaments.mscx where first note has stretch 2
// Also see fixTicks
2016-05-19 13:15:34 +02:00
qreal stretch = 0.0;
2018-01-17 13:25:23 +01:00
for (Element* e : segment.annotations()) {
if (e->isFermata())
stretch = qMax(stretch, toFermata(e)->timeStretch());
}
if (stretch != 0.0 && stretch != 1.0) {
qreal otempo = tempomap()->tempo(segment.tick());
qreal ntempo = otempo / stretch;
setTempo(segment.tick(), ntempo);
int etick = segment.tick() + segment.ticks() - 1;
auto e = tempomap()->find(etick);
if (e == tempomap()->end())
setTempo(etick, otempo);
2016-05-19 13:15:34 +02:00
}
}
else if (segment.isChordRestType()) {
// chord symbols need to be layouted in parts too
for (Element* e : segment.annotations()) {
if (e->isHarmony())
e->layout();
}
}
2016-05-19 13:15:34 +02:00
}
// update time signature map
// create event if measure len and time signature are different
// even if they are equivalent 4/4 vs 2/2
// also check if nominal time signature has changed
2016-05-19 13:15:34 +02:00
if (isMaster() && ((!measure->len().identical(lc.sig)
&& measure->len() != lc.sig * measure->mmRestCount())
2016-10-18 15:41:00 +02:00
|| (lc.prevMeasure && lc.prevMeasure->isMeasure()
&& !measure->timesig().identical(toMeasure(lc.prevMeasure)->timesig()))))
{
if (measure->isMMRest())
lc.sig = measure->mmRestFirst()->len();
else
lc.sig = measure->len();
2016-05-19 13:15:34 +02:00
sigmap()->add(lc.tick, SigEvent(lc.sig, measure->timesig(), measure->no()));
}
2016-02-06 11:41:16 +01:00
2017-03-08 13:12:26 +01:00
Segment* seg = measure->findSegmentR(SegmentType::StartRepeatBarLine, 0);
2016-10-18 15:41:00 +02:00
if (measure->repeatStart()) {
if (!seg)
2017-03-08 13:12:26 +01:00
seg = measure->getSegmentR(SegmentType::StartRepeatBarLine, 0);
measure->barLinesSetSpan(seg); // this also creates necessary barlines
2016-10-18 15:41:00 +02:00
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);
2016-02-11 18:20:16 +01:00
for (Segment& s : measure->segments()) {
2016-02-15 12:23:28 +01:00
// DEBUG: relayout grace notes as beaming/flags may have changed
2016-03-02 13:20:19 +01:00
if (s.isChordRestType()) {
2016-02-15 12:23:28 +01:00
for (Element* e : s.elist()) {
if (e && e->isChord()) {
2016-05-19 13:15:34 +02:00
Chord* chord = toChord(e);
chord->layout();
if (chord->tremolo()) // debug
chord->tremolo()->layout();
2016-02-15 12:23:28 +01:00
}
}
}
2016-03-02 13:20:19 +01:00
else if (s.isEndBarLineType())
2016-01-04 14:48:58 +01:00
continue;
2016-08-17 16:40:01 +02:00
s.createShapes();
2012-05-26 14:26:10 +02:00
}
2016-05-19 13:15:34 +02:00
2016-01-04 14:48:58 +01:00
lc.tick += measure->ticks();
}
2012-05-26 14:26:10 +02:00
2016-04-01 14:57:24 +02:00
//---------------------------------------------------------
// isTopBeam
//---------------------------------------------------------
static bool isTopBeam(ChordRest* cr)
{
if (cr->beam() && cr->beam()->elements().front() == cr) {
Beam* b = cr->beam();
bool movedUp = true;
for (ChordRest* cr1 : b->elements()) {
if (cr1->staffMove() >= 0) {
movedUp = false;
break;
}
}
if (!b->cross() && !movedUp)
return true;
}
return false;
}
2016-08-24 14:49:34 +02:00
//---------------------------------------------------------
// notTopBeam
//---------------------------------------------------------
2016-04-01 14:57:24 +02:00
static bool notTopBeam(ChordRest* cr)
{
if (cr->beam() && cr->beam()->elements().front() == cr) {
Beam* b = cr->beam();
if (b->cross())
return true;
bool movedUp = true;
for (ChordRest* cr1 : b->elements()) {
if (cr1->staffMove() >= 0) {
movedUp = false;
break;
}
}
if (movedUp)
return true;
}
return false;
}
//---------------------------------------------------------
2016-08-24 14:49:34 +02:00
// findLyricsMaxY
//---------------------------------------------------------
static qreal findLyricsMaxY(Segment& s, int staffIdx)
{
qreal yMax = 0.0;
if (!s.isChordRestType())
return yMax;
2017-03-20 20:28:10 +01:00
for (int voice = 0; voice < VOICES; ++voice) {
ChordRest* cr = s.cr(staffIdx * VOICES + voice);
2017-07-21 12:05:23 +02:00
if (cr && !cr->lyrics().empty()) {
2017-03-20 20:28:10 +01:00
Shape sh;
for (Lyrics* l : cr->lyrics()) {
if (l->autoplace() && l->placeBelow()) {
l->rUserYoffset() = 0.0;
2017-09-19 15:20:06 +02:00
// sh.add(l->bbox().translated(l->pos()));
sh.add(l->bbox().translated(l->pos() + s.pos()));
2017-03-20 20:28:10 +01:00
}
}
2018-03-27 15:36:00 +02:00
qreal lyricsMinTopDistance = s.score()->styleP(Sid::lyricsMinTopDistance);
2017-03-20 20:28:10 +01:00
for (Lyrics* l : cr->lyrics()) {
if (l->autoplace() && l->placeBelow()) {
2017-09-19 15:20:06 +02:00
// qreal y = s.staffShape(staffIdx).minVerticalDistance(sh);
qreal y = s.measure()->staffShape(staffIdx).minVerticalDistance(sh);
2017-03-20 20:28:10 +01:00
if (y > -lyricsMinTopDistance)
yMax = qMax(yMax, y + lyricsMinTopDistance);
}
}
}
}
return yMax;
}
static qreal findLyricsMaxY(Measure* m, int staffIdx)
{
qreal yMax = 0.0;
for (Segment& s : m->segments())
yMax = qMax(yMax, findLyricsMaxY(s, staffIdx));
return yMax;
}
2016-08-24 14:49:34 +02:00
//---------------------------------------------------------
// findLyricsMinY
//---------------------------------------------------------
static qreal findLyricsMinY(Segment& s, int staffIdx)
{
qreal yMin = 0.0;
if (!s.isChordRestType())
return yMin;
2017-03-20 20:28:10 +01:00
for (int voice = 0; voice < VOICES; ++voice) {
ChordRest* cr = s.cr(staffIdx * VOICES + voice);
2017-07-21 12:05:23 +02:00
if (cr && !cr->lyrics().empty()) {
2017-03-20 20:28:10 +01:00
Shape sh;
for (Lyrics* l : cr->lyrics()) {
if (l->autoplace() && l->placeAbove()) {
l->rUserYoffset() = 0.0;
2017-09-19 15:20:06 +02:00
sh.add(l->bbox().translated(l->pos() + s.pos()));
2017-03-20 20:28:10 +01:00
}
2016-08-24 14:49:34 +02:00
}
2018-03-27 15:36:00 +02:00
qreal lyricsMinTopDistance = s.score()->styleP(Sid::lyricsMinTopDistance);
2017-03-20 20:28:10 +01:00
for (Lyrics* l : cr->lyrics()) {
if (l->autoplace() && l->placeAbove()) {
2017-09-19 15:20:06 +02:00
qreal y = sh.minVerticalDistance(s.measure()->staffShape(staffIdx));
2017-03-20 20:28:10 +01:00
if (y > -lyricsMinTopDistance)
yMin = qMin(yMin, -y -lyricsMinTopDistance);
}
2016-08-24 14:49:34 +02:00
}
}
}
return yMin;
}
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;
2017-03-20 20:28:10 +01:00
for (int voice = 0; voice < VOICES; ++voice) {
ChordRest* cr = s.cr(staffIdx * VOICES + voice);
if (cr && !cr->lyrics().empty()) {
Shape sh;
2018-03-27 15:36:00 +02:00
qreal lyricsMinBottomDistance = s.score()->styleP(Sid::lyricsMinBottomDistance);
2017-03-20 20:28:10 +01:00
for (Lyrics* l : cr->lyrics()) {
if (l->autoplace() && l->placeBelow()) {
l->rUserYoffset() = yMax;
sh.add(l->bbox().translated(l->pos()+cr->pos()).adjusted(0.0, 0.0, 0.0, lyricsMinBottomDistance));
}
}
2017-03-20 20:28:10 +01:00
s.staffShape(staffIdx).add(sh);
s.measure()->staffShape(staffIdx).add(sh.translated(s.pos()));
}
}
}
static void applyLyricsMax(Measure* m, int staffIdx, qreal yMax)
{
for (Segment& s : m->segments())
applyLyricsMax(s, staffIdx, yMax);
}
2016-08-24 14:49:34 +02:00
//---------------------------------------------------------
// applyLyricsMin
//---------------------------------------------------------
static void applyLyricsMin(ChordRest* cr, int staffIdx, qreal yMin)
2016-08-24 14:49:34 +02:00
{
Shape sh;
2018-03-27 15:36:00 +02:00
qreal lyricsMinBottomDistance = cr->score()->styleP(Sid::lyricsMinBottomDistance);
for (Lyrics* l : cr->lyrics()) {
if (l->autoplace() && l->placeAbove()) {
l->rUserYoffset() = yMin;
sh.add(l->bbox().translated(l->pos()).adjusted(0.0, -lyricsMinBottomDistance, 0.0, 0.0));
2016-08-24 14:49:34 +02:00
}
}
cr->segment()->staffShape(staffIdx).add(sh);
2017-03-17 11:51:03 +01:00
cr->measure()->staffShape(staffIdx).add(sh.translated(cr->pos() + cr->segment()->pos()));
2016-08-24 14:49:34 +02:00
}
static void applyLyricsMin(Measure* m, int staffIdx, qreal yMin)
2016-08-24 14:49:34 +02:00
{
for (Segment& s : m->segments()) {
if (s.isChordRestType()) {
2017-03-20 20:28:10 +01:00
for (int voice = 0; voice < VOICES; ++voice) {
ChordRest* cr = s.cr(staffIdx * VOICES + voice);
if (cr)
applyLyricsMin(cr, staffIdx, yMin);
}
}
}
2016-08-24 14:49:34 +02:00
}
//---------------------------------------------------------
// restoreBeams
//---------------------------------------------------------
static void restoreBeams(Measure* m)
{
2017-03-08 13:12:26 +01:00
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)) {
cr->beam()->layout();
s->staffShape(cr->staffIdx()).add(cr->beam()->shape().translated(-(cr->segment()->pos()+m->pos())));
}
}
}
}
}
2017-09-19 15:20:06 +02:00
//---------------------------------------------------------
// layoutLyrics
//---------------------------------------------------------
void Score::layoutLyrics(System* system)
{
//
// vertical align lyrics
//
2018-03-27 15:36:00 +02:00
VerticalAlignRange ar = VerticalAlignRange(styleI(Sid::autoplaceVerticalAlignRange));
2017-09-19 15:20:06 +02:00
switch (ar) {
case VerticalAlignRange::MEASURE:
for (MeasureBase* mb : system->measures()) {
if (!mb->isMeasure())
continue;
Measure* m = toMeasure(mb);
for (int staffIdx = system->firstVisibleStaff(); staffIdx < nstaves(); staffIdx = system->nextVisibleStaff(staffIdx)) {
qreal yMax = findLyricsMaxY(m, staffIdx);
applyLyricsMax(m, staffIdx, yMax);
}
}
break;
case VerticalAlignRange::SYSTEM:
for (int staffIdx = system->firstVisibleStaff(); staffIdx < nstaves(); staffIdx = system->nextVisibleStaff(staffIdx)) {
qreal yMax = 0.0;
qreal yMin = 0.0;
for (MeasureBase* mb : system->measures()) {
if (!mb->isMeasure())
continue;
yMax = qMax(yMax, findLyricsMaxY(toMeasure(mb), staffIdx));
yMin = qMin(yMin, findLyricsMinY(toMeasure(mb), staffIdx));
}
for (MeasureBase* mb : system->measures()) {
if (!mb->isMeasure())
continue;
applyLyricsMax(toMeasure(mb), staffIdx, yMax);
applyLyricsMin(toMeasure(mb), staffIdx, yMin);
}
}
break;
case VerticalAlignRange::SEGMENT:
for (MeasureBase* mb : system->measures()) {
if (!mb->isMeasure())
continue;
Measure* m = toMeasure(mb);
for (int staffIdx = system->firstVisibleStaff(); staffIdx < nstaves(); staffIdx = system->nextVisibleStaff(staffIdx)) {
for (Segment& s : m->segments()) {
qreal yMax = findLyricsMaxY(s, staffIdx);
applyLyricsMax(s, staffIdx, yMax);
}
}
}
break;
}
// align lyrics line segments
std::vector<LyricsLineSegment*> ll;
for (SpannerSegment* ss : system->spannerSegments()) {
if (ss->isLyricsLineSegment()) {
LyricsLineSegment* lls = toLyricsLineSegment(ss);
lls->rUserYoffset() = lls->lyricsLine()->lyrics()->rUserYoffset();
}
}
2017-09-19 15:20:06 +02:00
}
//---------------------------------------------------------
// layoutTies
//---------------------------------------------------------
static void layoutTies(Chord* ch, System* system, int stick)
{
for (Note* note : ch->notes()) {
if (note->tieFor())
note->tieFor()->layoutFor(system);
if (note->tieBack()) {
Tie* tie = note->tieBack();
if (tie->startNote()->tick() < stick)
tie->layoutBack(system);
}
}
}
2017-09-19 15:20:06 +02:00
2018-01-03 09:59:50 +01:00
//---------------------------------------------------------
// processLines
//---------------------------------------------------------
static void processLines(System* system, std::vector<Spanner*> lines, bool align)
2018-01-03 09:59:50 +01:00
{
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) {
qreal y = segments[0]->userOff().y();
for (unsigned i = 1; i < segments.size(); ++i)
y = qMax(y, segments[i]->userOff().y());
for (auto ss : segments)
ss->rUserYoffset() = y;
}
2018-01-03 09:59:50 +01:00
//
// add shapes to staff shapes
//
for (MeasureBase* mb : system->measures()) {
if (!mb->isMeasure())
continue;
Measure* m = toMeasure(mb);
for (SpannerSegment* ss : segments) {
// spanner shape must be translated from system coordinate space
// to measure coordinate space
Shape* shape = &m->staffShape(ss->staffIdx());
shape->add(ss->shape().translated(ss->pos() - m->pos()));
}
}
}
2016-07-19 14:36:09 +02:00
//---------------------------------------------------------
2016-01-04 14:48:58 +01:00
// collectSystem
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
2016-01-04 14:48:58 +01:00
System* Score::collectSystem(LayoutContext& lc)
{
2017-01-05 11:23:47 +01:00
if (!lc.curMeasure)
2016-01-04 14:48:58 +01:00
return 0;
System* system = getNextSystem(lc);
2016-01-04 14:48:58 +01:00
system->setInstrumentNames(lc.startWithLongNames);
2014-12-30 15:14:05 +01:00
qreal minWidth = 0;
bool firstMeasure = true;
2016-12-14 09:56:16 +01:00
bool createHeader = false;
2018-03-27 15:36:00 +02:00
qreal systemWidth = styleD(Sid::pagePrintableWidth) * DPI;
2016-10-18 15:41:00 +02:00
system->setWidth(systemWidth);
2016-01-04 14:48:58 +01:00
while (lc.curMeasure) { // collect measure for system
System* oldSystem = lc.curMeasure->system();
2017-03-14 17:00:38 +01:00
system->appendMeasure(lc.curMeasure);
2012-05-26 14:26:10 +02:00
qreal ww = 0; // width of current measure
2012-05-26 14:26:10 +02:00
if (lc.curMeasure->isMeasure()) {
2016-02-17 14:54:23 +01:00
Measure* m = toMeasure(lc.curMeasure);
2016-10-20 11:32:07 +02:00
if (firstMeasure) {
system->layoutSystem(minWidth);
minWidth += system->leftMargin();
2016-10-20 11:32:07 +02:00
if (m->repeatStart()) {
2017-03-08 13:12:26 +01:00
Segment* s = m->findSegmentR(SegmentType::StartRepeatBarLine, 0);
2016-10-20 11:32:07 +02:00
if (!s->enabled())
s->setEnabled(true);
}
2016-10-18 15:41:00 +02:00
m->addSystemHeader(lc.firstSystem);
2016-12-14 09:56:16 +01:00
firstMeasure = false;
createHeader = false;
}
else {
if (createHeader) {
m->addSystemHeader(false);
createHeader = false;
}
else if (m->header())
m->removeSystemHeader();
2016-10-20 11:32:07 +02:00
}
2016-01-04 14:48:58 +01:00
2017-12-08 13:44:57 +01:00
m->createEndBarLines(true);
Measure* nm = m->nextMeasure();
if (nm)
m->addSystemTrailer(nm);
m->computeMinWidth();
ww = m->width();
}
else if (lc.curMeasure->isHBox()) {
lc.curMeasure->computeMinWidth();
ww = lc.curMeasure->width();
2016-12-14 09:56:16 +01:00
createHeader = toHBox(lc.curMeasure)->createSystemHeader();
}
else {
// vbox:
getNextMeasure(lc);
system->layout2(); // compute staff distances
return system;
2012-05-26 14:26:10 +02:00
}
2016-01-04 14:48:58 +01:00
// check if lc.curMeasure fits, remove if not
2016-10-18 15:41:00 +02:00
// collect at least one measure and the break
2012-05-26 14:26:10 +02:00
bool doBreak = (system->measures().size() > 1) && !lineMode() && ((minWidth + ww) > systemWidth);
if (doBreak) {
if (lc.prevMeasure->noBreak() && system->measures().size() > 2) {
// remove last two measures
// TODO: check more measures for noBreak()
system->measures().pop_back();
system->measures().pop_back();
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->measures().pop_back();
lc.curMeasure->setSystem(oldSystem);
break;
}
2012-05-26 14:26:10 +02:00
}
2016-01-04 14:48:58 +01:00
if (lc.prevMeasure && lc.prevMeasure->isMeasure() && lc.prevMeasure->system() == system) {
//
2016-10-18 15:41:00 +02:00
// 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
2016-02-17 14:54:23 +01:00
Measure* m = toMeasure(lc.prevMeasure);
if (m->trailer()) {
qreal ow = m->width();
2016-10-18 15:41:00 +02:00
m->removeSystemTrailer();
minWidth += m->width() - ow;
}
2016-10-20 11:32:07 +02:00
// if the prev measure is an end repeat and the cur measure
2016-10-18 15:41:00 +02:00
// 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* m = toMeasure(lc.curMeasure);
2016-10-20 11:32:07 +02:00
if (m->repeatStart()) {
2017-03-08 13:12:26 +01:00
Segment* s = m->findSegmentR(SegmentType::StartRepeatBarLine, 0);
2017-03-20 18:24:16 +01:00
if (!s->enabled()) {
s->setEnabled(true);
m->computeMinWidth();
ww = m->width();
2016-10-18 15:41:00 +02:00
}
}
}
minWidth += m->createEndBarLines(false); // create final barLine
2012-05-26 14:26:10 +02:00
}
MeasureBase* mb = lc.curMeasure;
2016-10-20 11:32:07 +02:00
bool lineBreak = false;
2016-01-04 14:48:58 +01:00
switch (_layoutMode) {
case LayoutMode::PAGE:
case LayoutMode::SYSTEM:
2016-10-18 15:41:00 +02:00
lineBreak = mb->pageBreak() || mb->lineBreak() || mb->sectionBreak();
2016-01-04 14:48:58 +01:00
break;
case LayoutMode::FLOAT:
case LayoutMode::LINE:
2016-10-18 15:41:00 +02:00
lineBreak = false;
2012-05-26 14:26:10 +02:00
break;
}
2016-10-18 15:41:00 +02:00
getNextMeasure(lc);
minWidth += ww;
2017-01-05 11:23:47 +01:00
if (lc.endTick < lc.prevMeasure->tick()) {
2016-04-15 16:44:48 +02:00
// TODO: we may check if another measure fits in this system
2016-10-18 15:41:00 +02:00
if (lc.prevMeasure == lc.systemOldMeasure) {
2016-04-15 16:44:48 +02:00
lc.rangeDone = true;
2017-01-05 11:23:47 +01:00
if (lc.curMeasure && lc.curMeasure->isMeasure()) {
restoreBeams(toMeasure(lc.curMeasure));
2017-01-05 11:23:47 +01:00
toMeasure(lc.curMeasure)->stretchMeasure(lc.curMeasure->width());
}
2016-04-15 16:44:48 +02:00
break;
}
2016-01-04 14:48:58 +01:00
}
2017-01-18 14:16:33 +01:00
// ElementType nt = lc.curMeasure ? lc.curMeasure->type() : ElementType::INVALID;
mb = lc.curMeasure;
bool tooWide = false; // minWidth + minMeasureWidth > systemWidth; // TODO: noBreak
2016-10-20 11:32:07 +02:00
if (!lineMode() && (lineBreak || !mb || mb->isVBox() || mb->isTBox() || mb->isFBox() || tooWide))
2016-10-18 15:41:00 +02:00
break;
2012-05-26 14:26:10 +02:00
}
2016-10-18 15:41:00 +02:00
2016-01-04 14:48:58 +01:00
//
2016-10-18 15:41:00 +02:00
// now we have a complete set of measures for this system
2016-01-04 14:48:58 +01:00
//
// prevMeasure is the last measure in the system
if (lc.prevMeasure && lc.prevMeasure->isMeasure()) {
qreal w = toMeasure(lc.prevMeasure)->createEndBarLines(true);
minWidth += w;
}
2016-05-18 15:43:47 +02:00
hideEmptyStaves(system, lc.firstSystem);
2016-05-18 15:43:47 +02:00
if (!lineMode()) {
//-------------------------------------------------------
// add system trailer if needed
// (cautionary time/key signatures etc)
//-------------------------------------------------------
2012-05-26 14:26:10 +02:00
Measure* m = system->lastMeasure();
if (m) {
Measure* nm = m->nextMeasure();
if (nm) {
qreal w = m->width();
m->addSystemTrailer(nm);
if (m->trailer())
m->computeMinWidth();
minWidth += m->width() - w;
}
2016-01-04 14:48:58 +01:00
}
}
//
// stretch incomplete row
//
qreal rest;
if (lineMode() || MScore::noHorizontalStretch)
rest = 0;
else {
qreal mw = system->leftMargin(); // DEBUG
qreal totalWeight = 0.0;
2015-07-25 08:43:02 +02:00
for (MeasureBase* mb : system->measures()) {
if (mb->isHBox()) {
mw += mb->width();
2016-10-18 15:41:00 +02:00
}
else if (mb->isMeasure()) {
Measure* m = toMeasure(mb);
mw += m->width(); // measures are stretched already with basicStretch()
totalWeight += m->ticks() * m->basicStretch();
}
}
2016-10-18 15:41:00 +02:00
2016-10-18 15:46:23 +02:00
#ifndef NDEBUG
if (!qFuzzyCompare(mw, minWidth))
2016-12-14 09:56:16 +01:00
qDebug("==layoutSystem %6d old %.1f new %.1f", system->measures().front()->tick(), minWidth, mw);
2016-10-18 15:46:23 +02:00
#endif
rest = systemWidth - minWidth;
//
// dont stretch last system row, if accumulated minWidth is <= lastSystemFillLimit
//
2018-03-27 15:36:00 +02:00
if (lc.curMeasure == 0 && ((minWidth / systemWidth) <= styleD(Sid::lastSystemFillLimit))) {
if (minWidth > rest)
rest = rest * .5;
else
rest = minWidth;
2015-07-25 08:43:02 +02:00
}
rest /= totalWeight;
}
2016-01-04 14:48:58 +01:00
QPointF pos;
firstMeasure = true;
for (MeasureBase* mb : system->measures()) {
qreal ww = mb->width();
if (mb->isMeasure()) {
if (firstMeasure) {
pos.rx() += system->leftMargin();
firstMeasure = false;
2016-10-18 15:41:00 +02:00
}
mb->setPos(pos);
Measure* m = toMeasure(mb);
qreal stretch = m->basicStretch();
if (!lineMode())
ww += rest * m->ticks() * stretch;
m->stretchMeasure(ww);
2016-12-12 14:55:35 +01:00
m->layoutStaffLines();
}
else if (mb->isHBox()) {
mb->setPos(pos + QPointF(toHBox(mb)->topGap(), 0.0));
mb->layout();
2015-07-25 08:43:02 +02:00
}
else if (mb->isVBox())
mb->setPos(pos);
pos.rx() += ww;
2012-05-26 14:26:10 +02:00
}
2018-01-11 11:39:14 +01:00
system->setWidth(pos.x());
2016-01-04 14:48:58 +01:00
//
// layout tuplet
//
for (MeasureBase* mb : system->measures()) {
if (!mb->isMeasure())
continue;
Measure* m = toMeasure(mb);
static const SegmentType st { SegmentType::ChordRest };
for (int track = 0; track < score()->ntracks(); ++track) {
if (!score()->staff(track / VOICES)->show()) {
track += VOICES-1;
continue;
}
for (Segment* s = m->first(st); s; s = s->next(st)) {
ChordRest* cr = s->cr(track);
if (!cr)
continue;
DurationElement* de = cr;
while (de->tuplet() && de->tuplet()->elements().front() == de) {
Tuplet* t = de->tuplet();
t->layout();
s->staffShape(t->staffIdx()).add(t->shape().translated(-s->pos()));
de = de->tuplet();
}
}
}
}
2017-02-08 13:27:35 +01:00
//
2017-08-02 18:19:08 +02:00
// compute measure shape
2017-02-08 13:27:35 +01:00
//
for (int si = 0; si < score()->nstaves(); ++si) {
for (MeasureBase* mb : system->measures()) {
if (!mb->isMeasure())
continue;
Measure* m = toMeasure(mb);
2017-08-02 18:19:08 +02:00
Shape& ss = m->staffShape(si);
ss.clear();
for (Segment& s : m->segments()) {
if (s.isTimeSigType()) // hack: ignore time signatures
continue;
2017-08-02 18:19:08 +02:00
ss.add(s.staffShape(si).translated(s.pos()));
}
2017-08-02 18:19:08 +02:00
ss.add(m->staffLines(si)->bbox());
2017-02-08 13:27:35 +01:00
}
}
2016-08-02 17:00:49 +02:00
//
// layout
// - beams
// - RehearsalMark, StaffText
// - Dynamic
2017-02-08 13:27:35 +01:00
// - update the segment shape + measure shape
2016-08-02 17:00:49 +02:00
//
//
2018-01-11 11:39:14 +01:00
int stick = system->measures().front()->tick();
int etick = system->measures().back()->endTick();
//
// layout slurs
//
if (etick > stick) { // ignore vbox
auto spanners = score()->spannerMap().findOverlapping(stick, etick);
std::vector<Spanner*> spanner;
for (auto interval : spanners) {
Spanner* sp = interval.value;
if (sp->tick() < etick && sp->tick2() >= stick) {
2018-01-11 11:39:14 +01:00
if (sp->isSlur())
spanner.push_back(sp);
}
}
processLines(system, spanner, false);
2018-01-11 11:39:14 +01:00
}
2017-12-06 15:06:32 +01:00
std::vector<Dynamic*> dynamics;
2016-03-18 14:35:15 +01:00
for (MeasureBase* mb : system->measures()) {
if (!mb->isMeasure())
continue;
2017-03-08 13:12:26 +01:00
SegmentType st = SegmentType::ChordRest;
2017-02-08 13:27:35 +01:00
Measure* m = toMeasure(mb);
for (Segment* s = m->first(st); s; s = s->next(st)) {
2016-03-18 14:35:15 +01:00
for (Element* e : s->elist()) {
2016-12-23 12:05:18 +01:00
if (!e)
continue;
if (e->isChordRest()) {
2016-03-18 14:35:15 +01:00
ChordRest* cr = toChordRest(e);
2016-06-01 13:24:32 +02:00
if (isTopBeam(cr)) {
2016-03-18 14:35:15 +01:00
cr->beam()->layout();
2017-08-09 16:01:12 +02:00
Shape shape(cr->beam()->shape().translated(-(cr->segment()->pos()+mb->pos())));
s->staffShape(cr->staffIdx()).add(shape);
m->staffShape(cr->staffIdx()).add(shape.translated(s->pos()));
2016-06-01 13:24:32 +02:00
}
2017-07-19 17:53:45 +02:00
if (e->isChord()) {
Chord* c = toChord(e);
for (Chord* ch : c->graceNotes())
layoutTies(ch, system, stick);
layoutTies(c, system, stick);
2018-01-22 10:12:49 +01:00
c->layoutArticulations2();
2017-07-19 17:53:45 +02:00
}
2016-03-18 14:35:15 +01:00
}
}
2016-05-02 13:41:41 +02:00
for (Element* e : s->annotations()) {
if (e->visible() && e->isDynamic()) {
2016-07-19 14:36:09 +02:00
Dynamic* d = toDynamic(e);
d->layout();
if (d->autoplace()) {
// If dynamic is at start or end of a hairpin
// don't autoplace. This is done later on layout of hairpin
// and allows horizontal alignment of dynamic and hairpin.
int tick = d->tick();
auto si = score()->spannerMap().findOverlapping(tick, tick);
bool doAutoplace = true;
for (auto is : si) {
Spanner* sp = is.value;
sp->computeStartElement();
sp->computeEndElement();
if (sp->isHairpin()
&& (lookupDynamic(sp->startElement()) == d
|| lookupDynamic(sp->endElement()) == d))
doAutoplace = false;
}
if (doAutoplace) {
2016-07-19 14:36:09 +02:00
d->doAutoplace();
2017-12-06 15:06:32 +01:00
dynamics.push_back(d);
2016-07-19 14:36:09 +02:00
}
}
}
2016-10-20 11:32:07 +02:00
else if (e->isFiguredBass())
e->layout();
2016-05-02 13:41:41 +02:00
}
2016-03-18 14:35:15 +01:00
}
}
2016-08-17 12:52:35 +02:00
2017-12-06 15:06:32 +01:00
// add dynamics shape to staff shape
for (Dynamic* d : dynamics) {
int si = d->staffIdx();
Segment* s = d->segment();
s->staffShape(si).add(d->shape().translated(d->pos()));
Measure* m = s->measure();
m->staffShape(si).add(d->shape().translated(s->pos() + d->pos()));
}
//
// layout SpannerSegments for current system
//
2016-07-01 12:42:15 +02:00
if (etick > stick) { // ignore vbox
auto spanners = score()->spannerMap().findOverlapping(stick, etick);
2017-06-23 19:48:10 +02:00
2018-01-03 09:59:50 +01:00
std::vector<Spanner*> ottavas;
std::vector<Spanner*> spanner;
std::vector<Spanner*> pedal;
2018-01-03 09:59:50 +01:00
for (auto interval : spanners) {
Spanner* sp = interval.value;
if (sp->tick() < etick && sp->tick2() > stick) {
2018-01-03 09:59:50 +01:00
if (sp->isOttava())
ottavas.push_back(sp);
else if (sp->isPedal())
pedal.push_back(sp);
2018-01-11 11:39:14 +01:00
else if (!sp->isSlur()) // slurs are already handled
2018-01-03 09:59:50 +01:00
spanner.push_back(sp);
}
2016-07-10 12:00:57 +02:00
}
processLines(system, ottavas, false);
processLines(system, pedal, true);
processLines(system, spanner, false);
2018-01-03 09:59:50 +01:00
2016-07-10 12:00:57 +02:00
//
// vertical align volta segments
//
2018-01-03 09:59:50 +01:00
std::vector<SpannerSegment*> voltaSegments;
for (SpannerSegment* ss : system->spannerSegments()) {
if (ss->isVoltaSegment())
voltaSegments.push_back(ss);
}
2016-07-10 12:00:57 +02:00
if (voltaSegments.size() > 1) {
qreal y = 0;
for (SpannerSegment* ss : voltaSegments)
y = qMin(y, ss->userOff().y());
for (SpannerSegment* ss : voltaSegments)
ss->setUserYoffset(y);
}
for (Spanner* sp : _unmanagedSpanner) {
if (sp->tick() >= etick || sp->tick2() < stick)
continue;
sp->layout();
}
2016-08-02 17:00:49 +02:00
//
// add SpannerSegment shapes to staff shapes
//
2016-08-02 17:00:49 +02:00
for (MeasureBase* mb : system->measures()) {
if (!mb->isMeasure())
continue;
Measure* m = toMeasure(mb);
for (SpannerSegment* ss : system->spannerSegments()) {
Spanner* sp = ss->spanner();
if (sp->tick() < m->endTick() && sp->tick2() > m->tick()) {
// spanner shape must be translated from system coordinate space
// to measure coordinate space
Shape* shape = &m->staffShape(sp->staffIdx());
if (ss->isLyricsLineSegment())
shape->add(ss->shape().translated(-m->pos()));
else
shape->add(ss->shape().translated(ss->pos() - m->pos()));
2016-08-02 17:00:49 +02:00
}
}
}
}
2017-07-04 16:43:25 +02:00
2018-02-01 10:37:12 +01:00
//
// TempoText, Fermata
//
2017-07-04 16:43:25 +02:00
for (MeasureBase* mb : system->measures()) {
if (!mb->isMeasure())
continue;
SegmentType st = SegmentType::ChordRest;
Measure* m = toMeasure(mb);
for (Segment* s = m->first(st); s; s = s->next(st)) {
for (Element* e : s->annotations()) {
if (e->isTempoText()) {
TempoText* tt = toTempoText(e);
setTempo(tt->segment(), tt->tempo());
tt->layout();
}
2018-01-16 13:38:17 +01:00
else if (e->isFermata()) {
e->layout();
int si = e->staffIdx();
s->staffShape(si).add(e->shape().translated(e->pos()));
m->staffShape(si).add(e->shape().translated(s->pos() + e->pos()));
}
2017-07-04 16:43:25 +02:00
}
}
}
2018-02-01 10:37:12 +01:00
//
// Jump, Marker
//
for (MeasureBase* mb : system->measures()) {
if (!mb->isMeasure())
continue;
Measure* m = toMeasure(mb);
for (Element* e : m->el()) {
if (e->visible() && (e->isJump() || e->isMarker()))
e->layout();
}
}
//
// RehearsalMark, StaffText, FretDiagram
//
for (MeasureBase* mb : system->measures()) {
if (!mb->isMeasure())
continue;
SegmentType st = SegmentType::ChordRest;
Measure* m = toMeasure(mb);
for (Segment* s = m->first(st); s; s = s->next(st)) {
for (Element* e : s->annotations()) {
2018-02-01 10:37:12 +01:00
if (e->visible() && (e->isRehearsalMark() || e->isStaffText() || e->isFretDiagram()))
e->layout();
}
}
}
2017-09-19 15:20:06 +02:00
layoutLyrics(system);
system->layout2(); // compute staff distances
2016-03-18 14:35:15 +01:00
2016-08-17 17:27:09 +02:00
Measure* lm = system->lastMeasure();
if (lm) {
lc.firstSystem = lm->sectionBreak() && _layoutMode != LayoutMode::FLOAT;
2016-10-13 14:28:00 +02:00
lc.startWithLongNames = lc.firstSystem && lm->sectionBreakElement()->startWithLongNames();
2016-08-17 17:27:09 +02:00
}
2016-01-04 14:48:58 +01:00
return system;
2012-05-26 14:26:10 +02:00
}
2016-03-29 16:01:44 +02:00
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
2016-01-04 14:48:58 +01:00
// collectPage
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
2017-01-05 11:23:47 +01:00
void LayoutContext::collectPage()
2012-05-26 14:26:10 +02:00
{
2018-03-27 15:36:00 +02:00
const qreal slb = score->styleP(Sid::staffLowerBorder);
2017-01-05 11:23:47 +01:00
bool breakPages = score->layoutMode() != LayoutMode::SYSTEM;
qreal y = prevSystem ? prevSystem->y() + prevSystem->height() : page->tm();
qreal ey = page->height() - page->bm();
2012-05-26 14:26:10 +02:00
2017-01-05 11:23:47 +01:00
System* nextSystem = 0;
int systemIdx = -1;
2012-05-26 14:26:10 +02:00
2017-01-05 11:23:47 +01:00
for (int k = 0;;++k) {
2016-01-04 14:48:58 +01:00
//
// calculate distance to previous system
//
qreal distance;
2017-01-05 11:23:47 +01:00
if (prevSystem)
distance = prevSystem->minDistance(curSystem);
2016-01-04 14:48:58 +01:00
else {
// this is the first system on page
2017-10-27 13:06:22 +02:00
if (curSystem->vbox())
distance = 0.0;
else {
2018-03-27 15:36:00 +02:00
distance = score->styleP(Sid::staffUpperBorder);
2017-10-27 13:06:22 +02:00
for (MeasureBase* mb : curSystem->measures()) {
if (mb->isMeasure()) {
Measure* m = toMeasure(mb);
Spacer* sp = m->vspacerUp(0);
if (sp) {
if (sp->spacerType() == SpacerType::FIXED) {
distance = sp->gap();
break;
}
else
distance = qMax(distance, sp->gap());
}
distance = qMax(distance, -m->staffShape(0).top());
}
}
}
2016-01-04 14:48:58 +01:00
}
//TODO-ws ?? distance += score->staves().front()->userDist();
2016-01-04 14:48:58 +01:00
y += distance;
2017-01-05 11:23:47 +01:00
curSystem->setPos(page->lm(), y);
2017-03-14 17:00:38 +01:00
#ifndef NDEBUG
2017-01-05 11:23:47 +01:00
for (System* s : page->systems()) {
if (s == curSystem)
2017-03-14 17:00:38 +01:00
qDebug("bad system %d", k);
2017-01-05 11:23:47 +01:00
}
2017-03-14 17:00:38 +01:00
#endif
2017-01-05 11:23:47 +01:00
page->appendSystem(curSystem);
y += curSystem->height();
2012-05-26 14:26:10 +02:00
2016-01-04 14:48:58 +01:00
//
// check for page break or if next system will fit on page
//
2017-01-05 11:23:47 +01:00
if (rangeDone) {
2016-03-24 12:39:18 +01:00
// take next system unchanged
2017-01-05 11:23:47 +01:00
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++);
}
}
}
2016-03-24 12:39:18 +01:00
}
2016-05-25 10:51:59 +02:00
else {
2017-01-05 11:23:47 +01:00
nextSystem = score->collectSystem(*this);
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()) {
2017-01-31 12:21:44 +01:00
systemList = systems;
2017-01-05 11:23:47 +01:00
systems.clear();
measureNo = 0;
startWithLongNames = true;
firstSystem = true;
tick = 0;
prevMeasure = 0;
curMeasure = 0;
nextMeasure = ms->measures()->first();
ms->getNextMeasure(*this);
nextSystem = ms->collectSystem(*this);
2018-03-27 15:36:00 +02:00
ms->setScoreFont(ScoreFont::fontFactory(ms->styleSt(Sid::MusicalSymbolFont)));
2017-01-05 11:23:47 +01:00
ms->setNoteHeadWidth(ms->scoreFont()->width(SymId::noteheadBlack, ms->spatium() / SPATIUM20));
}
else {
rangeDone = true;
systemIdx = 0;
nextSystem = score->systems().value(systemIdx++);
}
}
}
2016-05-25 10:51:59 +02:00
}
2017-01-05 11:23:47 +01:00
prevSystem = curSystem;
Q_ASSERT(curSystem != nextSystem);
curSystem = nextSystem;
bool breakPage = !curSystem || (breakPages && prevSystem->pageBreak());
2016-01-04 14:48:58 +01:00
if (!breakPage) {
2017-01-05 11:23:47 +01:00
qreal dist = prevSystem->minDistance(curSystem) + curSystem->height();
VBox* vbox = curSystem->vbox();
2016-03-02 13:20:19 +01:00
if (vbox)
dist += vbox->bottomGap();
2017-01-05 11:23:47 +01:00
else if (!prevSystem->hasFixedDownDistance())
dist += qMax(curSystem->minBottom(), slb);
breakPage = (y + dist) >= ey && breakPages;
2016-01-04 14:48:58 +01:00
}
if (breakPage) {
2017-01-05 11:23:47 +01:00
VBox* vbox = prevSystem->vbox();
qreal dist = vbox ? vbox->bottomGap() : qMax(prevSystem->minBottom(), slb);
2016-01-04 14:48:58 +01:00
layoutPage(page, ey - (y + dist));
2012-05-26 14:26:10 +02:00
break;
2016-01-04 14:48:58 +01:00
}
2012-05-26 14:26:10 +02:00
}
2016-03-02 13:20:19 +01:00
int stick = -1;
for (System* s : page->systems()) {
2017-01-05 11:23:47 +01:00
Score* score = s->score();
2016-03-02 13:20:19 +01:00
for (MeasureBase* mb : s->measures()) {
if (!mb->isMeasure())
continue;
Measure* m = toMeasure(mb);
if (stick == -1)
stick = m->tick();
2017-01-05 11:23:47 +01:00
for (int track = 0; track < score->ntracks(); ++track) {
2016-03-02 13:20:19 +01:00
for (Segment* segment = m->first(); segment; segment = segment->next()) {
Element* e = segment->element(track);
if (!e)
continue;
if (e->isChordRest()) {
2017-01-05 11:23:47 +01:00
if (!score->staff(track2staff(track))->show())
2016-03-02 13:20:19 +01:00
continue;
ChordRest* cr = toChordRest(e);
2016-04-01 14:57:24 +02:00
if (notTopBeam(cr)) // layout cross staff beams
2016-03-02 13:20:19 +01:00
cr->beam()->layout();
if (cr->isChord()) {
Chord* c = toChord(cr);
for (Chord* cc : c->graceNotes()) {
if (cc->beam() && cc->beam()->elements().front() == cc)
cc->beam()->layout();
for (Note* n : cc->notes()) {
Tie* tie = n->tieFor();
if (tie)
tie->layout();
for (Spanner* sp : n->spannerFor())
sp->layout();
}
for (Element* e : cc->el()) {
if (e->isSlur())
e->layout();
}
}
c->layoutArpeggio2();
for (Note* n : c->notes()) {
Tie* tie = n->tieFor();
if (tie)
tie->layout();
for (Spanner* sp : n->spannerFor())
sp->layout();
}
}
}
else if (e->isBarLine())
2016-12-23 12:05:18 +01:00
toBarLine(e)->layout2();
2016-03-02 13:20:19 +01:00
}
}
m->layout2();
}
2016-03-02 13:20:19 +01:00
}
page->rebuildBspTree();
2012-05-26 14:26:10 +02:00
}
//---------------------------------------------------------
2016-01-04 14:48:58 +01:00
// doLayout
2017-01-05 11:23:47 +01:00
// do a complete (re-) layout
2012-05-26 14:26:10 +02:00
//---------------------------------------------------------
2016-01-04 14:48:58 +01:00
void Score::doLayout()
2012-05-26 14:26:10 +02:00
{
2017-01-05 11:23:47 +01:00
doLayoutRange(0, -1);
2016-03-02 13:20:19 +01:00
}
//---------------------------------------------------------
// doLayoutRange
//---------------------------------------------------------
void Score::doLayoutRange(int stick, int etick)
{
2018-02-12 16:34:33 +01:00
if (stick == -1 && etick == -1)
abort();
if (!last()) {
qDeleteAll(_systems);
_systems.clear();
qDeleteAll(pages());
pages().clear();
return;
}
2017-10-13 12:12:11 +02:00
// qDebug("%p %d-%d %s systems %d", this, stick, etick, isMaster() ? "Master" : "Part", int(_systems.size()));
bool layoutAll = stick <= 0 && (etick < 0 || etick >= last()->endTick());
2016-08-04 17:29:07 +02:00
if (stick < 0)
stick = 0;
2017-01-05 11:23:47 +01:00
if (etick < 0)
etick = last()->endTick();
2017-01-05 11:23:47 +01:00
2016-03-02 13:20:19 +01:00
LayoutContext lc;
2016-03-24 12:39:18 +01:00
lc.endTick = etick;
2018-03-27 15:36:00 +02:00
_scoreFont = ScoreFont::fontFactory(style().value(Sid::MusicalSymbolFont).toString());
2016-03-02 13:20:19 +01:00
_noteHeadWidth = _scoreFont->width(SymId::noteheadBlack, spatium() / SPATIUM20);
2016-03-18 09:29:16 +01:00
if (cmdState().layoutFlags & LayoutFlag::FIX_PITCH_VELO)
2016-03-02 13:20:19 +01:00
updateVelo();
2016-03-18 09:29:16 +01:00
if (cmdState().layoutFlags & LayoutFlag::PLAY_EVENTS)
2016-03-02 13:20:19 +01:00
createPlayEvents();
2016-01-04 14:48:58 +01:00
//---------------------------------------------------
2016-03-02 13:20:19 +01:00
// initialize layout context lc
2016-01-04 14:48:58 +01:00
//---------------------------------------------------
2012-08-01 18:00:27 +02:00
2017-01-16 20:51:12 +01:00
MeasureBase* m = tick2measure(stick);
if (m == 0)
m = first();
2016-03-18 09:29:16 +01:00
// start layout one measure earlier to handle clefs and cautionary elements
2017-01-05 11:23:47 +01:00
if (m->prevMeasureMM())
m = m->prevMeasureMM();
2017-01-16 20:51:12 +01:00
else if (m->prev())
m = m->prev();
2017-02-23 11:41:53 +01:00
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
2017-01-05 11:23:47 +01:00
2017-03-14 17:00:38 +01:00
if (!m->system() && m->isMeasure() && toMeasure(m)->hasMMRest()) {
qDebug(" dont start with mmrest");
2017-01-16 20:51:12 +01:00
m = toMeasure(m)->mmRest();
2017-03-14 17:00:38 +01:00
}
2016-02-06 11:41:16 +01:00
2017-06-02 10:27:32 +02:00
// qDebug("start <%s> tick %d, system %p", m->name(), m->tick(), m->system());
2017-01-05 11:23:47 +01:00
lc.score = m->score();
2017-01-16 20:51:12 +01:00
2017-03-14 17:00:38 +01:00
if (!layoutAll && m->system()) {
System* system = m->system();
int systemIndex = _systems.indexOf(system);
2017-01-05 11:23:47 +01:00
lc.page = system->page();
lc.curPage = pageIdx(lc.page);
if (lc.curPage == -1)
lc.curPage = 0;
2017-01-16 20:51:12 +01:00
lc.curSystem = system;
2017-01-05 11:23:47 +01:00
lc.systemList = _systems.mid(systemIndex);
if (systemIndex == 0)
lc.nextMeasure = _measures.first();
2017-03-14 17:00:38 +01:00
else {
System* prevSystem = _systems[systemIndex-1];
lc.nextMeasure = prevSystem->measures().back()->next();
}
2017-01-05 11:23:47 +01:00
_systems.erase(_systems.begin() + systemIndex, _systems.end());
if (!lc.nextMeasure->prevMeasure()) {
2017-01-05 11:23:47 +01:00
lc.measureNo = 0;
lc.tick = 0;
}
else {
2017-01-05 11:23:47 +01:00
lc.measureNo = lc.nextMeasure->no();
lc.tick = lc.nextMeasure->tick();
}
2017-01-05 11:23:47 +01:00
}
else {
2017-06-02 10:27:32 +02:00
// qDebug("layoutAll, systems %p %d", &_systems, int(_systems.size()));
2017-03-14 17:00:38 +01:00
//lc.measureNo = 0;
//lc.tick = 0;
// qDeleteAll(_systems);
// _systems.clear();
// lc.systemList = _systems;
// _systems.clear();
for (System* s : _systems) {
for (Bracket* b : s->brackets()) {
2017-03-31 13:03:15 +02:00
if (b->selected()) {
_selection.elements().removeOne(b);
_selection.updateState();
setSelectionChanged(true);
}
}
2017-03-14 17:00:38 +01:00
for (SpannerSegment* ss : s->spannerSegments())
ss->setParent(0);
2018-01-25 13:04:06 +01:00
s->setParent(nullptr);
2017-03-14 17:00:38 +01:00
}
for (MeasureBase* mb = first(); mb; mb = mb->next()) {
mb->setSystem(0);
if (mb->isMeasure() && toMeasure(mb)->mmRest())
toMeasure(mb)->mmRest()->setSystem(0);
}
2017-11-20 15:57:31 +01:00
// qDeleteAll(_systems);
2017-03-14 17:00:38 +01:00
_systems.clear();
qDeleteAll(pages());
pages().clear();
2017-01-05 11:23:47 +01:00
lc.nextMeasure = _measures.first();
}
2017-01-05 11:23:47 +01:00
lc.prevMeasure = 0;
2012-10-26 11:42:54 +02:00
2017-01-05 11:23:47 +01:00
getNextMeasure(lc);
lc.curSystem = collectSystem(lc);
if (!lc.page) {
lc.page = new Page(this);
pages().push_back(lc.page);
lc.prevSystem = 0;
}
else {
QList<System*>& systems = lc.page->systems();
int i = systems.indexOf(lc.curSystem);
2018-02-01 10:37:12 +01:00
// qDebug("clear page systems from %d", i);
2017-03-14 17:00:38 +01:00
if (i <= -1)
2017-01-05 11:23:47 +01:00
systems.clear();
else {
systems.erase(systems.begin() + i, systems.end());
lc.prevSystem = systems.empty() ? 0 : systems.back();
2016-05-25 10:51:59 +02:00
}
2016-01-04 14:48:58 +01:00
}
2017-01-05 11:23:47 +01:00
lc.page->bbox().setRect(0.0, 0.0, loWidth(), loHeight());
lc.page->setNo(lc.curPage);
qreal x = 0.0;
qreal y = 0.0;
if (lc.curPage) {
Page* prevPage = pages()[lc.curPage-1];
if (MScore::verticalOrientation())
y = prevPage->pos().y() + lc.page->height() + MScore::verticalPageGap;
else {
qreal gap = (lc.curPage + pageNumberOffset()) & 1 ? MScore::horizontalPageGapOdd : MScore::horizontalPageGapEven;
x = prevPage->pos().x() + lc.page->width() + gap;
}
2016-03-18 09:29:16 +01:00
}
2017-01-05 11:23:47 +01:00
++lc.curPage;
lc.page->setPos(x, y);
2016-03-18 09:29:16 +01:00
2017-01-05 11:23:47 +01:00
lc.layout();
2012-10-26 11:42:54 +02:00
2016-01-04 14:48:58 +01:00
for (MuseScoreView* v : viewer)
v->layoutChanged();
2012-10-26 11:42:54 +02:00
}
2013-05-13 18:49:17 +02:00
2017-01-05 11:23:47 +01:00
//---------------------------------------------------------
// layout
//---------------------------------------------------------
void LayoutContext::layout()
{
for (;;) {
collectPage();
System* s = page->system(0);
MeasureBase* m = s->measures().back();
if (!curSystem || (rangeDone && m->tick() > endTick))
break;
Page* prevPage = page;
if (curPage >= score->npages()) {
page = new Page(score);
score->pages().push_back(page);
}
else {
page = score->pages()[curPage];
page->systems().clear();
}
page->bbox().setRect(0.0, 0.0, score->loWidth(), score->loHeight());
page->setNo(curPage);
qreal x = 0.0;
qreal y = 0.0;
if (curPage) {
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);
prevSystem = 0;
}
if (!curSystem) {
while (score->npages() > curPage) // Remove not needed pages. TODO: make undoable:
score->pages().takeLast();
}
score->systems().append(systemList); // TODO
}
2013-05-13 18:49:17 +02:00
}